diff options
| author | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
|---|---|---|
| committer | AlexanderCurl <alexc@alexc.hu> | 2026-04-18 17:46:06 +0100 |
| commit | 70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69 (patch) | |
| tree | ab1123d4067c1b086dd6faa7ee4ea643236b565a /raveos-gnome-theme/theme-data/extensions/installed/appindicatorsupport@rgcjonas.gmail.com/statusNotifierWatcher.js | |
| parent | 5d94c0a7d44a2255b81815a52a7056a94a39842d (diff) | |
| download | RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.tar.gz RaveOS-PKGBUILD-70ca3fef77ee8bdc6e3ac28589a6fa08c024cc69.zip | |
Replaced file structures for themes in the correct format
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.js | 320 |
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; + } +} |