parent
1fc5a48bf9
commit
f522c984c4
64 changed files with 182 additions and 50 deletions
nixosModules/ags-v2/config/services
156
nixosModules/ags-v2/config/services/brightness.ts
Normal file
156
nixosModules/ags-v2/config/services/brightness.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
import { execAsync, interval } from 'astal';
|
||||
import GObject, { register, property } from 'astal/gobject';
|
||||
|
||||
|
||||
const SCREEN_ICONS: Record<number, string> = {
|
||||
90: 'display-brightness-high-symbolic',
|
||||
70: 'display-brightness-medium-symbolic',
|
||||
20: 'display-brightness-low-symbolic',
|
||||
5: 'display-brightness-off-symbolic',
|
||||
};
|
||||
|
||||
const INTERVAL = 500;
|
||||
|
||||
|
||||
@register()
|
||||
class Brightness extends GObject.Object {
|
||||
declare private _kbd: string;
|
||||
declare private _caps: string;
|
||||
|
||||
declare private _screen: number;
|
||||
|
||||
@property(Number)
|
||||
get screen() {
|
||||
return this._screen;
|
||||
};
|
||||
|
||||
set screen(percent) {
|
||||
if (percent < 0) {
|
||||
percent = 0;
|
||||
}
|
||||
|
||||
if (percent > 1) {
|
||||
percent = 1;
|
||||
}
|
||||
|
||||
percent = parseFloat(percent.toFixed(2));
|
||||
|
||||
execAsync(`brightnessctl s ${percent * 100}% -q`)
|
||||
.then(() => {
|
||||
this._screen = percent;
|
||||
this.notify('screen');
|
||||
this._getScreenIcon();
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
private _screenIcon = 'display-brightness-high-symbolic';
|
||||
|
||||
@property(String)
|
||||
get screenIcon() {
|
||||
return this._screenIcon;
|
||||
}
|
||||
|
||||
declare private _kbdMax: number;
|
||||
declare private _kbdLevel: number;
|
||||
|
||||
@property(Number)
|
||||
get kbdLevel() {
|
||||
return this._kbdLevel;
|
||||
}
|
||||
|
||||
set kbdLevel(value) {
|
||||
if (value < 0 || value > this._kbdMax) {
|
||||
return;
|
||||
}
|
||||
|
||||
execAsync(`brightnessctl -d ${this._kbd} s ${value} -q`)
|
||||
.then(() => {
|
||||
this._kbdLevel = value;
|
||||
this.notify('kbd-level');
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
|
||||
declare private _capsLevel: number;
|
||||
|
||||
@property(Number)
|
||||
get capsLevel() {
|
||||
return this._capsLevel;
|
||||
}
|
||||
|
||||
private _capsIcon = 'caps-lock-symbolic';
|
||||
|
||||
@property(String)
|
||||
get capsIcon() {
|
||||
return this._capsIcon;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is to basically have the constructor run when I want and
|
||||
* still export this to wherever I need to.
|
||||
*
|
||||
* @param o params
|
||||
* @param o.kbd name of kbd in brightnessctl
|
||||
* @param o.caps name of caps_lock in brightnessctl
|
||||
*/
|
||||
public async initService({ kbd = '', caps = '' }) {
|
||||
this._kbd = kbd;
|
||||
this._caps = caps;
|
||||
try {
|
||||
this._monitorKbdState();
|
||||
this._kbdMax = Number(await execAsync(`brightnessctl -d ${this._kbd} m`));
|
||||
|
||||
this._screen = Number(await execAsync('brightnessctl g')) /
|
||||
Number(await execAsync('brightnessctl m'));
|
||||
}
|
||||
catch (_e) {
|
||||
console.error('missing dependancy: brightnessctl');
|
||||
}
|
||||
}
|
||||
|
||||
private _getScreenIcon() {
|
||||
const brightness = this._screen * 100;
|
||||
|
||||
// eslint-disable-next-line
|
||||
for (const threshold of [4, 19, 69, 89]) {
|
||||
if (brightness > threshold + 1) {
|
||||
this._screenIcon = SCREEN_ICONS[threshold + 1];
|
||||
this.notify('screen-icon');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _monitorKbdState() {
|
||||
const timer = interval(INTERVAL, () => {
|
||||
execAsync(`brightnessctl -d ${this._kbd} g`).then(
|
||||
(out) => {
|
||||
if (parseInt(out) !== this._kbdLevel) {
|
||||
this._kbdLevel = parseInt(out);
|
||||
this.notify('kbd-level');
|
||||
}
|
||||
},
|
||||
).catch(() => {
|
||||
timer?.cancel();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public fetchCapsState() {
|
||||
execAsync(`brightnessctl -d ${this._caps} g`)
|
||||
.then((out) => {
|
||||
this._capsLevel = Number(out);
|
||||
this._capsIcon = this._capsLevel ?
|
||||
'caps-lock-symbolic' :
|
||||
'capslock-disabled-symbolic';
|
||||
|
||||
this.notify('caps-icon');
|
||||
this.notify('caps-level');
|
||||
})
|
||||
.catch(logError);
|
||||
}
|
||||
}
|
||||
|
||||
const brightnessService = new Brightness();
|
||||
|
||||
export default brightnessService;
|
193
nixosModules/ags-v2/config/services/monitor-clicks.ts
Normal file
193
nixosModules/ags-v2/config/services/monitor-clicks.ts
Normal file
|
@ -0,0 +1,193 @@
|
|||
import { subprocess } from 'astal';
|
||||
import { App } from 'astal/gtk3';
|
||||
import GObject, { register, signal } from 'astal/gobject';
|
||||
|
||||
import AstalIO from 'gi://AstalIO';
|
||||
|
||||
import { hyprMessage } from '../lib';
|
||||
|
||||
const ON_RELEASE_TRIGGERS = [
|
||||
'released',
|
||||
'TOUCH_UP',
|
||||
'HOLD_END',
|
||||
];
|
||||
const ON_CLICK_TRIGGERS = [
|
||||
'pressed',
|
||||
'TOUCH_DOWN',
|
||||
];
|
||||
|
||||
/* Types */
|
||||
import PopupWindow from '../widgets/misc/popup-window';
|
||||
import { CursorPos, Layer, LayerResult } from '../lib';
|
||||
|
||||
|
||||
@register()
|
||||
export default class MonitorClicks extends GObject.Object {
|
||||
@signal(Boolean)
|
||||
declare procStarted: (state: boolean) => void;
|
||||
|
||||
@signal(Boolean)
|
||||
declare procDestroyed: (state: boolean) => void;
|
||||
|
||||
@signal(String)
|
||||
declare released: (procLine: string) => void;
|
||||
|
||||
@signal(String)
|
||||
declare clicked: (procLine: string) => void;
|
||||
|
||||
private process = null as AstalIO.Process | null;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.#initAppConnection();
|
||||
}
|
||||
|
||||
startProc() {
|
||||
if (this.process) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.process = subprocess(
|
||||
['libinput', 'debug-events'],
|
||||
(output) => {
|
||||
if (output.includes('cancelled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ON_RELEASE_TRIGGERS.some((p) => output.includes(p))) {
|
||||
MonitorClicks.detectClickedOutside('released');
|
||||
this.emit('released', output);
|
||||
}
|
||||
|
||||
if (ON_CLICK_TRIGGERS.some((p) => output.includes(p))) {
|
||||
MonitorClicks.detectClickedOutside('clicked');
|
||||
this.emit('clicked', output);
|
||||
}
|
||||
},
|
||||
);
|
||||
this.emit('proc-started', true);
|
||||
}
|
||||
|
||||
killProc() {
|
||||
if (this.process) {
|
||||
this.process.kill();
|
||||
this.process = null;
|
||||
this.emit('proc-destroyed', true);
|
||||
}
|
||||
}
|
||||
|
||||
#initAppConnection() {
|
||||
App.connect('window-toggled', () => {
|
||||
const anyVisibleAndClosable =
|
||||
(App.get_windows() as PopupWindow[]).some((w) => {
|
||||
const closable = w.close_on_unfocus &&
|
||||
!(
|
||||
w.close_on_unfocus === 'none' ||
|
||||
w.close_on_unfocus === 'stay'
|
||||
);
|
||||
|
||||
return w.visible && closable;
|
||||
});
|
||||
|
||||
if (anyVisibleAndClosable) {
|
||||
this.startProc();
|
||||
}
|
||||
|
||||
else {
|
||||
this.killProc();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static async detectClickedOutside(clickStage: string) {
|
||||
const toClose = ((App.get_windows() as PopupWindow[])).some((w) => {
|
||||
const closable = (
|
||||
w.close_on_unfocus &&
|
||||
w.close_on_unfocus === clickStage
|
||||
);
|
||||
|
||||
return w.visible && closable;
|
||||
});
|
||||
|
||||
if (!toClose) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const layers = JSON.parse(await hyprMessage('j/layers')) as LayerResult;
|
||||
const pos = JSON.parse(await hyprMessage('j/cursorpos')) as CursorPos;
|
||||
|
||||
Object.values(layers).forEach((key) => {
|
||||
const overlayLayer = key.levels['3'];
|
||||
|
||||
if (overlayLayer) {
|
||||
const noCloseWidgetsNames = [
|
||||
'bar-',
|
||||
'osk',
|
||||
];
|
||||
|
||||
const getNoCloseWidgets = (names: string[]) => {
|
||||
const arr = [] as Layer[];
|
||||
|
||||
names.forEach((name) => {
|
||||
arr.push(
|
||||
overlayLayer.find(
|
||||
(n) => n.namespace.startsWith(name),
|
||||
) ||
|
||||
// Return an empty Layer if widget doesn't exist
|
||||
{
|
||||
address: '',
|
||||
x: 0,
|
||||
y: 0,
|
||||
w: 0,
|
||||
h: 0,
|
||||
namespace: '',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
return arr;
|
||||
};
|
||||
const clickIsOnWidget = (w: Layer) => {
|
||||
return (
|
||||
pos.x > w.x && pos.x < w.x + w.w &&
|
||||
pos.y > w.y && pos.y < w.y + w.h
|
||||
);
|
||||
};
|
||||
|
||||
const noCloseWidgets = getNoCloseWidgets(noCloseWidgetsNames);
|
||||
|
||||
const widgets = overlayLayer.filter((n) => {
|
||||
let window = null as null | PopupWindow;
|
||||
|
||||
if (App.get_windows().some((win) => win.name === n.namespace)) {
|
||||
window = (App.get_window(n.namespace) as PopupWindow);
|
||||
}
|
||||
|
||||
return window &&
|
||||
window.close_on_unfocus &&
|
||||
window.close_on_unfocus ===
|
||||
clickStage;
|
||||
});
|
||||
|
||||
if (noCloseWidgets.some(clickIsOnWidget)) {
|
||||
// Don't handle clicks when on certain widgets
|
||||
}
|
||||
else {
|
||||
widgets.forEach((w) => {
|
||||
if (!(
|
||||
pos.x > w.x && pos.x < w.x + w.w &&
|
||||
pos.y > w.y && pos.y < w.y + w.h
|
||||
)) {
|
||||
App.get_window(w.namespace)?.set_visible(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue