[gnome] Update extensions
This commit is contained in:
@ -24,6 +24,7 @@ import St from 'gi://St';
|
||||
import * as Params from 'resource:///org/gnome/shell/misc/params.js';
|
||||
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
|
||||
|
||||
import * as DBusUtils from './dbusUtils.js';
|
||||
import * as IconCache from './iconCache.js';
|
||||
import * as Util from './util.js';
|
||||
import * as Interfaces from './interfaces.js';
|
||||
@ -218,6 +219,8 @@ class AppIndicatorProxy extends DBusProxy {
|
||||
return;
|
||||
}
|
||||
|
||||
const cancellable = this._cancellable;
|
||||
|
||||
if (!params.get_type().equal(AppIndicatorProxy.TUPLE_TYPE)) {
|
||||
// If the property includes arguments, we can just queue the signal emission
|
||||
const [value] = params.unpack();
|
||||
@ -238,7 +241,7 @@ class AppIndicatorProxy extends DBusProxy {
|
||||
return;
|
||||
|
||||
this._signalsAccumulator = new PromiseUtils.TimeoutPromise(
|
||||
MAX_UPDATE_FREQUENCY, GLib.PRIORITY_DEFAULT_IDLE, this._cancellable);
|
||||
MAX_UPDATE_FREQUENCY, GLib.PRIORITY_DEFAULT_IDLE, cancellable);
|
||||
try {
|
||||
await this._signalsAccumulator;
|
||||
const refreshPropertiesPromises =
|
||||
@ -460,7 +463,7 @@ export class AppIndicator extends Signals.EventEmitter {
|
||||
}
|
||||
|
||||
try {
|
||||
this._commandLine = await Util.getProcessName(this.busName,
|
||||
this._commandLine = await DBusUtils.getProcessName(this.busName,
|
||||
cancellable, GLib.PRIORITY_LOW);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
|
||||
@ -196,7 +196,7 @@ export class DbusMenuItem extends Signals.EventEmitter {
|
||||
if (!data)
|
||||
data = GLib.Variant.new_int32(0);
|
||||
|
||||
this._client.sendEvent(this._id, event, data, timestamp);
|
||||
return this._client.sendEvent(this._id, event, data, timestamp);
|
||||
}
|
||||
|
||||
getId() {
|
||||
@ -533,14 +533,18 @@ export const DBusClient = GObject.registerClass({
|
||||
}
|
||||
}
|
||||
|
||||
sendEvent(id, event, params, timestamp) {
|
||||
async sendEvent(id, event, params, timestamp) {
|
||||
if (!this.gNameOwner)
|
||||
return;
|
||||
|
||||
this.EventAsync(id, event, params, timestamp, this._cancellable).catch(e => {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
logError(e);
|
||||
});
|
||||
try {
|
||||
await this.EventAsync(id, event, params, timestamp, this._cancellable);
|
||||
} catch (e) {
|
||||
if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
return;
|
||||
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
_onPropertiesUpdated([changed, removed]) {
|
||||
@ -628,6 +632,8 @@ const MenuItemFactory = {
|
||||
shellItem, MenuItemFactory._onActivate);
|
||||
|
||||
shellItem.connect('destroy', () => {
|
||||
shellItem._dbusItemCancellable?.cancel();
|
||||
shellItem._dbusItemCancellable = null;
|
||||
shellItem._dbusItem = null;
|
||||
shellItem._dbusClient = null;
|
||||
shellItem._icon = null;
|
||||
@ -655,7 +661,7 @@ const MenuItemFactory = {
|
||||
menu._parent._openedSubMenu = menu;
|
||||
}
|
||||
|
||||
this._dbusItem.handleEvent('opened', null, 0);
|
||||
this._dbusItem.handleEvent('opened', null, 0).catch(logError);
|
||||
this._dbusItem.sendAboutToShow();
|
||||
} else {
|
||||
if (NEED_NESTED_SUBMENU_FIX) {
|
||||
@ -664,7 +670,7 @@ const MenuItemFactory = {
|
||||
menu._openedSubMenu.close(false);
|
||||
}
|
||||
|
||||
this._dbusItem.handleEvent('closed', null, 0);
|
||||
this._dbusItem.handleEvent('closed', null, 0).catch(logError);
|
||||
}
|
||||
},
|
||||
|
||||
@ -674,7 +680,7 @@ const MenuItemFactory = {
|
||||
this._dbusClient.indicator.provideActivationToken(timestamp);
|
||||
|
||||
this._dbusItem.handleEvent('clicked', GLib.Variant.new('i', 0),
|
||||
timestamp);
|
||||
timestamp).catch(logError);
|
||||
},
|
||||
|
||||
_onPropertyChanged(dbusItem, prop, _value) {
|
||||
@ -756,10 +762,15 @@ const MenuItemFactory = {
|
||||
this._icon.icon_name = iconName;
|
||||
} else if (iconData) {
|
||||
try {
|
||||
if (!this._dbusItemCancellable) {
|
||||
this._dbusItemCancellable = new Util.CancellableChild(
|
||||
this._dbusClient.cancellable);
|
||||
}
|
||||
|
||||
const inputStream = Gio.MemoryInputStream.new_from_bytes(
|
||||
iconData.get_data_as_bytes());
|
||||
this._icon.gicon = await GdkPixbuf.Pixbuf.new_from_stream_async(
|
||||
inputStream, this._dbusClient.cancellable);
|
||||
inputStream, this._dbusItemCancellable);
|
||||
} catch (e) {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
logError(e);
|
||||
@ -872,7 +883,7 @@ export class Client extends Signals.EventEmitter {
|
||||
menu._setOpenedSubMenu = this._setOpenedSubmenu.bind(this);
|
||||
|
||||
// connect handlers
|
||||
Util.connectSmart(menu, 'open-state-changed', this, this._onMenuOpened);
|
||||
Util.connectSmart(menu, 'open-state-changed', this, this._onMenuOpenStateChanged);
|
||||
Util.connectSmart(menu, 'destroy', this, this.destroy);
|
||||
|
||||
Util.connectSmart(this._rootItem, 'child-added', this, this._onRootChildAdded);
|
||||
@ -939,7 +950,7 @@ export class Client extends Signals.EventEmitter {
|
||||
MenuUtils.moveItemInMenu(this._rootMenu, dbusItem, newpos);
|
||||
}
|
||||
|
||||
_onMenuOpened(menu, state) {
|
||||
_onMenuOpenStateChanged(menu, state) {
|
||||
if (!this._rootItem)
|
||||
return;
|
||||
|
||||
@ -949,10 +960,18 @@ export class Client extends Signals.EventEmitter {
|
||||
if (this._openedSubMenu && this._openedSubMenu.isOpen)
|
||||
this._openedSubMenu.close();
|
||||
|
||||
this._rootItem.handleEvent('opened', null, 0);
|
||||
this._rootItem.handleEvent('opened', null, 0).catch(logError);
|
||||
this._rootItem.sendAboutToShow();
|
||||
} else {
|
||||
this._rootItem.handleEvent('closed', null, 0);
|
||||
this._rootItem.handleEvent('closed', null, 0).catch(e => {
|
||||
if (e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_OBJECT)) {
|
||||
// The menu hay have been removed at this point, thus do not
|
||||
// spam the users about this if it happens.
|
||||
return;
|
||||
}
|
||||
|
||||
logError(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,8 +40,13 @@ export const DBusProxy = GObject.registerClass({
|
||||
(_proxy, ...args) => this._onSignal(...args)));
|
||||
}
|
||||
|
||||
this._signalIds.push(this.connect('notify::g-name-owner', () =>
|
||||
this._onNameOwnerChanged()));
|
||||
this._signalIds.push(this.connect('notify::g-name-owner', () => {
|
||||
if (!this.gNameOwner) {
|
||||
this._cancellable.cancel();
|
||||
this._cancellable = new Gio.Cancellable();
|
||||
}
|
||||
this._onNameOwnerChanged();
|
||||
}));
|
||||
}
|
||||
|
||||
async initAsync(cancellable) {
|
||||
|
||||
@ -0,0 +1,121 @@
|
||||
// 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 {Logger} from './logger.js';
|
||||
|
||||
export const BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/;
|
||||
|
||||
Gio._promisify(Gio.DBusConnection.prototype, 'call');
|
||||
|
||||
export async function getUniqueBusName(bus, name, cancellable) {
|
||||
if (name[0] === ':')
|
||||
return name;
|
||||
|
||||
if (!bus)
|
||||
bus = Gio.DBus.session;
|
||||
|
||||
const variantName = new GLib.Variant('(s)', [name]);
|
||||
const [unique] = (await bus.call('org.freedesktop.DBus', '/', 'org.freedesktop.DBus',
|
||||
'GetNameOwner', variantName, new GLib.VariantType('(s)'),
|
||||
Gio.DBusCallFlags.NONE, -1, cancellable)).deep_unpack();
|
||||
|
||||
return unique;
|
||||
}
|
||||
|
||||
export async function getBusNames(bus, cancellable) {
|
||||
if (!bus)
|
||||
bus = Gio.DBus.session;
|
||||
|
||||
const [names] = (await bus.call('org.freedesktop.DBus', '/', 'org.freedesktop.DBus',
|
||||
'ListNames', null, new GLib.VariantType('(as)'), Gio.DBusCallFlags.NONE,
|
||||
-1, cancellable)).deep_unpack();
|
||||
|
||||
const uniqueNames = new Map();
|
||||
const requests = names.map(name => getUniqueBusName(bus, name, cancellable));
|
||||
const results = await Promise.allSettled(requests);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result.status === 'fulfilled') {
|
||||
let namesForBus = uniqueNames.get(result.value);
|
||||
if (!namesForBus) {
|
||||
namesForBus = new Set();
|
||||
uniqueNames.set(result.value, namesForBus);
|
||||
}
|
||||
if (result.value !== names[i])
|
||||
namesForBus.add(names[i]);
|
||||
} else if (!result.reason.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
Logger.debug(`Impossible to get the unique name of ${names[i]}: ${result.reason}`);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueNames;
|
||||
}
|
||||
|
||||
async function getProcessId(connectionName, cancellable = null, bus = Gio.DBus.session) {
|
||||
const res = await bus.call('org.freedesktop.DBus', '/',
|
||||
'org.freedesktop.DBus', 'GetConnectionUnixProcessID',
|
||||
new GLib.Variant('(s)', [connectionName]),
|
||||
new GLib.VariantType('(u)'),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
cancellable);
|
||||
const [pid] = res.deepUnpack();
|
||||
return pid;
|
||||
}
|
||||
|
||||
export async function getProcessName(connectionName, cancellable = null,
|
||||
priority = GLib.PRIORITY_DEFAULT, bus = Gio.DBus.session) {
|
||||
const pid = await getProcessId(connectionName, cancellable, bus);
|
||||
const cmdFile = Gio.File.new_for_path(`/proc/${pid}/cmdline`);
|
||||
const inputStream = await cmdFile.read_async(priority, cancellable);
|
||||
const bytes = await inputStream.read_bytes_async(2048, priority, cancellable);
|
||||
const textDecoder = new TextDecoder();
|
||||
return textDecoder.decode(bytes.toArray().map(v => !v ? 0x20 : v));
|
||||
}
|
||||
|
||||
export async function* introspectBusObject(bus, name, cancellable,
|
||||
interfaces = undefined, path = undefined) {
|
||||
if (!path)
|
||||
path = '/';
|
||||
|
||||
const [introspection] = (await bus.call(name, path, 'org.freedesktop.DBus.Introspectable',
|
||||
'Introspect', null, new GLib.VariantType('(s)'), Gio.DBusCallFlags.NONE,
|
||||
5000, cancellable)).deep_unpack();
|
||||
|
||||
const nodeInfo = Gio.DBusNodeInfo.new_for_xml(introspection);
|
||||
|
||||
if (!interfaces || dbusNodeImplementsInterfaces(nodeInfo, interfaces))
|
||||
yield {nodeInfo, path};
|
||||
|
||||
if (path === '/')
|
||||
path = '';
|
||||
|
||||
for (const subNodeInfo of nodeInfo.nodes) {
|
||||
const subPath = `${path}/${subNodeInfo.path}`;
|
||||
yield* introspectBusObject(bus, name, cancellable, interfaces, subPath);
|
||||
}
|
||||
}
|
||||
|
||||
function dbusNodeImplementsInterfaces(nodeInfo, interfaces) {
|
||||
if (!(nodeInfo instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces))
|
||||
return false;
|
||||
|
||||
return interfaces.some(iface => nodeInfo.lookup_interface(iface));
|
||||
}
|
||||
@ -20,13 +20,14 @@ import * as StatusNotifierWatcher from './statusNotifierWatcher.js';
|
||||
import * as Interfaces from './interfaces.js';
|
||||
import * as TrayIconsManager from './trayIconsManager.js';
|
||||
import * as Util from './util.js';
|
||||
import {Logger} from './logger.js';
|
||||
import {SettingsManager} from './settingsManager.js';
|
||||
|
||||
export default class AppIndicatorExtension extends Extension.Extension {
|
||||
constructor(...args) {
|
||||
super(...args);
|
||||
|
||||
Util.Logger.init(this);
|
||||
Logger.init(this);
|
||||
Interfaces.initialize(this);
|
||||
|
||||
this._isEnabled = false;
|
||||
@ -42,7 +43,7 @@ export default class AppIndicatorExtension extends Extension.Extension {
|
||||
global['--appindicator-extension-on-reload']();
|
||||
|
||||
global['--appindicator-extension-on-reload'] = () => {
|
||||
Util.Logger.debug('Reload detected, destroying old watchdog');
|
||||
Logger.debug('Reload detected, destroying old watchdog');
|
||||
this._watchDog.destroy();
|
||||
this._watchDog = null;
|
||||
};
|
||||
@ -84,6 +85,6 @@ export default class AppIndicatorExtension extends Extension.Extension {
|
||||
return;
|
||||
|
||||
this._statusNotifierWatcher = new StatusNotifierWatcher.StatusNotifierWatcher(
|
||||
this._watchDog);
|
||||
this, this._watchDog);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
let _defaultTheme;
|
||||
export function getDefaultTheme() {
|
||||
if (_defaultTheme)
|
||||
return _defaultTheme;
|
||||
|
||||
_defaultTheme = new St.IconTheme();
|
||||
return _defaultTheme;
|
||||
}
|
||||
|
||||
export function destroyDefaultTheme() {
|
||||
_defaultTheme = null;
|
||||
}
|
||||
@ -246,6 +246,10 @@ class IndicatorStatusIcon extends BaseStatusIcon {
|
||||
_init(indicator) {
|
||||
super._init(0.5, indicator.accessibleName,
|
||||
new AppIndicator.IconActor(indicator, DEFAULT_ICON_SIZE));
|
||||
|
||||
// Disable upstream's click gesture and fall back to vfunc_button_press_event etc.
|
||||
this._clickGesture?.set_enabled(false);
|
||||
|
||||
this._indicator = indicator;
|
||||
|
||||
this._lastClickTime = -1;
|
||||
|
||||
@ -0,0 +1,105 @@
|
||||
// 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 GLib from 'gi://GLib';
|
||||
|
||||
/**
|
||||
* Helper class for logging stuff
|
||||
*/
|
||||
export class Logger {
|
||||
static _logStructured(logLevel, message, extraFields = {}) {
|
||||
if (!Object.values(GLib.LogLevelFlags).includes(logLevel)) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING,
|
||||
'logLevel is not a valid GLib.LogLevelFlags');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Logger._levels.includes(logLevel))
|
||||
return;
|
||||
|
||||
let fields = {
|
||||
'SYSLOG_IDENTIFIER': Logger._uuid,
|
||||
'MESSAGE': `${message}`,
|
||||
};
|
||||
|
||||
let thisFile = null;
|
||||
const {stack} = new Error();
|
||||
for (let stackLine of stack.split('\n')) {
|
||||
stackLine = stackLine.replace('resource:///org/gnome/Shell/', '');
|
||||
const [code, line] = stackLine.split(':');
|
||||
const [func, file] = code.split(/@(.+)/);
|
||||
|
||||
if (!thisFile || thisFile === file) {
|
||||
thisFile = file;
|
||||
continue;
|
||||
}
|
||||
|
||||
fields = Object.assign(fields, {
|
||||
'CODE_FILE': file || '',
|
||||
'CODE_LINE': line || '',
|
||||
'CODE_FUNC': func || '',
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
GLib.log_structured(Logger._domain, logLevel, Object.assign(fields, extraFields));
|
||||
}
|
||||
|
||||
static init(extension) {
|
||||
if (Logger._domain)
|
||||
return;
|
||||
|
||||
const allLevels = Object.values(GLib.LogLevelFlags);
|
||||
const domains = GLib.getenv('G_MESSAGES_DEBUG');
|
||||
const {name: domain} = extension.metadata;
|
||||
Logger._uuid = extension.metadata.uuid;
|
||||
Logger._domain = domain.replaceAll(' ', '-');
|
||||
|
||||
if (domains === 'all' || (domains && domains.split(' ').includes(Logger._domain))) {
|
||||
Logger._levels = allLevels;
|
||||
} else {
|
||||
Logger._levels = allLevels.filter(
|
||||
l => l <= GLib.LogLevelFlags.LEVEL_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
static destroy() {
|
||||
delete Logger._domain;
|
||||
delete Logger._uuid;
|
||||
delete Logger._levels;
|
||||
}
|
||||
|
||||
static debug(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_DEBUG, message);
|
||||
}
|
||||
|
||||
static message(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_MESSAGE, message);
|
||||
}
|
||||
|
||||
static warn(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING, message);
|
||||
}
|
||||
|
||||
static error(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_ERROR, message);
|
||||
}
|
||||
|
||||
static critical(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_CRITICAL, message);
|
||||
}
|
||||
}
|
||||
@ -14,5 +14,5 @@
|
||||
],
|
||||
"url": "https://github.com/ubuntu/gnome-shell-extension-appindicator",
|
||||
"uuid": "appindicatorsupport@rgcjonas.gmail.com",
|
||||
"version": 63
|
||||
"version": 64
|
||||
}
|
||||
@ -22,10 +22,15 @@ 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';
|
||||
@ -39,7 +44,7 @@ const DEFAULT_ITEM_OBJECT_PATH = '/StatusNotifierItem';
|
||||
* The StatusNotifierWatcher class implements the StatusNotifierWatcher dbus object
|
||||
*/
|
||||
export class StatusNotifierWatcher {
|
||||
constructor(watchDog) {
|
||||
constructor(extension, watchDog) {
|
||||
this._watchDog = watchDog;
|
||||
this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(Interfaces.StatusNotifierWatcher, this);
|
||||
try {
|
||||
@ -62,7 +67,7 @@ export class StatusNotifierWatcher {
|
||||
Util.Logger.warn(`Failed to notify registered host ${WATCHER_OBJECT}`);
|
||||
}
|
||||
|
||||
this._seekStatusNotifierItems().catch(e => {
|
||||
this._seekStatusNotifierItems(extension).catch(e => {
|
||||
if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
|
||||
logError(e, 'Looking for StatusNotifierItem\'s');
|
||||
});
|
||||
@ -140,23 +145,38 @@ export class StatusNotifierWatcher {
|
||||
await this._registerItem(service, busName, objPath);
|
||||
}
|
||||
|
||||
async _seekStatusNotifierItems() {
|
||||
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 idle, so that it won't affect startup.
|
||||
// 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;
|
||||
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)];
|
||||
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));
|
||||
|
||||
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];
|
||||
@ -164,11 +184,24 @@ export class StatusNotifierWatcher {
|
||||
path === DEFAULT_ITEM_OBJECT_PATH ? service : null,
|
||||
name, path);
|
||||
Util.Logger.warn(`Using Brute-force mode for StatusNotifierItem ${id}`);
|
||||
this._registerItem(service, name, path);
|
||||
// eslint-disable-next-line no-await-in-loop
|
||||
await this._registerItem(service, name, path);
|
||||
}
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
}
|
||||
};
|
||||
await Promise.allSettled([...uniqueNames.keys()].map(n => introspectName(n)));
|
||||
}
|
||||
|
||||
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) {
|
||||
@ -181,9 +214,9 @@ export class StatusNotifierWatcher {
|
||||
if (service.charAt(0) === '/') { // looks like a path
|
||||
busName = invocation.get_sender();
|
||||
objPath = service;
|
||||
} else if (service.match(Util.BUS_ADDRESS_REGEX)) {
|
||||
} else if (service.match(DBusUtils.BUS_ADDRESS_REGEX)) {
|
||||
try {
|
||||
busName = await Util.getUniqueBusName(invocation.get_connection(),
|
||||
busName = await DBusUtils.getUniqueBusName(invocation.get_connection(),
|
||||
service, this._cancellable);
|
||||
} catch (e) {
|
||||
logError(e);
|
||||
|
||||
@ -0,0 +1,46 @@
|
||||
#!/usr/bin/env gjs -m
|
||||
|
||||
import GLib from 'gi://GLib';
|
||||
import Gio from 'gi://Gio';
|
||||
import GioUnix from 'gi://GioUnix';
|
||||
|
||||
import * as DBusUtils from '../dbusUtils.js';
|
||||
|
||||
async function 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...
|
||||
const cancellable = null;
|
||||
const bus = Gio.DBus.session;
|
||||
const uniqueNames = await DBusUtils.getBusNames(bus, cancellable);
|
||||
|
||||
const stdErrOutputStream = new GioUnix.OutputStream({fd: 1, closeFd: true});
|
||||
const introspectName = async name => {
|
||||
const nodes = DBusUtils.introspectBusObject(bus, name, cancellable,
|
||||
['org.kde.StatusNotifierItem']);
|
||||
const services = [...uniqueNames.get(name)];
|
||||
|
||||
for await (const node of nodes) {
|
||||
const {path} = node;
|
||||
stdErrOutputStream.write(`${JSON.stringify({services, name, path})}\n`,
|
||||
cancellable);
|
||||
}
|
||||
};
|
||||
await Promise.allSettled([...uniqueNames.keys()].map(n => introspectName(n)));
|
||||
}
|
||||
|
||||
function main(_argv) {
|
||||
const loop = new GLib.MainLoop(null, false);
|
||||
|
||||
let exitCode = 0;
|
||||
seekStatusNotifierItems().catch(e => {
|
||||
logError(e);
|
||||
exitCode = 1;
|
||||
}).finally(() => loop.quit());
|
||||
loop.run();
|
||||
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
imports.system.exit(main(ARGV));
|
||||
@ -23,11 +23,10 @@ import * as Main from 'resource:///org/gnome/shell/ui/main.js';
|
||||
import * as Config from 'resource:///org/gnome/shell/misc/config.js';
|
||||
import * as Signals from 'resource:///org/gnome/shell/misc/signals.js';
|
||||
|
||||
import {Logger} from './logger.js';
|
||||
import {BaseStatusIcon} from './indicatorStatusIcon.js';
|
||||
import {BUS_ADDRESS_REGEX} from './dbusUtils.js';
|
||||
|
||||
export const BUS_ADDRESS_REGEX = /([a-zA-Z0-9._-]+\.[a-zA-Z0-9.-]+)|(:[0-9]+\.[0-9]+)$/;
|
||||
|
||||
Gio._promisify(Gio.DBusConnection.prototype, 'call');
|
||||
Gio._promisify(Gio._LocalFilePrototype, 'read');
|
||||
Gio._promisify(Gio.InputStream.prototype, 'read_bytes_async');
|
||||
|
||||
@ -38,101 +37,6 @@ export function indicatorId(service, busName, objectPath) {
|
||||
return `${busName}@${objectPath}`;
|
||||
}
|
||||
|
||||
export async function getUniqueBusName(bus, name, cancellable) {
|
||||
if (name[0] === ':')
|
||||
return name;
|
||||
|
||||
if (!bus)
|
||||
bus = Gio.DBus.session;
|
||||
|
||||
const variantName = new GLib.Variant('(s)', [name]);
|
||||
const [unique] = (await bus.call('org.freedesktop.DBus', '/', 'org.freedesktop.DBus',
|
||||
'GetNameOwner', variantName, new GLib.VariantType('(s)'),
|
||||
Gio.DBusCallFlags.NONE, -1, cancellable)).deep_unpack();
|
||||
|
||||
return unique;
|
||||
}
|
||||
|
||||
export async function getBusNames(bus, cancellable) {
|
||||
if (!bus)
|
||||
bus = Gio.DBus.session;
|
||||
|
||||
const [names] = (await bus.call('org.freedesktop.DBus', '/', 'org.freedesktop.DBus',
|
||||
'ListNames', null, new GLib.VariantType('(as)'), Gio.DBusCallFlags.NONE,
|
||||
-1, cancellable)).deep_unpack();
|
||||
|
||||
const uniqueNames = new Map();
|
||||
const requests = names.map(name => getUniqueBusName(bus, name, cancellable));
|
||||
const results = await Promise.allSettled(requests);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (result.status === 'fulfilled') {
|
||||
let namesForBus = uniqueNames.get(result.value);
|
||||
if (!namesForBus) {
|
||||
namesForBus = new Set();
|
||||
uniqueNames.set(result.value, namesForBus);
|
||||
}
|
||||
namesForBus.add(result.value !== names[i] ? names[i] : null);
|
||||
} else if (!result.reason.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
|
||||
Logger.debug(`Impossible to get the unique name of ${names[i]}: ${result.reason}`);
|
||||
}
|
||||
}
|
||||
|
||||
return uniqueNames;
|
||||
}
|
||||
|
||||
async function getProcessId(connectionName, cancellable = null, bus = Gio.DBus.session) {
|
||||
const res = await bus.call('org.freedesktop.DBus', '/',
|
||||
'org.freedesktop.DBus', 'GetConnectionUnixProcessID',
|
||||
new GLib.Variant('(s)', [connectionName]),
|
||||
new GLib.VariantType('(u)'),
|
||||
Gio.DBusCallFlags.NONE,
|
||||
-1,
|
||||
cancellable);
|
||||
const [pid] = res.deepUnpack();
|
||||
return pid;
|
||||
}
|
||||
|
||||
export async function getProcessName(connectionName, cancellable = null,
|
||||
priority = GLib.PRIORITY_DEFAULT, bus = Gio.DBus.session) {
|
||||
const pid = await getProcessId(connectionName, cancellable, bus);
|
||||
const cmdFile = Gio.File.new_for_path(`/proc/${pid}/cmdline`);
|
||||
const inputStream = await cmdFile.read_async(priority, cancellable);
|
||||
const bytes = await inputStream.read_bytes_async(2048, priority, cancellable);
|
||||
const textDecoder = new TextDecoder();
|
||||
return textDecoder.decode(bytes.toArray().map(v => !v ? 0x20 : v));
|
||||
}
|
||||
|
||||
export async function* introspectBusObject(bus, name, cancellable,
|
||||
interfaces = undefined, path = undefined) {
|
||||
if (!path)
|
||||
path = '/';
|
||||
|
||||
const [introspection] = (await bus.call(name, path, 'org.freedesktop.DBus.Introspectable',
|
||||
'Introspect', null, new GLib.VariantType('(s)'), Gio.DBusCallFlags.NONE,
|
||||
5000, cancellable)).deep_unpack();
|
||||
|
||||
const nodeInfo = Gio.DBusNodeInfo.new_for_xml(introspection);
|
||||
|
||||
if (!interfaces || dbusNodeImplementsInterfaces(nodeInfo, interfaces))
|
||||
yield {nodeInfo, path};
|
||||
|
||||
if (path === '/')
|
||||
path = '';
|
||||
|
||||
for (const subNodeInfo of nodeInfo.nodes) {
|
||||
const subPath = `${path}/${subNodeInfo.path}`;
|
||||
yield* introspectBusObject(bus, name, cancellable, interfaces, subPath);
|
||||
}
|
||||
}
|
||||
|
||||
function dbusNodeImplementsInterfaces(nodeInfo, interfaces) {
|
||||
if (!(nodeInfo instanceof Gio.DBusNodeInfo) || !Array.isArray(interfaces))
|
||||
return false;
|
||||
|
||||
return interfaces.some(iface => nodeInfo.lookup_interface(iface));
|
||||
}
|
||||
|
||||
export class NameWatcher extends Signals.EventEmitter {
|
||||
constructor(name) {
|
||||
@ -268,87 +172,7 @@ export async function waitForStartupCompletion(cancellable) {
|
||||
await Main.layoutManager.connect_once('startup-complete', cancellable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class for logging stuff
|
||||
*/
|
||||
export class Logger {
|
||||
static _logStructured(logLevel, message, extraFields = {}) {
|
||||
if (!Object.values(GLib.LogLevelFlags).includes(logLevel)) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING,
|
||||
'logLevel is not a valid GLib.LogLevelFlags');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Logger._levels.includes(logLevel))
|
||||
return;
|
||||
|
||||
let fields = {
|
||||
'SYSLOG_IDENTIFIER': this.uuid,
|
||||
'MESSAGE': `${message}`,
|
||||
};
|
||||
|
||||
let thisFile = null;
|
||||
const {stack} = new Error();
|
||||
for (let stackLine of stack.split('\n')) {
|
||||
stackLine = stackLine.replace('resource:///org/gnome/Shell/', '');
|
||||
const [code, line] = stackLine.split(':');
|
||||
const [func, file] = code.split(/@(.+)/);
|
||||
|
||||
if (!thisFile || thisFile === file) {
|
||||
thisFile = file;
|
||||
continue;
|
||||
}
|
||||
|
||||
fields = Object.assign(fields, {
|
||||
'CODE_FILE': file || '',
|
||||
'CODE_LINE': line || '',
|
||||
'CODE_FUNC': func || '',
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
GLib.log_structured(Logger._domain, logLevel, Object.assign(fields, extraFields));
|
||||
}
|
||||
|
||||
static init(extension) {
|
||||
if (Logger._domain)
|
||||
return;
|
||||
|
||||
const allLevels = Object.values(GLib.LogLevelFlags);
|
||||
const domains = GLib.getenv('G_MESSAGES_DEBUG');
|
||||
const {name: domain} = extension.metadata;
|
||||
this.uuid = extension.metadata.uuid;
|
||||
Logger._domain = domain.replaceAll(' ', '-');
|
||||
|
||||
if (domains === 'all' || (domains && domains.split(' ').includes(Logger._domain))) {
|
||||
Logger._levels = allLevels;
|
||||
} else {
|
||||
Logger._levels = allLevels.filter(
|
||||
l => l <= GLib.LogLevelFlags.LEVEL_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
static debug(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_DEBUG, message);
|
||||
}
|
||||
|
||||
static message(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_MESSAGE, message);
|
||||
}
|
||||
|
||||
static warn(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_WARNING, message);
|
||||
}
|
||||
|
||||
static error(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_ERROR, message);
|
||||
}
|
||||
|
||||
static critical(message) {
|
||||
Logger._logStructured(GLib.LogLevelFlags.LEVEL_CRITICAL, message);
|
||||
}
|
||||
}
|
||||
export {Logger};
|
||||
|
||||
export function versionCheck(required) {
|
||||
const current = Config.PACKAGE_VERSION;
|
||||
|
||||
Reference in New Issue
Block a user