🔧 chore(modules/backlight): better implementation

This commit is contained in:
retrozinndev
2025-08-27 13:13:20 -03:00
parent e765a33335
commit f25996d5eb
3 changed files with 192 additions and 188 deletions
+108 -102
View File
@@ -1,92 +1,46 @@
import { monitorFile, readFile, writeFile } from "ags/file"; import { monitorFile, readFile } from "ags/file";
import GObject, { getter, ParamSpec, register, setter, signal } from "ags/gobject"; import { exec } from "ags/process";
import GObject, { getter, ParamSpec, setter, signal } from "ags/gobject";
import Gio from "gi://Gio?version=2.0"; import Gio from "gi://Gio?version=2.0";
export { Backlight }; export namespace Backlights {
@register({ GTypeName: "Backlight" })
class Backlight extends GObject.Object {
private static _backlights: Array<Backlight> = []; let instance: Backlights;
public static get backlights() {
return this._backlights;
};
private static default: Backlight; export function getDefault(): Backlights {
if(!instance)
instance = new Backlights;
readonly #name: string; return instance;
#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<Backlight>)
get default() { return Backlight.default; }
@getter(Object as unknown as ParamSpec<Array<Backlight>>)
get backlights() { return Backlight.backlights; }
@getter(Boolean)
get isDefault() { return this.path === this.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) export class Backlights extends GObject.Object {
get maxBrightness() { return this.#maxBrightness;}; static {
GObject.registerClass({
GTypeName: "Backlights"
}, this);
}
public static $gtype: GObject.GType<Backlights>;
#backlights: Array<Backlight> = [];
#default: Backlight|null = null;
#available: boolean = false;
@getter(Array as unknown as ParamSpec<Array<Backlight>>)
get backlights() { return this.#backlights; }
@getter(Backlight as unknown as ParamSpec<Backlight|null>)
get default() { return this.#default; }
/** true if there are any backlights available */
@getter(Boolean) @getter(Boolean)
get available() { return this.#available; } get available() { return this.#available; }
public scan(): Array<Backlight> {
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;
}
return num;
}
public static scan(): Array<Backlight> {
const dir = Gio.File.new_for_path(`/sys/class/backlight`), const dir = Gio.File.new_for_path(`/sys/class/backlight`),
backlights: Array<Backlight> = []; backlights: Array<Backlight> = [];
@@ -103,21 +57,89 @@ class Backlight extends GObject.Object {
return []; return [];
} }
Backlight._backlights = backlights; if(backlights.length < 1 && this.#available === true) {
backlights.forEach(bk => bk.notify("backlights")); 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 backlights;
} }
public setDefault(bk: Backlight): void {
this.#default = bk;
this.notify("default");
}
constructor(scan: boolean = true) {
super();
scan && this.scan();
}
}
export class Backlight extends GObject.Object {
static {
GObject.registerClass({
GTypeName: "Backlight"
}, this);
}
public static $gtype: GObject.GType<Backlight>;
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 // intel_backlight is mostly the default on laptops
constructor(name: string = "intel_backlight") { constructor(name: string = "intel_backlight") {
super(); super();
// check if backlight exists // check if backlight exists
if(!Gio.File.new_for_path(`/sys/class/backlight/${name}/brightness`).query_exists(null)) { 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}"`); 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.#name = name;
this.#path = `/sys/class/backlight/${name}`; this.#path = `/sys/class/backlight/${name}`;
@@ -147,7 +169,7 @@ class Backlight extends GObject.Object {
private writeBrightness(level: number): boolean { private writeBrightness(level: number): boolean {
try { try {
writeFile(`${this.#path}/brightness`, level.toString()); exec(`brightnessctl -d ${this.#name} s ${level}`);
return true; return true;
} catch(e) { } catch(e) {
console.error(`Backlight: Couldn't set brightness for "${this.#name}". Stderr: ${e}`); console.error(`Backlight: Couldn't set brightness for "${this.#name}". Stderr: ${e}`);
@@ -156,26 +178,9 @@ class Backlight extends GObject.Object {
return false; return false;
} }
public static getDefault(): Backlight|null {
if(this.default)
return this.default;
if(this.backlights.length < 1)
this.scan();
const first = this.backlights[0];
if(first) {
try {
this.default = first;
return this.default;
} catch(_) {}
}
return null;
}
vfunc_dispose(): void { vfunc_dispose(): void {
this.#monitor.cancel(); this.#monitor.cancel();
getDefault().disconnect(this.#conn);
} }
public emit<Signal extends keyof typeof this.$signals>( public emit<Signal extends keyof typeof this.$signals>(
@@ -184,4 +189,5 @@ class Backlight extends GObject.Object {
): void { ): void {
super.emit(signal, ...args); super.emit(signal, ...args);
} }
}
} }
+5 -7
View File
@@ -4,7 +4,7 @@ import { Pages } from "./Pages";
import { PageSound } from "./pages/Sound"; import { PageSound } from "./pages/Sound";
import { PageMicrophone } from "./pages/Microphone"; import { PageMicrophone } from "./pages/Microphone";
import { createBinding, With } from "ags"; import { createBinding, With } from "ags";
import { Backlight } from "../../modules/backlight"; import { Backlights } from "../../modules/backlight";
import AstalWp from "gi://AstalWp"; import AstalWp from "gi://AstalWp";
import { PageBacklight } from "./pages/Backlight"; import { PageBacklight } from "./pages/Backlight";
@@ -50,10 +50,9 @@ export function Sliders() {
slidersPages?.toggle(PageMicrophone)} /> slidersPages?.toggle(PageMicrophone)} />
</Gtk.Box>} </Gtk.Box>}
</With> </With>
<Gtk.Box visible={Boolean(Backlight.getDefault())}> <Gtk.Box visible={createBinding(Backlights.getDefault(), "available")}>
{Backlight.getDefault() && <With value={createBinding(Backlights.getDefault(), "default")}>
<With value={createBinding(Backlight.getDefault()!, "default")}> {(bklight: Backlights.Backlight|null) => bklight &&
{(bklight: Backlight) => bklight &&
<Gtk.Box class={"backlight"} spacing={3}> <Gtk.Box class={"backlight"} spacing={3}>
<Gtk.Button onClicked={() => { <Gtk.Button onClicked={() => {
bklight.brightness = bklight.maxBrightness bklight.brightness = bklight.maxBrightness
@@ -63,7 +62,7 @@ export function Sliders() {
<Astal.Slider drawValue={false} hexpand value={createBinding(bklight, "brightness")} <Astal.Slider drawValue={false} hexpand value={createBinding(bklight, "brightness")}
max={bklight.maxBrightness} max={bklight.maxBrightness}
onChangeValue={(_, __, value) => { onChangeValue={(_, __, value) => {
Backlight.getDefault()!.brightness = value bklight.brightness = value
}} }}
/> />
<Gtk.Button class={"more"} iconName={"go-next-symbolic"} onClicked={() => <Gtk.Button class={"more"} iconName={"go-next-symbolic"} onClicked={() =>
@@ -71,7 +70,6 @@ export function Sliders() {
</Gtk.Box> </Gtk.Box>
} }
</With> </With>
}
</Gtk.Box> </Gtk.Box>
<Pages $={(self) => slidersPages = self} /> <Pages $={(self) => slidersPages = self} />
</Gtk.Box> </Gtk.Box>
@@ -1,6 +1,6 @@
import { Astal, Gtk } from "ags/gtk4"; import { Astal, Gtk } from "ags/gtk4";
import { tr } from "../../../i18n/intl"; import { tr } from "../../../i18n/intl";
import { Backlight } from "../../../modules/backlight"; import { Backlights } from "../../../modules/backlight";
import { Page } from "./Page"; import { Page } from "./Page";
import { createBinding, With } from "ags"; import { createBinding, With } from "ags";
import { addSliderMarksFromMinMax } from "../../../modules/utils"; import { addSliderMarksFromMinMax } from "../../../modules/utils";
@@ -11,8 +11,8 @@ export const PageBacklight = new Page({
title: tr("control_center.pages.backlight.title"), title: tr("control_center.pages.backlight.title"),
description: tr("control_center.pages.backlight.description"), description: tr("control_center.pages.backlight.description"),
content: () => ( content: () => (
<With value={createBinding(Backlight.getDefault()!, "backlights")}> <With value={createBinding(Backlights.getDefault(), "backlights")}>
{(bklights: Array<Backlight>) => bklights.length > 0 && {(bklights: Array<Backlights.Backlight>) => bklights.length > 0 &&
<Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={6}> <Gtk.Box orientation={Gtk.Orientation.VERTICAL} spacing={6}>
{bklights.map((bklight, i) => {bklights.map((bklight, i) =>
<Gtk.Box class={"bklight"} orientation={Gtk.Orientation.VERTICAL} <Gtk.Box class={"bklight"} orientation={Gtk.Orientation.VERTICAL}
@@ -36,6 +36,6 @@ export const PageBacklight = new Page({
headerButtons: [{ headerButtons: [{
icon: "arrow-circular-top-right", icon: "arrow-circular-top-right",
tooltipText: tr("control_center.pages.backlight.refresh"), tooltipText: tr("control_center.pages.backlight.refresh"),
actionClicked: () => Backlight.scan() actionClicked: () => Backlights.getDefault().scan()
}] }]
}); });