[gnome] Update extensions

This commit is contained in:
2026-04-09 09:51:44 -04:00
parent dadc78f2f4
commit 505c67d292
61 changed files with 1729 additions and 365 deletions

View File

@ -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)) {

View File

@ -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);
});
}
}

View File

@ -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) {

View File

@ -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));
}

View File

@ -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);
}
}

View File

@ -0,0 +1,12 @@
let _defaultTheme;
export function getDefaultTheme() {
if (_defaultTheme)
return _defaultTheme;
_defaultTheme = new St.IconTheme();
return _defaultTheme;
}
export function destroyDefaultTheme() {
_defaultTheme = null;
}

View File

@ -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;

View File

@ -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);
}
}

View File

@ -14,5 +14,5 @@
],
"url": "https://github.com/ubuntu/gnome-shell-extension-appindicator",
"uuid": "appindicatorsupport@rgcjonas.gmail.com",
"version": 63
"version": 64
}

View File

@ -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);

View File

@ -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));

View File

@ -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;