From f25996d5eb5e1f52135ef3785913a9453b67ab16 Mon Sep 17 00:00:00 2001 From: retrozinndev Date: Wed, 27 Aug 2025 13:13:20 -0300 Subject: [PATCH] :wrench: chore(modules/backlight): better implementation --- src/modules/backlight.ts | 330 +++++++++--------- src/widget/control-center/Sliders.tsx | 42 ++- src/widget/control-center/pages/Backlight.tsx | 8 +- 3 files changed, 192 insertions(+), 188 deletions(-) diff --git a/src/modules/backlight.ts b/src/modules/backlight.ts index f5b5082..0d12a20 100644 --- a/src/modules/backlight.ts +++ b/src/modules/backlight.ts @@ -1,187 +1,193 @@ -import { monitorFile, readFile, writeFile } from "ags/file"; -import GObject, { getter, ParamSpec, register, setter, signal } from "ags/gobject"; +import { monitorFile, readFile } from "ags/file"; +import { exec } from "ags/process"; +import GObject, { getter, ParamSpec, setter, signal } from "ags/gobject"; + import Gio from "gi://Gio?version=2.0"; -export { Backlight }; -@register({ GTypeName: "Backlight" }) -class Backlight extends GObject.Object { +export namespace Backlights { - private static _backlights: Array = []; - public static get backlights() { - return this._backlights; - }; - - private static default: Backlight; - - readonly #name: string; - #path: string; - #maxBrightness: number; - #brightness: number; - #available: boolean = true; - #monitor: Gio.FileMonitor; - - @signal(Number) brightnessChanged(_: number): void {}; - - @getter(String) - get name() { return this.#name; } - - @getter(String) - get path() { return this.#path; } - - @getter(GObject.Object as unknown as ParamSpec) - get default() { return Backlight.default; } - - @getter(Object as unknown as ParamSpec>) - get backlights() { return Backlight.backlights; } + let instance: Backlights; - @getter(Boolean) - get isDefault() { return this.path === this.default?.path; } + export function getDefault(): Backlights { + if(!instance) + instance = new Backlights; - @getter(Number) - get brightness() { return this.#brightness; }; - @setter(Number) - set brightness(level: number) { - if(!this.writeBrightness(level)) return; - - this.#brightness = level; - this.notify("brightness"); - this.emit("brightness-changed", level); + return instance; } - @getter(Number) - get maxBrightness() { return this.#maxBrightness;}; - - @getter(Boolean) - get available() { return this.#available; } - - - declare $signals: GObject.Object.SignalSignatures & { - "brightness-changed": (value: number) => void - }; - - public static setDefault(backlight: Backlight): void { - const prev = this.default; - this.default = backlight; - - prev && prev.notify("is-default"); - backlight.notify("is-default"); - this.backlights.forEach(bk => bk.notify("default")); - } - - public static amount(): number { - const dir = Gio.File.new_for_path(`/sys/class/backlight`); - let num: number = 0, - fileEnum: Gio.FileEnumerator; - - try { - fileEnum = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); - - for(const _ of fileEnum) - num++; - } catch(_) { - return num; + export class Backlights extends GObject.Object { + static { + GObject.registerClass({ + GTypeName: "Backlights" + }, this); } - - return num; - } - - public static scan(): Array { - const dir = Gio.File.new_for_path(`/sys/class/backlight`), - backlights: Array = []; - - let fileEnum: Gio.FileEnumerator; - - try { - fileEnum = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); - for(const backlight of fileEnum) { - try { - backlights.push(new Backlight(backlight.get_name())); - } catch(_) {} - } - } catch(_) { - return []; - } - - Backlight._backlights = backlights; - backlights.forEach(bk => bk.notify("backlights")); - return backlights; - } - - // intel_backlight is mostly the default on laptops - constructor(name: string = "intel_backlight") { - super(); - - // check if backlight exists - if(!Gio.File.new_for_path(`/sys/class/backlight/${name}/brightness`).query_exists(null)) { - this.#available = false; - this.notify("available"); - throw new Error(`Brightness: Couldn't find brightness for "${name}"`); - } - - this.#name = name; - this.#path = `/sys/class/backlight/${name}`; - this.notify("path"); - this.#maxBrightness = Number.parseInt(readFile(`${this.#path}/max_brightness`)); - this.notify("max-brightness"); - this.#brightness = Number.parseInt(readFile(`${this.#path}/brightness`)) + public static $gtype: GObject.GType; - this.#monitor = monitorFile(`/sys/class/backlight/${name}/brightness`, () => { - this.#brightness = this.readBrightness(); - this.notify("brightness"); - this.emit("brightness-changed", this.brightness); - }); - } + #backlights: Array = []; + #default: Backlight|null = null; + #available: boolean = false; + - private readBrightness(): number { - try { - const brightness = Number.parseInt(readFile(`${this.#path}/brightness`)); - return brightness; - } catch(e) { - console.error(`Backlight: An error occurred while reading brightness from "${this.#name}"`); - } + @getter(Array as unknown as ParamSpec>) + get backlights() { return this.#backlights; } - return this.#brightness ?? this.#maxBrightness ?? 0; - } + @getter(Backlight as unknown as ParamSpec) + get default() { return this.#default; } - private writeBrightness(level: number): boolean { - try { - writeFile(`${this.#path}/brightness`, level.toString()); - return true; - } catch(e) { - console.error(`Backlight: Couldn't set brightness for "${this.#name}". Stderr: ${e}`); - } + /** true if there are any backlights available */ + @getter(Boolean) + get available() { return this.#available; } - return false; - } + public scan(): Array { + const dir = Gio.File.new_for_path(`/sys/class/backlight`), + backlights: Array = []; - public static getDefault(): Backlight|null { - if(this.default) - return this.default; + let fileEnum: Gio.FileEnumerator; - if(this.backlights.length < 1) - this.scan(); - - const first = this.backlights[0]; - if(first) { try { - this.default = first; - return this.default; - } catch(_) {} + fileEnum = dir.enumerate_children("standard::*", Gio.FileQueryInfoFlags.NONE, null); + for(const backlight of fileEnum) { + try { + backlights.push(new Backlight(backlight.get_name())); + } catch(_) {} + } + } catch(_) { + return []; + } + + if(backlights.length < 1 && this.#available === true) { + this.#available = false; + this.notify("available"); + } + + if(this.#backlights.length < 1 && backlights.length > 0) { + this.#available = true; + this.notify("available"); + } + + this.#backlights = backlights; + this.notify("backlights"); + + return backlights; } - return null; + public setDefault(bk: Backlight): void { + this.#default = bk; + this.notify("default"); + } + + constructor(scan: boolean = true) { + super(); + scan && this.scan(); + } } - vfunc_dispose(): void { - this.#monitor.cancel(); - } + export class Backlight extends GObject.Object { + static { + GObject.registerClass({ + GTypeName: "Backlight" + }, this); + } + public static $gtype: GObject.GType; - public emit( - signal: Signal, - ...args: Parameters<(typeof this.$signals)[Signal]> - ): void { - super.emit(signal, ...args); + readonly #name: string; + #path: string; + #maxBrightness: number; + #brightness: number; + #monitor: Gio.FileMonitor; + #conn: number; + + @signal(Number) brightnessChanged(_: number): void {}; + + @getter(String) + get name() { return this.#name; } + + @getter(String) + get path() { return this.#path; } + + @getter(Boolean) + get isDefault() { return this.path === getDefault().default?.path; } + + @getter(Number) + get brightness() { return this.#brightness; }; + @setter(Number) + set brightness(level: number) { + if(!this.writeBrightness(level)) return; + + this.#brightness = level; + this.notify("brightness"); + this.emit("brightness-changed", level); + } + + @getter(Number) + get maxBrightness() { return this.#maxBrightness;}; + + + declare $signals: GObject.Object.SignalSignatures & { + "brightness-changed": (value: number) => void + }; + + // intel_backlight is mostly the default on laptops + constructor(name: string = "intel_backlight") { + super(); + + // check if backlight exists + if(!Gio.File.new_for_path(`/sys/class/backlight/${name}/brightness`).query_exists(null)) + throw new Error(`Brightness: Couldn't find brightness for "${name}"`); + + // notify :is-default on default backlight change + this.#conn = getDefault().connect("notify::default", () => + this.notify("is-default")); + + this.#name = name; + this.#path = `/sys/class/backlight/${name}`; + this.notify("path"); + this.#maxBrightness = Number.parseInt(readFile(`${this.#path}/max_brightness`)); + this.notify("max-brightness"); + this.#brightness = Number.parseInt(readFile(`${this.#path}/brightness`)) + + + this.#monitor = monitorFile(`/sys/class/backlight/${name}/brightness`, () => { + this.#brightness = this.readBrightness(); + this.notify("brightness"); + this.emit("brightness-changed", this.brightness); + }); + } + + private readBrightness(): number { + try { + const brightness = Number.parseInt(readFile(`${this.#path}/brightness`)); + return brightness; + } catch(e) { + console.error(`Backlight: An error occurred while reading brightness from "${this.#name}"`); + } + + return this.#brightness ?? this.#maxBrightness ?? 0; + } + + private writeBrightness(level: number): boolean { + try { + exec(`brightnessctl -d ${this.#name} s ${level}`); + return true; + } catch(e) { + console.error(`Backlight: Couldn't set brightness for "${this.#name}". Stderr: ${e}`); + } + + return false; + } + + vfunc_dispose(): void { + this.#monitor.cancel(); + getDefault().disconnect(this.#conn); + } + + public emit( + signal: Signal, + ...args: Parameters<(typeof this.$signals)[Signal]> + ): void { + super.emit(signal, ...args); + } } } diff --git a/src/widget/control-center/Sliders.tsx b/src/widget/control-center/Sliders.tsx index d06e3f4..9d2341d 100644 --- a/src/widget/control-center/Sliders.tsx +++ b/src/widget/control-center/Sliders.tsx @@ -4,7 +4,7 @@ import { Pages } from "./Pages"; import { PageSound } from "./pages/Sound"; import { PageMicrophone } from "./pages/Microphone"; import { createBinding, With } from "ags"; -import { Backlight } from "../../modules/backlight"; +import { Backlights } from "../../modules/backlight"; import AstalWp from "gi://AstalWp"; import { PageBacklight } from "./pages/Backlight"; @@ -50,28 +50,26 @@ export function Sliders() { slidersPages?.toggle(PageMicrophone)} /> } - - {Backlight.getDefault() && - - {(bklight: Backlight) => bklight && - - { - bklight.brightness = bklight.maxBrightness - }} iconName={"display-brightness-symbolic"} - /> + + + {(bklight: Backlights.Backlight|null) => bklight && + + { + bklight.brightness = bklight.maxBrightness + }} iconName={"display-brightness-symbolic"} + /> - { - Backlight.getDefault()!.brightness = value - }} - /> - - slidersPages?.toggle(PageBacklight)} /> - - } - - } + { + bklight.brightness = value + }} + /> + + slidersPages?.toggle(PageBacklight)} /> + + } + slidersPages = self} /> diff --git a/src/widget/control-center/pages/Backlight.tsx b/src/widget/control-center/pages/Backlight.tsx index 4683384..6fa92b7 100644 --- a/src/widget/control-center/pages/Backlight.tsx +++ b/src/widget/control-center/pages/Backlight.tsx @@ -1,6 +1,6 @@ import { Astal, Gtk } from "ags/gtk4"; import { tr } from "../../../i18n/intl"; -import { Backlight } from "../../../modules/backlight"; +import { Backlights } from "../../../modules/backlight"; import { Page } from "./Page"; import { createBinding, With } from "ags"; import { addSliderMarksFromMinMax } from "../../../modules/utils"; @@ -11,8 +11,8 @@ export const PageBacklight = new Page({ title: tr("control_center.pages.backlight.title"), description: tr("control_center.pages.backlight.description"), content: () => ( - - {(bklights: Array) => bklights.length > 0 && + + {(bklights: Array) => bklights.length > 0 && {bklights.map((bklight, i) => Backlight.scan() + actionClicked: () => Backlights.getDefault().scan() }] });