summaryrefslogtreecommitdiff
path: root/raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js
diff options
context:
space:
mode:
Diffstat (limited to 'raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js')
-rw-r--r--raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js320
1 files changed, 320 insertions, 0 deletions
diff --git a/raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js b/raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js
new file mode 100644
index 0000000..2c5e54f
--- /dev/null
+++ b/raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js
@@ -0,0 +1,320 @@
+// This file is part of the AppIndicator/KStatusNotifierItem GNOME Shell extension
+//
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+import Gio from 'gi://Gio';
+import GLib from 'gi://GLib';
+
+import * as AppIndicator from './appIndicator.js';
+import * as IndicatorStatusIcon from './indicatorStatusIcon.js';
+import * as Interfaces from './interfaces.js';
+import * as PromiseUtils from './promiseUtils.js';
+import * as Util from './util.js';
+import * as DBusUtils from './dbusUtils.js';
+import * as DBusMenu from './dbusMenu.js';
+
+import {DBusProxy} from './dbusProxy.js';
+
+Gio._promisify(Gio.Subprocess.prototype, 'wait_async');
+Gio._promisify(Gio.Subprocess.prototype, 'communicate_async');
+Gio._promisify(Gio.DataInputStream.prototype, 'read_line_async', 'read_line_finish_utf8');
+
+
+// TODO: replace with org.freedesktop and /org/freedesktop when approved
+const KDE_PREFIX = 'org.kde';
+
+export const WATCHER_BUS_NAME = `${KDE_PREFIX}.StatusNotifierWatcher`;
+const WATCHER_OBJECT = '/StatusNotifierWatcher';
+
+const DEFAULT_ITEM_OBJECT_PATH = '/StatusNotifierItem';
+
+/*
+ * The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object
+ */
+export class StatusNotifierWatcher {
+ constructor(extension, watchDog) {
+ this._watchDog = watchDog;
+ this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this);
+ try {
+ this._dbusImpl.export(Gio.DBus.session, WATCHER_OBJECT);
+ } catch (e) {
+ Util.Logger.warn(`Failed to export ${WATCHER_OBJECT}`);
+ logError(e);
+ }
+ this._cancellable = new Gio.Cancellable();
+ this._everAcquiredName = false;
+ this._ownName = Gio.DBus.session.own_name(WATCHER_BUS_NAME,
+ Gio.BusNameOwnerFlags.NONE,
+ this._acquiredName.bind(this),
+ this._lostName.bind(this));
+ this._items = new Map();
+
+ try {
+ this._dbusImpl.emit_signal('StatusNotifierHostRegistered', null);
+ } catch (e) {
+ Util.Logger.warn(`Failed to notify registered host ${WATCHER_OBJECT}`);
+ }
+
+ this._seekStatusNotifierItems(extension).catch(e => {
+ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+ logError(e, 'Looking for StatusNotifierItem\'s');
+ });
+ }
+
+ _acquiredName() {
+ this._everAcquiredName = true;
+ this._watchDog.nameAcquired = true;
+ }
+
+ _lostName() {
+ if (this._everAcquiredName)
+ Util.Logger.debug(`Lost name${WATCHER_BUS_NAME}`);
+ else
+ Util.Logger.warn(`Failed to acquire ${WATCHER_BUS_NAME}`);
+ this._watchDog.nameAcquired = false;
+ }
+
+ async _registerItem(service, busName, objPath) {
+ const id = Util.indicatorId(service, busName, objPath);
+
+ if (this._items.has(id)) {
+ Util.Logger.warn(`Item ${id} is already registered`);
+ return;
+ }
+
+ Util.Logger.debug(`Registering StatusNotifierItem ${id}`);
+
+ try {
+ const indicator = new AppIndicator.AppIndicator(service, busName, objPath);
+ this._items.set(id, indicator);
+ indicator.connect('destroy', () => this._onIndicatorDestroyed(indicator));
+
+ indicator.connect('name-owner-changed', async () => {
+ if (!indicator.hasNameOwner) {
+ try {
+ await new PromiseUtils.TimeoutPromise(500,
+ GLib.PRIORITY_DEFAULT, this._cancellable);
+ if (this._items.has(id) && !indicator.hasNameOwner)
+ indicator.destroy();
+ } catch (e) {
+ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+ logError(e);
+ }
+ }
+ });
+
+ // if the desktop is not ready delay the icon creation and signal emissions
+ await Util.waitForStartupCompletion(indicator.cancellable);
+ const statusIcon = new IndicatorStatusIcon.IndicatorStatusIcon(indicator);
+ IndicatorStatusIcon.addIconToPanel(statusIcon);
+
+ this._dbusImpl.emit_signal('StatusNotifierItemRegistered',
+ GLib.Variant.new('(s)', [indicator.uniqueId]));
+ this._dbusImpl.emit_property_changed('RegisteredStatusNotifierItems',
+ GLib.Variant.new('as', this.RegisteredStatusNotifierItems));
+ } catch (e) {
+ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+ logError(e);
+ throw e;
+ }
+ }
+
+ async _ensureItemRegistered(service, busName, objPath) {
+ const id = Util.indicatorId(service, busName, objPath);
+ const item = this._items.get(id);
+
+ if (item) {
+ // delete the old one and add the new indicator
+ Util.Logger.debug(`Attempting to re-register ${id}; resetting instead`);
+ item.reset();
+ return;
+ }
+
+ await this._registerItem(service, busName, objPath);
+ }
+
+ async _seekStatusNotifierItems(extension) {
+ // Some indicators (*coff*, dropbox, *coff*) do not re-register again
+ // when the plugin is enabled/disabled, thus we need to manually look
+ // for the objects in the session bus that implements the
+ // StatusNotifierItem interface... However let's do it after a low
+ // priority timeout, and using an external process so that it won't
+ // affect startup or memory (as it seems that gjs is not great at
+ // handling the memory of the bus analyzer async code).
+ const cancellable = this._cancellable;
+ await new PromiseUtils.TimeoutSecondsPromise(2, GLib.PRIORITY_LOW, cancellable);
+ const busAnalyzer = GLib.build_filenamev([
+ extension.path, 'tools', 'busAnalyzer.js',
+ ]);
+
+ const subProcess = Gio.Subprocess.new(['gjs', '-m', busAnalyzer],
+ Gio.SubprocessFlags.STDOUT_PIPE | Gio.SubprocessFlags.STDERR_PIPE);
+
+ const stdOut = subProcess.get_stdout_pipe();
+ const dataInputStream = new Gio.DataInputStream({base_stream: stdOut});
+ const textDecoder = new TextDecoder();
+
+ while (true) {
+ // eslint-disable-next-line no-await-in-loop
+ const [line] = await dataInputStream.read_line_async(GLib.PRIORITY_DEFAULT,
+ cancellable);
+ if (!line)
+ break;
+
+ try {
+ const {services, name, path} = JSON.parse(textDecoder.decode(line));
+ const ids = [null, ...services].map(s => Util.indicatorId(s, name, path));
+
+ if (ids.every(id => !this._items.has(id))) {
+ const service = services.find(s =>
+ s && s.startsWith('org.kde.StatusNotifierItem')) || services[0];
+ const id = Util.indicatorId(
+ path === DEFAULT_ITEM_OBJECT_PATH ? service : null,
+ name, path);
+ Util.Logger.warn(`Using Brute-force mode for StatusNotifierItem ${id}`);
+ // eslint-disable-next-line no-await-in-loop
+ await this._registerItem(service, name, path);
+ }
+ } catch (e) {
+ logError(e);
+ }
+ }
+
+ const [, stdErr] = await subProcess.communicate_async(null, cancellable);
+ await subProcess.wait_async(cancellable);
+
+ if (subProcess.get_exit_status() !== 0) {
+ const errorLines = textDecoder.decode(stdErr.toArray()).split('\n');
+ const error = new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED,
+ errorLines[0]);
+ error.stack = `${errorLines.slice(3).join('\n')}${error.stack}`;
+ throw error;
+ }
+ }
+
+ async RegisterStatusNotifierItemAsync(params, invocation) {
+ // it would be too easy if all application behaved the same
+ // instead, ayatana patched gnome apps to send a path
+ // while kde apps send a bus name
+ const [service] = params;
+ let busName, objPath;
+
+ if (service.charAt(0) === '/') { // looks like a path
+ busName = invocation.get_sender();
+ objPath = service;
+ } else if (service.match(DBusUtils.BUS_ADDRESS_REGEX)) {
+ try {
+ busName = await DBusUtils.getUniqueBusName(invocation.get_connection(),
+ service, this._cancellable);
+ } catch (e) {
+ logError(e);
+ }
+ objPath = DEFAULT_ITEM_OBJECT_PATH;
+ }
+
+ if (!busName || !objPath) {
+ const error = `Impossible to register an indicator for parameters '${
+ service.toString()}'`;
+ Util.Logger.warn(error);
+
+ invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
+ error);
+ return;
+ }
+
+ try {
+ await this._ensureItemRegistered(service, busName, objPath);
+ invocation.return_value(null);
+ } catch (e) {
+ if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
+ logError(e);
+ invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
+ e.message);
+ }
+ }
+
+ _onIndicatorDestroyed(indicator) {
+ const {uniqueId} = indicator;
+ this._items.delete(uniqueId);
+
+ try {
+ this._dbusImpl.emit_signal('StatusNotifierItemUnregistered',
+ GLib.Variant.new('(s)', [uniqueId]));
+ this._dbusImpl.emit_property_changed('RegisteredStatusNotifierItems',
+ GLib.Variant.new('as', this.RegisteredStatusNotifierItems));
+ } catch (e) {
+ Util.Logger.warn(`Failed to emit signals: ${e}`);
+ }
+ }
+
+ RegisterStatusNotifierHostAsync(_service, invocation) {
+ invocation.return_error_literal(
+ Gio.DBusError,
+ Gio.DBusError.NOT_SUPPORTED,
+ 'Registering additional notification hosts is not supported');
+ }
+
+ IsNotificationHostRegistered() {
+ return true;
+ }
+
+ get RegisteredStatusNotifierItems() {
+ return Array.from(this._items.values()).map(i => i.uniqueId);
+ }
+
+ get IsStatusNotifierHostRegistered() {
+ return true;
+ }
+
+ get ProtocolVersion() {
+ return 0;
+ }
+
+ destroy() {
+ if (this._isDestroyed)
+ return;
+
+ // this doesn't do any sync operation and doesn't allow us to hook up
+ // the event of being finished which results in our unholy debounce hack
+ // (see extension.js)
+ this._items.forEach(indicator => indicator.destroy());
+ this._cancellable.cancel();
+
+ try {
+ this._dbusImpl.emit_signal('StatusNotifierHostUnregistered', null);
+ } catch (e) {
+ Util.Logger.warn(`Failed to emit uinregistered signal: ${e}`);
+ }
+
+ Gio.DBus.session.unown_name(this._ownName);
+
+ try {
+ this._dbusImpl.unexport();
+ } catch (e) {
+ Util.Logger.warn(`Failed to unexport watcher object: ${e}`);
+ }
+
+ DBusMenu.DBusClient.destroy();
+ AppIndicator.AppIndicatorProxy.destroy();
+ DBusProxy.destroy();
+ Util.destroyDefaultTheme();
+
+ this._dbusImpl.run_dispose();
+ delete this._dbusImpl;
+
+ delete this._items;
+ this._isDestroyed = true;
+ }
+}