@gjsify/node-gi 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,14 +34,14 @@ import { addSignalMethods, _connect, _disconnect, _emit } from './_signals.js';
34
34
  // mixin never ReferenceErrors. Idempotent; globals.js's richer versions win if
35
35
  // already present.
36
36
  function ensureAmbientLog() {
37
- const g = globalThis;
38
- if (typeof g.log === 'undefined') g.log = (...args) => console.error(args.map((a) => String(a)).join(' '));
39
- if (typeof g.logError === 'undefined') {
40
- g.logError = (error, prefix) => {
41
- const head = prefix ? `${prefix}: ` : '';
42
- console.error(head + (error && error.stack ? error.stack : String(error)));
43
- };
44
- }
37
+ const g = globalThis;
38
+ if (typeof g.log === 'undefined') g.log = (...args) => console.error(args.map((a) => String(a)).join(' '));
39
+ if (typeof g.logError === 'undefined') {
40
+ g.logError = (error, prefix) => {
41
+ const head = prefix ? `${prefix}: ` : '';
42
+ console.error(head + (error && error.stack ? error.stack : String(error)));
43
+ };
44
+ }
45
45
  }
46
46
 
47
47
  /**
@@ -54,696 +54,719 @@ function ensureAmbientLog() {
54
54
  * @returns {{ DBus: object, DBusProxy: object, DBusExportedObject: object }}
55
55
  */
