Branch data Line data Source code
1 : 120 : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
2 : : // SPDX-FileCopyrightText: 2011 Giovanni Campagna
3 : :
4 : 22 : var GLib = imports.gi.GLib;
5 : 22 : var GjsPrivate = imports.gi.GjsPrivate;
6 : 22 : const Signals = imports._signals;
7 : 22 : const {_createWrappersForPlatformSpecificNamespace} = imports._common;
8 : 22 : const {setMainLoopHook} = imports._promiseNative;
9 : : var Gio;
10 : :
11 : : // Ensures that a Gio.UnixFDList being passed into or out of a DBus method with
12 : : // a parameter type that includes 'h' somewhere, actually has entries in it for
13 : : // each of the indices being passed as an 'h' parameter.
14 : 12 : function _validateFDVariant(variant, fdList) {
15 [ - + ][ - + ]: 12 : switch (String.fromCharCode(variant.classify())) {
[ - + ][ - + ]
[ - + ][ - + ]
[ - + ][ - + ]
[ - + ][ - + ]
[ - + ][ - + ]
[ + + ][ - + ]
[ - + ][ - + ]
[ + - ][ # # ]
16 : : case 'b':
17 : : case 'y':
18 : : case 'n':
19 : : case 'q':
20 : : case 'i':
21 : : case 'u':
22 : : case 'x':
23 : : case 't':
24 : : case 'd':
25 : : case 'o':
26 : : case 'g':
27 : : case 's':
28 : 0 : return;
29 : 6 : case 'h': {
30 : 6 : const val = variant.get_handle();
31 : 6 : const numFds = fdList.get_length();
32 [ + + ]: 6 : if (val >= numFds) {
33 : 4 : throw new Error(`handle ${val} is out of range of Gio.UnixFDList ` +
34 : 2 : `containing ${numFds} FDs`);
35 : : }
36 : 4 : return;
37 : : }
38 : : case 'v':
39 : 0 : _validateFDVariant(variant.get_variant(), fdList);
40 : 0 : return;
41 : 0 : case 'm': {
42 : 0 : let val = variant.get_maybe();
43 [ # # ]: 0 : if (val)
44 : 0 : _validateFDVariant(val, fdList);
45 : 0 : return;
46 : : }
47 : : case 'a':
48 : : case '(':
49 : 6 : case '{': {
50 : 6 : let nElements = variant.n_children();
51 [ + + ]: 10 : for (let ix = 0; ix < nElements; ix++)
52 : 6 : _validateFDVariant(variant.get_child_value(ix), fdList);
53 : 4 : return;
54 : : }
55 : : }
56 : :
57 : 0 : throw new Error('Assertion failure: this code should not be reached');
58 : 8 : }
59 : :
60 : 65 : function _proxyInvoker(methodName, sync, inSignature, argArray) {
61 : : var replyFunc;
62 : 65 : var flags = 0;
63 : 65 : var cancellable = null;
64 : 65 : let fdList = null;
65 : :
66 : : // Convert argArray to a *real* array
67 : 65 : argArray = Array.prototype.slice.call(argArray);
68 : :
69 : : // The default replyFunc only logs the responses
70 : 65 : replyFunc = _logReply;
71 : :
72 : 65 : var signatureLength = inSignature.length;
73 : 65 : var minNumberArgs = signatureLength;
74 : 65 : var maxNumberArgs = signatureLength + 4;
75 : :
76 [ + - ]: 65 : if (argArray.length < minNumberArgs) {
77 : 0 : throw new Error(`Not enough arguments passed for method: ${
78 : 0 : methodName}. Expected ${minNumberArgs}, got ${argArray.length}`);
79 [ + - ]: 65 : } else if (argArray.length > maxNumberArgs) {
80 : 0 : throw new Error(`Too many arguments passed for method ${methodName}. ` +
81 : 0 : `Maximum is ${maxNumberArgs} including one callback, ` +
82 : 0 : 'Gio.Cancellable, Gio.UnixFDList, and/or flags');
83 : : }
84 : :
85 [ + + ]: 137 : while (argArray.length > signatureLength) {
86 : 71 : var argNum = argArray.length - 1;
87 : 71 : var arg = argArray.pop();
88 [ + + ][ + + ]: 71 : if (typeof arg === 'function' && !sync) {
89 : 65 : replyFunc = arg;
90 [ + - ]: 6 : } else if (typeof arg === 'number') {
91 : 0 : flags = arg;
92 [ + - ]: 6 : } else if (arg instanceof Gio.Cancellable) {
93 : 0 : cancellable = arg;
94 [ - + ]: 6 : } else if (arg instanceof Gio.UnixFDList) {
95 : 6 : fdList = arg;
96 : : } else {
97 : 0 : throw new Error(`Argument ${argNum} of method ${methodName} is ` +
98 : 0 : `${typeof arg}. It should be a callback, flags, ` +
99 : 0 : 'Gio.UnixFDList, or a Gio.Cancellable');
100 : : }
101 : : }
102 : :
103 : 65 : const inTypeString = `(${inSignature.join('')})`;
104 : 65 : const inVariant = new GLib.Variant(inTypeString, argArray);
105 [ + + ]: 65 : if (inTypeString.includes('h')) {
106 [ + + ]: 8 : if (!fdList) {
107 : 4 : throw new Error(`Method ${methodName} with input type containing ` +
108 : 2 : '\'h\' must have a Gio.UnixFDList as an argument');
109 : : }
110 : 6 : _validateFDVariant(inVariant, fdList);
111 : : }
112 : :
113 : 61 : var asyncCallback = (proxy, result) => {
114 : 61 : try {
115 [ # # ][ # # ]: 0 : const [outVariant, outFdList] =
[ # # ][ # # ]
116 [ - + ][ - + ]: 61 : proxy.call_with_unix_fd_list_finish(result);
[ - + ]
117 : 48 : replyFunc(outVariant.deepUnpack(), null, outFdList);
118 : 13 : } catch (e) {
119 : 13 : replyFunc([], e, null);
120 : : }
121 : : };
122 : :
123 [ + - ]: 61 : if (sync) {
124 [ # # ][ # # ]: 0 : const [outVariant, outFdList] = this.call_with_unix_fd_list_sync(
[ # # ][ # # ]
[ # # ][ # # ]
[ # # ]
125 : 0 : methodName, inVariant, flags, -1, fdList, cancellable);
126 [ # # ]: 0 : if (fdList)
127 : 0 : return [outVariant.deepUnpack(), outFdList];
128 : 0 : return outVariant.deepUnpack();
129 : : }
130 : :
131 : 122 : return this.call_with_unix_fd_list(methodName, inVariant, flags, -1, fdList,
132 : 61 : cancellable, asyncCallback);
133 : 61 : }
134 : :
135 : 0 : function _logReply(result, exc) {
136 [ # # ]: 0 : if (exc !== null)
137 : 0 : log(`Ignored exception from dbus method: ${exc}`);
138 : : }
139 : :
140 : 448 : function _makeProxyMethod(method, sync) {
141 : : var i;
142 : 448 : var name = method.name;
143 : 448 : var inArgs = method.in_args;
144 : 448 : var inSignature = [];
145 [ + + ]: 800 : for (i = 0; i < inArgs.length; i++)
146 : 352 : inSignature.push(inArgs[i].signature);
147 : :
148 : 513 : return function (...args) {
149 : 65 : return _proxyInvoker.call(this, name, sync, inSignature, args);
150 : : };
151 : : }
152 : :
153 : 6 : function _convertToNativeSignal(proxy, senderName, signalName, parameters) {
154 : 6 : Signals._emit.call(proxy, signalName, senderName, parameters.deepUnpack());
155 : : }
156 : :
157 : 6 : function _propertyGetter(name) {
158 : 6 : let value = this.get_cached_property(name);
159 [ - + ]: 6 : return value ? value.deepUnpack() : null;
160 : 6 : }
161 : :
162 : 2 : function _propertySetter(name, signature, value) {
163 : 2 : let variant = new GLib.Variant(signature, value);
164 : 2 : this.set_cached_property(name, variant);
165 : :
166 : 4 : this.call('org.freedesktop.DBus.Properties.Set',
167 : 2 : new GLib.Variant('(ssv)', [this.g_interface_name, name, variant]),
168 : 2 : Gio.DBusCallFlags.NONE, -1, null,
169 : 2 : (proxy, result) => {
170 : 2 : try {
171 : 2 : this.call_finish(result);
172 : 0 : } catch (e) {
173 : 0 : log(`Could not set property ${name} on remote object ${
174 : 0 : this.g_object_path}: ${e.message}`);
175 : : }
176 : : });
177 : : }
178 : :
179 : 8 : function _addDBusConvenience(proxyInstance) {
180 : 8 : const info = proxyInstance.g_interface_info;
181 [ + - ]: 8 : if (!info)
182 : 0 : return;
183 : :
184 [ - + ]: 8 : if (info.signals.length > 0)
185 : 8 : proxyInstance.connect('g-signal', _convertToNativeSignal);
186 : :
187 [ + + ]: 232 : for (const method of info.methods) {
188 : 224 : const remoteMethod = _makeProxyMethod(method, false);
189 : 224 : proxyInstance[`${method.name}Remote`] = remoteMethod;
190 : 224 : proxyInstance[`${method.name}Sync`] = _makeProxyMethod(method, true);
191 : 256 : proxyInstance[`${method.name}Async`] = function (...args) {
192 : 32 : return new Promise((resolve, reject) => {
193 : 32 : args.push((result, error, fdList) => {
194 [ + + ]: 30 : if (error)
195 : 6 : reject(error);
196 [ + + ]: 24 : else if (fdList)
197 : 2 : resolve([result, fdList]);
198 : : else
199 : 22 : resolve(result);
200 : : });
201 [ + + ]: 32 : remoteMethod.call(this, ...args);
202 : : });
203 : : };
204 : : }
205 : :
206 [ + + ]: 40 : for (const {name, signature, flags} of info.properties) {
207 : 32 : let getter = () => {
208 : 2 : throw new Error(`Property ${name} is not readable`);
209 : : };
210 : 32 : let setter = () => {
211 : 1 : throw new Error(`Property ${name} is not writable`);
212 : : };
213 : :
214 [ + + ]: 32 : if (flags & Gio.DBusPropertyInfoFlags.READABLE)
215 : 24 : getter = _propertyGetter.bind(proxyInstance, name);
216 : :
217 [ + + ]: 32 : if (flags & Gio.DBusPropertyInfoFlags.WRITABLE)
218 : 16 : setter = _propertySetter.bind(proxyInstance, name, signature);
219 : :
220 : 64 : Object.defineProperty(proxyInstance, name, {
221 : 32 : get: getter,
222 : 32 : set: setter,
223 : 32 : configurable: false,
224 : 32 : enumerable: true,
225 : : });
226 : : }
227 : 0 : }
228 : :
229 : 1 : function _makeProxyWrapper(interfaceXml) {
230 : 1 : var info = _newInterfaceInfo(interfaceXml);
231 : 1 : var iname = info.name;
232 [ - + ]: 1 : return class extends Gio.DBusProxy {
233 [ + + ][ - + ]: 4 : constructor(bus, name, object, asyncCallback, cancellable = null,
234 : 4 : flags = Gio.DBusProxyFlags.NONE) {
235 : 8 : const obj = new Gio.DBusProxy({
236 : 4 : g_connection: bus,
237 : 4 : g_interface_name: iname,
238 : 4 : g_interface_info: info,
239 : 4 : g_name: name,
240 : 4 : g_flags: flags,
241 : 4 : g_object_path: object,
242 : : });
243 : :
244 [ + + ]: 4 : if (asyncCallback) {
245 : 3 : obj.init_async(GLib.PRIORITY_DEFAULT, cancellable)
246 : 5 : .then(() => asyncCallback(obj, null))
247 : 4 : .catch(e => asyncCallback(null, e));
248 : : } else {
249 : 1 : obj.init(cancellable);
250 : : }
251 : : // For backwards compatibility, return a new instance of DBusProxy,
252 : : // overriding `this`
253 : 3 : return obj;
254 : 3 : }
255 : :
256 [ + + ][ - + ]: 4 : static newAsync(bus, name, object, cancellable = null,
257 : 3 : flags = Gio.DBusProxyFlags.NONE) {
258 : 6 : const obj = new Gio.DBusProxy({
259 : 3 : g_connection: bus,
260 : 3 : g_interface_name: info.name,
261 : 3 : g_interface_info: info,
262 : 3 : g_name: name,
263 : 3 : g_flags: flags,
264 : 3 : g_object_path: object,
265 : : });
266 : :
267 : 3 : return new Promise((resolve, reject) =>
268 : 3 : obj.init_async(GLib.PRIORITY_DEFAULT, cancellable)
269 : 5 : .then(() => resolve(obj))
270 : 3 : .catch(reject));
271 : 3 : }
272 : : };
273 : : }
274 : :
275 : :
276 : 3 : function _newNodeInfo(constructor, value) {
277 [ - + ]: 3 : if (typeof value === 'string')
278 : 3 : return constructor(value);
279 : 0 : throw TypeError(`Invalid type ${Object.prototype.toString.call(value)}`);
280 : : }
281 : :
282 : 2 : function _newInterfaceInfo(value) {
283 : 2 : var nodeInfo = Gio.DBusNodeInfo.new_for_xml(value);
284 : 2 : return nodeInfo.interfaces[0];
285 : : }
286 : :
287 : 44 : function _injectToMethod(klass, method) {
288 : 44 : var previous = klass[method];
289 : :
290 : 51 : klass[method] = function (...args) {
291 : 7 : _addDBusConvenience(this);
292 : 7 : return previous.apply(this, args);
293 : : };
294 : : }
295 : :
296 : 88 : function _injectToStaticMethod(klass, method) {
297 : 88 : var previous = klass[method];
298 : :
299 : 89 : klass[method] = function (...parameters) {
300 : 1 : let obj = previous.apply(this, parameters);
301 : 1 : _addDBusConvenience(obj);
302 : 1 : return obj;
303 : 1 : };
304 : : }
305 : :
306 : 22 : function _wrapFunction(klass, method, addition) {
307 : 22 : var previous = klass[method];
308 : :
309 : 25 : klass[method] = function (...args) {
310 : 3 : args.unshift(previous);
311 : 3 : return addition.apply(this, args);
312 : : };
313 : : }
314 : :
315 : 36 : function _makeOutSignature(args) {
316 : 36 : var ret = '(';
317 [ + + ]: 80 : for (var i = 0; i < args.length; i++)
318 : 44 : ret += args[i].signature;
319 : :
320 : 36 : return `${ret})`;
321 : : }
322 : :
323 : 40 : function _handleDBusReply(invocation, ret) {
324 [ + + ]: 40 : if (ret === undefined) {
325 : : // undefined (no return value) is the empty tuple
326 : 4 : ret = new GLib.Variant('()', []);
327 : : }
328 : :
329 : 40 : try {
330 : 40 : let outFdList = null;
331 [ + + ]: 40 : if (!(ret instanceof GLib.Variant)) {
332 : : // attempt packing according to out signature
333 : 36 : const outArgs = invocation.get_method_info().out_args;
334 : 36 : const outSignature = _makeOutSignature(outArgs);
335 [ + + ]: 36 : if (outSignature.includes('h') &&
336 [ + + ]: 2 : ret[ret.length - 1] instanceof Gio.UnixFDList) {
337 : 2 : outFdList = ret.pop();
338 [ + + ]: 34 : } else if (outArgs.length === 1) {
339 : : // if one arg, we don't require the handler wrapping it
340 : : // into an Array
341 : 28 : ret = [ret];
342 : : }
343 : 36 : ret = new GLib.Variant(outSignature, ret);
344 : : }
345 : 38 : invocation.return_value_with_unix_fd_list(ret, outFdList);
346 : 2 : } catch (e) {
347 : 2 : logError(e, `Exception in method call: ${invocation.get_method_name()}`);
348 : :
349 : : // if we don't do this, the other side will never see a reply
350 : 4 : invocation.return_dbus_error('org.gnome.gjs.JSError.ValueError',
351 : 2 : 'Service implementation returned an incorrect value type');
352 : : }
353 : : }
354 : :
355 : 9 : function _handleDBusError(invocation, e) {
356 [ + - ]: 9 : if (e instanceof GLib.Error) {
357 : 0 : invocation.return_gerror(e);
358 : 0 : return;
359 : : }
360 : :
361 : 9 : let {name} = e;
362 [ - + ]: 9 : if (!name.includes('.')) {
363 : : // likely to be a normal JS error
364 : 9 : name = `org.gnome.gjs.JSError.${name}`;
365 : : }
366 : 9 : logError(e, `Exception in method call: ${invocation.get_method_name()}`);
367 : 9 : invocation.return_dbus_error(name, e.message);
368 : 0 : }
369 : :
370 : 61 : function _handleMethodCall(methodName, parameters, invocation) {
371 : : // prefer a sync version if available
372 : 61 : const method = this[methodName];
373 [ + + ]: 61 : if (method) {
374 : 45 : let retval;
375 : 45 : try {
376 : 45 : const args = parameters.deepUnpack();
377 : 45 : args.push(invocation.get_message().get_unix_fd_list());
378 : 45 : retval = method.apply(this, args);
379 : 3 : } catch (e) {
380 : 3 : _handleDBusError(invocation, e);
381 : 3 : return;
382 : : }
383 : :
384 : : // eslint-disable-next-line no-unused-expressions
385 [ + + ][ + + ]: 48 : retval?.then?.(r => _handleDBusReply(invocation, r))?.catch?.(
[ - + ][ + - ]
[ + + ]
386 : 6 : e => _handleDBusError(invocation, e)) ??
387 : 38 : _handleDBusReply(invocation, retval);
388 : :
389 : 42 : return;
390 : : }
391 : :
392 : 16 : const asyncMethod = this[`${methodName}Async`];
393 [ + + ]: 16 : if (asyncMethod) {
394 : 8 : function maybeHandleError(e) {
395 [ + + ]: 8 : if (_methodInvocations.has(invocation)) {
396 : 4 : logError(e, `Exception in method call: ${invocation.get_method_name()}`);
397 : 4 : return;
398 : : }
399 : :
400 : 4 : _handleDBusError(invocation, e);
401 : : };
402 : :
403 : 14 : const fdList = invocation.get_message().get_unix_fd_list();
404 : 14 : let ret;
405 : 14 : try {
406 : 14 : ret = asyncMethod.call(this, parameters.deepUnpack(), invocation, fdList);
407 : 4 : } catch (e) {
408 : 4 : maybeHandleError(e);
409 : 4 : return;
410 : : }
411 : :
412 [ + + ][ + - ]: 10 : ret?.catch?.(maybeHandleError);
413 : : } else {
414 : 2 : logError(new Error(), `Missing handler for DBus method ${methodName}`);
415 : 4 : invocation.return_gerror(new Gio.DBusError({
416 : 2 : code: Gio.DBusError.UNKNOWN_METHOD,
417 : 2 : message: `Method ${methodName} is not implemented`,
418 : : }));
419 : : }
420 : 49 : }
421 : :
422 : 9 : function _handlePropertyGet(info, impl, propertyName) {
423 : 9 : let propInfo = info.lookup_property(propertyName);
424 : 9 : let jsval = this[propertyName];
425 [ - + ][ + + ]: 9 : if (jsval?.get_type_string?.() === propInfo.signature)
[ + + ]
426 : 3 : return jsval;
427 [ - + ]: 6 : else if (jsval !== undefined)
428 : 6 : return new GLib.Variant(propInfo.signature, jsval);
429 : : else
430 : 0 : return null;
431 : 9 : }
432 : :
433 : 2 : function _handlePropertySet(info, impl, propertyName, newValue) {
434 : 2 : this[propertyName] = newValue.deepUnpack();
435 : : }
436 : :
437 : 1 : function _wrapJSObject(interfaceInfo, jsObj) {
438 : : var info;
439 [ + - ]: 1 : if (interfaceInfo instanceof Gio.DBusInterfaceInfo)
440 : 0 : info = interfaceInfo;
441 : : else
442 : 1 : info = Gio.DBusInterfaceInfo.new_for_xml(interfaceInfo);
443 : 1 : info.cache_build();
444 : :
445 : 1 : var impl = new GjsPrivate.DBusImplementation({g_interface_info: info});
446 : 62 : impl.connect('handle-method-call', function (_, ...args) {
447 : 61 : return _handleMethodCall.apply(jsObj, args);
448 : : });
449 : 1 : impl.connect('handle-property-get', function (self, propertyName) {
450 : 9 : return _handlePropertyGet.call(jsObj, info, self, propertyName);
451 : : });
452 : 1 : impl.connect('handle-property-set', function (self, propertyName, value) {
453 : 2 : return _handlePropertySet.call(jsObj, info, self, propertyName, value);
454 : : });
455 : :
456 : 1 : return impl;
457 : : }
458 : :
459 : 3 : function* _listModelIterator() {
460 : 3 : let _index = 0;
461 : 3 : const _len = this.get_n_items();
462 [ + + ]: 124 : while (_index < _len)
463 : 120 : yield this.get_item(_index++);
464 : 3 : }
465 : :
466 [ + + ]: 30 : function _promisify(proto, asyncFunc, finishFunc = undefined) {
467 [ + + ]: 30 : if (proto[asyncFunc] === undefined)
468 : 1 : throw new Error(`${proto} has no method named ${asyncFunc}`);
469 : :
470 [ + + ]: 29 : if (finishFunc === undefined) {
471 [ - + ][ + + ]: 28 : if (asyncFunc.endsWith('_begin') || asyncFunc.endsWith('_async'))
472 : 27 : finishFunc = `${asyncFunc.slice(0, -5)}finish`;
473 : : else
474 : 1 : finishFunc = `${asyncFunc}_finish`;
475 : : }
476 : :
477 [ + + ]: 29 : if (proto[finishFunc] === undefined)
478 : 1 : throw new Error(`${proto} has no method named ${finishFunc}`);
479 : :
480 : 28 : const originalFuncName = `_original_${asyncFunc}`;
481 [ + + ]: 28 : if (proto[originalFuncName] !== undefined)
482 : 2 : return;
483 : 26 : proto[originalFuncName] = proto[asyncFunc];
484 : 38 : proto[asyncFunc] = function (...args) {
485 [ + + ]: 12 : if (args.length === this[originalFuncName].length)
486 [ + - ][ # # ]: 3 : return this[originalFuncName](...args);
487 : 18 : return new Promise((resolve, reject) => {
488 : 9 : let {stack: callStack} = new Error();
489 [ + + ]: 9 : this[originalFuncName](...args, function (source, res) {
490 : 9 : try {
491 [ - + ][ - + ]: 9 : const result = source !== null && source[finishFunc] !== undefined
492 : 9 : ? source[finishFunc](res)
493 : 0 : : proto[finishFunc](res);
494 [ + + ][ - + ]: 7 : if (Array.isArray(result) && result.length > 1 && result[0] === true)
[ + + ]
495 : 2 : result.shift();
496 : 7 : resolve(result);
497 : 2 : } catch (error) {
498 : 2 : callStack = callStack.split('\n').filter(line =>
499 : 31 : line.indexOf('_promisify/') === -1).join('\n');
500 [ - + ]: 2 : if (error.stack)
501 : 2 : error.stack += `### Promise created here: ###\n${callStack}`;
502 : : else
503 : 0 : error.stack = callStack;
504 : 2 : reject(error);
505 : : }
506 : : });
507 : : });
508 : : };
509 : 2 : }
510 : :
511 : 13 : function _notIntrospectableError(funcName, replacement) {
512 : 13 : return new Error(`${funcName} is not introspectable. Use ${replacement} instead.`);
513 : : }
514 : :
515 : 10 : function _warnNotIntrospectable(funcName, replacement) {
516 : 10 : logError(_notIntrospectableError(funcName, replacement));
517 : : }
518 : :
519 : 22 : const _methodInvocations = new WeakMap();
520 : :
521 : 22 : function _init() {
522 : 22 : Gio = this;
523 : :
524 : 23 : Gio.Application.prototype.runAsync = function (...args) {
525 : 1 : return new Promise((resolve, reject) => {
526 : 1 : setMainLoopHook(() => {
527 : 1 : try {
528 [ + - ][ # # ]: 1 : resolve(this.run(...args));
529 : 0 : } catch (error) {
530 : 0 : reject(error);
531 : : }
532 : : });
533 : : });
534 : : };
535 : :
536 : 22 : _createWrappersForPlatformSpecificNamespace(Gio);
537 : :
538 : 22 : Gio.DBus = {
539 : : // Namespace some functions
540 : 22 : get: Gio.bus_get,
541 : 22 : get_finish: Gio.bus_get_finish,
542 : 22 : get_sync: Gio.bus_get_sync,
543 : :
544 : 22 : own_name: Gio.bus_own_name,
545 : 22 : own_name_on_connection: Gio.bus_own_name_on_connection,
546 : 22 : unown_name: Gio.bus_unown_name,
547 : :
548 : 22 : watch_name: Gio.bus_watch_name,
549 : 22 : watch_name_on_connection: Gio.bus_watch_name_on_connection,
550 : 22 : unwatch_name: Gio.bus_unwatch_name,
551 : : };
552 : :
553 : 44 : Object.defineProperties(Gio.DBus, {
554 : 22 : 'session': {
555 : 22 : get() {
556 : 10 : return Gio.bus_get_sync(Gio.BusType.SESSION, null);
557 : : },
558 : 22 : enumerable: false,
559 : : },
560 : 22 : 'system': {
561 : 22 : get() {
562 : 0 : return Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
563 : : },
564 : 22 : enumerable: false,
565 : : },
566 : : });
567 : :
568 : 22 : Gio.DBusConnection.prototype.watch_name = function (name, flags, appeared, vanished) {
569 : 0 : return Gio.bus_watch_name_on_connection(this, name, flags, appeared, vanished);
570 : : };
571 : 22 : Gio.DBusConnection.prototype.unwatch_name = function (id) {
572 : 0 : return Gio.bus_unwatch_name(id);
573 : : };
574 : 22 : Gio.DBusConnection.prototype.own_name = function (name, flags, acquired, lost) {
575 : 1 : return Gio.bus_own_name_on_connection(this, name, flags, acquired, lost);
576 : : };
577 : 22 : Gio.DBusConnection.prototype.unown_name = function (id) {
578 : 1 : return Gio.bus_unown_name(id);
579 : : };
580 : :
581 : 22 : _injectToMethod(Gio.DBusProxy.prototype, 'init');
582 : 22 : _promisify(Gio.DBusProxy.prototype, 'init_async');
583 : 22 : _injectToMethod(Gio.DBusProxy.prototype, 'init_async');
584 : 22 : _injectToStaticMethod(Gio.DBusProxy, 'new_sync');
585 : 22 : _injectToStaticMethod(Gio.DBusProxy, 'new_finish');
586 : 22 : _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_sync');
587 : 22 : _injectToStaticMethod(Gio.DBusProxy, 'new_for_bus_finish');
588 : 22 : Gio.DBusProxy.prototype.connectSignal = Signals._connect;
589 : 22 : Gio.DBusProxy.prototype.disconnectSignal = Signals._disconnect;
590 : :
591 : 22 : Gio.DBusProxy.makeProxyWrapper = _makeProxyWrapper;
592 : :
593 : : // Wrap the invocation return methods to catch if the method has already
594 : : // returned, in fact in such case we should not try to return anything
595 : : // especially because the Gio.DBusMethodInvocation.return_* methods take
596 : : // the ownership of the invocation itself and, calling it more than once
597 : : // may lead to memory issues.
598 : : // The invocated methods are saved in a weak map so that we do not
599 : : // artificially create a toggle reference that may keep the method
600 : : // invocation alive forever.
601 [ - + ][ - + ]: 352 : Object.entries(Object.getOwnPropertyDescriptors(Gio.DBusMethodInvocation.prototype)).forEach(([symbol, desc]) => {
[ - + ][ # # ]
[ # # ][ # # ]
[ # # ]
602 [ + + ]: 330 : if (!symbol.startsWith('return_'))
603 : 220 : return;
604 : :
605 : 110 : const originalMethod = desc.value;
606 : 175 : desc.value = function (...args) {
607 : 65 : const oldInvocation = _methodInvocations.get(this);
608 [ + + ]: 65 : if (oldInvocation) {
609 : : // We do not throw here, not to break compatibility, but
610 : : // we make this a no-op, to prevent potential memory issues.
611 : 8 : logError(new Error(), `${this} (${oldInvocation.methodName}) ` +
612 : 8 : `already returned @\n${oldInvocation.previousCallStack.split(
613 : 8 : '\n').slice(1).join('\n')}`);
614 : 4 : return;
615 : : }
616 : :
617 : 122 : _methodInvocations.set(this, {
618 : 61 : methodName: this.get_method_name(),
619 : 61 : previousCallStack: new Error().stack,
620 : : });
621 : 61 : return originalMethod.apply(this, args);
622 : 65 : };
623 : :
624 : 110 : Object.defineProperty(Gio.DBusMethodInvocation.prototype, symbol, desc);
625 : 220 : });
626 : :
627 : : // Some helpers
628 : 22 : _wrapFunction(Gio.DBusNodeInfo, 'new_for_xml', _newNodeInfo);
629 : 22 : Gio.DBusInterfaceInfo.new_for_xml = _newInterfaceInfo;
630 : :
631 : 22 : Gio.DBusExportedObject = GjsPrivate.DBusImplementation;
632 : 22 : Gio.DBusExportedObject.wrapJSObject = _wrapJSObject;
633 : :
634 : : // ListStore
635 : 22 : Gio.ListStore.prototype[Symbol.iterator] = _listModelIterator;
636 : 22 : Gio.ListStore.prototype.insert_sorted = function (item, compareFunc) {
637 : 10 : return GjsPrivate.list_store_insert_sorted(this, item, compareFunc);
638 : : };
639 : 22 : Gio.ListStore.prototype.sort = function (compareFunc) {
640 : 1 : return GjsPrivate.list_store_sort(this, compareFunc);
641 : : };
642 : :
643 : : // Promisify
644 : 22 : Gio._promisify = _promisify;
645 : :
646 : : // Temporary Gio.File.prototype fix
647 : 22 : Gio._LocalFilePrototype = Gio.File.new_for_path('/').constructor.prototype;
648 : :
649 : 22 : Gio.File.prototype.replace_contents_async = function replace_contents_async(contents, etag, make_backup, flags, cancellable, callback) {
650 [ + + ]: 4 : if (typeof contents === 'string')
651 : 2 : contents = new TextEncoder().encode(contents);
652 : 4 : return this.replace_contents_bytes_async(contents, etag, make_backup, flags, cancellable, callback);
653 : : };
654 : :
655 : : // Best-effort attempt to replace set_attribute(), which is not
656 : : // introspectable due to the pointer argument
657 : 22 : Gio.File.prototype.set_attribute = function set_attribute(attribute, type, value, flags, cancellable) {
658 : 5 : _warnNotIntrospectable('Gio.File.prototype.set_attribute', 'set_attribute_{type}');
659 : :
660 : 5 : switch (type) {
661 [ - + ]: 5 : case Gio.FileAttributeType.STRING:
662 : 0 : return this.set_attribute_string(attribute, value, flags, cancellable);
663 [ - + ]: 5 : case Gio.FileAttributeType.BYTE_STRING:
664 : 0 : return this.set_attribute_byte_string(attribute, value, flags, cancellable);
665 [ + + ]: 5 : case Gio.FileAttributeType.UINT32:
666 : 1 : return this.set_attribute_uint32(attribute, value, flags, cancellable);
667 [ - + ]: 4 : case Gio.FileAttributeType.INT32:
668 : 0 : return this.set_attribute_int32(attribute, value, flags, cancellable);
669 [ + + ]: 4 : case Gio.FileAttributeType.UINT64:
670 : 1 : return this.set_attribute_uint64(attribute, value, flags, cancellable);
671 [ - + ]: 3 : case Gio.FileAttributeType.INT64:
672 : 0 : return this.set_attribute_int64(attribute, value, flags, cancellable);
673 [ + + ]: 3 : case Gio.FileAttributeType.INVALID:
674 [ + + ]: 2 : case Gio.FileAttributeType.BOOLEAN:
675 [ + - ]: 1 : case Gio.FileAttributeType.OBJECT:
676 [ # # ]: 0 : case Gio.FileAttributeType.STRINGV:
677 : 3 : throw _notIntrospectableError('This attribute type', 'Gio.FileInfo');
678 : : }
679 : : };
680 : :
681 : 22 : Gio.FileInfo.prototype.set_attribute = function set_attribute(attribute, type, value) {
682 : 5 : _warnNotIntrospectable('Gio.FileInfo.prototype.set_attribute', 'set_attribute_{type}');
683 : :
684 : 5 : switch (type) {
685 [ + + ]: 5 : case Gio.FileAttributeType.INVALID:
686 : 1 : return this.remove_attribute(attribute);
687 [ - + ]: 4 : case Gio.FileAttributeType.STRING:
688 : 0 : return this.set_attribute_string(attribute, value);
689 [ - + ]: 4 : case Gio.FileAttributeType.BYTE_STRING:
690 : 0 : return this.set_attribute_byte_string(attribute, value);
691 [ + + ]: 4 : case Gio.FileAttributeType.BOOLEAN:
692 : 1 : return this.set_attribute_boolean(attribute, value);
693 [ + + ]: 3 : case Gio.FileAttributeType.UINT32:
694 : 1 : return this.set_attribute_uint32(attribute, value);
695 [ - + ]: 2 : case Gio.FileAttributeType.INT32:
696 : 0 : return this.set_attribute_int32(attribute, value);
697 [ + + ]: 2 : case Gio.FileAttributeType.UINT64:
698 : 1 : return this.set_attribute_uint64(attribute, value);
699 [ - + ]: 1 : case Gio.FileAttributeType.INT64:
700 : 0 : return this.set_attribute_int64(attribute, value);
701 [ + - ]: 1 : case Gio.FileAttributeType.OBJECT:
702 : 1 : return this.set_attribute_object(attribute, value);
703 [ # # ]: 0 : case Gio.FileAttributeType.STRINGV:
704 : 0 : return this.set_attribute_stringv(attribute, value);
705 : : }
706 : : };
707 : :
708 : 23 : Gio.InputStream.prototype.createSyncIterator = function* createSyncIterator(count) {
709 [ - + ]: 9 : while (true) {
710 : 9 : const bytes = this.read_bytes(count, null);
711 [ + + ]: 9 : if (bytes.get_size() === 0)
712 : 1 : return;
713 : 8 : yield bytes;
714 : : }
715 : 1 : };
716 : :
717 [ - + ]: 23 : Gio.InputStream.prototype.createAsyncIterator = async function* createAsyncIterator(
718 : 1 : count, ioPriority = GLib.PRIORITY_DEFAULT) {
719 : 1 : const self = this;
720 : :
721 : 0 : function next() {
722 : 9 : return new Promise((resolve, reject) => {
723 : 9 : self.read_bytes_async(count, ioPriority, null, (_self, res) => {
724 : 9 : try {
725 : 9 : const bytes = self.read_bytes_finish(res);
726 : 9 : resolve(bytes);
727 : 0 : } catch (err) {
728 : 0 : reject(err);
729 : : }
730 : : });
731 : : });
732 : : }
733 : :
734 [ - + ]: 9 : while (true) {
735 : : // eslint-disable-next-line no-await-in-loop
736 [ - + ]: 9 : const bytes = await next();
737 [ + + ]: 9 : if (bytes.get_size() === 0)
738 : 1 : return;
739 [ - + ]: 8 : yield bytes;
740 : : }
741 : 1 : };
742 : :
743 : 23 : Gio.FileEnumerator.prototype[Symbol.iterator] = function* FileEnumeratorIterator() {
744 [ + + ]: 39 : while (true) {
745 : 38 : try {
746 : 38 : const info = this.next_file(null);
747 [ + + ]: 38 : if (info === null)
748 : 1 : break;
749 : 37 : yield info;
750 : 0 : } catch (err) {
751 : 0 : this.close(null);
752 : 0 : throw err;
753 : : }
754 : : }
755 : 1 : this.close(null);
756 : 1 : };
757 : :
758 : 23 : Gio.FileEnumerator.prototype[Symbol.asyncIterator] = async function* AsyncFileEnumeratorIterator() {
759 : 1 : const self = this;
760 : :
761 : 47 : function next() {
762 : 38 : return new Promise((resolve, reject) => {
763 : 38 : self.next_files_async(1, GLib.PRIORITY_DEFAULT, null, (_self, res) => {
764 : 38 : try {
765 : 38 : const files = self.next_files_finish(res);
766 [ + + ]: 38 : resolve(files.length === 0 ? null : files[0]);
767 : 0 : } catch (err) {
768 : 0 : reject(err);
769 : : }
770 : : });
771 : : });
772 : : }
773 : :
774 : 1 : function close() {
775 : 1 : return new Promise((resolve, reject) => {
776 : 1 : self.close_async(GLib.PRIORITY_DEFAULT, null, (_self, res) => {
777 : 1 : try {
778 : 1 : resolve(self.close_finish(res));
779 : 0 : } catch (err) {
780 : 0 : reject(err);
781 : : }
782 : : });
783 : : });
784 : : }
785 : :
786 [ + + ]: 39 : while (true) {
787 : 38 : try {
788 : : // eslint-disable-next-line no-await-in-loop
789 [ - + ]: 38 : const info = await next();
790 [ + + ]: 38 : if (info === null)
791 : 1 : break;
792 [ - + ]: 37 : yield info;
793 : 0 : } catch (err) {
794 : : // eslint-disable-next-line no-await-in-loop
795 [ # # ]: 0 : await close();
796 : 0 : throw err;
797 : : }
798 : : }
799 : :
800 [ - + ]: 1 : return close();
801 : 1 : };
802 : :
803 : : // Override Gio.Settings and Gio.SettingsSchema - the C API asserts if
804 : : // trying to access a nonexistent schema or key, which is not handy for
805 : : // shell-extension writers
806 : :
807 : 22 : Gio.SettingsSchema.prototype._realGetKey = Gio.SettingsSchema.prototype.get_key;
808 : 22 : Gio.SettingsSchema.prototype.get_key = function (key) {
809 [ + + ]: 2 : if (!this.has_key(key))
810 : 1 : throw new Error(`GSettings key ${key} not found in schema ${this.get_id()}`);
811 : 1 : return this._realGetKey(key);
812 : : };
813 : :
814 : 22 : Gio.Settings.prototype._realMethods = Object.assign({}, Gio.Settings.prototype);
815 : :
816 [ + + ]: 660 : function createCheckedMethod(method, checkMethod = '_checkKey') {
817 : 717 : return function (id, ...args) {
818 : 57 : this[checkMethod](id);
819 [ + + ]: 27 : return this._realMethods[method].call(this, id, ...args);
820 : : };
821 : : }
822 : :
823 : 44 : Object.assign(Gio.Settings.prototype, {
824 : 22 : _realInit: Gio.Settings.prototype._init, // add manually, not enumerable
825 [ + + ]: 40 : _init(props = {}) {
826 : : // 'schema' is a deprecated alias for schema_id
827 : 18 : const schemaIdProp = ['schema', 'schema-id', 'schema_id',
828 : 90 : 'schemaId'].find(prop => prop in props);
829 : 18 : const settingsSchemaProp = ['settings-schema', 'settings_schema',
830 : 71 : 'settingsSchema'].find(prop => prop in props);
831 [ + + ][ + + ]: 18 : if (!schemaIdProp && !settingsSchemaProp) {
832 : 1 : throw new Error('One of property \'schema-id\' or ' +
833 : : '\'settings-schema\' are required for Gio.Settings');
834 : : }
835 [ + + ][ + + ]: 17 : if (settingsSchemaProp && !(props[settingsSchemaProp] instanceof Gio.SettingsSchema))
836 : 1 : throw new Error(`Value of property '${settingsSchemaProp}' is not of type Gio.SettingsSchema`);
837 : :
838 : 16 : const source = Gio.SettingsSchemaSource.get_default();
839 [ + + ]: 16 : const settingsSchema = settingsSchemaProp
840 : 1 : ? props[settingsSchemaProp]
841 : 15 : : source.lookup(props[schemaIdProp], true);
842 : :
843 [ + + ]: 16 : if (!settingsSchema)
844 : 1 : throw new Error(`GSettings schema ${props[schemaIdProp]} not found`);
845 : :
846 : 15 : const settingsSchemaPath = settingsSchema.get_path();
847 [ + + ][ + + ]: 15 : if (props['path'] === undefined && !settingsSchemaPath) {
848 : 2 : throw new Error('Attempting to create schema ' +
849 : 1 : `'${settingsSchema.get_id()}' without a path`);
850 : : }
851 : :
852 [ + + ][ - + ]: 14 : if (props['path'] !== undefined && settingsSchemaPath &&
853 [ + + ]: 1 : props['path'] !== settingsSchemaPath) {
854 : 2 : throw new Error(`GSettings created for path '${props['path']}'` +
855 : 1 : `, but schema specifies '${settingsSchemaPath}'`);
856 : : }
857 : :
858 : 13 : return this._realInit(props);
859 : 13 : },
860 : :
861 : 22 : _checkKey(key) {
862 : : // Avoid using has_key(); checking a JS array is faster than calling
863 : : // through G-I.
864 [ + + ]: 55 : if (!this._keys)
865 : 11 : this._keys = this.settings_schema.list_keys();
866 : :
867 [ + + ]: 55 : if (!this._keys.includes(key))
868 : 29 : throw new Error(`GSettings key ${key} not found in schema ${this.schema_id}`);
869 : : },
870 : :
871 : 22 : _checkChild(name) {
872 [ - + ]: 2 : if (!this._children)
873 : 2 : this._children = this.list_children();
874 : :
875 [ + + ]: 2 : if (!this._children.includes(name))
876 : 1 : throw new Error(`Child ${name} not found in GSettings schema ${this.schema_id}`);
877 : : },
878 : :
879 : 22 : get_boolean: createCheckedMethod('get_boolean'),
880 : 22 : set_boolean: createCheckedMethod('set_boolean'),
881 : 22 : get_double: createCheckedMethod('get_double'),
882 : 22 : set_double: createCheckedMethod('set_double'),
883 : 22 : get_enum: createCheckedMethod('get_enum'),
884 : 22 : set_enum: createCheckedMethod('set_enum'),
885 : 22 : get_flags: createCheckedMethod('get_flags'),
886 : 22 : set_flags: createCheckedMethod('set_flags'),
887 : 22 : get_int: createCheckedMethod('get_int'),
888 : 22 : set_int: createCheckedMethod('set_int'),
889 : 22 : get_int64: createCheckedMethod('get_int64'),
890 : 22 : set_int64: createCheckedMethod('set_int64'),
891 : 22 : get_string: createCheckedMethod('get_string'),
892 : 22 : set_string: createCheckedMethod('set_string'),
893 : 22 : get_strv: createCheckedMethod('get_strv'),
894 : 22 : set_strv: createCheckedMethod('set_strv'),
895 : 22 : get_uint: createCheckedMethod('get_uint'),
896 : 22 : set_uint: createCheckedMethod('set_uint'),
897 : 22 : get_uint64: createCheckedMethod('get_uint64'),
898 : 22 : set_uint64: createCheckedMethod('set_uint64'),
899 : 22 : get_value: createCheckedMethod('get_value'),
900 : 22 : set_value: createCheckedMethod('set_value'),
901 : :
902 : 22 : bind: createCheckedMethod('bind'),
903 : 22 : bind_writable: createCheckedMethod('bind_writable'),
904 : 22 : create_action: createCheckedMethod('create_action'),
905 : 22 : get_default_value: createCheckedMethod('get_default_value'),
906 : 22 : get_user_value: createCheckedMethod('get_user_value'),
907 : 22 : is_writable: createCheckedMethod('is_writable'),
908 : 22 : reset: createCheckedMethod('reset'),
909 : :
910 : 22 : get_child: createCheckedMethod('get_child', '_checkChild'),
911 : : });
912 : :
913 : : // ActionMap
914 : : // add_action_entries is not introspectable
915 : : // https://gitlab.gnome.org/GNOME/gjs/-/issues/407
916 : 22 : Gio.ActionMap.prototype.add_action_entries = function add_action_entries(entries) {
917 [ + + ]: 12 : for (let {name, activate, parameter_type, state, change_state} of entries) {
918 [ + + ]: 9 : if (typeof parameter_type === 'string') {
919 [ + + ]: 3 : if (!GLib.variant_type_string_is_valid(parameter_type))
920 : 1 : throw new Error(`parameter_type "${parameter_type}" is not a valid VariantType`);
921 : :
922 : 2 : parameter_type = new GLib.VariantType(parameter_type);
923 : : }
924 : :
925 [ + + ]: 8 : if (typeof state === 'boolean')
926 : 1 : state = GLib.Variant.new_boolean(state);
927 : :
928 [ + + ]: 8 : if (typeof state === 'string')
929 : 3 : state = GLib.Variant.parse(null, state, null, null);
930 : :
931 : 14 : const action = new Gio.SimpleAction({
932 : 7 : name,
933 [ + + ]: 7 : parameter_type: parameter_type instanceof GLib.VariantType ? parameter_type : null,
934 [ + + ]: 7 : state: state instanceof GLib.Variant ? state : null,
935 : : });
936 : :
937 [ + + ]: 7 : if (typeof activate === 'function')
938 : 1 : action.connect('activate', activate.bind(action));
939 : :
940 [ + + ]: 7 : if (typeof change_state === 'function')
941 : 1 : action.connect('change-state', change_state.bind(action));
942 : :
943 : 7 : this.add_action(action);
944 : : }
945 : : };
946 : : }
|