a5d33026f0
Returning a GLib.Error from a method invocation will encode the error when sent over the wire. In case the error itself is already an encoded remote error, just passing it on will result in double wrapping. Avoid that by stripping any remote error information before returning it. Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/3159>
213 lines
5.5 KiB
JavaScript
213 lines
5.5 KiB
JavaScript
import Gio from 'gi://Gio';
|
|
import GLib from 'gi://GLib';
|
|
|
|
import {programArgs} from 'system';
|
|
|
|
const Signals = imports.signals;
|
|
|
|
const IDLE_SHUTDOWN_TIME = 2; // s
|
|
|
|
export class ServiceImplementation {
|
|
constructor(info, objectPath) {
|
|
this._objectPath = objectPath;
|
|
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(info, this);
|
|
|
|
this._injectTracking('return_dbus_error');
|
|
this._injectTracking('return_error_literal');
|
|
this._injectTracking('return_gerror');
|
|
this._injectTracking('return_value');
|
|
this._injectTracking('return_value_with_unix_fd_list');
|
|
|
|
this._senders = new Map();
|
|
this._holdCount = 0;
|
|
|
|
this._shellName = this._getUniqueShellName();
|
|
|
|
this._hasSignals = this._dbusImpl.get_info().signals.length > 0;
|
|
this._shutdownTimeoutId = 0;
|
|
|
|
// subclasses may override this to disable automatic shutdown
|
|
this._autoShutdown = true;
|
|
|
|
this._queueShutdownCheck();
|
|
}
|
|
|
|
// subclasses may override this to own additional names
|
|
register() {
|
|
}
|
|
|
|
export() {
|
|
this._dbusImpl.export(Gio.DBus.session, this._objectPath);
|
|
}
|
|
|
|
unexport() {
|
|
this._dbusImpl.unexport();
|
|
}
|
|
|
|
hold() {
|
|
this._holdCount++;
|
|
}
|
|
|
|
release() {
|
|
if (this._holdCount === 0) {
|
|
logError(new Error('Unmatched call to release()'));
|
|
return;
|
|
}
|
|
|
|
this._holdCount--;
|
|
|
|
if (this._holdCount === 0)
|
|
this._queueShutdownCheck();
|
|
}
|
|
|
|
/**
|
|
* Complete @invocation with an appropriate error if @error is set;
|
|
* useful for implementing early returns from method implementations.
|
|
*
|
|
* @param {Gio.DBusMethodInvocation}
|
|
* @param {Error}
|
|
*
|
|
* @returns {bool} - true if @invocation was completed
|
|
*/
|
|
|
|
_handleError(invocation, error) {
|
|
if (error === null)
|
|
return false;
|
|
|
|
if (error instanceof GLib.Error) {
|
|
Gio.DBusError.strip_remote_error(error);
|
|
invocation.return_gerror(error);
|
|
} else {
|
|
let name = error.name;
|
|
if (!name.includes('.')) // likely a normal JS error
|
|
name = `org.gnome.gjs.JSError.${name}`;
|
|
invocation.return_dbus_error(name, error.message);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
_maybeShutdown() {
|
|
if (!this._autoShutdown)
|
|
return;
|
|
|
|
if (GLib.getenv('SHELL_DBUS_PERSIST'))
|
|
return;
|
|
|
|
if (this._holdCount > 0)
|
|
return;
|
|
|
|
this.emit('shutdown');
|
|
}
|
|
|
|
_queueShutdownCheck() {
|
|
if (this._shutdownTimeoutId)
|
|
GLib.source_remove(this._shutdownTimeoutId);
|
|
|
|
this._shutdownTimeoutId = GLib.timeout_add_seconds(
|
|
GLib.PRIORITY_DEFAULT, IDLE_SHUTDOWN_TIME,
|
|
() => {
|
|
this._shutdownTimeoutId = 0;
|
|
this._maybeShutdown();
|
|
|
|
return GLib.SOURCE_REMOVE;
|
|
});
|
|
}
|
|
|
|
_trackSender(sender) {
|
|
if (this._senders.has(sender))
|
|
return;
|
|
|
|
if (sender === this._shellName)
|
|
return; // don't track the shell
|
|
|
|
this.hold();
|
|
this._senders.set(sender,
|
|
this._dbusImpl.get_connection().watch_name(
|
|
sender,
|
|
Gio.BusNameWatcherFlags.NONE,
|
|
null,
|
|
() => this._untrackSender(sender)));
|
|
}
|
|
|
|
_untrackSender(sender) {
|
|
const id = this._senders.get(sender);
|
|
|
|
if (id)
|
|
this._dbusImpl.get_connection().unwatch_name(id);
|
|
|
|
if (this._senders.delete(sender))
|
|
this.release();
|
|
}
|
|
|
|
_injectTracking(methodName) {
|
|
const {prototype} = Gio.DBusMethodInvocation;
|
|
const origMethod = prototype[methodName];
|
|
const that = this;
|
|
|
|
prototype[methodName] = function (...args) {
|
|
origMethod.apply(this, args);
|
|
|
|
if (that._hasSignals)
|
|
that._trackSender(this.get_sender());
|
|
|
|
that._queueShutdownCheck();
|
|
};
|
|
}
|
|
|
|
_getUniqueShellName() {
|
|
try {
|
|
const res = Gio.DBus.session.call_sync(
|
|
'org.freedesktop.DBus',
|
|
'/org/freedesktop/DBus',
|
|
'org.freedesktop.DBus',
|
|
'GetNameOwner',
|
|
new GLib.Variant('(s)', ['org.gnome.Shell']),
|
|
null,
|
|
Gio.DBusCallFlags.NONE,
|
|
-1,
|
|
null);
|
|
const [name] = res.deepUnpack();
|
|
return name;
|
|
} catch (e) {
|
|
console.warn(`Failed to resolve shell name: ${e.message}`);
|
|
return '';
|
|
}
|
|
}
|
|
}
|
|
Signals.addSignalMethods(ServiceImplementation.prototype);
|
|
|
|
export class DBusService {
|
|
constructor(name, service) {
|
|
this._name = name;
|
|
this._service = service;
|
|
this._loop = new GLib.MainLoop(null, false);
|
|
|
|
this._service.connect('shutdown', () => this._loop.quit());
|
|
}
|
|
|
|
async runAsync() {
|
|
// Bail out when not running under gnome-shell
|
|
Gio.DBus.watch_name(Gio.BusType.SESSION,
|
|
'org.gnome.Shell',
|
|
Gio.BusNameWatcherFlags.NONE,
|
|
null,
|
|
() => this._loop.quit());
|
|
|
|
this._service.register();
|
|
|
|
let flags = Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT;
|
|
if (programArgs.includes('--replace'))
|
|
flags |= Gio.BusNameOwnerFlags.REPLACE;
|
|
|
|
Gio.DBus.own_name(Gio.BusType.SESSION,
|
|
this._name,
|
|
flags,
|
|
() => this._service.export(),
|
|
null,
|
|
() => this._loop.quit());
|
|
|
|
await this._loop.runAsync();
|
|
}
|
|
}
|