gnome-shell/js/misc/objectManager.js
Jonas Dreßler 8431e7ae51 misc/objectManager: Fix emission of object-removed signal
Emitting this signal is broken right now: We check for a length of 0 on
this._objects[objectPath], but the
`this._objects[objectPath][interfaceName] = null` we do before the
check doesn't actually remove the key, it only sets the value to null,
leaving the key around and thus the amount of entries in the object doesn't
change.

Fix that by using the delete statement instead, "delete" properly removes
the key and thus affects the amount of entries in the object, making our
length === 0 check effective.

Part-of: <https://gitlab.gnome.org/GNOME/gnome-shell/-/merge_requests/2730>
2023-04-05 18:43:51 +00:00

262 lines
8.0 KiB
JavaScript

// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ObjectManager */
const { Gio, GLib } = imports.gi;
const Params = imports.misc.params;
const Signals = imports.misc.signals;
// Specified in the D-Bus specification here:
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
const ObjectManagerIface = `
<node>
<interface name="org.freedesktop.DBus.ObjectManager">
<method name="GetManagedObjects">
<arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
</method>
<signal name="InterfacesAdded">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="a{sa{sv}}" />
</signal>
<signal name="InterfacesRemoved">
<arg name="objectPath" type="o"/>
<arg name="interfaces" type="as" />
</signal>
</interface>
</node>`;
const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);
var ObjectManager = class extends Signals.EventEmitter {
constructor(params) {
super();
params = Params.parse(params, {
connection: null,
name: null,
objectPath: null,
knownInterfaces: null,
cancellable: null,
onLoaded: null,
});
this._connection = params.connection;
this._serviceName = params.name;
this._managerPath = params.objectPath;
this._cancellable = params.cancellable;
this._managerProxy = new Gio.DBusProxy({
g_connection: this._connection,
g_interface_name: ObjectManagerInfo.name,
g_interface_info: ObjectManagerInfo,
g_name: this._serviceName,
g_object_path: this._managerPath,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
this._interfaceInfos = {};
this._objects = {};
this._interfaces = {};
this._onLoaded = params.onLoaded;
if (params.knownInterfaces)
this._registerInterfaces(params.knownInterfaces);
this._initManagerProxy();
}
_completeLoad() {
if (this._onLoaded)
this._onLoaded();
}
async _addInterface(objectPath, interfaceName) {
let info = this._interfaceInfos[interfaceName];
if (!info)
return;
const proxy = new Gio.DBusProxy({
g_connection: this._connection,
g_name: this._serviceName,
g_object_path: objectPath,
g_interface_name: interfaceName,
g_interface_info: info,
g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START,
});
try {
await proxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable);
} catch (e) {
logError(e, `could not initialize proxy for interface ${interfaceName}`);
return;
}
let isNewObject;
if (!this._objects[objectPath]) {
this._objects[objectPath] = {};
isNewObject = true;
} else {
isNewObject = false;
}
this._objects[objectPath][interfaceName] = proxy;
if (!this._interfaces[interfaceName])
this._interfaces[interfaceName] = [];
this._interfaces[interfaceName].push(proxy);
if (isNewObject)
this.emit('object-added', objectPath);
this.emit('interface-added', interfaceName, proxy);
}
_removeInterface(objectPath, interfaceName) {
if (!this._objects[objectPath])
return;
let proxy = this._objects[objectPath][interfaceName];
if (this._interfaces[interfaceName]) {
let index = this._interfaces[interfaceName].indexOf(proxy);
if (index >= 0)
this._interfaces[interfaceName].splice(index, 1);
if (this._interfaces[interfaceName].length === 0)
delete this._interfaces[interfaceName];
}
this.emit('interface-removed', interfaceName, proxy);
delete this._objects[objectPath][interfaceName];
if (Object.keys(this._objects[objectPath]).length === 0) {
delete this._objects[objectPath];
this.emit('object-removed', objectPath);
}
}
async _initManagerProxy() {
try {
await this._managerProxy.init_async(
GLib.PRIORITY_DEFAULT, this._cancellable);
} catch (e) {
logError(e, `could not initialize object manager for object ${this._serviceName}`);
this._completeLoad();
return;
}
this._managerProxy.connectSignal('InterfacesAdded',
(objectManager, sender, [objectPath, interfaces]) => {
let interfaceNames = Object.keys(interfaces);
for (let i = 0; i < interfaceNames.length; i++)
this._addInterface(objectPath, interfaceNames[i]);
});
this._managerProxy.connectSignal('InterfacesRemoved',
(objectManager, sender, [objectPath, interfaceNames]) => {
for (let i = 0; i < interfaceNames.length; i++)
this._removeInterface(objectPath, interfaceNames[i]);
});
if (Object.keys(this._interfaceInfos).length === 0) {
this._completeLoad();
return;
}
this._managerProxy.connect('notify::g-name-owner', () => {
if (this._managerProxy.g_name_owner)
this._onNameAppeared();
else
this._onNameVanished();
});
if (this._managerProxy.g_name_owner)
this._onNameAppeared();
}
async _onNameAppeared() {
try {
const [objects] = await this._managerProxy.GetManagedObjectsAsync();
if (!objects) {
this._completeLoad();
return;
}
const objectPaths = Object.keys(objects);
await Promise.allSettled(objectPaths.flatMap(objectPath => {
const object = objects[objectPath];
const interfaceNames = Object.getOwnPropertyNames(object);
return interfaceNames.map(
ifaceName => this._addInterface(objectPath, ifaceName));
}));
} catch (error) {
logError(error, `could not get remote objects for service ${this._serviceName} path ${this._managerPath}`);
} finally {
this._completeLoad();
}
}
_onNameVanished() {
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let objectPath = objectPaths[i];
let object = this._objects[objectPath];
let interfaceNames = Object.keys(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
if (object[interfaceName])
this._removeInterface(objectPath, interfaceName);
}
}
}
_registerInterfaces(interfaces) {
for (let i = 0; i < interfaces.length; i++) {
let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
this._interfaceInfos[info.name] = info;
}
}
getProxy(objectPath, interfaceName) {
let object = this._objects[objectPath];
if (!object)
return null;
return object[interfaceName];
}
getProxiesForInterface(interfaceName) {
let proxyList = this._interfaces[interfaceName];
if (!proxyList)
return [];
return proxyList;
}
getAllProxies() {
let proxies = [];
let objectPaths = Object.keys(this._objects);
for (let i = 0; i < objectPaths.length; i++) {
let object = this._objects[objectPaths];
let interfaceNames = Object.keys(object);
for (let j = 0; j < interfaceNames.length; j++) {
let interfaceName = interfaceNames[j];
if (object[interfaceName])
proxies.push(object(interfaceName));
}
}
return proxies;
}
};