// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*- import Gio from 'gi://Gio'; import GLib from 'gi://GLib'; import * as Params from './params.js'; import * as Signals from './signals.js'; // Specified in the D-Bus specification here: // http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager const ObjectManagerIface = ` `; const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface); export class ObjectManager 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; } }