import Gio from "gi://Gio"; import GObject, { register } from "astal/gobject"; import { getDbusXml } from "@/utils"; import GLib from "gi://GLib"; let dbusConnectionResolve: ((connection: any) => void) | null = null; export const Connection: Promise = new Promise( (resolve) => (dbusConnectionResolve = resolve), ); const ownerId = Gio.bus_own_name( Gio.BusType.SESSION, "dev.ezri.VoidShell", Gio.BusNameOwnerFlags.NONE, (connection) => { dbusConnectionResolve?.(connection); }, () => {}, () => {}, ); @register() export class DBusObject extends GObject.Object { protected dbusObj: Gio.DBusExportedObject | null = null; #objectPath: string; constructor(iface: string, objectPath: string) { super(); this.#objectPath = objectPath; getDbusXml(iface).then(async (xml) => { try { this.dbusObj = Gio.DBusExportedObject.wrapJSObject(xml, this.proxify()); this.dbusObj.export(await Connection, objectPath); } catch (e) { console.error(`Error exporting to D-Bus: ${e}`); } }); } /** * Creates a proxy of the dbus object that returns object paths of DBusObjects * rather than the objects themselves. * This proxy should be used with the DBus export, and should not be used in any other context. * * Due to the way this works, setting DBus object paths is NOT SUPPORTED! Use a method if you really need to do that. */ private proxify() { return new Proxy(this, { get(target, property: keyof DBusObject) { const marshall_func = `marshall_${String(property)}` as string & keyof typeof target; if ( marshall_func in target && typeof target[marshall_func] === "function" ) { const variant: GLib.Variant = target[marshall_func](); return variant.deepUnpack(); } if (!(property in target)) { return undefined; } if (target[property] instanceof DBusObject) { return target[property].objectPath; } if (target[property] instanceof Array) { return target[property].map((prop) => prop instanceof DBusObject ? prop.objectPath : prop, ); } return target[property]; }, }); } private marshallValue( property: string & keyof this, value: any, ): GLib.Variant | null { const marshall_func_name = `marshall_${property}` as string & keyof this; if ( marshall_func_name in this && typeof this[marshall_func_name] === "function" ) { return this[marshall_func_name](); } switch (typeof value) { case "string": return GLib.Variant.new_string(value); case "number": return GLib.Variant.new_double(value); case "bigint": return GLib.Variant.new_int64(Number(value)); case "boolean": return GLib.Variant.new_boolean(value); case "symbol": return null; case "object": if (value instanceof Array) { if (value.every((val) => val instanceof DBusObject)) { return new GLib.Variant( "ao", value.map((val) => val.objectPath), ); } return new GLib.Variant( "av", value.map(this.marshallValue.bind(this)), ); } else { console.warn( "No marshall function found for object type, cannot implicitly marshall JS objects. Returning null.", ); return null; } case "undefined": return null; case "function": return null; } } // private deepMarshallObject(obj: Object): GLib.Variant { // if (obj instanceof DBusObject) { // return GLib.Variant.new_object_path(obj.objectPath); // } // if (obj instanceof Array) { // return new GLib.Variant( // "av", // obj.map(this.marshallValue.bind(this)).filter((val) => val !== null), // ); // } // const newObj = Object.entries(obj).reduce((obj, [key, value]) => { // const toEmit = this.marshallValue(, value); // if (toEmit !== null) { // obj[key] = toEmit; // } // return obj; // }, {} as any); // return new GLib.Variant("a{sv}", newObj); // } protected emitDBusProperty(property: Prop) { const toEmit = this.marshallValue(property, this[property]); if (!toEmit) { return; } this.dbusObj?.emit_property_changed(property, toEmit); } get objectPath(): string { return this.#objectPath; } }