🔧 chore(modules/backlight): better implementation
This commit is contained in:
+168
-162
@@ -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<Backlight> = [];
|
||||
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<Backlight>)
|
||||
get default() { return Backlight.default; }
|
||||
|
||||
@getter(Object as unknown as ParamSpec<Array<Backlight>>)
|
||||
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<Backlight> {
|
||||
const dir = Gio.File.new_for_path(`/sys/class/backlight`),
|
||||
backlights: Array<Backlight> = [];
|
||||
|
||||
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<Backlights>;
|
||||
|
||||
|
||||
this.#monitor = monitorFile(`/sys/class/backlight/${name}/brightness`, () => {
|
||||
this.#brightness = this.readBrightness();
|
||||
this.notify("brightness");
|
||||
this.emit("brightness-changed", this.brightness);
|
||||
});
|
||||
}
|
||||
#backlights: Array<Backlight> = [];
|
||||
#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<Array<Backlight>>)
|
||||
get backlights() { return this.#backlights; }
|
||||
|
||||
return this.#brightness ?? this.#maxBrightness ?? 0;
|
||||
}
|
||||
@getter(Backlight as unknown as ParamSpec<Backlight|null>)
|
||||
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<Backlight> {
|
||||
const dir = Gio.File.new_for_path(`/sys/class/backlight`),
|
||||
backlights: Array<Backlight> = [];
|
||||
|
||||
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<Backlight>;
|
||||
|
||||
public emit<Signal extends keyof typeof this.$signals>(
|
||||
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 extends keyof typeof this.$signals>(
|
||||
signal: Signal,
|
||||
...args: Parameters<(typeof this.$signals)[Signal]>
|
||||
): void {
|
||||
super.emit(signal, ...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user