From 751ad07e151f0abfd111c4d800d172f726ffa33c Mon Sep 17 00:00:00 2001 From: 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> Date: Wed, 15 Jan 2025 20:58:01 +1100 Subject: brightness and volume osds --- services/monitors.ts | 127 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 services/monitors.ts (limited to 'services') diff --git a/services/monitors.ts b/services/monitors.ts new file mode 100644 index 0000000..78a0161 --- /dev/null +++ b/services/monitors.ts @@ -0,0 +1,127 @@ +import { GObject, execAsync, property, register } from "astal"; +import AstalHyprland from "gi://AstalHyprland"; + +@register({ GTypeName: "Monitor" }) +export class Monitor extends GObject.Object { + readonly monitor: AstalHyprland.Monitor; + readonly width: number; + readonly height: number; + readonly id: number; + readonly serial: string; + readonly name: string; + readonly description: string; + + @property(AstalHyprland.Workspace) + get activeWorkspace() { + return this.monitor.activeWorkspace; + } + + isDdc: boolean = false; + busNum?: string; + + #brightness: number = 0; + + @property(Number) + get brightness() { + return this.#brightness; + } + + set brightness(value) { + value = Math.min(1, Math.max(0, value)); + + this.#brightness = value; + this.notify("brightness"); + execAsync( + this.isDdc + ? `ddcutil -b ${this.busNum} setvcp 10 ${Math.round(value * 100)}` + : `brightnessctl set ${Math.floor(value * 100)}% -q` + ).catch(console.error); + } + + constructor(monitor: AstalHyprland.Monitor) { + super(); + + this.monitor = monitor; + this.width = monitor.width; + this.height = monitor.height; + this.id = monitor.id; + this.serial = monitor.serial; + this.name = monitor.name; + this.description = monitor.description; + + monitor.connect("notify::active-workspace", () => this.notify("active-workspace")); + + execAsync("ddcutil detect --brief") + .then(out => { + this.isDdc = out.split("\n\n").some(display => { + if (!/^Display \d+/.test(display)) return false; + const lines = display.split("\n"); + if (lines[3].split(":")[3] !== monitor.serial) return false; + this.busNum = lines[1].split("/dev/i2c-")[1]; + return true; + }); + }) + .catch(() => (this.isDdc = false)) + .finally(async () => { + if (this.isDdc) { + const info = (await execAsync(`ddcutil -b ${this.busNum} getvcp 10 --brief`)).split(" "); + this.#brightness = Number(info[3]) / Number(info[4]); + } else + this.#brightness = + Number(await execAsync("brightnessctl get")) / Number(await execAsync("brightnessctl max")); + }); + } +} + +@register({ GTypeName: "Monitors" }) +export default class Monitors extends GObject.Object { + static instance: Monitors; + static get_default() { + if (!this.instance) this.instance = new Monitors(); + + return this.instance; + } + + readonly #map: Map = new Map(); + + @property(Object) + get map() { + return this.#map; + } + + @property(Object) + get list() { + return Array.from(this.#map.values()); + } + + @property(Monitor) + get active() { + return this.#map.get(AstalHyprland.get_default().focusedMonitor.id)!; + } + + #notify() { + this.notify("map"); + this.notify("list"); + } + + forEach(fn: (monitor: Monitor) => void) { + for (const monitor of this.#map.values()) fn(monitor); + } + + constructor() { + super(); + + const hyprland = AstalHyprland.get_default(); + + for (const monitor of hyprland.monitors) this.#map.set(monitor.id, new Monitor(monitor)); + if (this.#map.size > 0) this.#notify(); + + hyprland.connect("monitor-added", (_, monitor) => { + this.#map.set(monitor.id, new Monitor(monitor)); + this.#notify(); + }); + hyprland.connect("monitor-removed", (_, id) => this.#map.delete(id) && this.#notify()); + + hyprland.connect("notify::focused-monitor", () => this.notify("active")); + } +} -- cgit v1.2.3-freya