import GObject, { register, getter, setter } from "ags/gobject"; import { monitorFile, readFileAsync } from "ags/file"; import { exec, execAsync } from "ags/process"; import { onCleanup } from "ags"; const get = (args: string) => Number(exec(`brightnessctl ${args}`)); const screen = exec(`bash -c "ls -w1 /sys/class/backlight | head -1"`); const kbd = exec(`bash -c "ls -w1 /sys/class/leds | head -1"`); @register({ GTypeName: "Brightness" }) export default class Brightness extends GObject.Object { // @ts-ignore - notify method is provided by GObject at runtime notify(property: string): void; static instance: Brightness; static get_default() { if (!this.instance) this.instance = new Brightness(); return this.instance; } #hasBacklight = false; #kbdMax = 0; #kbd = 0; #screenMax = 0; #screen = 0; #screenMonitor: any = null; #kbdMonitor: any = null; #settingScreen = false; #settingKbd = false; constructor() { super(); this.initializeHardware(); } private initializeHardware(): void { this.#hasBacklight = exec(`bash -c "ls /sys/class/backlight"`).length > 0; if (!this.#hasBacklight) return; try { this.#kbdMax = get(`--device ${kbd} max`); this.#kbd = get(`--device ${kbd} get`); this.#screenMax = get("max"); this.#screen = get("get") / this.#screenMax; this.#screenMonitor = monitorFile( `/sys/class/backlight/${screen}/brightness`, async (f) => { if (this.#settingScreen) return; try { const v = await readFileAsync(f); const newValue = Number(v) / this.#screenMax; if (Math.abs(this.#screen - newValue) > 0.001) { this.#screen = newValue; this.notify("screen"); } } catch (error) { console.error("Error reading screen brightness:", error); } }, ); this.#kbdMonitor = monitorFile( `/sys/class/leds/${kbd}/brightness`, async (f) => { if (this.#settingKbd) return; try { const v = await readFileAsync(f); const newValue = Number(v); if (this.#kbd !== newValue) { this.#kbd = newValue; this.notify("kbd"); } } catch (error) { console.error("Error reading keyboard brightness:", error); } }, ); onCleanup(() => { this.#screenMonitor?.cancel?.(); this.#kbdMonitor?.cancel?.(); }); } catch (error) { console.error("Error initializing brightness controls:", error); this.#hasBacklight = false; } } @getter(Boolean) get hasBacklight(): boolean { return this.#hasBacklight; } @getter(Number) get kbd(): number { return this.#kbd; } @setter(Number) set kbd(value: number) { if (!this.#hasBacklight || value < 0 || value > this.#kbdMax) return; this.#settingKbd = true; execAsync(`brightnessctl -d ${kbd} s ${value} -q`) .then(() => { setTimeout(() => (this.#settingKbd = false), 100); }) .catch((error) => { console.error("Error setting keyboard brightness:", error); this.#settingKbd = false; }); } @getter(Number) get screen(): number { return this.#screen; } @setter(Number) set screen(percent: number) { if (!this.#hasBacklight) return; percent = Math.max(0.01, Math.min(1, percent)); // Minimum 1% to avoid complete darkness this.#screen = percent; this.notify("screen"); this.#settingScreen = true; execAsync(`brightnessctl -d ${screen} set ${Math.floor(percent * 100)}% -q`) .then(() => { setTimeout(() => (this.#settingScreen = false), 200); }) .catch((error) => { console.error("Error setting screen brightness:", error); this.#screen = get("get") / this.#screenMax; this.notify("screen"); this.#settingScreen = false; }); } }