| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447 |
- // 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 GObject from 'gi://GObject';
- import St from 'gi://St';
- 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 {BaseStatusIcon} from './indicatorStatusIcon.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');
- export function indicatorId(service, busName, objectPath) {
- if (service !== busName && service?.match(BUS_ADDRESS_REGEX))
- return service;
- 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) {
- super();
- this._watcherId = Gio.DBus.session.watch_name(name,
- Gio.BusNameWatcherFlags.NONE, () => {
- this._nameOnBus = true;
- Logger.debug(`Name ${name} appeared`);
- this.emit('changed');
- this.emit('appeared');
- }, () => {
- this._nameOnBus = false;
- Logger.debug(`Name ${name} vanished`);
- this.emit('changed');
- this.emit('vanished');
- });
- }
- destroy() {
- this.emit('destroy');
- Gio.DBus.session.unwatch_name(this._watcherId);
- delete this._watcherId;
- }
- get nameOnBus() {
- return !!this._nameOnBus;
- }
- }
- function connectSmart3A(src, signal, handler) {
- const id = src.connect(signal, handler);
- let destroyId = 0;
- if (src.connect && (!(src instanceof GObject.Object) || GObject.signal_lookup('destroy', src))) {
- destroyId = src.connect('destroy', () => {
- src.disconnect(id);
- src.disconnect(destroyId);
- });
- }
- return [id, destroyId];
- }
- function connectSmart4A(src, signal, target, method) {
- if (typeof method !== 'function')
- throw new TypeError('Unsupported function');
- method = method.bind(target);
- const signalId = src.connect(signal, method);
- const onDestroy = () => {
- src.disconnect(signalId);
- if (srcDestroyId)
- src.disconnect(srcDestroyId);
- if (tgtDestroyId)
- target.disconnect(tgtDestroyId);
- };
- // GObject classes might or might not have a destroy signal
- // JS Classes will not complain when connecting to non-existent signals
- const srcDestroyId = src.connect && (!(src instanceof GObject.Object) ||
- GObject.signal_lookup('destroy', src)) ? src.connect('destroy', onDestroy) : 0;
- const tgtDestroyId = target.connect && (!(target instanceof GObject.Object) ||
- GObject.signal_lookup('destroy', target)) ? target.connect('destroy', onDestroy) : 0;
- return [signalId, srcDestroyId, tgtDestroyId];
- }
- // eslint-disable-next-line valid-jsdoc
- /**
- * Connect signals to slots, and remove the connection when either source or
- * target are destroyed
- *
- * Usage:
- * Util.connectSmart(srcOb, 'signal', tgtObj, 'handler')
- * or
- * Util.connectSmart(srcOb, 'signal', () => { ... })
- */
- export function connectSmart(...args) {
- if (arguments.length === 4)
- return connectSmart4A(...args);
- else
- return connectSmart3A(...args);
- }
- function disconnectSmart3A(src, signalIds) {
- const [id, destroyId] = signalIds;
- src.disconnect(id);
- if (destroyId)
- src.disconnect(destroyId);
- }
- function disconnectSmart4A(src, tgt, signalIds) {
- const [signalId, srcDestroyId, tgtDestroyId] = signalIds;
- disconnectSmart3A(src, [signalId, srcDestroyId]);
- if (tgtDestroyId)
- tgt.disconnect(tgtDestroyId);
- }
- export function disconnectSmart(...args) {
- if (arguments.length === 2)
- return disconnectSmart3A(...args);
- else if (arguments.length === 3)
- return disconnectSmart4A(...args);
- throw new TypeError('Unexpected number of arguments');
- }
- let _defaultTheme;
- export function getDefaultTheme() {
- if (_defaultTheme)
- return _defaultTheme;
- _defaultTheme = new St.IconTheme();
- return _defaultTheme;
- }
- export function destroyDefaultTheme() {
- _defaultTheme = null;
- }
- // eslint-disable-next-line valid-jsdoc
- /**
- * Helper function to wait for the system startup to be completed.
- * Adding widgets before the desktop is ready to accept them can result in errors.
- */
- export async function waitForStartupCompletion(cancellable) {
- if (Main.layoutManager._startingUp)
- 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 function versionCheck(required) {
- const current = Config.PACKAGE_VERSION;
- const currentArray = current.split('.');
- const [major] = currentArray;
- return major >= required;
- }
- export function tryCleanupOldIndicators() {
- const indicatorType = BaseStatusIcon;
- const indicators = Object.values(Main.panel.statusArea).filter(i => i instanceof indicatorType);
- try {
- const panelBoxes = [
- Main.panel._leftBox, Main.panel._centerBox, Main.panel._rightBox,
- ];
- panelBoxes.forEach(box =>
- indicators.push(...box.get_children().filter(i => i instanceof indicatorType)));
- } catch (e) {
- logError(e);
- }
- new Set(indicators).forEach(i => i.destroy());
- }
- export function addActor(obj, actor) {
- if (obj.add_actor)
- obj.add_actor(actor);
- else
- obj.add_child(actor);
- }
- export function removeActor(obj, actor) {
- if (obj.remove_actor)
- obj.remove_actor(actor);
- else
- obj.remove_child(actor);
- }
- export const CancellableChild = GObject.registerClass({
- Properties: {
- 'parent': GObject.ParamSpec.object(
- 'parent', 'parent', 'parent',
- GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
- Gio.Cancellable.$gtype),
- },
- },
- class CancellableChild extends Gio.Cancellable {
- _init(parent) {
- if (parent && !(parent instanceof Gio.Cancellable))
- throw TypeError('Not a valid cancellable');
- super._init({parent});
- if (parent) {
- if (parent.is_cancelled()) {
- this.cancel();
- return;
- }
- this._connectToParent();
- }
- }
- _connectToParent() {
- this._connectId = this.parent.connect(() => {
- this._realCancel();
- if (this._disconnectIdle)
- return;
- this._disconnectIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
- delete this._disconnectIdle;
- this._disconnectFromParent();
- return GLib.SOURCE_REMOVE;
- });
- });
- }
- _disconnectFromParent() {
- if (this._connectId && !this._disconnectIdle) {
- this.parent.disconnect(this._connectId);
- delete this._connectId;
- }
- }
- _realCancel() {
- Gio.Cancellable.prototype.cancel.call(this);
- }
- cancel() {
- this._disconnectFromParent();
- this._realCancel();
- }
- });
|