From 574c560677e71963d604c16e7c3ca55b5f75fefb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20M=C3=BCllner?= Date: Wed, 4 Mar 2020 03:06:57 +0100 Subject: [PATCH] dbusServices: Add some base classes for small stand-alone services There are a couple of D-Bus services that are currently provided by gnome-shell for which it makes sense to move them fully or partially into separate processes: - screen recording (performance) - FDO notifications (security) - Extensions (portalization) Add some base classes and build system glue to take care of the common boilerplate. https://gitlab.gnome.org/GNOME/gnome-shell/merge_requests/547 --- js/dbusServices/dbus-service.in | 5 + js/dbusServices/dbus-service.service.in | 3 + js/dbusServices/dbusService.js | 159 ++++++++++++++++++++++++ js/dbusServices/meson.build | 40 ++++++ js/meson.build | 1 + meson.build | 1 + 6 files changed, 209 insertions(+) create mode 100644 js/dbusServices/dbus-service.in create mode 100644 js/dbusServices/dbus-service.service.in create mode 100644 js/dbusServices/dbusService.js create mode 100644 js/dbusServices/meson.build diff --git a/js/dbusServices/dbus-service.in b/js/dbusServices/dbus-service.in new file mode 100644 index 000000000..524166102 --- /dev/null +++ b/js/dbusServices/dbus-service.in @@ -0,0 +1,5 @@ +imports.package.start({ + name: '@PACKAGE_NAME@', + prefix: '@prefix@', + libdir: '@libdir@', +}); diff --git a/js/dbusServices/dbus-service.service.in b/js/dbusServices/dbus-service.service.in new file mode 100644 index 000000000..3b0d09abe --- /dev/null +++ b/js/dbusServices/dbus-service.service.in @@ -0,0 +1,3 @@ +[D-BUS Service] +Name=@service@ +Exec=@gjs@ @pkgdatadir@/@service@ diff --git a/js/dbusServices/dbusService.js b/js/dbusServices/dbusService.js new file mode 100644 index 000000000..21b8f9c4b --- /dev/null +++ b/js/dbusServices/dbusService.js @@ -0,0 +1,159 @@ +/* exported DBusService, ServiceImplementation */ + +const { Gio, GLib } = imports.gi; + +const Signals = imports.signals; + +const IDLE_SHUTDOWN_TIME = 2; // s + +var ServiceImplementation = class { + 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._hasSignals = this._dbusImpl.get_info().signals.length > 0; + this._shutdownTimeoutId = 0; + + // subclasses may override this to disable automatic shutdown + this._autoShutdown = true; + } + + // subclasses may override this to own additional names + register() { + } + + export() { + this._dbusImpl.export(Gio.DBus.session, this._objectPath); + } + + unexport() { + this._dbusImpl.unexport(); + } + + /** + * _handleError: + * @param {Gio.DBusMethodInvocation} + * @param {Error} + * + * Complete @invocation with an appropriate error if @error is set; + * useful for implementing early returns from method implementations. + * + * @returns {bool} - true if @invocation was completed + */ + + _handleError(invocation, error) { + if (error === null) + return false; + + if (error instanceof GLib.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 (this._senders.size > 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; + + 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._queueShutdownCheck(); + } + + _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(); + }; + } +}; +Signals.addSignalMethods(ServiceImplementation.prototype); + +var DBusService = class { + constructor(name, service) { + this._name = name; + this._service = service; + this._loop = new GLib.MainLoop(null, false); + + this._service.connect('shutdown', () => this._loop.quit()); + } + + run() { + // 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(); + + Gio.DBus.own_name(Gio.BusType.SESSION, + this._name, + Gio.BusNameOwnerFlags.REPLACE, + () => this._service.export(), + null, + () => this._loop.quit()); + + this._loop.run(); + } +}; diff --git a/js/dbusServices/meson.build b/js/dbusServices/meson.build new file mode 100644 index 000000000..c2c4d6392 --- /dev/null +++ b/js/dbusServices/meson.build @@ -0,0 +1,40 @@ +launcherconf = configuration_data() +launcherconf.set('PACKAGE_NAME', meson.project_name()) +launcherconf.set('prefix', prefix) +launcherconf.set('libdir', libdir) + +dbus_services = { +} + +config_dir = '@0@/..'.format(meson.current_build_dir()) + +foreach service, dir : dbus_services + configure_file( + input: 'dbus-service.in', + output: service, + configuration: launcherconf, + install_dir: pkgdatadir, + ) + + serviceconf = configuration_data() + serviceconf.set('service', service) + serviceconf.set('gjs', gjs.path()) + serviceconf.set('pkgdatadir', pkgdatadir) + + configure_file( + input: 'dbus-service.service.in', + output: service + '.service', + configuration: serviceconf, + install_dir: servicedir + ) + + gnome.compile_resources( + service + '.src', + service + '.src.gresource.xml', + dependencies: [config_js], + source_dir: ['.', '..', dir, config_dir], + gresource_bundle: true, + install: true, + install_dir: pkgdatadir + ) +endforeach diff --git a/js/meson.build b/js/meson.build index 4a572c53f..9a230d65d 100644 --- a/js/meson.build +++ b/js/meson.build @@ -1,4 +1,5 @@ subdir('misc') +subdir('dbusServices') js_resources = gnome.compile_resources( 'js-resources', 'js-resources.gresource.xml', diff --git a/meson.build b/meson.build index 8b22b7215..5d51db668 100644 --- a/meson.build +++ b/meson.build @@ -142,6 +142,7 @@ endif mutter_typelibdir = mutter_dep.get_pkgconfig_variable('typelibdir') python = find_program('python3') sassc = find_program('sassc') +gjs = find_program('gjs') cc = meson.get_compiler('c')