Add base gnome extensions
This commit is contained in:
@ -0,0 +1,287 @@
|
||||
// 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 DBusMenu from './dbusMenu.js';
|
||||
|
||||
import {DBusProxy} from './dbusProxy.js';
|
||||
|
||||
|
||||
// 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(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().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() {
|
||||
// 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 idle, so that it won't affect startup.
|
||||
const cancellable = this._cancellable;
|
||||
const bus = Gio.DBus.session;
|
||||
const uniqueNames = await Util.getBusNames(bus, cancellable);
|
||||
const introspectName = async name => {
|
||||
const nodes = Util.introspectBusObject(bus, name, cancellable,
|
||||
['org.kde.StatusNotifierItem']);
|
||||
const services = [...uniqueNames.get(name)];
|
||||
|
||||
for await (const node of nodes) {
|
||||
const {path} = node;
|
||||
const ids = 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}`);
|
||||
this._registerItem(service, name, path);
|
||||
}
|
||||
}
|
||||
};
|
||||
await Promise.allSettled([...uniqueNames.keys()].map(n => introspectName(n)));
|
||||
}
|
||||
|
||||
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(Util.BUS_ADDRESS_REGEX)) {
|
||||
try {
|
||||
busName = await Util.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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user