56
56
  export function createGioDBus(ctx) {
57
- const { Gio, GLib, unwrap, native } = ctx;
58
- // Scoped to actual DBus usage (first Gio.DBus* access), so a plain non-DBus
59
- // `import { requireGi }` adds no global side effects.
60
- ensureAmbientLog();
61
-
62
- // DBus RequestName reply codes (org.freedesktop.DBus).
63
- const REQUEST_NAME_PRIMARY_OWNER = 1;
64
- const REQUEST_NAME_ALREADY_OWNER = 4;
65
- const DBUS_NAME = 'org.freedesktop.DBus';
66
- const DBUS_PATH = '/org/freedesktop/DBus';
67
-
68
- function safeCallback(fn, ...args) {
69
- try {
70
- fn(...args);
71
- } catch (e) {
72
- logError(e, 'Exception in DBus name callback');
57
+ const { Gio, GLib, unwrap, native } = ctx;
58
+ // Scoped to actual DBus usage (first Gio.DBus* access), so a plain non-DBus
59
+ // `import { requireGi }` adds no global side effects.
60
+ ensureAmbientLog();
61
+
62
+ // DBus RequestName reply codes (org.freedesktop.DBus).
63
+ const REQUEST_NAME_PRIMARY_OWNER = 1;
64
+ const REQUEST_NAME_ALREADY_OWNER = 4;
65
+ const DBUS_NAME = 'org.freedesktop.DBus';
66
+ const DBUS_PATH = '/org/freedesktop/DBus';
67
+
68
+ function safeCallback(fn, ...args) {
69
+ try {
70
+ fn(...args);
71
+ } catch (e) {
72
+ logError(e, 'Exception in DBus name callback');
73
+ }
74
+ }
75
+
76
+ // Is `value` a wrapped node-gi instance of Gio.<typeName>? Used to classify the
77
+ // trailing flags/Cancellable/UnixFDList args a proxy method call may carry.
78
+ function isGioInstance(value, typeName) {
79
+ if (value === null || typeof value !== 'object') return false;
80
+ const handle = unwrap(value);
81
+ if (handle === value) return false; // not a wrapped instance
82
+ try {
83
+ return native.isInstanceOf(handle, 'Gio', typeName);
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ function _logReply(result, exc) {
90
+ if (exc !== null && exc !== undefined) log(`Ignored exception from dbus method: ${exc}`);
73
91
  }
74
- }
75
-
76
- // Is `value` a wrapped node-gi instance of Gio.<typeName>? Used to classify the
77
- // trailing flags/Cancellable/UnixFDList args a proxy method call may carry.
78
- function isGioInstance(value, typeName) {
79
- if (value === null || typeof value !== 'object') return false;
80
- const handle = unwrap(value);
81
- if (handle === value) return false; // not a wrapped instance
82
- try {
83
- return native.isInstanceOf(handle, 'Gio', typeName);
84
- } catch {
85
- return false;
92
+
93
+ // Ported from refs/gjs Gio.js `_proxyInvoker`. `wrappedProxy` is captured (GJS
94
+ // uses `this`) because node-gi delivers a RAW source handle to the async ready
95
+ // callback, so the wrapper must come from the closure to reach the L1 finish.
96
+ function _proxyInvoker(wrappedProxy, methodName, sync, inSignature, args) {
97
+ let replyFunc = _logReply;
98
+ let flags = 0;
99
+ let cancellable = null;
100
+ let fdList = null;
101
+ const argArray = Array.prototype.slice.call(args);
102
+
103
+ const signatureLength = inSignature.length;
104
+ const minNumberArgs = signatureLength;
105
+ const maxNumberArgs = signatureLength + 4;
106
+ if (argArray.length < minNumberArgs) {
107
+ throw new Error(
108
+ `Not enough arguments passed for method: ${methodName}. Expected ${minNumberArgs}, got ${argArray.length}`,
109
+ );
110
+ } else if (argArray.length > maxNumberArgs) {
111
+ throw new Error(
112
+ `Too many arguments passed for method ${methodName}. Maximum is ${maxNumberArgs} ` +
113
+ 'including one callback, Gio.Cancellable, Gio.UnixFDList, and/or flags',
114
+ );
115
+ }
116
+
117
+ while (argArray.length > signatureLength) {
118
+ const argNum = argArray.length - 1;
119
+ const arg = argArray.pop();
120
+ if (typeof arg === 'function' && !sync) replyFunc = arg;
121
+ else if (typeof arg === 'number') flags = arg;
122
+ else if (isGioInstance(arg, 'Cancellable')) cancellable = arg;
123
+ else if (isGioInstance(arg, 'UnixFDList')) fdList = arg;
124
+ else {
125
+ throw new Error(
126
+ `Argument ${argNum} of method ${methodName} is ${typeof arg}. It should be a callback, ` +
127
+ 'flags, Gio.UnixFDList, or a Gio.Cancellable',
128
+ );
129
+ }
130
+ }
131
+
132
+ const inTypeString = `(${inSignature.join('')})`;
133
+ const inVariant = new GLib.Variant(inTypeString, argArray);
134
+
135
+ // GJS always routes through call_with_unix_fd_list[_sync/_finish] (a superset
136
+ // that also carries GUnixFDList fd-passing). Those live in GioUnix and are ABSENT
137
+ // from the Gio typelib on Windows, so fall back to the cross-platform
138
+ // call[_sync/_finish] there — fd-passing is a Unix concept, so there is nothing to
139
+ // carry. Detection is by method presence (node-gi's L1 wrapper returns `undefined`
140
+ // for an absent method), keeping POSIX byte-identical: where the method exists the
141
+ // fd-list path is taken exactly as before.
142
+ if (sync) {
143
+ if (typeof wrappedProxy.call_with_unix_fd_list_sync === 'function') {
144
+ const result = wrappedProxy.call_with_unix_fd_list_sync(
145
+ methodName,
146
+ inVariant,
147
+ flags,
148
+ -1,
149
+ fdList,
150
+ cancellable,
151
+ );
152
+ const outVariant = Array.isArray(result) ? result[0] : result;
153
+ const outFdList = Array.isArray(result) ? result[1] : null;
154
+ if (fdList) return [outVariant.deepUnpack(), outFdList];
155
+ return outVariant.deepUnpack();
156
+ }
157
+ if (fdList) throw new Error(`${methodName}: Gio.UnixFDList fd-passing is not supported on this platform`);
158
+ const outVariant = wrappedProxy.call_sync(methodName, inVariant, flags, -1, cancellable);
159
+ return outVariant.deepUnpack();
160
+ }
161
+
162
+ if (typeof wrappedProxy.call_with_unix_fd_list === 'function') {
163
+ const asyncCallback = (_rawSource, res) => {
164
+ try {
165
+ const result = wrappedProxy.call_with_unix_fd_list_finish(res);
166
+ const outVariant = Array.isArray(result) ? result[0] : result;
167
+ const outFdList = Array.isArray(result) ? result[1] : null;
168
+ replyFunc(outVariant.deepUnpack(), null, outFdList);
169
+ } catch (e) {
170
+ replyFunc([], e, null);
171
+ }
172
+ };
173
+ return wrappedProxy.call_with_unix_fd_list(
174
+ methodName,
175
+ inVariant,
176
+ flags,
177
+ -1,
178
+ fdList,
179
+ cancellable,
180
+ asyncCallback,
181
+ );
182
+ }
183
+ if (fdList) throw new Error(`${methodName}: Gio.UnixFDList fd-passing is not supported on this platform`);
184
+ const asyncCallback = (_rawSource, res) => {
185
+ try {
186
+ const outVariant = wrappedProxy.call_finish(res);
187
+ replyFunc(outVariant.deepUnpack(), null, null);
188
+ } catch (e) {
189
+ replyFunc([], e, null);
190
+ }
191
+ };
192
+ return wrappedProxy.call(methodName, inVariant, flags, -1, cancellable, asyncCallback);
86
193
  }
87
- }
88
-
89
- function _logReply(result, exc) {
90
- if (exc !== null && exc !== undefined) log(`Ignored exception from dbus method: ${exc}`);
91
- }
92
-
93
- // Ported from refs/gjs Gio.js `_proxyInvoker`. `wrappedProxy` is captured (GJS
94
- // uses `this`) because node-gi delivers a RAW source handle to the async ready
95
- // callback, so the wrapper must come from the closure to reach the L1 finish.
96
- function _proxyInvoker(wrappedProxy, methodName, sync, inSignature, args) {
97
- let replyFunc = _logReply;
98
- let flags = 0;
99
- let cancellable = null;
100
- let fdList = null;
101
- const argArray = Array.prototype.slice.call(args);
102
-
103
- const signatureLength = inSignature.length;
104
- const minNumberArgs = signatureLength;
105
- const maxNumberArgs = signatureLength + 4;
106
- if (argArray.length < minNumberArgs) {
107
- throw new Error(
108
- `Not enough arguments passed for method: ${methodName}. Expected ${minNumberArgs}, got ${argArray.length}`,
109
- );
110
- } else if (argArray.length > maxNumberArgs) {
111
- throw new Error(
112
- `Too many arguments passed for method ${methodName}. Maximum is ${maxNumberArgs} ` +
113
- 'including one callback, Gio.Cancellable, Gio.UnixFDList, and/or flags',
114
- );
194
+
195
+ function _makeProxyMethod(wrappedProxy, method, sync) {
196
+ const name = method.name;
197
+ const inArgs = method.in_args;
198
+ const inSignature = [];
199
+ for (let i = 0; i < inArgs.length; i++) inSignature.push(inArgs[i].signature);
200
+ return function proxyMethod(...args) {
201
+ return _proxyInvoker(wrappedProxy, name, sync, inSignature, args);
202
+ };
203
+ }
204
+
205
+ function _propertyGetter(wrappedProxy, name) {
206
+ const value = wrappedProxy.get_cached_property(name);
207
+ return value ? value.deepUnpack() : null;
115
208
  }
116
209
 
117
- while (argArray.length > signatureLength) {
118
- const argNum = argArray.length - 1;
119
- const arg = argArray.pop();
120
- if (typeof arg === 'function' && !sync) replyFunc = arg;
121
- else if (typeof arg === 'number') flags = arg;
122
- else if (isGioInstance(arg, 'Cancellable')) cancellable = arg;
123
- else if (isGioInstance(arg, 'UnixFDList')) fdList = arg;
124
- else {
125
- throw new Error(
126
- `Argument ${argNum} of method ${methodName} is ${typeof arg}. It should be a callback, ` +
127
- 'flags, Gio.UnixFDList, or a Gio.Cancellable',
210
+ function _propertySetter(wrappedProxy, name, signature, value) {
211
+ const variant = new GLib.Variant(signature, value);
212
+ wrappedProxy.set_cached_property(name, variant);
213
+ wrappedProxy.call(
214
+ 'org.freedesktop.DBus.Properties.Set',
215
+ new GLib.Variant('(ssv)', [wrappedProxy.g_interface_name, name, variant]),
216
+ Gio.DBusCallFlags.NONE,
217
+ -1,
218
+ null,
219
+ (_rawSource, res) => {
220
+ try {
221
+ wrappedProxy.call_finish(res);
222
+ } catch (e) {
223
+ log(`Could not set property ${name} on remote object ${wrappedProxy.g_object_path}: ${e.message}`);
224
+ }
225
+ },
128
226
  );
129
- }
130
227
  }
131
228
 
132
- const inTypeString = `(${inSignature.join('')})`;
133
- const inVariant = new GLib.Variant(inTypeString, argArray);
134
-
135
- // GJS always routes through call_with_unix_fd_list[_sync/_finish] (a superset
136
- // that also carries GUnixFDList fd-passing). Those live in GioUnix and are ABSENT
137
- // from the Gio typelib on Windows, so fall back to the cross-platform
138
- // call[_sync/_finish] there — fd-passing is a Unix concept, so there is nothing to
139
- // carry. Detection is by method presence (node-gi's L1 wrapper returns `undefined`
140
- // for an absent method), keeping POSIX byte-identical: where the method exists the
141
- // fd-list path is taken exactly as before.
142
- if (sync) {
143
- if (typeof wrappedProxy.call_with_unix_fd_list_sync === 'function') {
144
- const result = wrappedProxy.call_with_unix_fd_list_sync(methodName, inVariant, flags, -1, fdList, cancellable);
145
- const outVariant = Array.isArray(result) ? result[0] : result;
146
- const outFdList = Array.isArray(result) ? result[1] : null;
147
- if (fdList) return [outVariant.deepUnpack(), outFdList];
148
- return outVariant.deepUnpack();
149
- }
150
- if (fdList) throw new Error(`${methodName}: Gio.UnixFDList fd-passing is not supported on this platform`);
151
- const outVariant = wrappedProxy.call_sync(methodName, inVariant, flags, -1, cancellable);
152
- return outVariant.deepUnpack();
229
+ // Attach the method/property/signal convenience to a freshly-inited DBusProxy
230
+ // instance wrapper. Ported from refs/gjs Gio.js `_addDBusConvenience`.
231
+ function _addDBusConvenience(wrappedProxy, info) {
232
+ // The `_signals` mixin bookkeeping lives on a dedicated plain emitter (see the
233
+ // module header): connectSignal/disconnectSignal delegate to it.
234
+ const emitter = {};
235
+ addSignalMethods(emitter);
236
+ wrappedProxy.connectSignal = (name, callback) => _connect.call(emitter, name, callback);
237
+ wrappedProxy.disconnectSignal = (id) => _disconnect.call(emitter, id);
238
+
239
+ const signals = info.signals;
240
+ if (signals && signals.length > 0) {
241
+ wrappedProxy.connect('g-signal', (_proxy, senderName, signalName, parameters) => {
242
+ _emit.call(emitter, signalName, senderName, parameters.deepUnpack());
243
+ });
244
+ }
245
+
246
+ const methods = info.methods;
247
+ for (let i = 0; i < methods.length; i++) {
248
+ const method = methods[i];
249
+ const name = method.name;
250
+ const remoteMethod = _makeProxyMethod(wrappedProxy, method, false);
251
+ wrappedProxy[`${name}Remote`] = remoteMethod;
252
+ wrappedProxy[`${name}Sync`] = _makeProxyMethod(wrappedProxy, method, true);
253
+ wrappedProxy[`${name}Async`] = function proxyAsyncMethod(...args) {
254
+ return new Promise((resolve, reject) => {
255
+ args.push((result, error, fdList) => {
256
+ if (error) reject(error);
257
+ else if (fdList) resolve([result, fdList]);
258
+ else resolve(result);
259
+ });
260
+ remoteMethod(...args);
261
+ });
262
+ };
263
+ }
264
+
265
+ const properties = info.properties;
266
+ for (let i = 0; i < properties.length; i++) {
267
+ const propInfo = properties[i];
268
+ const name = propInfo.name;
269
+ const signature = propInfo.signature;
270
+ const propFlags = propInfo.flags;
271
+ let getter = () => {
272
+ throw new Error(`Property ${name} is not readable`);
273
+ };
274
+ let setter = () => {
275
+ throw new Error(`Property ${name} is not writable`);
276
+ };
277
+ if (propFlags & Gio.DBusPropertyInfoFlags.READABLE) getter = () => _propertyGetter(wrappedProxy, name);
278
+ if (propFlags & Gio.DBusPropertyInfoFlags.WRITABLE) {
279
+ setter = (value) => _propertySetter(wrappedProxy, name, signature, value);
280
+ }
281
+ Object.defineProperty(wrappedProxy, name, {
282
+ get: getter,
283
+ set: setter,
284
+ configurable: false,
285
+ enumerable: true,
286
+ });
287
+ }
153
288
  }
154
289
 
155
- if (typeof wrappedProxy.call_with_unix_fd_list === 'function') {
156
- const asyncCallback = (_rawSource, res) => {
157
- try {
158
- const result = wrappedProxy.call_with_unix_fd_list_finish(res);
159
- const outVariant = Array.isArray(result) ? result[0] : result;
160
- const outFdList = Array.isArray(result) ? result[1] : null;
161
- replyFunc(outVariant.deepUnpack(), null, outFdList);
162
- } catch (e) {
163
- replyFunc([], e, null);
290
+ function _newInterfaceInfo(xml) {
291
+ const nodeInfo = Gio.DBusNodeInfo.new_for_xml(xml);
292
+ return nodeInfo.interfaces[0];
293
+ }
294
+
295
+ // Gio.DBusProxy.makeProxyWrapper(interfaceXml). Returns a constructor:
296
+ // new ProxyClass(bus, name, objectPath[, asyncCallback[, cancellable[, flags]]])
297
+ // and a static `ProxyClass.newAsync(bus, name, objectPath[, cancellable[, flags]])`.
298
+ function makeProxyWrapper(interfaceXml) {
299
+ const info = _newInterfaceInfo(interfaceXml);
300
+ const iname = info.name;
301
+
302
+ function ProxyClass(bus, name, object, asyncCallback, cancellable = null, flags = Gio.DBusProxyFlags.NONE) {
303
+ const obj = new Gio.DBusProxy({
304
+ g_connection: bus,
305
+ g_interface_name: iname,
306
+ g_interface_info: info,
307
+ g_name: name,
308
+ g_flags: flags,
309
+ g_object_path: object,
310
+ });
311
+
312
+ if (asyncCallback) {
313
+ obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, (_rawSource, res) => {
314
+ try {
315
+ obj.init_finish(res);
316
+ _addDBusConvenience(obj, info);
317
+ asyncCallback(obj, null);
318
+ } catch (e) {
319
+ asyncCallback(null, e);
320
+ }
321
+ });
322
+ } else {
323
+ obj.init(cancellable);
324
+ _addDBusConvenience(obj, info);
325
+ }
326
+ return obj;
164
327
  }
165
- };
166
- return wrappedProxy.call_with_unix_fd_list(methodName, inVariant, flags, -1, fdList, cancellable, asyncCallback);
328
+
329
+ ProxyClass.newAsync = function newAsync(
330
+ bus,
331
+ name,
332
+ object,
333
+ cancellable = null,
334
+ flags = Gio.DBusProxyFlags.NONE,
335
+ ) {
336
+ const obj = new Gio.DBusProxy({
337
+ g_connection: bus,
338
+ g_interface_name: info.name,
339
+ g_interface_info: info,
340
+ g_name: name,
341
+ g_flags: flags,
342
+ g_object_path: object,
343
+ });
344
+ return new Promise((resolve, reject) => {
345
+ obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, (_rawSource, res) => {
346
+ try {
347
+ obj.init_finish(res);
348
+ _addDBusConvenience(obj, info);
349
+ resolve(obj);
350
+ } catch (e) {
351
+ reject(e);
352
+ }
353
+ });
354
+ });
355
+ };
356
+
357
+ return ProxyClass;
167
358
  }
168
- if (fdList) throw new Error(`${methodName}: Gio.UnixFDList fd-passing is not supported on this platform`);
169
- const asyncCallback = (_rawSource, res) => {
170
- try {
171
- const outVariant = wrappedProxy.call_finish(res);
172
- replyFunc(outVariant.deepUnpack(), null, null);
173
- } catch (e) {
174
- replyFunc([], e, null);
175
- }
176
- };
177
- return wrappedProxy.call(methodName, inVariant, flags, -1, cancellable, asyncCallback);
178
- }
179
-
180
- function _makeProxyMethod(wrappedProxy, method, sync) {
181
- const name = method.name;
182
- const inArgs = method.in_args;
183
- const inSignature = [];
184
- for (let i = 0; i < inArgs.length; i++) inSignature.push(inArgs[i].signature);
185
- return function proxyMethod(...args) {
186
- return _proxyInvoker(wrappedProxy, name, sync, inSignature, args);
187
- };
188
- }
189
-
190
- function _propertyGetter(wrappedProxy, name) {
191
- const value = wrappedProxy.get_cached_property(name);
192
- return value ? value.deepUnpack() : null;
193
- }
194
-
195
- function _propertySetter(wrappedProxy, name, signature, value) {
196
- const variant = new GLib.Variant(signature, value);
197
- wrappedProxy.set_cached_property(name, variant);
198
- wrappedProxy.call(
199
- 'org.freedesktop.DBus.Properties.Set',
200
- new GLib.Variant('(ssv)', [wrappedProxy.g_interface_name, name, variant]),
201
- Gio.DBusCallFlags.NONE,
202
- -1,
203
- null,
204
- (_rawSource, res) => {
359
+
360
+ // ---- name owning ----
361
+ const ownedNames = new Map();
362
+ const watchedNames = new Map();
363
+ let nextToken = 1;
364
+
365
+ function busConnection(busType) {
366
+ return Gio.bus_get_sync(busType, null);
367
+ }
368
+
369
+ function requestName(connection, name, flags) {
370
+ const reply = connection.call_sync(
371
+ DBUS_NAME,
372
+ DBUS_PATH,
373
+ DBUS_NAME,
374
+ 'RequestName',
375
+ new GLib.Variant('(su)', [name, flags >>> 0]),
376
+ null,
377
+ Gio.DBusCallFlags.NONE,
378
+ -1,
379
+ null,
380
+ );
381
+ return reply.deepUnpack()[0];
382
+ }
383
+
384
+ function ownNameCore(connection, name, flags, onBusAcquired, onNameAcquired, onNameLost) {
385
+ const token = nextToken++;
386
+ ownedNames.set(token, { connection, name });
387
+ let code;
205
388
  try {
206
- wrappedProxy.call_finish(res);
389
+ code = requestName(connection, name, flags);
207
390
  } catch (e) {
208
- log(`Could not set property ${name} on remote object ${wrappedProxy.g_object_path}: ${e.message}`);
391
+ ownedNames.delete(token);
392
+ throw e;
209
393
  }
210
- },
211
- );
212
- }
213
-
214
- // Attach the method/property/signal convenience to a freshly-inited DBusProxy
215
- // instance wrapper. Ported from refs/gjs Gio.js `_addDBusConvenience`.
216
- function _addDBusConvenience(wrappedProxy, info) {
217
- // The `_signals` mixin bookkeeping lives on a dedicated plain emitter (see the
218
- // module header): connectSignal/disconnectSignal delegate to it.
219
- const emitter = {};
220
- addSignalMethods(emitter);
221
- wrappedProxy.connectSignal = (name, callback) => _connect.call(emitter, name, callback);
222
- wrappedProxy.disconnectSignal = (id) => _disconnect.call(emitter, id);
223
-
224
- const signals = info.signals;
225
- if (signals && signals.length > 0) {
226
- wrappedProxy.connect('g-signal', (_proxy, senderName, signalName, parameters) => {
227
- _emit.call(emitter, signalName, senderName, parameters.deepUnpack());
228
- });
394
+ // GJS fires the acquired/lost callbacks from the main loop, never synchronously.
395
+ GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
396
+ if (typeof onBusAcquired === 'function') safeCallback(onBusAcquired, connection, name);
397
+ const acquired = code === REQUEST_NAME_PRIMARY_OWNER || code === REQUEST_NAME_ALREADY_OWNER;
398
+ if (acquired) {
399
+ if (typeof onNameAcquired === 'function') safeCallback(onNameAcquired, connection, name);
400
+ } else if (typeof onNameLost === 'function') {
401
+ safeCallback(onNameLost, connection, name);
402
+ }
403
+ return false; // G_SOURCE_REMOVE
404
+ });
405
+ return token;
229
406
  }
230
407
 
231
- const methods = info.methods;
232
- for (let i = 0; i < methods.length; i++) {
233
- const method = methods[i];
234
- const name = method.name;
235
- const remoteMethod = _makeProxyMethod(wrappedProxy, method, false);
236
- wrappedProxy[`${name}Remote`] = remoteMethod;
237
- wrappedProxy[`${name}Sync`] = _makeProxyMethod(wrappedProxy, method, true);
238
- wrappedProxy[`${name}Async`] = function proxyAsyncMethod(...args) {
239
- return new Promise((resolve, reject) => {
240
- args.push((result, error, fdList) => {
241
- if (error) reject(error);
242
- else if (fdList) resolve([result, fdList]);
243
- else resolve(result);
244
- });
245
- remoteMethod(...args);
246
- });
247
- };
408
+ function own_name(busType, name, flags, onBusAcquired, onNameAcquired, onNameLost) {
409
+ return ownNameCore(busConnection(busType), name, flags, onBusAcquired, onNameAcquired, onNameLost);
248
410
  }
249
411
 
250
- const properties = info.properties;
251
- for (let i = 0; i < properties.length; i++) {
252
- const propInfo = properties[i];
253
- const name = propInfo.name;
254
- const signature = propInfo.signature;
255
- const propFlags = propInfo.flags;
256
- let getter = () => {
257
- throw new Error(`Property ${name} is not readable`);
258
- };
259
- let setter = () => {
260
- throw new Error(`Property ${name} is not writable`);
261
- };
262
- if (propFlags & Gio.DBusPropertyInfoFlags.READABLE) getter = () => _propertyGetter(wrappedProxy, name);
263
- if (propFlags & Gio.DBusPropertyInfoFlags.WRITABLE) {
264
- setter = (value) => _propertySetter(wrappedProxy, name, signature, value);
265
- }
266
- Object.defineProperty(wrappedProxy, name, {
267
- get: getter,
268
- set: setter,
269
- configurable: false,
270
- enumerable: true,
271
- });
412
+ function own_name_on_connection(connection, name, flags, onNameAcquired, onNameLost) {
413
+ return ownNameCore(connection, name, flags, undefined, onNameAcquired, onNameLost);
272
414
  }
273
- }
274
-
275
- function _newInterfaceInfo(xml) {
276
- const nodeInfo = Gio.DBusNodeInfo.new_for_xml(xml);
277
- return nodeInfo.interfaces[0];
278
- }
279
-
280
- // Gio.DBusProxy.makeProxyWrapper(interfaceXml). Returns a constructor:
281
- // new ProxyClass(bus, name, objectPath[, asyncCallback[, cancellable[, flags]]])
282
- // and a static `ProxyClass.newAsync(bus, name, objectPath[, cancellable[, flags]])`.
283
- function makeProxyWrapper(interfaceXml) {
284
- const info = _newInterfaceInfo(interfaceXml);
285
- const iname = info.name;
286
-
287
- function ProxyClass(bus, name, object, asyncCallback, cancellable = null, flags = Gio.DBusProxyFlags.NONE) {
288
- const obj = new Gio.DBusProxy({
289
- g_connection: bus,
290
- g_interface_name: iname,
291
- g_interface_info: info,
292
- g_name: name,
293
- g_flags: flags,
294
- g_object_path: object,
295
- });
296
-
297
- if (asyncCallback) {
298
- obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, (_rawSource, res) => {
299
- try {
300
- obj.init_finish(res);
301
- _addDBusConvenience(obj, info);
302
- asyncCallback(obj, null);
303
- } catch (e) {
304
- asyncCallback(null, e);
305
- }
306
- });
307
- } else {
308
- obj.init(cancellable);
309
- _addDBusConvenience(obj, info);
310
- }
311
- return obj;
415
+
416
+ function unown_name(token) {
417
+ const entry = ownedNames.get(token);
418
+ if (!entry) return;
419
+ ownedNames.delete(token);
420
+ try {
421
+ entry.connection.call_sync(
422
+ DBUS_NAME,
423
+ DBUS_PATH,
424
+ DBUS_NAME,
425
+ 'ReleaseName',
426
+ new GLib.Variant('(s)', [entry.name]),
427
+ null,
428
+ Gio.DBusCallFlags.NONE,
429
+ -1,
430
+ null,
431
+ );
432
+ } catch {
433
+ /* connection already gone — nothing to release */
434
+ }
435
+ }
436
+
437
+ // ---- name watching (NameOwnerChanged subscription + GetNameOwner probe) ----
438
+ function getNameOwner(connection, name) {
439
+ try {
440
+ const reply = connection.call_sync(
441
+ DBUS_NAME,
442
+ DBUS_PATH,
443
+ DBUS_NAME,
444
+ 'GetNameOwner',
445
+ new GLib.Variant('(s)', [name]),
446
+ null,
447
+ Gio.DBusCallFlags.NONE,
448
+ -1,
449
+ null,
450
+ );
451
+ return reply.deepUnpack()[0];
452
+ } catch {
453
+ return null; // NameHasNoOwner
454
+ }
312
455
  }
313
456
 
314
- ProxyClass.newAsync = function newAsync(bus, name, object, cancellable = null, flags = Gio.DBusProxyFlags.NONE) {
315
- const obj = new Gio.DBusProxy({
316
- g_connection: bus,
317
- g_interface_name: info.name,
318
- g_interface_info: info,
319
- g_name: name,
320
- g_flags: flags,
321
- g_object_path: object,
322
- });
323
- return new Promise((resolve, reject) => {
324
- obj.init_async(GLib.PRIORITY_DEFAULT, cancellable, (_rawSource, res) => {
325
- try {
326
- obj.init_finish(res);
327
- _addDBusConvenience(obj, info);
328
- resolve(obj);
329
- } catch (e) {
330
- reject(e);
331
- }
457
+ function watchNameCore(connection, name, onNameAppeared, onNameVanished) {
458
+ const token = nextToken++;
459
+ let lastOwner = null;
460
+
461
+ const dispatch = () => {
462
+ const owner = getNameOwner(connection, name);
463
+ if (owner && owner.length > 0) {
464
+ if (owner !== lastOwner && typeof onNameAppeared === 'function')
465
+ safeCallback(onNameAppeared, connection, name, owner);
466
+ lastOwner = owner;
467
+ } else {
468
+ if (lastOwner !== null && typeof onNameVanished === 'function')
469
+ safeCallback(onNameVanished, connection, name);
470
+ lastOwner = null;
471
+ }
472
+ };
473
+
474
+ // arg0-filtered subscription: NameOwnerChanged whose first arg is `name`.
475
+ const subId = connection.signal_subscribe(
476
+ DBUS_NAME,
477
+ DBUS_NAME,
478
+ 'NameOwnerChanged',
479
+ DBUS_PATH,
480
+ name,
481
+ Gio.DBusSignalFlags.NONE,
482
+ () => dispatch(),
483
+ );
484
+ watchedNames.set(token, { connection, subId });
485
+
486
+ // Initial state, delivered from the loop like g_bus_watch_name's GetNameOwner.
487
+ GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
488
+ const owner = getNameOwner(connection, name);
489
+ if (owner && owner.length > 0) {
490
+ lastOwner = owner;
491
+ if (typeof onNameAppeared === 'function') safeCallback(onNameAppeared, connection, name, owner);
492
+ } else if (typeof onNameVanished === 'function') {
493
+ safeCallback(onNameVanished, connection, name);
494
+ }
495
+ return false; // G_SOURCE_REMOVE
332
496
  });
333
- });
334
- };
497
+ return token;
498
+ }
335
499
 
336
- return ProxyClass;
337
- }
338
-
339
- // ---- name owning ----
340
- const ownedNames = new Map();
341
- const watchedNames = new Map();
342
- let nextToken = 1;
343
-
344
- function busConnection(busType) {
345
- return Gio.bus_get_sync(busType, null);
346
- }
347
-
348
- function requestName(connection, name, flags) {
349
- const reply = connection.call_sync(
350
- DBUS_NAME,
351
- DBUS_PATH,
352
- DBUS_NAME,
353
- 'RequestName',
354
- new GLib.Variant('(su)', [name, flags >>> 0]),
355
- null,
356
- Gio.DBusCallFlags.NONE,
357
- -1,
358
- null,
359
- );
360
- return reply.deepUnpack()[0];
361
- }
362
-
363
- function ownNameCore(connection, name, flags, onBusAcquired, onNameAcquired, onNameLost) {
364
- const token = nextToken++;
365
- ownedNames.set(token, { connection, name });
366
- let code;
367
- try {
368
- code = requestName(connection, name, flags);
369
- } catch (e) {
370
- ownedNames.delete(token);
371
- throw e;
500
+ function watch_name(busType, name, _flags, onNameAppeared, onNameVanished) {
501
+ return watchNameCore(busConnection(busType), name, onNameAppeared, onNameVanished);
372
502
  }
373
- // GJS fires the acquired/lost callbacks from the main loop, never synchronously.
374
- GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
375
- if (typeof onBusAcquired === 'function') safeCallback(onBusAcquired, connection, name);
376
- const acquired = code === REQUEST_NAME_PRIMARY_OWNER || code === REQUEST_NAME_ALREADY_OWNER;
377
- if (acquired) {
378
- if (typeof onNameAcquired === 'function') safeCallback(onNameAcquired, connection, name);
379
- } else if (typeof onNameLost === 'function') {
380
- safeCallback(onNameLost, connection, name);
381
- }
382
- return false; // G_SOURCE_REMOVE
383
- });
384
- return token;
385
- }
386
-
387
- function own_name(busType, name, flags, onBusAcquired, onNameAcquired, onNameLost) {
388
- return ownNameCore(busConnection(busType), name, flags, onBusAcquired, onNameAcquired, onNameLost);
389
- }
390
-
391
- function own_name_on_connection(connection, name, flags, onNameAcquired, onNameLost) {
392
- return ownNameCore(connection, name, flags, undefined, onNameAcquired, onNameLost);
393
- }
394
-
395
- function unown_name(token) {
396
- const entry = ownedNames.get(token);
397
- if (!entry) return;
398
- ownedNames.delete(token);
399
- try {
400
- entry.connection.call_sync(
401
- DBUS_NAME,
402
- DBUS_PATH,
403
- DBUS_NAME,
404
- 'ReleaseName',
405
- new GLib.Variant('(s)', [entry.name]),
406
- null,
407
- Gio.DBusCallFlags.NONE,
408
- -1,
409
- null,
410
- );
411
- } catch {
412
- /* connection already gone — nothing to release */
503
+
504
+ function watch_name_on_connection(connection, name, _flags, onNameAppeared, onNameVanished) {
505
+ return watchNameCore(connection, name, onNameAppeared, onNameVanished);
413
506
  }
414
- }
415
-
416
- // ---- name watching (NameOwnerChanged subscription + GetNameOwner probe) ----
417
- function getNameOwner(connection, name) {
418
- try {
419
- const reply = connection.call_sync(
420
- DBUS_NAME,
421
- DBUS_PATH,
422
- DBUS_NAME,
423
- 'GetNameOwner',
424
- new GLib.Variant('(s)', [name]),
425
- null,
426
- Gio.DBusCallFlags.NONE,
427
- -1,
428
- null,
429
- );
430
- return reply.deepUnpack()[0];
431
- } catch {
432
- return null; // NameHasNoOwner
507
+
508
+ function unwatch_name(token) {
509
+ const entry = watchedNames.get(token);
510
+ if (!entry) return;
511
+ watchedNames.delete(token);
512
+ try {
513
+ entry.connection.signal_unsubscribe(entry.subId);
514
+ } catch {
515
+ /* connection already gone */
516
+ }
433
517
  }
434
- }
435
-
436
- function watchNameCore(connection, name, onNameAppeared, onNameVanished) {
437
- const token = nextToken++;
438
- let lastOwner = null;
439
-
440
- const dispatch = () => {
441
- const owner = getNameOwner(connection, name);
442
- if (owner && owner.length > 0) {
443
- if (owner !== lastOwner && typeof onNameAppeared === 'function') safeCallback(onNameAppeared, connection, name, owner);
444
- lastOwner = owner;
445
- } else {
446
- if (lastOwner !== null && typeof onNameVanished === 'function') safeCallback(onNameVanished, connection, name);
447
- lastOwner = null;
448
- }
518
+
519
+ // ---- Gio.DBus namespace ----
520
+ const DBus = {
521
+ get: Gio.bus_get,
522
+ get_finish: Gio.bus_get_finish,
523
+ get_sync: Gio.bus_get_sync,
524
+ own_name,
525
+ own_name_on_connection,
526
+ unown_name,
527
+ watch_name,
528
+ watch_name_on_connection,
529
+ unwatch_name,
449
530
  };
531
+ Object.defineProperty(DBus, 'session', {
532
+ get() {
533
+ return Gio.bus_get_sync(Gio.BusType.SESSION, null);
534
+ },
535
+ enumerable: false,
536
+ });
537
+ Object.defineProperty(DBus, 'system', {
538
+ get() {
539
+ return Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
540
+ },
541
+ enumerable: false,
542
+ });
450
543
 
451
- // arg0-filtered subscription: NameOwnerChanged whose first arg is `name`.
452
- const subId = connection.signal_subscribe(
453
- DBUS_NAME,
454
- DBUS_NAME,
455
- 'NameOwnerChanged',
456
- DBUS_PATH,
457
- name,
458
- Gio.DBusSignalFlags.NONE,
459
- () => dispatch(),
460
- );
461
- watchedNames.set(token, { connection, subId });
462
-
463
- // Initial state, delivered from the loop like g_bus_watch_name's GetNameOwner.
464
- GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
465
- const owner = getNameOwner(connection, name);
466
- if (owner && owner.length > 0) {
467
- lastOwner = owner;
468
- if (typeof onNameAppeared === 'function') safeCallback(onNameAppeared, connection, name, owner);
469
- } else if (typeof onNameVanished === 'function') {
470
- safeCallback(onNameVanished, connection, name);
471
- }
472
- return false; // G_SOURCE_REMOVE
544
+ // ---- Gio.DBusProxy (adds the static makeProxyWrapper to the introspected class) ----
545
+ const DBusProxy = new Proxy(Gio.DBusProxy, {
546
+ get(target, prop) {
547
+ if (prop === 'makeProxyWrapper') return makeProxyWrapper;
548
+ return target[prop];
549
+ },
550
+ has(target, prop) {
551
+ return prop === 'makeProxyWrapper' || prop in target;
552
+ },
473
553
  });
474
- return token;
475
- }
476
-
477
- function watch_name(busType, name, _flags, onNameAppeared, onNameVanished) {
478
- return watchNameCore(busConnection(busType), name, onNameAppeared, onNameVanished);
479
- }
480
-
481
- function watch_name_on_connection(connection, name, _flags, onNameAppeared, onNameVanished) {
482
- return watchNameCore(connection, name, onNameAppeared, onNameVanished);
483
- }
484
-
485
- function unwatch_name(token) {
486
- const entry = watchedNames.get(token);
487
- if (!entry) return;
488
- watchedNames.delete(token);
489
- try {
490
- entry.connection.signal_unsubscribe(entry.subId);
491
- } catch {
492
- /* connection already gone */
554
+
555
+ // ---- Gio.DBusExportedObject (export side) ---------------------------------
556
+ //
557
+ // Ported from refs/gjs Gio.js (_wrapJSObject + _handleMethodCall /
558
+ // _handlePropertyGet / _handlePropertySet). GJS builds this on the C type
559
+ // GjsPrivate.DBusImplementation (a GObject with handle-method-call /
560
+ // handle-property-get / handle-property-set signals); node-gi has no such type,
561
+ // so it instead drives the INTROSPECTABLE
562
+ // g_dbus_connection_register_object_with_closures2 — three GClosures whose
563
+ // marshal the engine now supports. The closure param shapes match glib's
564
+ // register_with_closures_on_* trampolines (gio/gdbusconnection.c):
565
+ // method_call: (conn, sender, path, iface, method, params:Variant, invocation)
566
+ // get_property: (conn, sender, path, iface, prop) -> Variant|null
567
+ // set_property: (conn, sender, path, iface, prop, value:Variant) -> boolean
568
+ // Ownership: register_object_data_new refs+sinks its own copy of each closure
569
+ // and frees them on unregister_object, so the JS handler (and everything it
570
+ // captures) lives exactly as long as the registration. The closures2 variant
571
+ // does NOT transfer the invocation ref to the closure — the engine's
572
+ // transfer-full-instance handling refs the invocation before a return_* call,
573
+ // matching gjs's introspection-annotation-driven behaviour.
574
+
575
+ function _makeOutSignature(args) {
576
+ let ret = '(';
577
+ for (let i = 0; i < args.length; i++) ret += args[i].signature;
578
+ return `${ret})`;
493
579
  }
494
- }
495
-
496
- // ---- Gio.DBus namespace ----
497
- const DBus = {
498
- get: Gio.bus_get,
499
- get_finish: Gio.bus_get_finish,
500
- get_sync: Gio.bus_get_sync,
501
- own_name,
502
- own_name_on_connection,
503
- unown_name,
504
- watch_name,
505
- watch_name_on_connection,
506
- unwatch_name,
507
- };
508
- Object.defineProperty(DBus, 'session', {
509
- get() {
510
- return Gio.bus_get_sync(Gio.BusType.SESSION, null);
511
- },
512
- enumerable: false,
513
- });
514
- Object.defineProperty(DBus, 'system', {
515
- get() {
516
- return Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
517
- },
518
- enumerable: false,
519
- });
520
-
521
- // ---- Gio.DBusProxy (adds the static makeProxyWrapper to the introspected class) ----
522
- const DBusProxy = new Proxy(Gio.DBusProxy, {
523
- get(target, prop) {
524
- if (prop === 'makeProxyWrapper') return makeProxyWrapper;
525
- return target[prop];
526
- },
527
- has(target, prop) {
528
- return prop === 'makeProxyWrapper' || prop in target;
529
- },
530
- });
531
-
532
- // ---- Gio.DBusExportedObject (export side) ---------------------------------
533
- //
534
- // Ported from refs/gjs Gio.js (_wrapJSObject + _handleMethodCall /
535
- // _handlePropertyGet / _handlePropertySet). GJS builds this on the C type
536
- // GjsPrivate.DBusImplementation (a GObject with handle-method-call /
537
- // handle-property-get / handle-property-set signals); node-gi has no such type,
538
- // so it instead drives the INTROSPECTABLE
539
- // g_dbus_connection_register_object_with_closures2 — three GClosures whose
540
- // marshal the engine now supports. The closure param shapes match glib's
541
- // register_with_closures_on_* trampolines (gio/gdbusconnection.c):
542
- // method_call: (conn, sender, path, iface, method, params:Variant, invocation)
543
- // get_property: (conn, sender, path, iface, prop) -> Variant|null
544
- // set_property: (conn, sender, path, iface, prop, value:Variant) -> boolean
545
- // Ownership: register_object_data_new refs+sinks its own copy of each closure
546
- // and frees them on unregister_object, so the JS handler (and everything it
547
- // captures) lives exactly as long as the registration. The closures2 variant
548
- // does NOT transfer the invocation ref to the closure — the engine's
549
- // transfer-full-instance handling refs the invocation before a return_* call,
550
- // matching gjs's introspection-annotation-driven behaviour.
551
-
552
- function _makeOutSignature(args) {
553
- let ret = '(';
554
- for (let i = 0; i < args.length; i++) ret += args[i].signature;
555
- return `${ret})`;
556
- }
557
-
558
- function _handleDBusReply(invocation, ret) {
559
- if (ret === undefined) ret = new GLib.Variant('()', []);
560
- try {
561
- if (!(ret instanceof GLib.Variant)) {
562
- const outArgs = invocation.get_method_info().out_args;
563
- const outSignature = _makeOutSignature(outArgs);
564
- if (outArgs.length === 1) ret = [ret];
565
- ret = new GLib.Variant(outSignature, ret);
566
- }
567
- invocation.return_value(ret);
568
- } catch (e) {
569
- logError(e, `Exception in method call: ${invocation.get_method_name()}`);
570
- invocation.return_dbus_error(
571
- 'org.gnome.gjs.JSError.ValueError',
572
- 'Service implementation returned an incorrect value type',
573
- );
580
+
581
+ function _handleDBusReply(invocation, ret) {
582
+ if (ret === undefined) ret = new GLib.Variant('()', []);
583
+ try {
584
+ if (!(ret instanceof GLib.Variant)) {
585
+ const outArgs = invocation.get_method_info().out_args;
586
+ const outSignature = _makeOutSignature(outArgs);
587
+ if (outArgs.length === 1) ret = [ret];
588
+ ret = new GLib.Variant(outSignature, ret);
589
+ }
590
+ invocation.return_value(ret);
591
+ } catch (e) {
592
+ logError(e, `Exception in method call: ${invocation.get_method_name()}`);
593
+ invocation.return_dbus_error(
594
+ 'org.gnome.gjs.JSError.ValueError',
595
+ 'Service implementation returned an incorrect value type',
596
+ );
597
+ }
574
598
  }
575
- }
576
599
 
577
- function _handleDBusError(invocation, e) {
578
- if (e instanceof GLib.Error) {
579
- invocation.return_gerror(e);
580
- return;
600
+ function _handleDBusError(invocation, e) {
601
+ if (e instanceof GLib.Error) {
602
+ invocation.return_gerror(e);
603
+ return;
604
+ }
605
+ let name = e && e.name ? e.name : 'Error';
606
+ if (!name.includes('.')) name = `org.gnome.gjs.JSError.${name}`;
607
+ logError(e, `Exception in method call: ${invocation.get_method_name()}`);
608
+ invocation.return_dbus_error(name, e && e.message ? e.message : String(e));
581
609
  }
582
- let name = e && e.name ? e.name : 'Error';
583
- if (!name.includes('.')) name = `org.gnome.gjs.JSError.${name}`;
584
- logError(e, `Exception in method call: ${invocation.get_method_name()}`);
585
- invocation.return_dbus_error(name, e && e.message ? e.message : String(e));
586
- }
587
-
588
- function _handleMethodCall(jsObj, methodName, parameters, invocation) {
589
- // GJS appends the message's Gio.UnixFDList as a trailing argument (null when
590
- // the call carries no fds) — mirror it so fd-aware services see the same
591
- // shape (refs/gjs Gio.js _handleMethodCall). Gio.DBusMessage.get_unix_fd_list
592
- // is GioUnix (fd-passing) — absent from the Gio typelib on Windows — so guard by
593
- // method presence and pass null there (no fds to carry). POSIX byte-identical:
594
- // where the method exists it is taken exactly as before.
595
- const msg = invocation.get_message();
596
- const fdList = typeof msg.get_unix_fd_list === 'function' ? msg.get_unix_fd_list() : null;
597
- const method = jsObj[methodName];
598
- if (typeof method === 'function') {
599
- let retval;
600
- try {
601
- const args = parameters.deepUnpack();
602
- args.push(fdList);
603
- retval = method.apply(jsObj, args);
604
- } catch (e) {
605
- _handleDBusError(invocation, e);
606
- return;
607
- }
608
- // A Promise-returning (async) method resolves to the reply.
609
- if (retval && typeof retval.then === 'function') {
610
- retval.then(
611
- (r) => _handleDBusReply(invocation, r),
612
- (e) => _handleDBusError(invocation, e),
610
+
611
+ function _handleMethodCall(jsObj, methodName, parameters, invocation) {
612
+ // GJS appends the message's Gio.UnixFDList as a trailing argument (null when
613
+ // the call carries no fds) — mirror it so fd-aware services see the same
614
+ // shape (refs/gjs Gio.js _handleMethodCall). Gio.DBusMessage.get_unix_fd_list
615
+ // is GioUnix (fd-passing) — absent from the Gio typelib on Windows — so guard by
616
+ // method presence and pass null there (no fds to carry). POSIX byte-identical:
617
+ // where the method exists it is taken exactly as before.
618
+ const msg = invocation.get_message();
619
+ const fdList = typeof msg.get_unix_fd_list === 'function' ? msg.get_unix_fd_list() : null;
620
+ const method = jsObj[methodName];
621
+ if (typeof method === 'function') {
622
+ let retval;
623
+ try {
624
+ const args = parameters.deepUnpack();
625
+ args.push(fdList);
626
+ retval = method.apply(jsObj, args);
627
+ } catch (e) {
628
+ _handleDBusError(invocation, e);
629
+ return;
630
+ }
631
+ // A Promise-returning (async) method resolves to the reply.
632
+ if (retval && typeof retval.then === 'function') {
633
+ retval.then(
634
+ (r) => _handleDBusReply(invocation, r),
635
+ (e) => _handleDBusError(invocation, e),
636
+ );
637
+ return;
638
+ }
639
+ _handleDBusReply(invocation, retval);
640
+ return;
641
+ }
642
+
643
+ const asyncMethod = jsObj[`${methodName}Async`];
644
+ if (typeof asyncMethod === 'function') {
645
+ let ret;
646
+ try {
647
+ ret = asyncMethod.call(jsObj, parameters.deepUnpack(), invocation, fdList);
648
+ } catch (e) {
649
+ _handleDBusError(invocation, e);
650
+ return;
651
+ }
652
+ if (ret && typeof ret.catch === 'function') ret.catch((e) => _handleDBusError(invocation, e));
653
+ return;
654
+ }
655
+
656
+ logError(new Error(), `Missing handler for DBus method ${methodName}`);
657
+ invocation.return_dbus_error(
658
+ 'org.freedesktop.DBus.Error.UnknownMethod',
659
+ `Method ${methodName} is not implemented`,
613
660
  );
614
- return;
615
- }
616
- _handleDBusReply(invocation, retval);
617
- return;
618
661
  }
619
662
 
620
- const asyncMethod = jsObj[`${methodName}Async`];
621
- if (typeof asyncMethod === 'function') {
622
- let ret;
623
- try {
624
- ret = asyncMethod.call(jsObj, parameters.deepUnpack(), invocation, fdList);
625
- } catch (e) {
626
- _handleDBusError(invocation, e);
627
- return;
628
- }
629
- if (ret && typeof ret.catch === 'function') ret.catch((e) => _handleDBusError(invocation, e));
630
- return;
663
+ function _handlePropertyGet(jsObj, info, propertyName) {
664
+ const propInfo = info.lookup_property(propertyName);
665
+ const jsval = jsObj[propertyName];
666
+ if (jsval === undefined || jsval === null) return null;
667
+ if (jsval instanceof GLib.Variant) return jsval;
668
+ return new GLib.Variant(propInfo.signature, jsval);
631
669
  }
632
670
 
633
- logError(new Error(), `Missing handler for DBus method ${methodName}`);
634
- invocation.return_dbus_error(
635
- 'org.freedesktop.DBus.Error.UnknownMethod',
636
- `Method ${methodName} is not implemented`,
637
- );
638
- }
639
-
640
- function _handlePropertyGet(jsObj, info, propertyName) {
641
- const propInfo = info.lookup_property(propertyName);
642
- const jsval = jsObj[propertyName];
643
- if (jsval === undefined || jsval === null) return null;
644
- if (jsval instanceof GLib.Variant) return jsval;
645
- return new GLib.Variant(propInfo.signature, jsval);
646
- }
647
-
648
- function _handlePropertySet(jsObj, propertyName, newValue) {
649
- jsObj[propertyName] = newValue.deepUnpack();
650
- return true;
651
- }
652
-
653
- function wrapJSObject(interfaceInfo, jsObj) {
654
- let info;
655
- if (isGioInstance(interfaceInfo, 'DBusInterfaceInfo')) info = interfaceInfo;
656
- else info = _newInterfaceInfo(interfaceInfo);
657
- // cache_build is required so lookup_property/method work off the info.
658
- info.cache_build();
659
- const ifaceName = info.name;
660
-
661
- // Every connection this object is exported on: { connection, registrationId }.
662
- const registrations = [];
663
-
664
- const impl = {
665
- get_info: () => info,
666
- get_object_path: () => impl._objectPath,
667
- get_connection: () => impl._connection,
668
- _objectPath: null,
669
- _connection: null,
670
-
671
- export(connection, path) {
672
- // register_object_with_closures2 (since 2.84): closures for method call /
673
- // get property / set property. Passing JS functions marshals each to a
674
- // real GClosure (the engine's GClosure IN-arg support). Returns the
675
- // registration id (throws on error).
676
- const id = connection.register_object_with_closures2(
677
- path,
678
- info,
679
- (_conn, _sender, _path, _iface, method, params, invocation) =>
680
- _handleMethodCall(jsObj, method, params, invocation),
681
- (_conn, _sender, _path, _iface, prop) => _handlePropertyGet(jsObj, info, prop),
682
- (_conn, _sender, _path, _iface, prop, value) => _handlePropertySet(jsObj, prop, value),
683
- );
684
- registrations.push({ connection, id });
685
- impl._connection = connection;
686
- impl._objectPath = path;
687
- return id;
688
- },
689
-
690
- unexport() {
691
- for (const { connection, id } of registrations.splice(0)) {
692
- connection.unregister_object(id);
693
- }
694
- impl._connection = null;
695
- },
696
-
697
- unexport_from_connection(connection) {
698
- const handle = unwrap(connection);
699
- for (let i = registrations.length - 1; i >= 0; i--) {
700
- if (unwrap(registrations[i].connection) === handle) {
701
- registrations[i].connection.unregister_object(registrations[i].id);
702
- registrations.splice(i, 1);
703
- }
704
- }
705
- if (registrations.length === 0) impl._connection = null;
706
- },
671
+ function _handlePropertySet(jsObj, propertyName, newValue) {
672
+ jsObj[propertyName] = newValue.deepUnpack();
673
+ return true;
674
+ }
707
675
 
708
- emit_signal(name, parameters = null) {
709
- for (const { connection } of registrations) {
710
- connection.emit_signal(null, impl._objectPath, ifaceName, name, parameters);
711
- }
712
- },
713
-
714
- emit_property_changed(name, value) {
715
- const changed = {};
716
- changed[name] = value;
717
- const params = new GLib.Variant('(sa{sv}as)', [ifaceName, changed, []]);
718
- for (const { connection } of registrations) {
719
- connection.emit_signal(
720
- null,
721
- impl._objectPath,
722
- 'org.freedesktop.DBus.Properties',
723
- 'PropertiesChanged',
724
- params,
725
- );
726
- }
727
- },
676
+ function wrapJSObject(interfaceInfo, jsObj) {
677
+ let info;
678
+ if (isGioInstance(interfaceInfo, 'DBusInterfaceInfo')) info = interfaceInfo;
679
+ else info = _newInterfaceInfo(interfaceInfo);
680
+ // cache_build is required so lookup_property/method work off the info.
681
+ info.cache_build();
682
+ const ifaceName = info.name;
683
+
684
+ // Every connection this object is exported on: { connection, registrationId }.
685
+ const registrations = [];
686
+
687
+ const impl = {
688
+ get_info: () => info,
689
+ get_object_path: () => impl._objectPath,
690
+ get_connection: () => impl._connection,
691
+ _objectPath: null,
692
+ _connection: null,
693
+
694
+ export(connection, path) {
695
+ // register_object_with_closures2 (since 2.84): closures for method call /
696
+ // get property / set property. Passing JS functions marshals each to a
697
+ // real GClosure (the engine's GClosure IN-arg support). Returns the
698
+ // registration id (throws on error).
699
+ const id = connection.register_object_with_closures2(
700
+ path,
701
+ info,
702
+ (_conn, _sender, _path, _iface, method, params, invocation) =>
703
+ _handleMethodCall(jsObj, method, params, invocation),
704
+ (_conn, _sender, _path, _iface, prop) => _handlePropertyGet(jsObj, info, prop),
705
+ (_conn, _sender, _path, _iface, prop, value) => _handlePropertySet(jsObj, prop, value),
706
+ );
707
+ registrations.push({ connection, id });
708
+ impl._connection = connection;
709
+ impl._objectPath = path;
710
+ return id;
711
+ },
712
+
713
+ unexport() {
714
+ for (const { connection, id } of registrations.splice(0)) {
715
+ connection.unregister_object(id);
716
+ }
717
+ impl._connection = null;
718
+ },
719
+
720
+ unexport_from_connection(connection) {
721
+ const handle = unwrap(connection);
722
+ for (let i = registrations.length - 1; i >= 0; i--) {
723
+ if (unwrap(registrations[i].connection) === handle) {
724
+ registrations[i].connection.unregister_object(registrations[i].id);
725
+ registrations.splice(i, 1);
726
+ }
727
+ }
728
+ if (registrations.length === 0) impl._connection = null;
729
+ },
730
+
731
+ emit_signal(name, parameters = null) {
732
+ for (const { connection } of registrations) {
733
+ connection.emit_signal(null, impl._objectPath, ifaceName, name, parameters);
734
+ }
735
+ },
736
+
737
+ emit_property_changed(name, value) {
738
+ const changed = {};
739
+ changed[name] = value;
740
+ const params = new GLib.Variant('(sa{sv}as)', [ifaceName, changed, []]);
741
+ for (const { connection } of registrations) {
742
+ connection.emit_signal(
743
+ null,
744
+ impl._objectPath,
745
+ 'org.freedesktop.DBus.Properties',
746
+ 'PropertiesChanged',
747
+ params,
748
+ );
749
+ }
750
+ },
751
+
752
+ flush() {
753
+ for (const { connection } of registrations) connection.flush(null);
754
+ },
755
+ };
756
+ // camelCase aliases (GJS's DBusImplementation exposes snake_case; keep both
757
+ // so either spelling works, matching the rest of the node-gi surface).
758
+ impl.getInfo = impl.get_info;
759
+ impl.getObjectPath = impl.get_object_path;
760
+ impl.getConnection = impl.get_connection;
761
+ impl.unexportFromConnection = impl.unexport_from_connection;
762
+ impl.emitSignal = impl.emit_signal;
763
+ impl.emitPropertyChanged = impl.emit_property_changed;
764
+ return impl;
765
+ }
728
766
 
729
- flush() {
730
- for (const { connection } of registrations) connection.flush(null);
731
- },
732
- };
733
- // camelCase aliases (GJS's DBusImplementation exposes snake_case; keep both
734
- // so either spelling works, matching the rest of the node-gi surface).
735
- impl.getInfo = impl.get_info;
736
- impl.getObjectPath = impl.get_object_path;
737
- impl.getConnection = impl.get_connection;
738
- impl.unexportFromConnection = impl.unexport_from_connection;
739
- impl.emitSignal = impl.emit_signal;
740
- impl.emitPropertyChanged = impl.emit_property_changed;
741
- return impl;
742
- }
743
-
744
- const DBusExportedObject = { wrapJSObject };
745
-
746
- return { DBus, DBusProxy, DBusExportedObject };
767
+ const DBusExportedObject = { wrapJSObject };
768
+
769
+ return { DBus, DBusProxy, DBusExportedObject };
747
770
  }
748
771
 
749
772
  export default createGioDBus;