// SPDX-License-Identifier: MIT OR LGPL-2.0-or-later // SPDX-FileCopyrightText: 2011 Giovanni Campagna var GLib = imports.gi.GLib; var GjsPrivate = imports.gi.GjsPrivate; var Signals = imports.signals; var Gio; // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with // a parameter type that includes 'h' somewhere, actually has entries in it for // each of the indices being passed as an 'h' parameter. function _validateFDVariant(variant, fdList) { switch (String.fromCharCode(variant.classify())) { case 'b': case 'y': case 'n': case 'q': case 'i': case 'u': case 'x': case 't': case 'd': case 'o': case 'g': case 's': return; case 'h': { const val = variant.get_handle(); const numFds = fdList.get_length(); if (val >= numFds) { throw new Error(`handle ${val} is out of range of Gio.UnixFDList ` + `containing ${numFds} FDs`); } return; } case 'v': _validateFDVariant(variant.get_variant(), fdList); return; case 'm': { let val = variant.get_maybe(); if (val) _validateFDVariant(val, fdList); return; } case 'a': case '(': case '{': { let nElements = variant.n_children(); for (let ix = 0; ix < nElements; ix++) _validateFDVariant(variant.get_child_value(ix), fdList); return; } } throw new Error('Assertion failure: this code should not be reached'); } function _proxyInvoker(methodName, sync, inSignature, argArray) { var replyFunc; var flags = 0; var cancellable = null; let fdList = null; /* Convert argArray to a *real* array */ argArray = Array.prototype.slice.call(argArray); /* The default replyFunc only logs the responses */ replyFunc = _logReply; var signatureLength = inSignature.length; var minNumberArgs = signatureLength; var maxNumberArgs = signatureLength + 4; if (argArray.length < minNumberArgs) { throw new Error(`Not enough arguments passed for method: ${ methodName}. Expected ${minNumberArgs}, got ${argArray.length}`); } else if (argArray.length > maxNumberArgs) { throw new Error(`Too many arguments passed for method ${methodName}. ` + `Maximum is ${maxNumberArgs} including one callback, ` + 'Gio.Cancellable, Gio.UnixFDList, and/or flags'); } while (argArray.length > signatureLength) { var argNum = argArray.length - 1; var arg = argArray.pop(); if (typeof arg === 'function' && !sync) { replyFunc = arg; } else if (typeof arg === 'number') { flags = arg; } else if (arg instanceof Gio.Cancellable) { cancellable = arg; } else if (arg instanceof Gio.UnixFDList) { fdList = arg; } else { throw new Error(`Argument ${argNum} of method ${methodName} is ` + `${typeof arg}. It should be a callback, flags, ` + 'Gio.UnixFDList, or a Gio.Cancellable'); } } const inTypeString = `(${inSignature.join('')})`; const inVariant = new GLib.Variant(inTypeString, argArray); if (inTypeString.includes('h')) { if (!fdList) { throw new Error(`Method ${methodName} with input type containing ` + '\'h\' must have a Gio.UnixFDList as an argument'); } _validateFDVariant(inVariant, fdList); } var asyncCallback = (proxy, result) => { try { const [outVariant, outFdList] = proxy.call_with_unix_fd_list_finish(result); replyFunc(outVariant.deepUnpack(), null, outFdList); } catch (e) { replyFunc([], e, null); } }; if (sync) { const [outVariant, outFdList] = this.call_with_unix_fd_list_sync( methodName, inVariant, flags, -1, fdList, cancellable); if (fdList) return [outVariant.deepUnpack(), outFdList]; return outVariant.deepUnpack(); } return this.call_with_unix_fd_list(methodName, inVariant, flags, -1, fdList, cancellable, asyncCallback); } function _logReply(result, exc) { if (exc !== null) log(`Ignored exception from dbus method: ${exc}`); } function _makeProxyMethod(method, sync) { var i; var name = method.name; var inArgs = method.in_args; var inSignature = []; for (i = 0; i < inArgs.length; i++) inSignature.push(inArgs[i].signature); return function (...args) { return _proxyInvoker.call(this, name, sync, inSignature, args); }; } function _convertToNativeSignal(proxy, senderName, signalName, parameters) { Signals._emit.call(proxy, signalName, senderName, parameters.deepUnpack()); } function _propertyGetter(name) { let value = this.get_cached_property(name); return value ? value.deepUnpack() : null; } function _propertySetter(name, signature, value) { let variant = new GLib.Variant(signature, value); this.set_cached_property(name, variant); this.call('org.freedesktop.DBus.Properties.Set', new GLib.Variant('(ssv)', [this.g_interface_name, name, variant]), Gio.DBusCallFlags.NONE, -1, null, (proxy, result) => { try { this.call_finish(result); } catch (e) { log(`Could not set property ${name} on remote object ${ this.g_object_path}: ${e.message}`); } }); } function _addDBusConvenience() { let info = this.g_interface_info; if (!info) return; if (info.signals.length > 0) this.connect('g-signal', _convertToNativeSignal); let i, methods = info.methods; for (i = 0; i < methods.length; i++) { var method = methods[i]; let remoteMethod = _makeProxyMethod(methods[i], false); this[`${method.name}Remote`] = remoteMethod; this[`${method.name}Sync`] = _makeProxyMethod(methods[i], true); this[`${method.name}Async`] = function (...args) { return new Promise((resolve, reject) => { args.push((result, error, fdList) => { if (error) reject(error); else if (fdList) resolve([result, fdList]); else resolve(result); }); remoteMethod.call(this, ...args); }); }; } let properties = info.properties; for (i = 0; i < properties.length; i++) { let name = properties[i].name; let signature = properties[i].signature; let flags = properties[i].flags; let getter = () => { throw new Error(`Property ${name} is not readable`); }; let setter = () => { throw new Error(`Property ${name} is not writable`); }; if (flags & Gio.DBusPropertyInfoFlags.READABLE) getter = _propertyGetter.bind(this, name); if (flags & Gio.DBusPropertyInfoFlags.WRITABLE) setter = _propertySetter.bind(this, name, signature); Object.defineProperty(this, name, { get: getter, set: setter, configurable: false, enumerable: true, }); } } function _makeProxyWrapper(interfaceXml) { var info = _newInterfaceInfo(interfaceXml); var iname = info.name; function wrapper(bus, name, object, asyncCallback, cancellable, flags = Gio.DBusProxyFlags.NONE) { var obj = new Gio.DBusProxy({ g_connection: bus, g_interface_name: iname, g_interface_info: info, g_name: name, g_flags: flags, g_object_path: object, }); if (!cancellable) cancellable = null; if (asyncCallback) { obj.init_async(GLib.PRIORITY_DEFAULT, cancellable).then( () => asyncCallback(obj, null)).catch(e => asyncCallback(null, e)); } else { obj.init(cancellable); } return obj; } wrapper.newAsync = function newAsync(bus, name, object, cancellable, flags = Gio.DBusProxyFlags.NONE) { const obj = new Gio.DBusProxy({ g_connection: bus, g_interface_name: info.name, g_interface_info: info, g_name: name, g_flags: flags, g_object_path: object, }); return new Promise((resolve, reject) => obj.init_async(GLib.PRIORITY_DEFAULT, cancellable ?? null).then( () => resolve(obj)).catch(reject)); }; return wrapper; } function _newNodeInfo(constructor, value) { if (typeof value === 'string') return constructor(value); throw TypeError(`Invalid type ${Object.prototype.toString.call(value)}`); } function _newInterfaceInfo(value) { var nodeInfo = Gio.DBusNodeInfo.new_for_xml(value); return nodeInfo.interfaces[0]; } function _injectToMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { addition.apply(this, args); return previous.apply(this, args); }; } function _injectToStaticMethod(klass, method, addition) { var previous = klass[method]; klass[method] = function (...parameters) { let obj = previous.apply(this, parameters); addition.apply(obj, parameters); return obj; }; } function _wrapFunction(klass, method, addition) { var previous = klass[method]; klass[method] = function (...args) { args.unshift(previous); return addition.apply(this, args); }; } function _makeOutSignature(args) { var ret = '('; for (var i = 0; i < args.length; i++) ret += args[i].signature; return `${ret})`; } function _handleMethodCall(info, impl, methodName, parameters, invocation) { // prefer a sync version if available if (this[methodName]) { let retval; try { const fdList = invocation.get_message().get_unix_fd_list(); retval = this[methodName](...parameters.deepUnpack(), fdList); } catch (e) { if (e instanceof GLib.Error) { invocation.return_gerror(e); } else { let name = e.name; if (!name.includes('.')) { // likely to be a normal JS error name = `org.gnome.gjs.JSError.${name}`; } logError(e, `Exception in method call: ${methodName}`); invocation.return_dbus_error(name, e.message); } return; } if (retval === undefined) { // undefined (no return value) is the empty tuple retval = new GLib.Variant('()', []); } try { let outFdList = null; if (!(retval instanceof GLib.Variant)) { // attempt packing according to out signature let methodInfo = info.lookup_method(methodName); let outArgs = methodInfo.out_args; let outSignature = _makeOutSignature(outArgs); if (outSignature.includes('h') && retval[retval.length - 1] instanceof Gio.UnixFDList) { outFdList = retval.pop(); } else if (outArgs.length === 1) { // if one arg, we don't require the handler wrapping it // into an Array retval = [retval]; } retval = new GLib.Variant(outSignature, retval); } invocation.return_value_with_unix_fd_list(retval, outFdList); } catch (e) { // if we don't do this, the other side will never see a reply invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError', 'Service implementation returned an incorrect value type'); } } else if (this[`${methodName}Async`]) { const fdList = invocation.get_message().get_unix_fd_list(); this[`${methodName}Async`](parameters.deepUnpack(), invocation, fdList); } else { log(`Missing handler for DBus method ${methodName}`); invocation.return_gerror(new Gio.DBusError({ code: Gio.DBusError.UNKNOWN_METHOD, message: `Method ${methodName} is not implemented`, })); } } function _handlePropertyGet(info, impl, propertyName) { let propInfo = info.lookup_property(propertyName); let jsval = this[propertyName]; if (jsval?.get_type_string?.() === propInfo.signature) return jsval; else if (jsval !== undefined) return new GLib.Variant(propInfo.signature, jsval); else return null; } function _handlePropertySet(info, impl, propertyName, newValue) { this[propertyName] = newValue.deepUnpack(); } function _wrapJSObject(interfaceInfo, jsObj) { var info; if (interfaceInfo instanceof Gio.DBusInterfaceInfo) info = interfaceInfo; else info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo); info.cache_build(); var impl = new GjsPrivate.DBusImplementation({g_interface_info: info}); impl.connect('handle-method-call', function (self, methodName, parameters, invocation) { return _handleMethodCall.call(jsObj, info, self, methodName, parameters, invocation); }); impl.connect('handle-property-get', function (self, propertyName) { return _handlePropertyGet.call(jsObj, info, self, propertyName); }); impl.connect('handle-property-set', function (self, propertyName, value) { return _handlePropertySet.call(jsObj, info, self, propertyName, value); }); return impl; } function* _listModelIterator() { let _index = 0; const _len = this.get_n_items(); while (_index < _len) yield this.get_item(_index++); } function _promisify(proto, asyncFunc, finishFunc = undefined) { if (proto[asyncFunc] === undefined) throw new Error(`${proto} has no method named ${asyncFunc}`); if (finishFunc === undefined) { if (asyncFunc.endsWith('_begin') || asyncFunc.endsWith('_async')) finishFunc = `${asyncFunc.slice(0, -5)}finish`; else finishFunc = `${asyncFunc}_finish`; } if (proto[finishFunc] === undefined) throw new Error(`${proto} has no method named ${finishFunc}`); const originalFuncName = `_original_${asyncFunc}`; if (proto[originalFuncName] !== undefined) return; proto[originalFuncName] = proto[asyncFunc]; proto[asyncFunc] = function (...args) { if (args.length === this[originalFuncName].length) return this[originalFuncName](...args); return new Promise((resolve, reject) => { let {stack: callStack} = new Error(); this[originalFuncName](...args, function (source, res) { try { const result = source !== null && source[finishFunc] !== undefined ? source[finishFunc](res) : proto[finishFunc](res); if (Array.isArray(result) && result.length > 1 && result[0] === true) result.shift(); resolve(result); } catch (error) { callStack = callStack.split('\n').filter(line => line.indexOf('_promisify/') === -1).join('\n'); if (error.stack) error.stack += `### Promise created here: ###\n${callStack}`; else error.stack = callStack; reject(error); } }); }); }; } function _notIntrospectableError(funcName, replacement) { return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`); } function _warnNotIntrospectable(funcName, replacement) { logError(_notIntrospectableError(funcName, replacement)); } function _init() { Gio = this; Gio.Application.prototype.runAsync = GLib.MainLoop.prototype.runAsync; Gio.DBus = { // Namespace some functions get: Gio.bus_get, get_finish: Gio.bus_get_finish, get_sync: Gio.bus_get_sync, own_name: Gio.bus_own_name, own_name_on_connection: Gio.bus_own_name_on_connection, unown_name: Gio.bus_unown_name, watch_name: Gio.bus_watch_name, watch_name_on_connection: Gio.bus_watch_name_on_connection, unwatch_name: Gio.bus_unwatch_name, }; Object.defineProperties(Gio.DBus, { 'session': { get() { return Gio.bus_get_sync(Gio.BusType.SESSION, null); }, enumerable: false, }, 'system': { get() { return Gio.bus_get_sync(Gio.BusType.SYSTEM, null); }, enumerable: false, }, }); Gio.DBusConnection.prototype.watch_name = function (name, flags, appeared, vanished) { return Gio.bus_watch_name_on_connection(this, name, flags, appeared, vanished); }; Gio.DBusConnection.prototype.unwatch_name = function (id) { return Gio.bus_unwatch_name(id); }; Gio.DBusConnection.prototype.own_name = function (name, flags, acquired, lost) { return Gio.bus_own_name_on_connection(this, name, flags, acquired, lost); }; Gio.DBusConnection.prototype.unown_name = function (id) { return Gio.bus_unown_name(id); }; _injectToMethod(Gio.DBusProxy.prototype, 'init', _addDBusConvenience); _promisify(Gio.DBusProxy.prototype, 'init_async'); _injectToMethod(Gio.DBusProxy.prototype, 'init_async', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_finish', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_sync', _addDBusConvenience); _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_finish', _addDBusConvenience); Gio.DBusProxy.prototype.connectSignal = Signals._connect; Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect; Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper; // Some helpers _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo); Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo; Gio.DBusExportedObject = GjsPrivate.DBusImplementation; Gio.DBusExportedObject.wrapJSObject = _wrapJSObject; // ListStore Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator; Gio.ListStore.prototype.insert_sorted = function (item, compareFunc) { return GjsPrivate.list_store_insert_sorted(this, item, compareFunc); }; Gio.ListStore.prototype.sort = function (compareFunc) { return GjsPrivate.list_store_sort(this, compareFunc); }; // Promisify Gio._promisify = _promisify; // Temporary Gio.File.prototype fix Gio._LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype; Gio.File.prototype.replace_contents_async = function replace_contents_async(contents, etag, make_backup, flags, cancellable, callback) { return this.replace_contents_bytes_async(contents, etag, make_backup, flags, cancellable, callback); }; // Best-effort attempt to replace set_attribute(), which is not // introspectable due to the pointer argument Gio.File.prototype.set_attribute = function set_attribute(attribute, type, value, flags, cancellable) { _warnNotIntrospectable('Gio.File.prototype.set_attribute', 'set_attribute_{type}'); switch (type) { case Gio.FileAttributeType.STRING: return this.set_attribute_string(attribute, value, flags, cancellable); case Gio.FileAttributeType.BYTE_STRING: return this.set_attribute_byte_string(attribute, value, flags, cancellable); case Gio.FileAttributeType.UINT32: return this.set_attribute_uint32(attribute, value, flags, cancellable); case Gio.FileAttributeType.INT32: return this.set_attribute_int32(attribute, value, flags, cancellable); case Gio.FileAttributeType.UINT64: return this.set_attribute_uint64(attribute, value, flags, cancellable); case Gio.FileAttributeType.INT64: return this.set_attribute_int64(attribute, value, flags, cancellable); case Gio.FileAttributeType.INVALID: case Gio.FileAttributeType.BOOLEAN: case Gio.FileAttributeType.OBJECT: case Gio.FileAttributeType.STRINGV: throw _notIntrospectableError('This attribute type', 'Gio.FileInfo'); } }; Gio.FileInfo.prototype.set_attribute = function set_attribute(attribute, type, value) { _warnNotIntrospectable('Gio.FileInfo.prototype.set_attribute', 'set_attribute_{type}'); switch (type) { case Gio.FileAttributeType.INVALID: return this.remove_attribute(attribute); case Gio.FileAttributeType.STRING: return this.set_attribute_string(attribute, value); case Gio.FileAttributeType.BYTE_STRING: return this.set_attribute_byte_string(attribute, value); case Gio.FileAttributeType.BOOLEAN: return this.set_attribute_boolean(attribute, value); case Gio.FileAttributeType.UINT32: return this.set_attribute_uint32(attribute, value); case Gio.FileAttributeType.INT32: return this.set_attribute_int32(attribute, value); case Gio.FileAttributeType.UINT64: return this.set_attribute_uint64(attribute, value); case Gio.FileAttributeType.INT64: return this.set_attribute_int64(attribute, value); case Gio.FileAttributeType.OBJECT: return this.set_attribute_object(attribute, value); case Gio.FileAttributeType.STRINGV: return this.set_attribute_stringv(attribute, value); } }; Gio.InputStream.prototype.createSyncIterator = function* createSyncIterator(count) { while (true) { const bytes = this.read_bytes(count, null); if (bytes.get_size() === 0) return; yield bytes; } }; Gio.InputStream.prototype.createAsyncIterator = async function* createAsyncIterator(count, ioPriority = Gio.PRIORITY_DEFAULT) { const self = this; function next() { return new Promise((resolve, reject) => { self.read_bytes_async(count, ioPriority, null, (_self, res) => { try { const bytes = self.read_bytes_finish(res); resolve(bytes); } catch (err) { reject(err); } }); }); } while (true) { // eslint-disable-next-line no-await-in-loop const bytes = await next(count); if (bytes.get_size() === 0) return; yield bytes; } }; Gio.FileEnumerator.prototype[Symbol.iterator] = function* FileEnumeratorIterator() { while (true) { try { const info = this.next_file(null); if (info === null) break; yield info; } catch (err) { this.close(null); throw err; } } this.close(null); }; Gio.FileEnumerator.prototype[Symbol.asyncIterator] = async function* AsyncFileEnumeratorIterator() { const self = this; function next() { return new Promise((resolve, reject) => { self.next_files_async(1, Gio.PRIORITY_DEFAULT, null, (_self, res) => { try { const files = self.next_files_finish(res); resolve(files.length === 0 ? null : files[0]); } catch (err) { reject(err); } }); }); } function close() { return new Promise((resolve, reject) => { self.close_async(Gio.PRIORITY_DEFAULT, null, (_self, res) => { try { resolve(self.close_finish(res)); } catch (err) { reject(err); } }); }); } while (true) { try { // eslint-disable-next-line no-await-in-loop const info = await next(); if (info === null) break; yield info; } catch (err) { // eslint-disable-next-line no-await-in-loop await close(); throw err; } } return close(); }; // Override Gio.Settings and Gio.SettingsSchema - the C API asserts if // trying to access a nonexistent schema or key, which is not handy for // shell-extension writers Gio.SettingsSchema.prototype._realGetKey = Gio.SettingsSchema.prototype.get_key; Gio.SettingsSchema.prototype.get_key = function (key) { if (!this.has_key(key)) throw new Error(`GSettings key ${key} not found in schema ${this.get_id()}`); return this._realGetKey(key); }; Gio.Settings.prototype._realMethods = Object.assign({}, Gio.Settings.prototype); function createCheckedMethod(method, checkMethod = '_checkKey') { return function (id, ...args) { this[checkMethod](id); return this._realMethods[method].call(this, id, ...args); }; } Object.assign(Gio.Settings.prototype, { _realInit: Gio.Settings.prototype._init, // add manually, not enumerable _init(props = {}) { // 'schema' is a deprecated alias for schema_id const schemaIdProp = ['schema', 'schema-id', 'schema_id', 'schemaId'].find(prop => prop in props); const settingsSchemaProp = ['settings-schema', 'settings_schema', 'settingsSchema'].find(prop => prop in props); if (!schemaIdProp && !settingsSchemaProp) { throw new Error('One of property \'schema-id\' or ' + '\'settings-schema\' are required for Gio.Settings'); } const source = Gio.SettingsSchemaSource.get_default(); const settingsSchema = settingsSchemaProp ? props[settingsSchemaProp] : source.lookup(props[schemaIdProp], true); if (!settingsSchema) throw new Error(`GSettings schema ${props[schemaIdProp]} not found`); const settingsSchemaPath = settingsSchema.get_path(); if (props['path'] === undefined && !settingsSchemaPath) { throw new Error('Attempting to create schema ' + `'${settingsSchema.get_id()}' without a path`); } if (props['path'] !== undefined && settingsSchemaPath && props['path'] !== settingsSchemaPath) { throw new Error(`GSettings created for path '${props['path']}'` + `, but schema specifies '${settingsSchemaPath}'`); } return this._realInit(props); }, _checkKey(key) { // Avoid using has_key(); checking a JS array is faster than calling // through G-I. if (!this._keys) this._keys = this.settings_schema.list_keys(); if (!this._keys.includes(key)) throw new Error(`GSettings key ${key} not found in schema ${this.schema_id}`); }, _checkChild(name) { if (!this._children) this._children = this.list_children(); if (!this._children.includes(name)) throw new Error(`Child ${name} not found in GSettings schema ${this.schema_id}`); }, get_boolean: createCheckedMethod('get_boolean'), set_boolean: createCheckedMethod('set_boolean'), get_double: createCheckedMethod('get_double'), set_double: createCheckedMethod('set_double'), get_enum: createCheckedMethod('get_enum'), set_enum: createCheckedMethod('set_enum'), get_flags: createCheckedMethod('get_flags'), set_flags: createCheckedMethod('set_flags'), get_int: createCheckedMethod('get_int'), set_int: createCheckedMethod('set_int'), get_int64: createCheckedMethod('get_int64'), set_int64: createCheckedMethod('set_int64'), get_string: createCheckedMethod('get_string'), set_string: createCheckedMethod('set_string'), get_strv: createCheckedMethod('get_strv'), set_strv: createCheckedMethod('set_strv'), get_uint: createCheckedMethod('get_uint'), set_uint: createCheckedMethod('set_uint'), get_uint64: createCheckedMethod('get_uint64'), set_uint64: createCheckedMethod('set_uint64'), get_value: createCheckedMethod('get_value'), set_value: createCheckedMethod('set_value'), bind: createCheckedMethod('bind'), bind_writable: createCheckedMethod('bind_writable'), create_action: createCheckedMethod('create_action'), get_default_value: createCheckedMethod('get_default_value'), get_user_value: createCheckedMethod('get_user_value'), is_writable: createCheckedMethod('is_writable'), reset: createCheckedMethod('reset'), get_child: createCheckedMethod('get_child', '_checkChild'), }); // ActionMap // add_action_entries is not introspectable // https://gitlab.gnome.org/GNOME/gjs/-/issues/407 Gio.ActionMap.prototype.add_action_entries = function add_action_entries(entries) { for (const {name, activate, parameter_type, state, change_state} of entries) { if (typeof parameter_type === 'string' && !GLib.variant_type_string_is_valid(parameter_type)) throw new Error(`parameter_type "${parameter_type}" is not a valid VariantType`); const action = new Gio.SimpleAction({ name, parameter_type: typeof parameter_type === 'string' ? new GLib.VariantType(parameter_type) : null, state: typeof state === 'string' ? GLib.Variant.parse(null, state, null, null) : null, }); if (typeof activate === 'function') action.connect('activate', activate.bind(action)); if (typeof change_state === 'function') action.connect('change-state', change_state.bind(action)); this.add_action(action); } }; }