256 lines
7.7 KiB
JavaScript
Executable File
256 lines
7.7 KiB
JavaScript
Executable File
// SPDX-FileCopyrightText: GSConnect Developers https://github.com/GSConnect
|
|
//
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
import Gio from 'gi://Gio';
|
|
import GjsPrivate from 'gi://GjsPrivate';
|
|
import GLib from 'gi://GLib';
|
|
import GObject from 'gi://GObject';
|
|
|
|
|
|
/*
|
|
* Some utility methods
|
|
*/
|
|
function toDBusCase(string) {
|
|
return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => {
|
|
return ltr.toUpperCase();
|
|
}).replace(/[\s_-]+/g, '');
|
|
}
|
|
|
|
function toUnderscoreCase(string) {
|
|
return string.replace(/(?:^\w|[A-Z]|_|\b\w)/g, (ltr, offset) => {
|
|
if (ltr === '_')
|
|
return '';
|
|
|
|
return (offset > 0) ? `_${ltr.toLowerCase()}` : ltr.toLowerCase();
|
|
}).replace(/[\s-]+/g, '');
|
|
}
|
|
|
|
|
|
/**
|
|
* DBus.Interface represents a DBus interface bound to an object instance, meant
|
|
* to be exported over DBus.
|
|
*/
|
|
export const Interface = GObject.registerClass({
|
|
GTypeName: 'GSConnectDBusInterface',
|
|
Implements: [Gio.DBusInterface],
|
|
Properties: {
|
|
'g-instance': GObject.ParamSpec.object(
|
|
'g-instance',
|
|
'Instance',
|
|
'The delegate GObject',
|
|
GObject.ParamFlags.READWRITE,
|
|
GObject.Object.$gtype
|
|
),
|
|
},
|
|
}, class Interface extends GjsPrivate.DBusImplementation {
|
|
|
|
_init(params) {
|
|
super._init({
|
|
g_instance: params.g_instance,
|
|
g_interface_info: params.g_interface_info,
|
|
});
|
|
|
|
// Cache member lookups
|
|
this._instanceHandlers = [];
|
|
this._instanceMethods = {};
|
|
this._instanceProperties = {};
|
|
|
|
const info = this.get_info();
|
|
this.connect('handle-method-call', this._call.bind(this._instance, info));
|
|
this.connect('handle-property-get', this._get.bind(this._instance, info));
|
|
this.connect('handle-property-set', this._set.bind(this._instance, info));
|
|
|
|
// Automatically forward known signals
|
|
const id = this._instance.connect('notify', this._notify.bind(this));
|
|
this._instanceHandlers.push(id);
|
|
|
|
for (const signal of info.signals) {
|
|
const type = `(${signal.args.map(arg => arg.signature).join('')})`;
|
|
const id = this._instance.connect(
|
|
signal.name,
|
|
this._emit.bind(this, signal.name, type)
|
|
);
|
|
|
|
this._instanceHandlers.push(id);
|
|
}
|
|
|
|
// Export if connection and object path were given
|
|
if (params.g_connection && params.g_object_path)
|
|
this.export(params.g_connection, params.g_object_path);
|
|
}
|
|
|
|
get g_instance() {
|
|
if (this._instance === undefined)
|
|
this._instance = null;
|
|
|
|
return this._instance;
|
|
}
|
|
|
|
set g_instance(instance) {
|
|
this._instance = instance;
|
|
}
|
|
|
|
/**
|
|
* Invoke an instance's method for a DBus method call.
|
|
*
|
|
* @param {Gio.DBusInterfaceInfo} info - The DBus interface
|
|
* @param {DBus.Interface} iface - The DBus interface
|
|
* @param {string} name - The DBus method name
|
|
* @param {GLib.Variant} parameters - The method parameters
|
|
* @param {Gio.DBusMethodInvocation} invocation - The method invocation info
|
|
*/
|
|
async _call(info, iface, name, parameters, invocation) {
|
|
let retval;
|
|
|
|
// Invoke the instance method
|
|
try {
|
|
const args = parameters.unpack().map(parameter => {
|
|
if (parameter.get_type_string() === 'h') {
|
|
const message = invocation.get_message();
|
|
const fds = message.get_unix_fd_list();
|
|
const idx = parameter.deepUnpack();
|
|
return fds.get(idx);
|
|
} else {
|
|
return parameter.recursiveUnpack();
|
|
}
|
|
});
|
|
|
|
retval = await this[name](...args);
|
|
} catch (e) {
|
|
if (e instanceof GLib.Error) {
|
|
invocation.return_gerror(e);
|
|
} else {
|
|
// likely to be a normal JS error
|
|
if (!e.name.includes('.'))
|
|
e.name = `org.gnome.gjs.JSError.${e.name}`;
|
|
|
|
invocation.return_dbus_error(e.name, e.message);
|
|
}
|
|
|
|
logError(e, `${this}: ${name}`);
|
|
return;
|
|
}
|
|
|
|
// `undefined` is an empty tuple on DBus
|
|
if (retval === undefined)
|
|
retval = new GLib.Variant('()', []);
|
|
|
|
// Return the instance result or error
|
|
try {
|
|
if (!(retval instanceof GLib.Variant)) {
|
|
const args = info.lookup_method(name).out_args;
|
|
retval = new GLib.Variant(
|
|
`(${args.map(arg => arg.signature).join('')})`,
|
|
(args.length === 1) ? [retval] : retval
|
|
);
|
|
}
|
|
|
|
invocation.return_value(retval);
|
|
} catch (e) {
|
|
invocation.return_dbus_error(
|
|
'org.gnome.gjs.JSError.ValueError',
|
|
'Service implementation returned an incorrect value type'
|
|
);
|
|
|
|
logError(e, `${this}: ${name}`);
|
|
}
|
|
}
|
|
|
|
_nativeProp(obj, name) {
|
|
if (this._instanceProperties[name] === undefined) {
|
|
let propName = name;
|
|
|
|
if (propName in obj)
|
|
this._instanceProperties[name] = propName;
|
|
|
|
if (this._instanceProperties[name] === undefined) {
|
|
propName = toUnderscoreCase(name);
|
|
|
|
if (propName in obj)
|
|
this._instanceProperties[name] = propName;
|
|
}
|
|
}
|
|
|
|
return this._instanceProperties[name];
|
|
}
|
|
|
|
_emit(name, type, obj, ...args) {
|
|
this.emit_signal(name, new GLib.Variant(type, args));
|
|
}
|
|
|
|
_get(info, iface, name) {
|
|
const nativeValue = this[iface._nativeProp(this, name)];
|
|
const propertyInfo = info.lookup_property(name);
|
|
|
|
if (nativeValue === undefined || propertyInfo === null)
|
|
return null;
|
|
|
|
return new GLib.Variant(propertyInfo.signature, nativeValue);
|
|
}
|
|
|
|
_set(info, iface, name, value) {
|
|
const nativeValue = value.recursiveUnpack();
|
|
|
|
this[iface._nativeProp(this, name)] = nativeValue;
|
|
}
|
|
|
|
_notify(obj, pspec) {
|
|
const name = toDBusCase(pspec.name);
|
|
const propertyInfo = this.get_info().lookup_property(name);
|
|
|
|
if (propertyInfo === null)
|
|
return;
|
|
|
|
this.emit_property_changed(
|
|
name,
|
|
new GLib.Variant(
|
|
propertyInfo.signature,
|
|
// Adjust for GJS's '-'/'_' conversion
|
|
this._instance[pspec.name.replace(/-/gi, '_')]
|
|
)
|
|
);
|
|
}
|
|
|
|
destroy() {
|
|
try {
|
|
for (const id of this._instanceHandlers)
|
|
this._instance.disconnect(id);
|
|
this._instanceHandlers = [];
|
|
|
|
this.flush();
|
|
this.unexport();
|
|
} catch (e) {
|
|
logError(e);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* Get a new, dedicated DBus connection on @busType
|
|
*
|
|
* @param {Gio.BusType} [busType] - a Gio.BusType constant
|
|
* @param {Gio.Cancellable} [cancellable] - an optional Gio.Cancellable
|
|
* @return {Promise<Gio.DBusConnection>} A new DBus connection
|
|
*/
|
|
export function newConnection(busType = Gio.BusType.SESSION, cancellable = null) {
|
|
return new Promise((resolve, reject) => {
|
|
Gio.DBusConnection.new_for_address(
|
|
Gio.dbus_address_get_for_bus_sync(busType, cancellable),
|
|
Gio.DBusConnectionFlags.AUTHENTICATION_CLIENT |
|
|
Gio.DBusConnectionFlags.MESSAGE_BUS_CONNECTION,
|
|
null,
|
|
cancellable,
|
|
(connection, res) => {
|
|
try {
|
|
resolve(Gio.DBusConnection.new_for_address_finish(res));
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
}
|
|
);
|
|
|
|
});
|
|
}
|
|
|