308 lines
8.9 KiB
JavaScript
308 lines
8.9 KiB
JavaScript
|
'use strict';
|
||
|
|
||
|
const Gio = imports.gi.Gio;
|
||
|
const GjsPrivate = imports.gi.GjsPrivate;
|
||
|
const GLib = imports.gi.GLib;
|
||
|
const GObject = imports.gi.GObject;
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Some utility methods
|
||
|
*/
|
||
|
function toCamelCase(string) {
|
||
|
return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => {
|
||
|
return (offset === 0) ? ltr.toLowerCase() : ltr.toUpperCase();
|
||
|
}).replace(/[\s_-]+/g, '');
|
||
|
}
|
||
|
|
||
|
function toDBusCase(string) {
|
||
|
return string.replace(/(?:^\w|[A-Z]|\b\w)/g, (ltr, offset) => {
|
||
|
return ltr.toUpperCase();
|
||
|
}).replace(/[\s_-]+/g, '');
|
||
|
}
|
||
|
|
||
|
function toHyphenCase(string) {
|
||
|
return string.replace(/(?:[A-Z])/g, (ltr, offset) => {
|
||
|
return (offset > 0) ? '-' + ltr.toLowerCase() : ltr.toLowerCase();
|
||
|
}).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, '');
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Build a GVariant type string from an method argument list.
|
||
|
*/
|
||
|
function _makeOutSignature(args) {
|
||
|
var ret = '(';
|
||
|
for (var i = 0; i < args.length; i++)
|
||
|
ret += args[i].signature;
|
||
|
|
||
|
return ret + ')';
|
||
|
}
|
||
|
|
||
|
|
||
|
/**
|
||
|
* DBus.Interface represents a DBus interface bound to an object instance, meant
|
||
|
* to be exported over DBus.
|
||
|
*/
|
||
|
var Interface = GObject.registerClass({
|
||
|
GTypeName: 'GSConnectDBusInterface'
|
||
|
}, class Interface extends GjsPrivate.DBusImplementation {
|
||
|
|
||
|
_init(params) {
|
||
|
super._init({
|
||
|
g_interface_info: params.g_interface_info
|
||
|
});
|
||
|
|
||
|
this._exportee = params.g_instance;
|
||
|
|
||
|
if (params.g_object_path) {
|
||
|
this.g_object_path = params.g_object_path;
|
||
|
}
|
||
|
|
||
|
// Bind Object
|
||
|
let info = this.get_info();
|
||
|
this._exportMethods(info);
|
||
|
this._exportProperties(info);
|
||
|
this._exportSignals(info);
|
||
|
|
||
|
// 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);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// HACK: for some reason the getter doesn't work properly on the parent
|
||
|
get g_interface_info() {
|
||
|
return this.get_info();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Invoke an instance's method for a DBus method call. Supports promises.
|
||
|
*/
|
||
|
async _call(info, memberName, parameters, invocation) {
|
||
|
// Convert member casing to native casing
|
||
|
let nativeName;
|
||
|
|
||
|
if (this[memberName] !== undefined) {
|
||
|
nativeName = memberName;
|
||
|
} else if (this[toUnderscoreCase(memberName)] !== undefined) {
|
||
|
nativeName = toUnderscoreCase(memberName);
|
||
|
} else if (this[toCamelCase(memberName)] !== undefined) {
|
||
|
nativeName = toCamelCase(memberName);
|
||
|
}
|
||
|
|
||
|
let retval;
|
||
|
|
||
|
try {
|
||
|
parameters = parameters.unpack().map(parameter => {
|
||
|
if (parameter.get_type_string() === 'h') {
|
||
|
let message = invocation.get_message();
|
||
|
let fds = message.get_unix_fd_list();
|
||
|
let idx = parameter.deepUnpack();
|
||
|
return fds.get(idx);
|
||
|
} else {
|
||
|
return parameter.recursiveUnpack();
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// await all method invocations to support Promise returns
|
||
|
retval = await this[nativeName].apply(this, parameters);
|
||
|
} catch (e) {
|
||
|
if (e instanceof GLib.Error) {
|
||
|
invocation.return_gerror(e);
|
||
|
} else {
|
||
|
let name = e.name;
|
||
|
|
||
|
if (name.includes('.')) {
|
||
|
// likely to be a normal JS error
|
||
|
name = `org.gnome.gjs.JSError.${name}`;
|
||
|
}
|
||
|
|
||
|
invocation.return_dbus_error(name, e.message);
|
||
|
logError(e, `${this}: ${memberName}`);
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// undefined (no return value) is the empty tuple
|
||
|
if (retval === undefined) {
|
||
|
retval = new GLib.Variant('()', []);
|
||
|
}
|
||
|
|
||
|
// Try manually packing a variant
|
||
|
try {
|
||
|
if (!(retval instanceof GLib.Variant)) {
|
||
|
let outArgs = info.lookup_method(memberName).out_args;
|
||
|
retval = new GLib.Variant(
|
||
|
_makeOutSignature(outArgs),
|
||
|
(outArgs.length == 1) ? [retval] : retval
|
||
|
);
|
||
|
}
|
||
|
|
||
|
invocation.return_value(retval);
|
||
|
|
||
|
// Without a response, the client will wait for timeout
|
||
|
} catch (e) {
|
||
|
invocation.return_dbus_error(
|
||
|
'org.gnome.gjs.JSError.ValueError',
|
||
|
'Service implementation returned an incorrect value type'
|
||
|
);
|
||
|
|
||
|
logError(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_exportMethods(info) {
|
||
|
if (info.methods.length === 0) return;
|
||
|
|
||
|
this.connect('handle-method-call', (impl, name, parameters, invocation) => {
|
||
|
return this._call.call(
|
||
|
this._exportee,
|
||
|
this.g_interface_info,
|
||
|
name,
|
||
|
parameters,
|
||
|
invocation
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_get(info, propertyName) {
|
||
|
let propertyInfo = info.lookup_property(propertyName);
|
||
|
let value;
|
||
|
|
||
|
// Check before assuming native DBus case
|
||
|
if (this[propertyName] !== undefined) {
|
||
|
value = this[propertyName];
|
||
|
} else {
|
||
|
value = this[toUnderscoreCase(propertyName)];
|
||
|
}
|
||
|
|
||
|
if (value !== undefined) {
|
||
|
return new GLib.Variant(propertyInfo.signature, value);
|
||
|
} else {
|
||
|
return null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_set(info, name, value) {
|
||
|
let nativeValue = value.recursiveUnpack();
|
||
|
|
||
|
if (this[name] !== undefined) {
|
||
|
this[name] = nativeValue;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (!this._nativeCase) {
|
||
|
if (this[toUnderscoreCase(name)] !== undefined) {
|
||
|
this._nativeCase = toUnderscoreCase;
|
||
|
} else if (this[toCamelCase(name)] !== undefined) {
|
||
|
this._nativeCase = toCamelCase;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
this[this._nativeCase(name)] = nativeValue;
|
||
|
}
|
||
|
|
||
|
_exportProperties(info) {
|
||
|
if (info.properties.length === 0) return;
|
||
|
|
||
|
this.connect('handle-property-get', (impl, name) => {
|
||
|
return this._get.call(this._exportee, info, name);
|
||
|
});
|
||
|
|
||
|
this.connect('handle-property-set', (impl, name, value) => {
|
||
|
return this._set.call(this._exportee, info, name, value);
|
||
|
});
|
||
|
|
||
|
this._exportee.connect('notify', (obj, paramSpec) => {
|
||
|
let name = toDBusCase(paramSpec.name);
|
||
|
let propertyInfo = this.g_interface_info.lookup_property(name);
|
||
|
|
||
|
if (propertyInfo) {
|
||
|
this.emit_property_changed(
|
||
|
name,
|
||
|
new GLib.Variant(
|
||
|
propertyInfo.signature,
|
||
|
// Adjust for GJS's '-'/'_' conversion
|
||
|
this._exportee[paramSpec.name.replace(/-/gi, '_')]
|
||
|
)
|
||
|
);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
_exportSignals(info) {
|
||
|
for (let signal of info.signals) {
|
||
|
this._exportee.connect(signal.name, (obj, ...args) => {
|
||
|
this.emit_signal(
|
||
|
signal.name,
|
||
|
new GLib.Variant(
|
||
|
`(${signal.args.map(arg => arg.signature).join('')})`,
|
||
|
args
|
||
|
)
|
||
|
);
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
|
||
|
destroy() {
|
||
|
this.flush();
|
||
|
this.unexport();
|
||
|
GObject.signal_handlers_destroy(this);
|
||
|
}
|
||
|
});
|
||
|
|
||
|
|
||
|
/**
|
||
|
* Get the DBus connection on @busType
|
||
|
*
|
||
|
* @param {Gio.BusType} [busType] - a Gio.BusType constant
|
||
|
* @param (Gio.Cancellable} [cancellable] - an optional Gio.Cancellable
|
||
|
*/
|
||
|
function getConnection(busType = Gio.BusType.SESSION, cancellable = null) {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
Gio.bus_get(busType, cancellable, (connection, res) => {
|
||
|
try {
|
||
|
resolve(Gio.bus_get_finish(res));
|
||
|
} catch (e) {
|
||
|
reject(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
|
||
|
*/
|
||
|
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);
|
||
|
}
|
||
|
}
|
||
|
);
|
||
|
|
||
|
});
|
||
|
}
|
||
|
|