154 lines
4.5 KiB
TypeScript

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<any> = new Promise<any>(
(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<Prop extends keyof this & string>(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;
}
}