diff --git a/src/modules/utils.ts b/src/modules/utils.ts index af015f9..821e405 100644 --- a/src/modules/utils.ts +++ b/src/modules/utils.ts @@ -6,7 +6,7 @@ import { getSymbolicIcon } from "./apps"; import GLib from "gi://GLib?version=2.0"; import Gio from "gi://Gio?version=2.0"; -import GObject from "gi://GObject?version=2.0"; +import GObject from "ags/gobject"; /** gnim doesn't export this, so we need to do it again */ @@ -230,8 +230,7 @@ export function construct(klass: Class, props: Record { klass[k as keyof Class] = v.get() as Class[keyof Class]; if(isGObject) - klass.notify(k.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}` - )); + klass.notify(k.replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`)); })); klass[k as keyof Class] = v.get() as Class[keyof Class]; @@ -277,3 +276,28 @@ export function createConnetions< add(gobj, gobj.connect(sig as string, callback as never)); }); } + +export function secureBinding< + GObj extends GObject.Object, + Prop extends keyof GObj, + Returns extends unknown|undefined +>( + gobj: GObj, + prop: Prop, + defaultValue: Returns +): Accessor { + const get = () => gobj ? gobj[prop] : defaultValue; + + return new Accessor( + get, + (notify) => { + const gobjectProp = (prop as string).replace(/[A-Z]/g, (s) => `-${s.toLowerCase()}`); + const id = gobj.connect(`notify::${gobjectProp}`, () => notify()); + return () => { + try { + gobj.disconnect(id); + } catch(e) {} + } + } + ); +} diff --git a/src/window/osd/index.tsx b/src/window/osd/index.tsx index 9c8d72b..0d04d0d 100644 --- a/src/window/osd/index.tsx +++ b/src/window/osd/index.tsx @@ -1,64 +1,35 @@ import { Astal, Gtk } from "ags/gtk4"; -import { Accessor, createBinding, createState, With } from "ags"; +import { createBinding, createState, With } from "ags"; import { Wireplumber } from "../../modules/volume"; import { Windows } from "../../windows"; import { Backlights } from "../../modules/backlight"; -import { construct, variableToBoolean } from "../../modules/utils"; +import { secureBinding, variableToBoolean } from "../../modules/utils"; -import GObject, { ParamSpec, property, register } from "ags/gobject"; import Pango from "gi://Pango?version=1.0"; import GLib from "gi://GLib?version=2.0"; +import AstalWp from "gi://AstalWp?version=0.1"; +import OSDMode from "./modules/osdmode"; -@register({ GTypeName: "OSDMode" }) -export class OSDMode extends GObject.Object { - readonly #subs: Array<() => void> = []; - @property(String) - icon: string = "image-missing"; - @property(Number) - value: number = 0; - @property(Number) - max: number = 100; - @property(String as unknown as ParamSpec) - text: string|null = null; - - constructor(props: { - icon: string | Accessor; - value: number | Accessor; - max?: number | Accessor; - text?: string | Accessor; - }) { - super(); - this.#subs = construct(this, props); - } - - vfunc_dispose(): void { - this.#subs.forEach(s => s()); - } -} - export const OSDModes = { - SINK: () => new OSDMode({ - icon: createBinding(Wireplumber.getWireplumber().defaultSpeaker, "volumeIcon"), - value: createBinding(Wireplumber.getWireplumber().defaultSpeaker, "volume"), - text: createBinding(Wireplumber.getWireplumber().defaultSpeaker, "description"), + sink: new OSDMode({ + icon: secureBinding(AstalWp.get_default().defaultSpeaker, "volumeIcon", + "audio-volume-high-symbolic"), + value: secureBinding(Wireplumber.getWireplumber().defaultSpeaker, "volume", 50), + text: secureBinding(Wireplumber.getWireplumber().defaultSpeaker, "description", + "Unknown Speaker"), max: Wireplumber.getDefault().getMaxSinkVolume() / 100 }), - BRIGHTNESS: () => Backlights.getDefault().available ? new OSDMode({ - icon: "display-brightness-symbolic", - value: createBinding(Backlights.getDefault().default, "brightness"), - max: createBinding(Backlights.getDefault().default, "maxBrightness"), - text: createBinding(Backlights.getDefault().default, "name") - }) - : new OSDMode({ + brightness: new OSDMode({ icon: "display-brightness-symbolic", - value: 100, - max: 100, - text: "No Backlight found" + value: secureBinding(Backlights.getDefault().default, "brightness", 100), + max: secureBinding(Backlights.getDefault().default, "maxBrightness", 100), + text: secureBinding(Backlights.getDefault().default, "name", "Unknown Backlight"), + available: createBinding(Backlights.getDefault(), "available") }) } -const [osdMode, setOSDMode] = createState(OSDModes.SINK); +const [osdMode, setOSDMode] = createState(OSDModes.sink); let osdTimer: (GLib.Source|undefined), osdTimeout = 3500; export const OSD = (mon: number) => @@ -66,9 +37,10 @@ export const OSD = (mon: number) => anchor={Astal.WindowAnchor.BOTTOM} focusable={false} marginBottom={80} monitor={mon}> - f)}> - {(_: () => OSDMode) => { - const mode = _ as unknown as OSDMode; // for some reason, gnim runs this function :broken_heart: + + {(mode: OSDMode) => { + if(!mode.available) return; + return void> = []; + + @property(String) + icon: string = "image-missing"; + + @property(Number) + value: number = 0; + + @property(Number) + max: number = 100; + + @property(gtype(String)) + text: string|null = null; + + @property(Boolean) + available: boolean = true; + + constructor(props: { + icon: string | Accessor; + value: number | Accessor; + max?: number | Accessor; + text?: string | Accessor; + available?: boolean | Accessor; + }) { + super(); + this.#subs = construct(this, props); + } + + vfunc_dispose(): void { + this.#subs.forEach(s => s()); + } +}