@gjsify/node-gi 0.26.1 → 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.
package/gi.js CHANGED
@@ -54,11 +54,14 @@ const RESERVED = new Set(['then', 'toString', 'valueOf', 'constructor', 'inspect
54
54
  // accessor to the GI method name (snake_case) and to a GObject property name
55
55
  // (kebab-case); a name that is already in the target case passes through.
56
56
  function camelToSnake(name) {
57
- return name.replace(/([A-Z])/g, '_$1').toLowerCase();
57
+ return name.replace(/([A-Z])/g, '_$1').toLowerCase();
58
58
  }
59
59
 
60
60
  function toKebab(name) {
61
- return name.replace(/([A-Z])/g, '-$1').replace(/_/g, '-').toLowerCase();
61
+ return name
62
+ .replace(/([A-Z])/g, '-$1')
63
+ .replace(/_/g, '-')
64
+ .toLowerCase();
62
65
  }
63
66
 
64
67
  // Per-user-function shim cache so repeated passes of the same callback marshal to
@@ -76,24 +79,24 @@ const callbackShims = new WeakMap();
76
79
  // engine as the raw variant handle).
77
80
  // Primitives and plain objects pass through both directions untouched.
78
81
  function wrapCallbackFn(fn) {
79
- let shim = callbackShims.get(fn);
80
- if (shim === undefined) {
81
- shim = (...args) => unwrapArg(fn(...args.map(wrapReturn)));
82
- callbackShims.set(fn, shim);
83
- }
84
- return shim;
82
+ let shim = callbackShims.get(fn);
83
+ if (shim === undefined) {
84
+ shim = (...args) => unwrapArg(fn(...args.map(wrapReturn)));
85
+ callbackShims.set(fn, shim);
86
+ }
87
+ return shim;
85
88
  }
86
89
 
87
90
  function unwrapArg(value) {
88
- if (value !== null && typeof value === 'object' && value[HANDLE] !== undefined) {
89
- return value[HANDLE];
90
- }
91
- if (typeof value === 'function') return wrapCallbackFn(value);
92
- return value;
91
+ if (value !== null && typeof value === 'object' && value[HANDLE] !== undefined) {
92
+ return value[HANDLE];
93
+ }
94
+ if (typeof value === 'function') return wrapCallbackFn(value);
95
+ return value;
93
96
  }
94
97
 
95
98
  function unwrapArgs(args) {
96
- return args.map(unwrapArg);
99
+ return args.map(unwrapArg);
97
100
  }
98
101
 
99
102
  // Normalize a construct-property dict: each KEY to the GObject canonical (kebab)
@@ -110,9 +113,9 @@ function unwrapArgs(args) {
110
113
  // `new Ns.Class({...})`, and constructType for a registerClass'd subclass plus its
111
114
  // `super({...})` chain-up — so all three accept camelCase identically to GJS.
112
115
  function unwrapProps(props) {
113
- const out = {};
114
- for (const key of Object.keys(props)) out[toKebab(key)] = unwrapArg(props[key]);
115
- return out;
116
+ const out = {};
117
+ for (const key of Object.keys(props)) out[toKebab(key)] = unwrapArg(props[key]);
118
+ return out;
116
119
  }
117
120
 
118
121
  // Symbol carrying an enum object's GError-domain descriptor ({ name, quark }) so
@@ -138,48 +141,48 @@ const ERROR_DOMAIN = Symbol('nodeGiErrorDomain');
138
141
 
139
142
  // Resolve a `matches` domain argument to a { name?, quark? } descriptor.
140
143
  function errorDomainOf(domain) {
141
- if (domain === null || domain === undefined) return null;
142
- if (typeof domain === 'object' && domain[ERROR_DOMAIN] !== undefined) return domain[ERROR_DOMAIN];
143
- if (typeof domain === 'string') return { name: domain };
144
- if (typeof domain === 'number') return { quark: domain };
145
- return null;
144
+ if (domain === null || domain === undefined) return null;
145
+ if (typeof domain === 'object' && domain[ERROR_DOMAIN] !== undefined) return domain[ERROR_DOMAIN];
146
+ if (typeof domain === 'string') return { name: domain };
147
+ if (typeof domain === 'number') return { quark: domain };
148
+ return null;
146
149
  }
147
150
 
148
151
  class GLibError extends Error {
149
- // GJS-shaped public constructor: `new GLib.Error(domain, code, message)` where
150
- // `domain` may be an error-enum object, a quark name string, or a numeric quark.
151
- constructor(domain, code, message) {
152
- super(typeof message === 'string' ? message : '');
153
- this.name = 'GLib.Error';
154
- const d = errorDomainOf(domain);
155
- this.domain = d !== null && d.name !== undefined ? d.name : typeof domain === 'string' ? domain : undefined;
156
- this.domainQuark = d !== null && d.quark !== undefined ? d.quark : typeof domain === 'number' ? domain : undefined;
157
- this.code = code;
158
- }
159
-
160
- // g_error_matches: true when the error's domain + code both match. Accepts the
161
- // domain as an error-enum object / name string / numeric quark (errorDomainOf).
162
- matches(domain, code) {
163
- const d = errorDomainOf(domain);
164
- if (d === null) return false;
165
- const domainMatch =
166
- (d.name !== undefined && d.name === this.domain) ||
167
- (d.quark !== undefined && d.quark === this.domainQuark);
168
- return domainMatch && code === this.code;
169
- }
170
-
171
- toString() {
172
- return `GLib.Error ${this.domain}: ${this.message}`;
173
- }
152
+ // GJS-shaped public constructor: `new GLib.Error(domain, code, message)` where
153
+ // `domain` may be an error-enum object, a quark name string, or a numeric quark.
154
+ constructor(domain, code, message) {
155
+ super(typeof message === 'string' ? message : '');
156
+ this.name = 'GLib.Error';
157
+ const d = errorDomainOf(domain);
158
+ this.domain = d !== null && d.name !== undefined ? d.name : typeof domain === 'string' ? domain : undefined;
159
+ this.domainQuark =
160
+ d !== null && d.quark !== undefined ? d.quark : typeof domain === 'number' ? domain : undefined;
161
+ this.code = code;
162
+ }
163
+
164
+ // g_error_matches: true when the error's domain + code both match. Accepts the
165
+ // domain as an error-enum object / name string / numeric quark (errorDomainOf).
166
+ matches(domain, code) {
167
+ const d = errorDomainOf(domain);
168
+ if (d === null) return false;
169
+ const domainMatch =
170
+ (d.name !== undefined && d.name === this.domain) || (d.quark !== undefined && d.quark === this.domainQuark);
171
+ return domainMatch && code === this.code;
172
+ }
173
+
174
+ toString() {
175
+ return `GLib.Error ${this.domain}: ${this.message}`;
176
+ }
174
177
  }
175
178
 
176
179
  // The engine calls this on a failed GI invoke with the GError's authoritative
177
180
  // fields (quark name + numeric quark + code + message) to build the thrown error.
178
181
  function buildGError(domainName, domainQuark, code, message) {
179
- const error = new GLibError(domainName, code, message);
180
- error.domain = domainName;
181
- error.domainQuark = domainQuark;
182
- return error;
182
+ const error = new GLibError(domainName, code, message);
183
+ error.domain = domainName;
184
+ error.domainQuark = domainQuark;
185
+ return error;
183
186
  }
184
187
  native.setErrorBuilder(buildGError);
185
188
 
@@ -201,25 +204,25 @@ const promisifiedMethods = new Map();
201
204
  // handles (e.g. GMainLoop) become method-routing proxies; everything else
202
205
  // (primitives, strings, null) passes through.
203
206
  function wrapReturn(value) {
204
- if (native.isGObjectHandle(value)) return wrapInstance(value);
205
- // A GLib.Variant is ALSO a boxed handle (by tag), so it must be checked first
206
- // to give it the Variant ergonomics rather than a plain method-routing proxy.
207
- if (native.isVariantHandle(value)) return wrapVariant(value);
208
- // A GParamSpec is a distinct GObject fundamental (tagged separately from boxed),
209
- // surfaced with the GObject.ParamSpec ergonomics (name/value_type/get_name()/…).
210
- if (native.isParamSpecHandle(value)) return wrapParamSpec(value);
211
- // A non-GObject GObject-fundamental (e.g. a GskRenderNode from Gtk.Snapshot.to_node):
212
- // an opaque, round-trippable pass-through handle (see wrapFundamental).
213
- if (native.isFundamentalHandle(value)) return wrapFundamental(value);
214
- if (native.isBoxedHandle(value)) return wrapBoxed(value);
215
- // A multi-value OUT tuple (return value + OUT params) — or a container OUT — is a
216
- // plain JS Array whose ELEMENTS may themselves be GObject/boxed/variant handles
217
- // (e.g. GLib.Regex.match → [matched, GLib.MatchInfo]; an array-of-objects OUT).
218
- // Recurse so a handle sitting inside the tuple is wrapped into a usable proxy,
219
- // not left as a raw External. A Node Buffer (byte array) is NOT Array.isArray, so
220
- // it passes through untouched; primitives/strings map to themselves.
221
- if (Array.isArray(value)) return value.map(wrapReturn);
222
- return value;
207
+ if (native.isGObjectHandle(value)) return wrapInstance(value);
208
+ // A GLib.Variant is ALSO a boxed handle (by tag), so it must be checked first
209
+ // to give it the Variant ergonomics rather than a plain method-routing proxy.
210
+ if (native.isVariantHandle(value)) return wrapVariant(value);
211
+ // A GParamSpec is a distinct GObject fundamental (tagged separately from boxed),
212
+ // surfaced with the GObject.ParamSpec ergonomics (name/value_type/get_name()/…).
213
+ if (native.isParamSpecHandle(value)) return wrapParamSpec(value);
214
+ // A non-GObject GObject-fundamental (e.g. a GskRenderNode from Gtk.Snapshot.to_node):
215
+ // an opaque, round-trippable pass-through handle (see wrapFundamental).
216
+ if (native.isFundamentalHandle(value)) return wrapFundamental(value);
217
+ if (native.isBoxedHandle(value)) return wrapBoxed(value);
218
+ // A multi-value OUT tuple (return value + OUT params) — or a container OUT — is a
219
+ // plain JS Array whose ELEMENTS may themselves be GObject/boxed/variant handles
220
+ // (e.g. GLib.Regex.match → [matched, GLib.MatchInfo]; an array-of-objects OUT).
221
+ // Recurse so a handle sitting inside the tuple is wrapped into a usable proxy,
222
+ // not left as a raw External. A Node Buffer (byte array) is NOT Array.isArray, so
223
+ // it passes through untouched; primitives/strings map to themselves.
224
+ if (Array.isArray(value)) return value.map(wrapReturn);
225
+ return value;
223
226
  }
224
227
 
225
228
  // Wrap a user's signal callback so each native signal argument is passed through
@@ -231,7 +234,7 @@ function wrapReturn(value) {
231
234
  // arg, so wrapReturn turns it into the cached, toggle-ref-canonical instance. For
232
235
  // `notify::x` the args are (object, pspec).
233
236
  function wrapSignalCallback(cb) {
234
- return (...args) => cb(...args.map(wrapReturn));
237
+ return (...args) => cb(...args.map(wrapReturn));
235
238
  }
236
239
 
237
240
  // Resolve a Gtk.Template `<signal handler="…">` handler NAME to a dispatcher that
@@ -252,17 +255,17 @@ function wrapSignalCallback(cb) {
252
255
  // handler name with no matching user-prototype method throws a clear error when
253
256
  // the signal fires.
254
257
  function resolveTemplateCallback(handle, handlerName) {
255
- return (...args) => {
256
- const proxy = wrapInstance(handle);
257
- const userProto = proxy[USER_PROTO];
258
- const desc = userProto !== undefined ? findProtoDescriptor(userProto, handlerName) : undefined;
259
- if (desc === undefined || typeof desc.value !== 'function') {
260
- throw new Error(
261
- `Gtk.Template: signal handler '${handlerName}' is not defined on ${proxy.constructor?.name ?? 'the instance'}`,
262
- );
263
- }
264
- return desc.value.apply(proxy, args.map(wrapReturn));
265
- };
258
+ return (...args) => {
259
+ const proxy = wrapInstance(handle);
260
+ const userProto = proxy[USER_PROTO];
261
+ const desc = userProto !== undefined ? findProtoDescriptor(userProto, handlerName) : undefined;
262
+ if (desc === undefined || typeof desc.value !== 'function') {
263
+ throw new Error(
264
+ `Gtk.Template: signal handler '${handlerName}' is not defined on ${proxy.constructor?.name ?? 'the instance'}`,
265
+ );
266
+ }
267
+ return desc.value.apply(proxy, args.map(wrapReturn));
268
+ };
266
269
  }
267
270
  native.setTemplateCallbackResolver(resolveTemplateCallback);
268
271
 
@@ -277,17 +280,17 @@ native.setTemplateCallbackResolver(resolveTemplateCallback);
277
280
  // one wrapper GtkBuilder-fetched children later resolve to). GJS-faithful
278
281
  // (gjs_object_constructor's native-construction branch runs the JS constructor).
279
282
  function runCtorForCObject(handle, gtypeName) {
280
- const cls = classesByGType.get(gtypeName);
281
- if (cls === undefined) return; // not a node-gi-registered class (shouldn't happen)
282
- const prev = adoptHandle;
283
- adoptHandle = handle;
284
- try {
285
- // No props: GtkBuilder already applied the construct properties to the C object;
286
- // the ctor's `params` arg is conventionally optional for a template child.
287
- Reflect.construct(cls, []);
288
- } finally {
289
- adoptHandle = prev;
290
- }
283
+ const cls = classesByGType.get(gtypeName);
284
+ if (cls === undefined) return; // not a node-gi-registered class (shouldn't happen)
285
+ const prev = adoptHandle;
286
+ adoptHandle = handle;
287
+ try {
288
+ // No props: GtkBuilder already applied the construct properties to the C object;
289
+ // the ctor's `params` arg is conventionally optional for a template child.
290
+ Reflect.construct(cls, []);
291
+ } finally {
292
+ adoptHandle = prev;
293
+ }
291
294
  }
292
295
  native.setConstructCallback(runCtorForCObject);
293
296
 
@@ -301,7 +304,7 @@ native.setConstructCallback(runCtorForCObject);
301
304
  // documented follow-up; today they are pass-through intermediates (build → hand to
302
305
  // a consumer → drop), which is what the GSK screenshot/render path needs.
303
306
  function wrapFundamental(handle) {
304
- return { [HANDLE]: handle, [Symbol.toStringTag]: 'GIFundamental' };
307
+ return { [HANDLE]: handle, [Symbol.toStringTag]: 'GIFundamental' };
305
308
  }
306
309
 
307
310
  // Wrap a boxed/struct/union handle so its methods are callable GJS-style
@@ -314,54 +317,56 @@ function wrapFundamental(handle) {
314
317
  // setBoxedField), 0 = neither (fall back to a method dispatcher, so an unknown
315
318
  // name still throws a clear "no method" at call time — the pre-fields behaviour).
316
319
  function wrapBoxed(handle) {
317
- const target = { [HANDLE]: handle };
318
- const methodDispatch = (prop) => (...args) =>
319
- wrapReturn(native.callBoxedMethod(handle, camelToSnake(prop), unwrapArgs(args)));
320
- return new Proxy(target, {
321
- get(t, prop) {
322
- if (prop === HANDLE) return handle;
323
- if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
324
- // A field set through the set trap below is stored as an expando on the
325
- // target (gjs allows writing a boxed field that GI declares unwritable to a
326
- // JS expando); surface it back before consulting introspection.
327
- if (Object.hasOwn(t, prop)) return t[prop];
328
- // GBytes convenience: `.toArray()` → the raw bytes as a Uint8Array (Node
329
- // Buffer). GBytes has no introspected `to_array`; gjs adds it to
330
- // GLib.Bytes.prototype (imports._byteArrayNative.fromGBytes), so mirror it
331
- // via the introspected get_data(). Gated on the boxed type so it never
332
- // shadows a same-named method on some other struct.
333
- if (prop === 'toArray' && native.boxedTypeName(handle) === 'GBytes') {
334
- return () => wrapReturn(native.callBoxedMethod(handle, 'get_data', []));
335
- }
336
- const snake = camelToSnake(prop);
337
- // boxedMemberKind: 0 = not a member (type info resolved) → `undefined`, so
338
- // `typeof boxed.noSuchName === 'undefined'` matches gjs (a fabricated
339
- // dispatcher would read `'function'` and break JS duck-typing such as
340
- // `if (typeof value.toArray === 'function')`); 1 = method; 2 = field; 3 =
341
- // undecidable (unregistered struct, no static info) → keep the dispatcher
342
- // fallback so a genuine method still resolves + throws a clear error at call.
343
- const kind = native.boxedMemberKind(handle, snake);
344
- if (kind === 2) return wrapReturn(native.getBoxedField(handle, snake));
345
- if (kind === 0) return undefined;
346
- return methodDispatch(prop);
347
- },
348
- set(t, prop, value) {
349
- if (prop === HANDLE || typeof prop !== 'string' || RESERVED.has(prop)) {
350
- return Reflect.set(t, prop, value);
351
- }
352
- const snake = camelToSnake(prop);
353
- if (native.boxedMemberKind(handle, snake) === 2) {
354
- native.setBoxedField(handle, snake, unwrapArg(value));
355
- return true;
356
- }
357
- return Reflect.set(t, prop, value);
358
- },
359
- has(t, prop) {
360
- if (prop === HANDLE || prop in t) return true;
361
- if (typeof prop !== 'string') return false;
362
- return native.boxedMemberKind(handle, camelToSnake(prop)) !== 0;
363
- },
364
- });
320
+ const target = { [HANDLE]: handle };
321
+ const methodDispatch =
322
+ (prop) =>
323
+ (...args) =>
324
+ wrapReturn(native.callBoxedMethod(handle, camelToSnake(prop), unwrapArgs(args)));
325
+ return new Proxy(target, {
326
+ get(t, prop) {
327
+ if (prop === HANDLE) return handle;
328
+ if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
329
+ // A field set through the set trap below is stored as an expando on the
330
+ // target (gjs allows writing a boxed field that GI declares unwritable to a
331
+ // JS expando); surface it back before consulting introspection.
332
+ if (Object.hasOwn(t, prop)) return t[prop];
333
+ // GBytes convenience: `.toArray()` → the raw bytes as a Uint8Array (Node
334
+ // Buffer). GBytes has no introspected `to_array`; gjs adds it to
335
+ // GLib.Bytes.prototype (imports._byteArrayNative.fromGBytes), so mirror it
336
+ // via the introspected get_data(). Gated on the boxed type so it never
337
+ // shadows a same-named method on some other struct.
338
+ if (prop === 'toArray' && native.boxedTypeName(handle) === 'GBytes') {
339
+ return () => wrapReturn(native.callBoxedMethod(handle, 'get_data', []));
340
+ }
341
+ const snake = camelToSnake(prop);
342
+ // boxedMemberKind: 0 = not a member (type info resolved) → `undefined`, so
343
+ // `typeof boxed.noSuchName === 'undefined'` matches gjs (a fabricated
344
+ // dispatcher would read `'function'` and break JS duck-typing such as
345
+ // `if (typeof value.toArray === 'function')`); 1 = method; 2 = field; 3 =
346
+ // undecidable (unregistered struct, no static info) → keep the dispatcher
347
+ // fallback so a genuine method still resolves + throws a clear error at call.
348
+ const kind = native.boxedMemberKind(handle, snake);
349
+ if (kind === 2) return wrapReturn(native.getBoxedField(handle, snake));
350
+ if (kind === 0) return undefined;
351
+ return methodDispatch(prop);
352
+ },
353
+ set(t, prop, value) {
354
+ if (prop === HANDLE || typeof prop !== 'string' || RESERVED.has(prop)) {
355
+ return Reflect.set(t, prop, value);
356
+ }
357
+ const snake = camelToSnake(prop);
358
+ if (native.boxedMemberKind(handle, snake) === 2) {
359
+ native.setBoxedField(handle, snake, unwrapArg(value));
360
+ return true;
361
+ }
362
+ return Reflect.set(t, prop, value);
363
+ },
364
+ has(t, prop) {
365
+ if (prop === HANDLE || prop in t) return true;
366
+ if (typeof prop !== 'string') return false;
367
+ return native.boxedMemberKind(handle, camelToSnake(prop)) !== 0;
368
+ },
369
+ });
365
370
  }
366
371
 
367
372
  // Wrap a GParamSpec handle (phase 2.7a) with the GJS-shaped GObject.ParamSpec
@@ -374,36 +379,36 @@ function wrapBoxed(handle) {
374
379
  // value_type/owner_type are native GType handles (like gjs's GType objects); the
375
380
  // GTypes' `.name` reads their type name via the introspected surface.
376
381
  function wrapParamSpec(handle) {
377
- const prop = (which) => native.paramSpecProp(handle, which);
378
- const pspec = {
379
- [HANDLE]: handle,
380
- get name() {
381
- return prop('name');
382
- },
383
- get nick() {
384
- return prop('nick');
385
- },
386
- get blurb() {
387
- return prop('blurb');
388
- },
389
- get flags() {
390
- return prop('flags');
391
- },
392
- get value_type() {
393
- return prop('valueType');
394
- },
395
- get owner_type() {
396
- return prop('ownerType');
397
- },
398
- get default_value() {
399
- return wrapReturn(prop('defaultValue'));
400
- },
401
- get_name: () => prop('name'),
402
- get_nick: () => prop('nick'),
403
- get_blurb: () => prop('blurb'),
404
- get_default_value: () => wrapReturn(prop('defaultValue')),
405
- };
406
- return pspec;
382
+ const prop = (which) => native.paramSpecProp(handle, which);
383
+ const pspec = {
384
+ [HANDLE]: handle,
385
+ get name() {
386
+ return prop('name');
387
+ },
388
+ get nick() {
389
+ return prop('nick');
390
+ },
391
+ get blurb() {
392
+ return prop('blurb');
393
+ },
394
+ get flags() {
395
+ return prop('flags');
396
+ },
397
+ get value_type() {
398
+ return prop('valueType');
399
+ },
400
+ get owner_type() {
401
+ return prop('ownerType');
402
+ },
403
+ get default_value() {
404
+ return wrapReturn(prop('defaultValue'));
405
+ },
406
+ get_name: () => prop('name'),
407
+ get_nick: () => prop('nick'),
408
+ get_blurb: () => prop('blurb'),
409
+ get_default_value: () => wrapReturn(prop('defaultValue')),
410
+ };
411
+ return pspec;
407
412
  }
408
413
 
409
414
  // ---- GLib.Variant ergonomics (the GJS GLib.Variant override, L1) ----
@@ -424,42 +429,41 @@ function wrapParamSpec(handle) {
424
429
  // unpacker left in place (an `a{sv}` value under deepUnpack, every child under
425
430
  // unpack(), …) becomes a GLib.Variant wrapper; arrays/plain dicts are walked.
426
431
  function wrapVariantResult(value) {
427
- if (value === null || typeof value !== 'object') return value;
428
- if (native.isVariantHandle(value)) return wrapVariant(value);
429
- if (value instanceof Uint8Array) return value; // `ay` bytes
430
- if (Array.isArray(value)) return value.map(wrapVariantResult);
431
- const out = {};
432
- for (const key of Object.keys(value)) out[key] = wrapVariantResult(value[key]);
433
- return out;
432
+ if (value === null || typeof value !== 'object') return value;
433
+ if (native.isVariantHandle(value)) return wrapVariant(value);
434
+ if (value instanceof Uint8Array) return value; // `ay` bytes
435
+ if (Array.isArray(value)) return value.map(wrapVariantResult);
436
+ const out = {};
437
+ for (const key of Object.keys(value)) out[key] = wrapVariantResult(value[key]);
438
+ return out;
434
439
  }
435
440
 
436
441
  // Wrap a boxed GLib.Variant handle with the GJS-shaped Variant surface. Carries
437
442
  // [HANDLE] so it round-trips back into the engine as a GVariant IN argument
438
443
  // (action.activate(variant), new_stateful state, change_state value, …).
439
444
  function wrapVariant(handle) {
440
- const target = { [HANDLE]: handle };
441
- const api = {
442
- unpack: () => wrapVariantResult(native.variantUnpack(handle, false, false)),
443
- deepUnpack: () => wrapVariantResult(native.variantUnpack(handle, true, false)),
444
- deep_unpack: () => wrapVariantResult(native.variantUnpack(handle, true, false)),
445
- recursiveUnpack: () => native.variantUnpack(handle, true, true),
446
- get_type_string: () => native.variantGetTypeString(handle),
447
- toString: () => `[object variant of type "${native.variantGetTypeString(handle)}"]`,
448
- };
449
- return new Proxy(target, {
450
- get(t, prop) {
451
- if (prop === HANDLE) return handle;
452
- if (typeof prop !== 'string') return t[prop];
453
- if (Object.hasOwn(api, prop)) return api[prop];
454
- if (RESERVED.has(prop)) return t[prop];
455
- // Any other GVariant method (n_children, get_child_value, print, …).
456
- return (...args) =>
457
- wrapReturn(native.callBoxedMethod(handle, camelToSnake(prop), unwrapArgs(args)));
458
- },
459
- has(t, prop) {
460
- return prop === HANDLE || (typeof prop === 'string' && Object.hasOwn(api, prop)) || prop in t;
461
- },
462
- });
445
+ const target = { [HANDLE]: handle };
446
+ const api = {
447
+ unpack: () => wrapVariantResult(native.variantUnpack(handle, false, false)),
448
+ deepUnpack: () => wrapVariantResult(native.variantUnpack(handle, true, false)),
449
+ deep_unpack: () => wrapVariantResult(native.variantUnpack(handle, true, false)),
450
+ recursiveUnpack: () => native.variantUnpack(handle, true, true),
451
+ get_type_string: () => native.variantGetTypeString(handle),
452
+ toString: () => `[object variant of type "${native.variantGetTypeString(handle)}"]`,
453
+ };
454
+ return new Proxy(target, {
455
+ get(t, prop) {
456
+ if (prop === HANDLE) return handle;
457
+ if (typeof prop !== 'string') return t[prop];
458
+ if (Object.hasOwn(api, prop)) return api[prop];
459
+ if (RESERVED.has(prop)) return t[prop];
460
+ // Any other GVariant method (n_children, get_child_value, print, …).
461
+ return (...args) => wrapReturn(native.callBoxedMethod(handle, camelToSnake(prop), unwrapArgs(args)));
462
+ },
463
+ has(t, prop) {
464
+ return prop === HANDLE || (typeof prop === 'string' && Object.hasOwn(api, prop)) || prop in t;
465
+ },
466
+ });
463
467
  }
464
468
 
465
469
  // Deep-unwrap a pack value so any nested GLib.Variant wrapper (carried at a `v`
@@ -468,52 +472,51 @@ function wrapVariant(handle) {
468
472
  // byte arrays, plain arrays and dict objects are walked; HANDLE-carrying
469
473
  // wrappers collapse to their handle.
470
474
  function unwrapVariantValue(value) {
471
- if (value === null || typeof value !== 'object') return value;
472
- if (value[HANDLE] !== undefined) return value[HANDLE];
473
- if (value instanceof Uint8Array) return value; // `ay` bytes
474
- if (Array.isArray(value)) return value.map(unwrapVariantValue);
475
- const out = {};
476
- for (const key of Object.keys(value)) out[key] = unwrapVariantValue(value[key]);
477
- return out;
475
+ if (value === null || typeof value !== 'object') return value;
476
+ if (value[HANDLE] !== undefined) return value[HANDLE];
477
+ if (value instanceof Uint8Array) return value; // `ay` bytes
478
+ if (Array.isArray(value)) return value.map(unwrapVariantValue);
479
+ const out = {};
480
+ for (const key of Object.keys(value)) out[key] = unwrapVariantValue(value[key]);
481
+ return out;
478
482
  }
479
483
 
480
484
  function packVariant(signature, value) {
481
- return wrapVariant(native.variantNew(signature, unwrapVariantValue(value)));
485
+ return wrapVariant(native.variantNew(signature, unwrapVariantValue(value)));
482
486
  }
483
487
 
484
488
  // `GLib.Variant` as a class-like object: `new GLib.Variant(sig, value)` and the
485
489
  // deprecated `GLib.Variant.new(sig, value)` both pack via the native engine.
486
490
  function makeVariantClass() {
487
- const ctor = function Variant(signature, value) {
488
- return packVariant(signature, value);
489
- };
490
- Object.defineProperty(ctor, 'name', { value: 'Variant', configurable: true });
491
- ctor.$gtypeName = 'GLib.Variant';
492
- ctor.new = (signature, value) => packVariant(signature, value);
493
- // `value instanceof GLib.Variant` — a wrapped Variant is a Proxy over a bare
494
- // `{[HANDLE]}` target (no class prototype), so the default instanceof always
495
- // returned false. Recognise any wrapper whose [HANDLE] is a native GVariant
496
- // boxed handle. Real GAction/GSettings/GLib.log_structured code branches on it.
497
- Object.defineProperty(ctor, Symbol.hasInstance, {
498
- value(instance) {
499
- if (instance === null || typeof instance !== 'object') return false;
500
- const handle = instance[HANDLE];
501
- return handle !== undefined && native.isVariantHandle(handle);
502
- },
503
- configurable: true,
504
- });
505
- return new Proxy(ctor, {
506
- get(t, prop) {
507
- if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
508
- // Other static constructors (e.g. new_from_bytes) route through the engine.
509
- const giName = camelToSnake(prop);
510
- return (...args) =>
511
- wrapReturn(native.callStaticMethod('GLib', 'Variant', giName, unwrapArgs(args)));
512
- },
513
- construct(_t, args) {
514
- return packVariant(args[0], args[1]);
515
- },
516
- });
491
+ const ctor = function Variant(signature, value) {
492
+ return packVariant(signature, value);
493
+ };
494
+ Object.defineProperty(ctor, 'name', { value: 'Variant', configurable: true });
495
+ ctor.$gtypeName = 'GLib.Variant';
496
+ ctor.new = (signature, value) => packVariant(signature, value);
497
+ // `value instanceof GLib.Variant` — a wrapped Variant is a Proxy over a bare
498
+ // `{[HANDLE]}` target (no class prototype), so the default instanceof always
499
+ // returned false. Recognise any wrapper whose [HANDLE] is a native GVariant
500
+ // boxed handle. Real GAction/GSettings/GLib.log_structured code branches on it.
501
+ Object.defineProperty(ctor, Symbol.hasInstance, {
502
+ value(instance) {
503
+ if (instance === null || typeof instance !== 'object') return false;
504
+ const handle = instance[HANDLE];
505
+ return handle !== undefined && native.isVariantHandle(handle);
506
+ },
507
+ configurable: true,
508
+ });
509
+ return new Proxy(ctor, {
510
+ get(t, prop) {
511
+ if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
512
+ // Other static constructors (e.g. new_from_bytes) route through the engine.
513
+ const giName = camelToSnake(prop);
514
+ return (...args) => wrapReturn(native.callStaticMethod('GLib', 'Variant', giName, unwrapArgs(args)));
515
+ },
516
+ construct(_t, args) {
517
+ return packVariant(args[0], args[1]);
518
+ },
519
+ });
517
520
  }
518
521
 
519
522
  const variantClass = makeVariantClass();
@@ -534,55 +537,55 @@ const variantClass = makeVariantClass();
534
537
  // convenience. Non-primitive types (enum/flags/boxed/object/param/variant) are
535
538
  // matched by g_type_is_a below, exactly like gjs's default switch branch.
536
539
  const GVALUE_PRIMITIVE_SETTERS = {
537
- gboolean: 'set_boolean',
538
- gchar: 'set_schar',
539
- guchar: 'set_uchar',
540
- gint: 'set_int',
541
- guint: 'set_uint',
542
- glong: 'set_long',
543
- gulong: 'set_ulong',
544
- gint64: 'set_int64',
545
- guint64: 'set_uint64',
546
- gfloat: 'set_float',
547
- gdouble: 'set_double',
548
- gchararray: 'set_string',
549
- GType: 'set_gtype',
550
- GVariant: 'set_variant',
551
- GParam: 'set_param',
540
+ gboolean: 'set_boolean',
541
+ gchar: 'set_schar',
542
+ guchar: 'set_uchar',
543
+ gint: 'set_int',
544
+ guint: 'set_uint',
545
+ glong: 'set_long',
546
+ gulong: 'set_ulong',
547
+ gint64: 'set_int64',
548
+ guint64: 'set_uint64',
549
+ gfloat: 'set_float',
550
+ gdouble: 'set_double',
551
+ gchararray: 'set_string',
552
+ GType: 'set_gtype',
553
+ GVariant: 'set_variant',
554
+ GParam: 'set_param',
552
555
  };
553
556
 
554
557
  // Pick the GValue setter for a (resolved) GType handle, mirroring gjs's constructor
555
558
  // switch: an exact fundamental → its setter, else g_type_is_a → flags/enum/boxed/…
556
559
  // (introspected GObject.type_name / type_is_a — both verified on node-gi).
557
560
  function gvalueSetterFor(gt) {
558
- const G = requireGi('GObject', '2.0');
559
- const prim = GVALUE_PRIMITIVE_SETTERS[G.type_name(gt)];
560
- if (prim !== undefined) return prim;
561
- if (G.type_is_a(gt, G.TYPE_FLAGS)) return 'set_flags';
562
- if (G.type_is_a(gt, G.TYPE_ENUM)) return 'set_enum';
563
- if (G.type_is_a(gt, G.TYPE_VARIANT)) return 'set_variant';
564
- if (G.type_is_a(gt, G.TYPE_PARAM)) return 'set_param';
565
- if (G.type_is_a(gt, G.TYPE_BOXED)) return 'set_boxed';
566
- if (G.type_is_a(gt, G.TYPE_OBJECT)) return 'set_object';
567
- throw new TypeError(`Invalid type argument '${G.type_name(gt)}' to the GObject.Value constructor`);
561
+ const G = requireGi('GObject', '2.0');
562
+ const prim = GVALUE_PRIMITIVE_SETTERS[G.type_name(gt)];
563
+ if (prim !== undefined) return prim;
564
+ if (G.type_is_a(gt, G.TYPE_FLAGS)) return 'set_flags';
565
+ if (G.type_is_a(gt, G.TYPE_ENUM)) return 'set_enum';
566
+ if (G.type_is_a(gt, G.TYPE_VARIANT)) return 'set_variant';
567
+ if (G.type_is_a(gt, G.TYPE_PARAM)) return 'set_param';
568
+ if (G.type_is_a(gt, G.TYPE_BOXED)) return 'set_boxed';
569
+ if (G.type_is_a(gt, G.TYPE_OBJECT)) return 'set_object';
570
+ throw new TypeError(`Invalid type argument '${G.type_name(gt)}' to the GObject.Value constructor`);
568
571
  }
569
572
 
570
573
  // Build the empty/convenience GValue: a zeroed GValue boxed handle wrapped with the
571
574
  // GValue struct methods; the 2-arg form inits it to `gtype` (a class ctor's $gtype
572
575
  // or a GType handle) and sets `value` via the matching setter.
573
576
  function buildValue(args) {
574
- const v = wrapBoxed(native.newGValue());
575
- if (args.length >= 2) {
576
- const gt = resolveGTypeArg(args[0]);
577
- // Resolve the setter BEFORE init so an unsupported type throws a clean JS
578
- // TypeError rather than driving g_value_init with a value-table-less type (a
579
- // GLib-CRITICAL that would abort under G_DEBUG=fatal-criticals) — same net
580
- // result as gjs's constructor, minus the noisy failed init.
581
- const setter = gvalueSetterFor(gt);
582
- v.init(gt);
583
- v[setter](args[1]);
584
- }
585
- return v;
577
+ const v = wrapBoxed(native.newGValue());
578
+ if (args.length >= 2) {
579
+ const gt = resolveGTypeArg(args[0]);
580
+ // Resolve the setter BEFORE init so an unsupported type throws a clean JS
581
+ // TypeError rather than driving g_value_init with a value-table-less type (a
582
+ // GLib-CRITICAL that would abort under G_DEBUG=fatal-criticals) — same net
583
+ // result as gjs's constructor, minus the noisy failed init.
584
+ const setter = gvalueSetterFor(gt);
585
+ v.init(gt);
586
+ v[setter](args[1]);
587
+ }
588
+ return v;
586
589
  }
587
590
 
588
591
  // `GObject.Value` as a class-like object: `new GObject.Value()` /
@@ -590,34 +593,29 @@ function buildValue(args) {
590
593
  // (type_compatible / type_transformable + camelCase) route through the engine, and
591
594
  // `value instanceof GObject.Value` recognises any wrapper over a GValue boxed handle.
592
595
  function makeValueClass() {
593
- const ctor = function Value(...args) {
594
- return buildValue(args);
595
- };
596
- Object.defineProperty(ctor, 'name', { value: 'Value', configurable: true });
597
- ctor.$gtypeName = 'GObject.Value';
598
- Object.defineProperty(ctor, Symbol.hasInstance, {
599
- value(instance) {
600
- if (instance === null || typeof instance !== 'object') return false;
601
- const handle = instance[HANDLE];
602
- return (
603
- handle !== undefined &&
604
- native.isBoxedHandle(handle) &&
605
- native.boxedTypeName(handle) === 'GValue'
606
- );
607
- },
608
- configurable: true,
609
- });
610
- return new Proxy(ctor, {
611
- get(t, prop) {
612
- if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
613
- const giName = camelToSnake(prop);
614
- return (...args) =>
615
- wrapReturn(native.callStaticMethod('GObject', 'Value', giName, unwrapArgs(args)));
616
- },
617
- construct(_t, args) {
618
- return buildValue(args);
619
- },
620
- });
596
+ const ctor = function Value(...args) {
597
+ return buildValue(args);
598
+ };
599
+ Object.defineProperty(ctor, 'name', { value: 'Value', configurable: true });
600
+ ctor.$gtypeName = 'GObject.Value';
601
+ Object.defineProperty(ctor, Symbol.hasInstance, {
602
+ value(instance) {
603
+ if (instance === null || typeof instance !== 'object') return false;
604
+ const handle = instance[HANDLE];
605
+ return handle !== undefined && native.isBoxedHandle(handle) && native.boxedTypeName(handle) === 'GValue';
606
+ },
607
+ configurable: true,
608
+ });
609
+ return new Proxy(ctor, {
610
+ get(t, prop) {
611
+ if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
612
+ const giName = camelToSnake(prop);
613
+ return (...args) => wrapReturn(native.callStaticMethod('GObject', 'Value', giName, unwrapArgs(args)));
614
+ },
615
+ construct(_t, args) {
616
+ return buildValue(args);
617
+ },
618
+ });
621
619
  }
622
620
 
623
621
  const valueClass = makeValueClass();
@@ -636,12 +634,12 @@ const valueClass = makeValueClass();
636
634
  // of the field bytes (or null for empty fields) — byte-for-byte the shape gjs's
637
635
  // `{...stringFields.recursiveUnpack()}` produces (verified vs gjs 1.88).
638
636
  const GLIB_OVERLAY_NAMES = new Set([
639
- 'log_structured',
640
- 'idle_add_once',
641
- 'timeout_add_once',
642
- 'timeout_add_seconds_once',
643
- 'log_set_writer_func',
644
- 'log_set_writer_default',
637
+ 'log_structured',
638
+ 'idle_add_once',
639
+ 'timeout_add_once',
640
+ 'timeout_add_seconds_once',
641
+ 'log_set_writer_func',
642
+ 'log_set_writer_default',
645
643
  ]);
646
644
 
647
645
  // GLib.log_structured(domain, level, fields): pack `fields` into an `a{sv}` and hand
@@ -649,86 +647,86 @@ const GLIB_OVERLAY_NAMES = new Set([
649
647
  // log_structured. Each field value is `ay` (Uint8Array), `s` (string) or a Variant
650
648
  // passed through; anything else is a TypeError, matching gjs.
651
649
  function makeLogStructured(baseNs) {
652
- return (logDomain, logLevel, fields) => {
653
- const variantFields = {};
654
- for (const key of Object.keys(fields)) {
655
- const field = fields[key];
656
- if (field instanceof Uint8Array) variantFields[key] = variantClass('ay', field);
657
- else if (typeof field === 'string') variantFields[key] = variantClass('s', field);
658
- else if (field instanceof variantClass) variantFields[key] = field;
659
- else {
660
- throw new TypeError(
661
- `Unsupported value ${field}, log_structured supports GLib.Variant, Uint8Array, and string values.`,
662
- );
663
- }
664
- }
665
- baseNs.log_variant(logDomain, logLevel, variantClass('a{sv}', variantFields));
666
- };
650
+ return (logDomain, logLevel, fields) => {
651
+ const variantFields = {};
652
+ for (const key of Object.keys(fields)) {
653
+ const field = fields[key];
654
+ if (field instanceof Uint8Array) variantFields[key] = variantClass('ay', field);
655
+ else if (typeof field === 'string') variantFields[key] = variantClass('s', field);
656
+ else if (field instanceof variantClass) variantFields[key] = field;
657
+ else {
658
+ throw new TypeError(
659
+ `Unsupported value ${field}, log_structured supports GLib.Variant, Uint8Array, and string values.`,
660
+ );
661
+ }
662
+ }
663
+ baseNs.log_variant(logDomain, logLevel, variantClass('a{sv}', variantFields));
664
+ };
667
665
  }
668
666
 
669
667
  function decorateGLibNamespace(baseNs) {
670
- // Per-namespace cache of the overlay functions that need the introspected baseNs
671
- // (log_structured → log_variant; the *_once helpers → idle_add/timeout_add).
672
- const cache = new Map();
673
- const overlay = (prop) => {
674
- if (cache.has(prop)) return cache.get(prop);
675
- let value;
676
- if (prop === 'log_structured') value = makeLogStructured(baseNs);
677
- // The one-shot idle/timeout conveniences (gjs GLib.js): auto-remove the source
678
- // after the callback runs, so the callback needs no `return SOURCE_REMOVE`.
679
- else if (prop === 'idle_add_once') {
680
- value = (priority, callback) =>
681
- baseNs.idle_add(priority, () => {
682
- callback();
683
- return baseNs.SOURCE_REMOVE;
684
- });
685
- } else if (prop === 'timeout_add_once') {
686
- value = (priority, interval, callback) =>
687
- baseNs.timeout_add(priority, interval, () => {
688
- callback();
689
- return baseNs.SOURCE_REMOVE;
690
- });
691
- } else if (prop === 'timeout_add_seconds_once') {
692
- value = (priority, interval, callback) =>
693
- baseNs.timeout_add_seconds(priority, interval, () => {
694
- callback();
695
- return baseNs.SOURCE_REMOVE;
696
- });
697
- } else if (prop === 'log_set_writer_func') {
698
- // The gjs GLib.js shape: a non-function clears the JS writer (logs fall
699
- // back to the default writer); a function becomes the structured-log
700
- // writer. NOTE (GLib contract, identical under gjs): the underlying
701
- // g_log_set_writer_func may only ever be installed ONCE per process — a
702
- // second install aborts inside GLib itself.
703
- value = (writerFunc) => {
704
- if (typeof writerFunc !== 'function') native.logSetWriterFunc(null);
705
- else native.logSetWriterFunc(writerFunc);
706
- };
707
- } else if (prop === 'log_set_writer_default') {
708
- value = () => native.logSetWriterDefault();
709
- }
710
- cache.set(prop, value);
711
- return value;
712
- };
713
- return new Proxy(baseNs, {
714
- get(t, prop) {
715
- if (prop === 'Variant') return variantClass;
716
- // GLib.Error is the L1 GError subclass (the engine throws instances of it,
717
- // and `new GLib.Error(domain, code, message)` constructs one), shadowing the
718
- // introspected boxed type so `instanceof GLib.Error` + `.matches()` work.
719
- if (prop === 'Error') return GLibError;
720
- if (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) return overlay(prop);
721
- return t[prop];
722
- },
723
- has(t, prop) {
724
- return (
725
- prop === 'Variant' ||
726
- prop === 'Error' ||
727
- (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) ||
728
- prop in t
729
- );
730
- },
731
- });
668
+ // Per-namespace cache of the overlay functions that need the introspected baseNs
669
+ // (log_structured → log_variant; the *_once helpers → idle_add/timeout_add).
670
+ const cache = new Map();
671
+ const overlay = (prop) => {
672
+ if (cache.has(prop)) return cache.get(prop);
673
+ let value;
674
+ if (prop === 'log_structured') value = makeLogStructured(baseNs);
675
+ // The one-shot idle/timeout conveniences (gjs GLib.js): auto-remove the source
676
+ // after the callback runs, so the callback needs no `return SOURCE_REMOVE`.
677
+ else if (prop === 'idle_add_once') {
678
+ value = (priority, callback) =>
679
+ baseNs.idle_add(priority, () => {
680
+ callback();
681
+ return baseNs.SOURCE_REMOVE;
682
+ });
683
+ } else if (prop === 'timeout_add_once') {
684
+ value = (priority, interval, callback) =>
685
+ baseNs.timeout_add(priority, interval, () => {
686
+ callback();
687
+ return baseNs.SOURCE_REMOVE;
688
+ });
689
+ } else if (prop === 'timeout_add_seconds_once') {
690
+ value = (priority, interval, callback) =>
691
+ baseNs.timeout_add_seconds(priority, interval, () => {
692
+ callback();
693
+ return baseNs.SOURCE_REMOVE;
694
+ });
695
+ } else if (prop === 'log_set_writer_func') {
696
+ // The gjs GLib.js shape: a non-function clears the JS writer (logs fall
697
+ // back to the default writer); a function becomes the structured-log
698
+ // writer. NOTE (GLib contract, identical under gjs): the underlying
699
+ // g_log_set_writer_func may only ever be installed ONCE per process — a
700
+ // second install aborts inside GLib itself.
701
+ value = (writerFunc) => {
702
+ if (typeof writerFunc !== 'function') native.logSetWriterFunc(null);
703
+ else native.logSetWriterFunc(writerFunc);
704
+ };
705
+ } else if (prop === 'log_set_writer_default') {
706
+ value = () => native.logSetWriterDefault();
707
+ }
708
+ cache.set(prop, value);
709
+ return value;
710
+ };
711
+ return new Proxy(baseNs, {
712
+ get(t, prop) {
713
+ if (prop === 'Variant') return variantClass;
714
+ // GLib.Error is the L1 GError subclass (the engine throws instances of it,
715
+ // and `new GLib.Error(domain, code, message)` constructs one), shadowing the
716
+ // introspected boxed type so `instanceof GLib.Error` + `.matches()` work.
717
+ if (prop === 'Error') return GLibError;
718
+ if (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) return overlay(prop);
719
+ return t[prop];
720
+ },
721
+ has(t, prop) {
722
+ return (
723
+ prop === 'Variant' ||
724
+ prop === 'Error' ||
725
+ (typeof prop === 'string' && GLIB_OVERLAY_NAMES.has(prop)) ||
726
+ prop in t
727
+ );
728
+ },
729
+ });
732
730
  }
733
731
 
734
732
  // Overlay the GJS Gio runtime statics on the introspected Gio namespace —
@@ -737,45 +735,45 @@ function decorateGLibNamespace(baseNs) {
737
735
  // is built by createGioDBus (overrides/gio-dbus.js) over the introspected Gio +
738
736
  // the ergonomic GLib. Every other member keeps resolving from introspection.
739
737
  function decorateGioNamespace(baseNs) {
740
- // Lazily construct the DBus surface: it needs the ergonomic GLib (`new
741
- // GLib.Variant`), and building it only on first `Gio.DBus*` access keeps a plain
742
- // `import Gio` (no DBus) from pulling GLib in.
743
- let dbus;
744
- const getDBus = () => {
745
- if (dbus === undefined) {
746
- dbus = createGioDBus({ Gio: baseNs, GLib: requireGi('GLib', '2.0'), unwrap: unwrapArg, native });
747
- }
748
- return dbus;
749
- };
750
- return new Proxy(baseNs, {
751
- get(t, prop) {
752
- if (prop === '_promisify') return promisify;
753
- if (prop === 'DBus') return getDBus().DBus;
754
- if (prop === 'DBusProxy') return getDBus().DBusProxy;
755
- if (prop === 'DBusExportedObject') return getDBus().DBusExportedObject;
756
- return t[prop];
757
- },
758
- has(t, prop) {
759
- return (
760
- prop === '_promisify' ||
761
- prop === 'DBus' ||
762
- prop === 'DBusProxy' ||
763
- prop === 'DBusExportedObject' ||
764
- prop in t
765
- );
766
- },
767
- });
738
+ // Lazily construct the DBus surface: it needs the ergonomic GLib (`new
739
+ // GLib.Variant`), and building it only on first `Gio.DBus*` access keeps a plain
740
+ // `import Gio` (no DBus) from pulling GLib in.
741
+ let dbus;
742
+ const getDBus = () => {
743
+ if (dbus === undefined) {
744
+ dbus = createGioDBus({ Gio: baseNs, GLib: requireGi('GLib', '2.0'), unwrap: unwrapArg, native });
745
+ }
746
+ return dbus;
747
+ };
748
+ return new Proxy(baseNs, {
749
+ get(t, prop) {
750
+ if (prop === '_promisify') return promisify;
751
+ if (prop === 'DBus') return getDBus().DBus;
752
+ if (prop === 'DBusProxy') return getDBus().DBusProxy;
753
+ if (prop === 'DBusExportedObject') return getDBus().DBusExportedObject;
754
+ return t[prop];
755
+ },
756
+ has(t, prop) {
757
+ return (
758
+ prop === '_promisify' ||
759
+ prop === 'DBus' ||
760
+ prop === 'DBusProxy' ||
761
+ prop === 'DBusExportedObject' ||
762
+ prop in t
763
+ );
764
+ },
765
+ });
768
766
  }
769
767
 
770
768
  // Walk a JS prototype chain (excluding Object.prototype) for an own property
771
769
  // descriptor of `prop` — used to surface a registerClass subclass's own methods
772
770
  // /getters/setters on the GObject wrapper.
773
771
  function findProtoDescriptor(proto, prop) {
774
- for (let p = proto; p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
775
- const d = Object.getOwnPropertyDescriptor(p, prop);
776
- if (d !== undefined) return d;
777
- }
778
- return undefined;
772
+ for (let p = proto; p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
773
+ const d = Object.getOwnPropertyDescriptor(p, prop);
774
+ if (d !== undefined) return d;
775
+ }
776
+ return undefined;
779
777
  }
780
778
 
781
779
  // Whether `proto` is the SAME as, or a more-derived descendant of, `maybeAncestor`
@@ -785,10 +783,10 @@ function findProtoDescriptor(proto, prop) {
785
783
  // prototype before the leaf ctor wraps it with the leaf's, so the leaf (descendant)
786
784
  // proto must be allowed to supersede the ancestor's — but never the reverse.
787
785
  function isProtoSameOrDescendantOf(proto, maybeAncestor) {
788
- for (let p = proto; p !== null; p = Object.getPrototypeOf(p)) {
789
- if (p === maybeAncestor) return true;
790
- }
791
- return false;
786
+ for (let p = proto; p !== null; p = Object.getPrototypeOf(p)) {
787
+ if (p === maybeAncestor) return true;
788
+ }
789
+ return false;
792
790
  }
793
791
 
794
792
  // L1 proxy-identity cache (the user-visible half of the toggle-ref bridge). The
@@ -818,34 +816,34 @@ const instanceCache = new WeakMap();
818
816
  const signalHandlerIds = new WeakMap(); // handle → Map<jsFunc, Set<handlerId>>
819
817
 
820
818
  function recordSignalHandler(handle, fn, id) {
821
- let byFn = signalHandlerIds.get(handle);
822
- if (byFn === undefined) {
823
- byFn = new Map();
824
- signalHandlerIds.set(handle, byFn);
825
- }
826
- let ids = byFn.get(fn);
827
- if (ids === undefined) {
828
- ids = new Set();
829
- byFn.set(fn, ids);
830
- }
831
- ids.add(id);
819
+ let byFn = signalHandlerIds.get(handle);
820
+ if (byFn === undefined) {
821
+ byFn = new Map();
822
+ signalHandlerIds.set(handle, byFn);
823
+ }
824
+ let ids = byFn.get(fn);
825
+ if (ids === undefined) {
826
+ ids = new Set();
827
+ byFn.set(fn, ids);
828
+ }
829
+ ids.add(id);
832
830
  }
833
831
 
834
832
  // Drop a handler id from the per-instance registry (on disconnect), pruning an
835
833
  // emptied function entry so a later by-func lookup reports zero matches.
836
834
  function forgetSignalHandlerId(handle, id) {
837
- const byFn = signalHandlerIds.get(handle);
838
- if (byFn === undefined) return;
839
- for (const [fn, ids] of byFn) {
840
- if (ids.delete(id) && ids.size === 0) byFn.delete(fn);
841
- }
835
+ const byFn = signalHandlerIds.get(handle);
836
+ if (byFn === undefined) return;
837
+ for (const [fn, ids] of byFn) {
838
+ if (ids.delete(id) && ids.size === 0) byFn.delete(fn);
839
+ }
842
840
  }
843
841
 
844
842
  // The connected handler ids for a given JS function on an instance (a fresh array;
845
843
  // empty when the function was never connected on this instance).
846
844
  function handlerIdsForFunc(handle, fn) {
847
- const ids = signalHandlerIds.get(handle)?.get(fn);
848
- return ids === undefined ? [] : [...ids];
845
+ const ids = signalHandlerIds.get(handle)?.get(fn);
846
+ return ids === undefined ? [] : [...ids];
849
847
  }
850
848
 
851
849
  // ---- G3 mutate-in-place registry ----
@@ -889,16 +887,16 @@ let adoptHandle;
889
887
  // resolves each. Assigned through the proxy (set trap → stored as an instance
890
888
  // expando), exactly as the old Subclass did.
891
889
  function assignTemplateChildren(instance, handle, reg) {
892
- if (reg.children !== undefined) {
893
- for (const childName of reg.children) {
894
- instance[childName.replace(/-/g, '_')] = wrapReturn(native.getTemplateChild(handle, childName));
890
+ if (reg.children !== undefined) {
891
+ for (const childName of reg.children) {
892
+ instance[childName.replace(/-/g, '_')] = wrapReturn(native.getTemplateChild(handle, childName));
893
+ }
895
894
  }
896
- }
897
- if (reg.internalChildren !== undefined) {
898
- for (const childName of reg.internalChildren) {
899
- instance[`_${childName.replace(/-/g, '_')}`] = wrapReturn(native.getTemplateChild(handle, childName));
895
+ if (reg.internalChildren !== undefined) {
896
+ for (const childName of reg.internalChildren) {
897
+ instance[`_${childName.replace(/-/g, '_')}`] = wrapReturn(native.getTemplateChild(handle, childName));
898
+ }
900
899
  }
901
- }
902
900
  }
903
901
 
904
902
  // Push construct-time GObject-property values through the class's own JS SETTERS.
@@ -919,14 +917,14 @@ function assignTemplateChildren(instance, handle, reg) {
919
917
  // touched, so a plain GObject property (no JS accessor) keeps the store as its single
920
918
  // backing store — unchanged.
921
919
  function flushConstructProperties(instance, handle, reg) {
922
- if (reg.constructPropertyNames === undefined) return;
923
- const up = instance[USER_PROTO];
924
- if (up === undefined) return;
925
- for (const name of reg.constructPropertyNames) {
926
- const desc = findProtoDescriptor(up, name);
927
- if (desc === undefined || typeof desc.set !== 'function') continue;
928
- desc.set.call(instance, wrapReturn(native.getProperty(handle, name)));
929
- }
920
+ if (reg.constructPropertyNames === undefined) return;
921
+ const up = instance[USER_PROTO];
922
+ if (up === undefined) return;
923
+ for (const name of reg.constructPropertyNames) {
924
+ const desc = findProtoDescriptor(up, name);
925
+ if (desc === undefined || typeof desc.set !== 'function') continue;
926
+ desc.set.call(instance, wrapReturn(native.getProperty(handle, name)));
927
+ }
930
928
  }
931
929
 
932
930
  // ---- Gio.Application.runAsync (L1, GJS-shaped) ----
@@ -966,15 +964,15 @@ function flushConstructProperties(instance, handle, reg) {
966
964
  // process loop) — for concurrent runtime-loop + GLib work there, drive GLib with
967
965
  // startMainContextPump instead of a blocking run.
968
966
  function applicationRunAsync(handle) {
969
- return new Promise((resolve, reject) => {
970
- setImmediate(() => {
971
- try {
972
- resolve(wrapReturn(native.callMethod(handle, 'run', unwrapArgs([[]]))));
973
- } catch (error) {
974
- reject(error);
975
- }
967
+ return new Promise((resolve, reject) => {
968
+ setImmediate(() => {
969
+ try {
970
+ resolve(wrapReturn(native.callMethod(handle, 'run', unwrapArgs([[]]))));
971
+ } catch (error) {
972
+ reject(error);
973
+ }
974
+ });
976
975
  });
977
- });
978
976
  }
979
977
 
980
978
  // ---- portable GLib main-context pump (Bun/Deno) ----
@@ -997,10 +995,10 @@ let pumpHoldsRuntime = false;
997
995
 
998
996
  /** Dispatch every currently-ready GLib source, then return. */
999
997
  function drainReadySources() {
1000
- let guard = 0;
1001
- while (native.iterateMainContext(false) && guard++ < 100000) {
1002
- /* drain ready sources */
1003
- }
998
+ let guard = 0;
999
+ while (native.iterateMainContext(false) && guard++ < 100000) {
1000
+ /* drain ready sources */
1001
+ }
1004
1002
  }
1005
1003
 
1006
1004
  // Zero-N-API view over the JS-armed-work counter — `pendingView[0] > 0` is
@@ -1036,10 +1034,10 @@ let pendingView = null;
1036
1034
  * sources, C-armed ones included.
1037
1035
  */
1038
1036
  function pumpBeat() {
1039
- if (pumpRefCount > pumpAutoRefs || pendingView[0] > 0) {
1040
- drainReadySources();
1041
- }
1042
- syncPumpKeepAlive();
1037
+ if (pumpRefCount > pumpAutoRefs || pendingView[0] > 0) {
1038
+ drainReadySources();
1039
+ }
1040
+ syncPumpKeepAlive();
1043
1041
  }
1044
1042
 
1045
1043
  /**
@@ -1059,12 +1057,12 @@ function pumpBeat() {
1059
1057
  * and it is now the SAME on node, bun and deno rather than node-only.
1060
1058
  */
1061
1059
  function syncPumpKeepAlive() {
1062
- if (pumpTimer === null) return;
1063
- const hold = pendingView[0] > 0; // zero-N-API — see pendingView above
1064
- if (hold === pumpHoldsRuntime) return;
1065
- if (hold) pumpTimer.ref?.();
1066
- else pumpTimer.unref?.();
1067
- pumpHoldsRuntime = hold;
1060
+ if (pumpTimer === null) return;
1061
+ const hold = pendingView[0] > 0; // zero-N-API — see pendingView above
1062
+ if (hold === pumpHoldsRuntime) return;
1063
+ if (hold) pumpTimer.ref?.();
1064
+ else pumpTimer.unref?.();
1065
+ pumpHoldsRuntime = hold;
1068
1066
  }
1069
1067
 
1070
1068
  /**
@@ -1079,41 +1077,41 @@ function syncPumpKeepAlive() {
1079
1077
  * @returns {() => void}
1080
1078
  */
1081
1079
  export function startMainContextPump() {
1082
- // Node: the libuv↔GLib bridge already co-pumps. GJS: the host loop IS
1083
- // GLib's default main context — a JS-timer pump would only iterate the
1084
- // context recursively from within its own dispatch.
1085
- if (native.isNodeRuntime || native.RUNTIME === 'gjs') return () => {};
1086
- pumpRefCount++;
1087
- if (pumpTimer === null) {
1088
- if (pendingView === null) pendingView = native.makePumpPendingCount();
1089
- // ~4 ms cadence: low latency without busy-spinning. Each beat drains every
1090
- // currently-ready source (iterateMainContext(false) returns false when none
1091
- // remain, bounding the inner loop) — gated on the program's own GLib work
1092
- // for the auto-armed pump, see pumpBeat — then re-evaluates the hold.
1093
- pumpTimer = setInterval(pumpBeat, 4);
1094
- // Start UNREF'd: the pump alone must never keep a finished program alive.
1095
- // syncPumpKeepAlive() refs it for exactly as long as GLib has work.
1096
- pumpTimer.unref?.();
1097
- pumpHoldsRuntime = false;
1098
- syncPumpKeepAlive();
1099
- }
1100
- let disposed = false;
1101
- return () => {
1102
- if (disposed) return;
1103
- disposed = true;
1104
- stopMainContextPump();
1105
- };
1080
+ // Node: the libuv↔GLib bridge already co-pumps. GJS: the host loop IS
1081
+ // GLib's default main context — a JS-timer pump would only iterate the
1082
+ // context recursively from within its own dispatch.
1083
+ if (native.isNodeRuntime || native.RUNTIME === 'gjs') return () => {};
1084
+ pumpRefCount++;
1085
+ if (pumpTimer === null) {
1086
+ if (pendingView === null) pendingView = native.makePumpPendingCount();
1087
+ // ~4 ms cadence: low latency without busy-spinning. Each beat drains every
1088
+ // currently-ready source (iterateMainContext(false) returns false when none
1089
+ // remain, bounding the inner loop) — gated on the program's own GLib work
1090
+ // for the auto-armed pump, see pumpBeat — then re-evaluates the hold.
1091
+ pumpTimer = setInterval(pumpBeat, 4);
1092
+ // Start UNREF'd: the pump alone must never keep a finished program alive.
1093
+ // syncPumpKeepAlive() refs it for exactly as long as GLib has work.
1094
+ pumpTimer.unref?.();
1095
+ pumpHoldsRuntime = false;
1096
+ syncPumpKeepAlive();
1097
+ }
1098
+ let disposed = false;
1099
+ return () => {
1100
+ if (disposed) return;
1101
+ disposed = true;
1102
+ stopMainContextPump();
1103
+ };
1106
1104
  }
1107
1105
 
1108
1106
  /** Decrement the main-context pump reference count; clears the timer at zero. */
1109
1107
  export function stopMainContextPump() {
1110
- if (native.isNodeRuntime || native.RUNTIME === 'gjs' || pumpRefCount === 0) return;
1111
- pumpRefCount--;
1112
- if (pumpRefCount === 0 && pumpTimer !== null) {
1113
- clearInterval(pumpTimer);
1114
- pumpTimer = null;
1115
- pumpHoldsRuntime = false;
1116
- }
1108
+ if (native.isNodeRuntime || native.RUNTIME === 'gjs' || pumpRefCount === 0) return;
1109
+ pumpRefCount--;
1110
+ if (pumpRefCount === 0 && pumpTimer !== null) {
1111
+ clearInterval(pumpTimer);
1112
+ pumpTimer = null;
1113
+ pumpHoldsRuntime = false;
1114
+ }
1117
1115
  }
1118
1116
 
1119
1117
  // bind_property_full / BindingGroup.bind_full: the transform functions are driven
@@ -1127,12 +1125,12 @@ export function stopMainContextPump() {
1127
1125
  // the incoming binding/source into chainable L1 values and unwraps the returned
1128
1126
  // target value back to a native handle (object/variant-valued properties).
1129
1127
  function wrapBindingTransform(fn) {
1130
- if (typeof fn !== 'function') return null;
1131
- return (binding, value) => {
1132
- const result = fn(wrapReturn(binding), wrapReturn(value));
1133
- if (!Array.isArray(result)) return result; // native reports + treats as FALSE
1134
- return [result[0], unwrap(result[1])];
1135
- };
1128
+ if (typeof fn !== 'function') return null;
1129
+ return (binding, value) => {
1130
+ const result = fn(wrapReturn(binding), wrapReturn(value));
1131
+ if (!Array.isArray(result)) return result; // native reports + treats as FALSE
1132
+ return [result[0], unwrap(result[1])];
1133
+ };
1136
1134
  }
1137
1135
 
1138
1136
  // gjs's GObject.Object.prototype signal-convenience methods + bind_property_full
@@ -1142,59 +1140,59 @@ function wrapBindingTransform(fn) {
1142
1140
  // single-handler g_signal_handler_* functions (verified to marshal a node-gi
1143
1141
  // GObject IN-arg); stop_emission_by_name through g_signal_stop_emission_by_name.
1144
1142
  function objectPrototypeShim(prop) {
1145
- switch (prop) {
1146
- case 'block_signal_handler':
1147
- case 'blockSignalHandler':
1148
- return (handle, args) =>
1149
- wrapReturn(native.callFunction('GObject', 'signal_handler_block', [handle, args[0]]));
1150
- case 'unblock_signal_handler':
1151
- case 'unblockSignalHandler':
1152
- return (handle, args) =>
1153
- wrapReturn(native.callFunction('GObject', 'signal_handler_unblock', [handle, args[0]]));
1154
- case 'stop_emission_by_name':
1155
- case 'stopEmissionByName':
1156
- return (handle, args) =>
1157
- wrapReturn(native.callFunction('GObject', 'signal_stop_emission_by_name', [handle, args[0]]));
1158
- case 'bind_property_full':
1159
- case 'bindPropertyFull':
1160
- return (handle, args) =>
1161
- wrapReturn(
1162
- native.bindPropertyFull(
1163
- handle,
1164
- args[0],
1165
- unwrap(args[1]),
1166
- args[2],
1167
- args[3],
1168
- wrapBindingTransform(args[4]),
1169
- wrapBindingTransform(args[5]),
1170
- ),
1171
- );
1172
- case 'bind_full':
1173
- case 'bindFull':
1174
- // GObject.BindingGroup.bind_full (gjs: GjsPrivate.g_binding_group_bind_full).
1175
- // Gated per instance: on any OTHER class a genuine `bind_full` method keeps
1176
- // resolving through the normal GI route. NOTE: without this shim the
1177
- // introspected name would resolve to bind_with_closures (its GIR shadow),
1178
- // whose write-back GValue contract a marshaled GClosure cannot satisfy.
1179
- return (handle, args) => {
1180
- if (!native.isInstanceOf(handle, 'GObject', 'BindingGroup')) {
1181
- return wrapReturn(native.callMethod(handle, 'bind_full', unwrapArgs(args)));
1182
- }
1183
- return wrapReturn(
1184
- native.bindingGroupBindFull(
1185
- handle,
1186
- args[0],
1187
- unwrap(args[1]),
1188
- args[2],
1189
- args[3],
1190
- wrapBindingTransform(args[4]),
1191
- wrapBindingTransform(args[5]),
1192
- ),
1193
- );
1194
- };
1195
- default:
1196
- return undefined;
1197
- }
1143
+ switch (prop) {
1144
+ case 'block_signal_handler':
1145
+ case 'blockSignalHandler':
1146
+ return (handle, args) =>
1147
+ wrapReturn(native.callFunction('GObject', 'signal_handler_block', [handle, args[0]]));
1148
+ case 'unblock_signal_handler':
1149
+ case 'unblockSignalHandler':
1150
+ return (handle, args) =>
1151
+ wrapReturn(native.callFunction('GObject', 'signal_handler_unblock', [handle, args[0]]));
1152
+ case 'stop_emission_by_name':
1153
+ case 'stopEmissionByName':
1154
+ return (handle, args) =>
1155
+ wrapReturn(native.callFunction('GObject', 'signal_stop_emission_by_name', [handle, args[0]]));
1156
+ case 'bind_property_full':
1157
+ case 'bindPropertyFull':
1158
+ return (handle, args) =>
1159
+ wrapReturn(
1160
+ native.bindPropertyFull(
1161
+ handle,
1162
+ args[0],
1163
+ unwrap(args[1]),
1164
+ args[2],
1165
+ args[3],
1166
+ wrapBindingTransform(args[4]),
1167
+ wrapBindingTransform(args[5]),
1168
+ ),
1169
+ );
1170
+ case 'bind_full':
1171
+ case 'bindFull':
1172
+ // GObject.BindingGroup.bind_full (gjs: GjsPrivate.g_binding_group_bind_full).
1173
+ // Gated per instance: on any OTHER class a genuine `bind_full` method keeps
1174
+ // resolving through the normal GI route. NOTE: without this shim the
1175
+ // introspected name would resolve to bind_with_closures (its GIR shadow),
1176
+ // whose write-back GValue contract a marshaled GClosure cannot satisfy.
1177
+ return (handle, args) => {
1178
+ if (!native.isInstanceOf(handle, 'GObject', 'BindingGroup')) {
1179
+ return wrapReturn(native.callMethod(handle, 'bind_full', unwrapArgs(args)));
1180
+ }
1181
+ return wrapReturn(
1182
+ native.bindingGroupBindFull(
1183
+ handle,
1184
+ args[0],
1185
+ unwrap(args[1]),
1186
+ args[2],
1187
+ args[3],
1188
+ wrapBindingTransform(args[4]),
1189
+ wrapBindingTransform(args[5]),
1190
+ ),
1191
+ );
1192
+ };
1193
+ default:
1194
+ return undefined;
1195
+ }
1198
1196
  }
1199
1197
 
1200
1198
  // Per-GType method feature-detection cache backing the wrapper's GJS-parity
@@ -1205,18 +1203,18 @@ function objectPrototypeShim(prop) {
1205
1203
  const methodPresenceCache = new Map(); // typeName -> Map<methodName, boolean>
1206
1204
 
1207
1205
  function instanceHasMethod(handle, name) {
1208
- const typeName = native.getTypeName(handle);
1209
- let perType = methodPresenceCache.get(typeName);
1210
- if (perType === undefined) {
1211
- perType = new Map();
1212
- methodPresenceCache.set(typeName, perType);
1213
- }
1214
- let present = perType.get(name);
1215
- if (present === undefined) {
1216
- present = native.hasMethod(handle, name);
1217
- perType.set(name, present);
1218
- }
1219
- return present;
1206
+ const typeName = native.getTypeName(handle);
1207
+ let perType = methodPresenceCache.get(typeName);
1208
+ if (perType === undefined) {
1209
+ perType = new Map();
1210
+ methodPresenceCache.set(typeName, perType);
1211
+ }
1212
+ let present = perType.get(name);
1213
+ if (present === undefined) {
1214
+ present = native.hasMethod(handle, name);
1215
+ perType.set(name, present);
1216
+ }
1217
+ return present;
1220
1218
  }
1221
1219
 
1222
1220
  // Wrap a live GObject handle as a GJS-shaped instance. When `userProto` is given
@@ -1225,171 +1223,170 @@ function instanceHasMethod(handle, name) {
1225
1223
  // wrapper as `this` — then falls back to GObject property get/set and GI method
1226
1224
  // routing. `.connect()/.emit()/.disconnect()` work in both modes.
1227
1225
  function wrapInstance(handle, userProto) {
1228
- const cached = instanceCache.get(handle);
1229
- if (cached !== undefined) {
1230
- // UPGRADE a cached wrapper's user prototype when this wrap supplies a MORE
1231
- // DERIVED one: an undefined cache (a subclass instance first seen generically —
1232
- // returned from store.get_item / a signal sender / resurrection), OR a cached
1233
- // ANCESTOR prototype (a multi-level chain where a vfunc fired during constructType
1234
- // wrapped the instance with the OVERRIDING ancestor's prototype, before the leaf
1235
- // ctor's wrapInstance(handle, leaf.prototype) runs — the leaf proto must win so
1236
- // the leaf's OWN methods resolve). Never DOWNGRADE: a more-derived cached proto is
1237
- // kept. The userProto lives on the target (read via the proxy here), so the
1238
- // upgrade is in place — identity (===) and any expando fields are preserved.
1239
- if (userProto !== undefined && userProto !== cached[USER_PROTO]) {
1240
- const current = cached[USER_PROTO];
1241
- if (current === undefined || isProtoSameOrDescendantOf(userProto, current)) {
1242
- cached[USER_PROTO] = userProto;
1243
- }
1244
- }
1245
- return cached;
1246
- }
1247
- const target = { [HANDLE]: handle };
1248
- if (userProto !== undefined) target[USER_PROTO] = userProto;
1249
- const proxy = new Proxy(target, {
1250
- get(t, prop) {
1251
- if (prop === HANDLE) return handle;
1252
- if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
1253
- switch (prop) {
1254
- case '$typeName':
1255
- // The instance's concrete RUNTIME GType name —
1256
- // g_type_name(G_OBJECT_TYPE(obj)) via native.getTypeName. node-gi hands
1257
- // back a GENERIC wrapper for a returned handle (it does not downcast to
1258
- // the runtime GType), so `constructor.$gtype` is the STATIC declared
1259
- // type; this getter reads the TRUE runtime type. It is the portable seam
1260
- // @gjsify/devtools' widget-tree DumpTree uses (GJS instead reads the
1261
- // concrete type off its already-downcast `constructor.$gtype.name`).
1262
- // Distinct from the CLASS-level `$gtypeName`, which is the DECLARED
1263
- // namespaced string (e.g. 'Gtk.Widget'); this is the raw runtime GType
1264
- // name (e.g. 'GtkWidget', 'AdwBin', 'FireworksWindow').
1265
- return native.getTypeName(handle);
1266
- case 'connect':
1267
- // Record the (user fn → id) mapping so signal_handlers_*_by_func can
1268
- // resolve the private-closure handler back to its ids (see the registry).
1269
- return (signal, cb) => {
1270
- const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), false);
1271
- recordSignalHandler(handle, cb, id);
1272
- return id;
1273
- };
1274
- case 'connect_after':
1275
- return (signal, cb) => {
1276
- const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), true);
1277
- recordSignalHandler(handle, cb, id);
1278
- return id;
1279
- };
1280
- case 'emit':
1281
- return (signal, ...args) =>
1282
- wrapReturn(native.emitSignal(handle, signal, unwrapArgs(args)));
1283
- case 'disconnect':
1284
- return (id) => {
1285
- forgetSignalHandlerId(handle, id);
1286
- return native.disconnectSignal(handle, id);
1287
- };
1288
- default:
1289
- break;
1290
- }
1291
- const up = t[USER_PROTO];
1292
- if (up !== undefined) {
1293
- const desc = findProtoDescriptor(up, prop);
1294
- if (desc !== undefined) {
1295
- if (typeof desc.value === 'function') return (...args) => desc.value.apply(proxy, args);
1296
- if (typeof desc.get === 'function') return desc.get.call(proxy);
1297
- return desc.value;
1226
+ const cached = instanceCache.get(handle);
1227
+ if (cached !== undefined) {
1228
+ // UPGRADE a cached wrapper's user prototype when this wrap supplies a MORE
1229
+ // DERIVED one: an undefined cache (a subclass instance first seen generically —
1230
+ // returned from store.get_item / a signal sender / resurrection), OR a cached
1231
+ // ANCESTOR prototype (a multi-level chain where a vfunc fired during constructType
1232
+ // wrapped the instance with the OVERRIDING ancestor's prototype, before the leaf
1233
+ // ctor's wrapInstance(handle, leaf.prototype) runs — the leaf proto must win so
1234
+ // the leaf's OWN methods resolve). Never DOWNGRADE: a more-derived cached proto is
1235
+ // kept. The userProto lives on the target (read via the proxy here), so the
1236
+ // upgrade is in place — identity (===) and any expando fields are preserved.
1237
+ if (userProto !== undefined && userProto !== cached[USER_PROTO]) {
1238
+ const current = cached[USER_PROTO];
1239
+ if (current === undefined || isProtoSameOrDescendantOf(userProto, current)) {
1240
+ cached[USER_PROTO] = userProto;
1241
+ }
1298
1242
  }
1299
- }
1300
- // Gio.Application.runAsync — the GJS override (refs/gjs Gio.js + GLib.js):
1301
- // a Promise-returning run() that does NOT block the Node microtask/event
1302
- // loop (see applicationRunAsync). Gated to GApplication instances
1303
- // (g_type_is_a, so Gtk/Adw.Application and registerClass subclasses qualify);
1304
- // placed AFTER the userProto lookup so a subclass may still override it, and
1305
- // before the GI-method fallback since there is no introspected `run_async`.
1306
- if (prop === 'runAsync' && native.isInstanceOf(handle, 'Gio', 'Application')) {
1307
- return () => applicationRunAsync(handle);
1308
- }
1309
- // gjs's GObject.Object.prototype signal conveniences (block_signal_handler /
1310
- // unblock_signal_handler / stop_emission_by_name) + bind_property_full and
1311
- // BindingGroup.bind_full (refs/gjs GObject.js). These are NOT introspected instance
1312
- // methods — they are gjs shims over the namespace-level g_signal_* functions —
1313
- // so route them explicitly before property / GI-method resolution.
1314
- const shim = objectPrototypeShim(prop);
1315
- if (shim !== undefined) return (...args) => shim(handle, args);
1316
- const propName = toKebab(prop);
1317
- if (native.hasProperty(handle, propName)) {
1318
- return wrapReturn(native.getProperty(handle, propName));
1319
- }
1320
- // Surface a plain JS field previously written on THIS wrapper. With the
1321
- // toggle-ref bridge the wrapper is now CANONICAL (one proxy per GObject,
1322
- // cached by the canonical native handle), so a plain field IS shared across
1323
- // the vfunc<->instance boundary and survives a round-trip + GC while C owns
1324
- // the object — a vfunc's `this` resolves to the same cached proxy as
1325
- // construct, and `store.get_item(x)` returns the same proxy a setter wrote
1326
- // to. Own-property only (set via the `set` trap), so an introspected GI
1327
- // method of the same name is never shadowed unless the user explicitly
1328
- // assigned an expando. (GObject PROPERTIES remain the right choice for state
1329
- // that must also be visible to C / other language bindings.)
1330
- if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
1331
- // LAST resort before treating an unknown name as a GI method: an INHERITED
1332
- // member (Object.prototype.hasOwnProperty / isPrototypeOf / … — RESERVED
1333
- // already covers toString/valueOf/etc.) must resolve to the real function,
1334
- // not a GI callMethod thunk that would throw on `inst.hasOwnProperty('x')`.
1335
- if (prop in t) return t[prop];
1336
- // A Gio._promisify'd async method (registerClass subclasses pick it up via
1337
- // their userProto above; introspected instances have no prototype chain, so
1338
- // they resolve it here from the registry, per-class). Bound to this instance.
1339
- const promisified = resolvePromisified(handle, camelToSnake(prop));
1340
- if (promisified !== undefined) return (...args) => promisified.apply(proxy, args);
1341
- // GJS parity: an UNKNOWN member is `undefined`, never a throw-on-call
1342
- // thunk — real consumers feature-detect optional native methods
1343
- // (`typeof gl.clearBufferfv === 'function'` gates `@gjsify/webgl`'s
1344
- // clearBuffer emulation; Excalibur's RenderTarget.blitToScreen was the
1345
- // exposing call on node-gi: the old unconditional thunk made the
1346
- // detection lie, then threw mid-frame). Presence is resolved via the
1347
- // SAME native walk callMethod uses (literal-first, snake alias second)
1348
- // and memoized per concrete GType — method GETs happen on every call.
1349
- // The LITERAL accessor name is passed through — the engine resolves it
1350
- // verbatim first (GJS fidelity: GIR names are exposed as-is, incl. Vala
1351
- // camelCase like Gwebgl's `getString`) and only falls back to the
1352
- // snake_case alias. Converting here would destroy a literal camelCase
1353
- // GI name.
1354
- if (instanceHasMethod(handle, prop)) {
1355
- return (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)));
1356
- }
1357
- return undefined;
1358
- },
1359
- set(t, prop, value) {
1360
- if (typeof prop === 'string') {
1361
- const up = t[USER_PROTO];
1362
- if (up !== undefined) {
1363
- const desc = findProtoDescriptor(up, prop);
1364
- if (desc !== undefined && typeof desc.set === 'function') {
1365
- desc.set.call(proxy, value);
1243
+ return cached;
1244
+ }
1245
+ const target = { [HANDLE]: handle };
1246
+ if (userProto !== undefined) target[USER_PROTO] = userProto;
1247
+ const proxy = new Proxy(target, {
1248
+ get(t, prop) {
1249
+ if (prop === HANDLE) return handle;
1250
+ if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
1251
+ switch (prop) {
1252
+ case '$typeName':
1253
+ // The instance's concrete RUNTIME GType name —
1254
+ // g_type_name(G_OBJECT_TYPE(obj)) via native.getTypeName. node-gi hands
1255
+ // back a GENERIC wrapper for a returned handle (it does not downcast to
1256
+ // the runtime GType), so `constructor.$gtype` is the STATIC declared
1257
+ // type; this getter reads the TRUE runtime type. It is the portable seam
1258
+ // @gjsify/devtools' widget-tree DumpTree uses (GJS instead reads the
1259
+ // concrete type off its already-downcast `constructor.$gtype.name`).
1260
+ // Distinct from the CLASS-level `$gtypeName`, which is the DECLARED
1261
+ // namespaced string (e.g. 'Gtk.Widget'); this is the raw runtime GType
1262
+ // name (e.g. 'GtkWidget', 'AdwBin', 'FireworksWindow').
1263
+ return native.getTypeName(handle);
1264
+ case 'connect':
1265
+ // Record the (user fn → id) mapping so signal_handlers_*_by_func can
1266
+ // resolve the private-closure handler back to its ids (see the registry).
1267
+ return (signal, cb) => {
1268
+ const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), false);
1269
+ recordSignalHandler(handle, cb, id);
1270
+ return id;
1271
+ };
1272
+ case 'connect_after':
1273
+ return (signal, cb) => {
1274
+ const id = native.connectSignal(handle, signal, wrapSignalCallback(cb), true);
1275
+ recordSignalHandler(handle, cb, id);
1276
+ return id;
1277
+ };
1278
+ case 'emit':
1279
+ return (signal, ...args) => wrapReturn(native.emitSignal(handle, signal, unwrapArgs(args)));
1280
+ case 'disconnect':
1281
+ return (id) => {
1282
+ forgetSignalHandlerId(handle, id);
1283
+ return native.disconnectSignal(handle, id);
1284
+ };
1285
+ default:
1286
+ break;
1287
+ }
1288
+ const up = t[USER_PROTO];
1289
+ if (up !== undefined) {
1290
+ const desc = findProtoDescriptor(up, prop);
1291
+ if (desc !== undefined) {
1292
+ if (typeof desc.value === 'function') return (...args) => desc.value.apply(proxy, args);
1293
+ if (typeof desc.get === 'function') return desc.get.call(proxy);
1294
+ return desc.value;
1295
+ }
1296
+ }
1297
+ // Gio.Application.runAsync — the GJS override (refs/gjs Gio.js + GLib.js):
1298
+ // a Promise-returning run() that does NOT block the Node microtask/event
1299
+ // loop (see applicationRunAsync). Gated to GApplication instances
1300
+ // (g_type_is_a, so Gtk/Adw.Application and registerClass subclasses qualify);
1301
+ // placed AFTER the userProto lookup so a subclass may still override it, and
1302
+ // before the GI-method fallback since there is no introspected `run_async`.
1303
+ if (prop === 'runAsync' && native.isInstanceOf(handle, 'Gio', 'Application')) {
1304
+ return () => applicationRunAsync(handle);
1305
+ }
1306
+ // gjs's GObject.Object.prototype signal conveniences (block_signal_handler /
1307
+ // unblock_signal_handler / stop_emission_by_name) + bind_property_full and
1308
+ // BindingGroup.bind_full (refs/gjs GObject.js). These are NOT introspected instance
1309
+ // methods — they are gjs shims over the namespace-level g_signal_* functions —
1310
+ // so route them explicitly before property / GI-method resolution.
1311
+ const shim = objectPrototypeShim(prop);
1312
+ if (shim !== undefined) return (...args) => shim(handle, args);
1313
+ const propName = toKebab(prop);
1314
+ if (native.hasProperty(handle, propName)) {
1315
+ return wrapReturn(native.getProperty(handle, propName));
1316
+ }
1317
+ // Surface a plain JS field previously written on THIS wrapper. With the
1318
+ // toggle-ref bridge the wrapper is now CANONICAL (one proxy per GObject,
1319
+ // cached by the canonical native handle), so a plain field IS shared across
1320
+ // the vfunc<->instance boundary and survives a round-trip + GC while C owns
1321
+ // the object — a vfunc's `this` resolves to the same cached proxy as
1322
+ // construct, and `store.get_item(x)` returns the same proxy a setter wrote
1323
+ // to. Own-property only (set via the `set` trap), so an introspected GI
1324
+ // method of the same name is never shadowed unless the user explicitly
1325
+ // assigned an expando. (GObject PROPERTIES remain the right choice for state
1326
+ // that must also be visible to C / other language bindings.)
1327
+ if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
1328
+ // LAST resort before treating an unknown name as a GI method: an INHERITED
1329
+ // member (Object.prototype.hasOwnProperty / isPrototypeOf / … — RESERVED
1330
+ // already covers toString/valueOf/etc.) must resolve to the real function,
1331
+ // not a GI callMethod thunk that would throw on `inst.hasOwnProperty('x')`.
1332
+ if (prop in t) return t[prop];
1333
+ // A Gio._promisify'd async method (registerClass subclasses pick it up via
1334
+ // their userProto above; introspected instances have no prototype chain, so
1335
+ // they resolve it here from the registry, per-class). Bound to this instance.
1336
+ const promisified = resolvePromisified(handle, camelToSnake(prop));
1337
+ if (promisified !== undefined) return (...args) => promisified.apply(proxy, args);
1338
+ // GJS parity: an UNKNOWN member is `undefined`, never a throw-on-call
1339
+ // thunk — real consumers feature-detect optional native methods
1340
+ // (`typeof gl.clearBufferfv === 'function'` gates `@gjsify/webgl`'s
1341
+ // clearBuffer emulation; Excalibur's RenderTarget.blitToScreen was the
1342
+ // exposing call on node-gi: the old unconditional thunk made the
1343
+ // detection lie, then threw mid-frame). Presence is resolved via the
1344
+ // SAME native walk callMethod uses (literal-first, snake alias second)
1345
+ // and memoized per concrete GType — method GETs happen on every call.
1346
+ // The LITERAL accessor name is passed through — the engine resolves it
1347
+ // verbatim first (GJS fidelity: GIR names are exposed as-is, incl. Vala
1348
+ // camelCase like Gwebgl's `getString`) and only falls back to the
1349
+ // snake_case alias. Converting here would destroy a literal camelCase
1350
+ // GI name.
1351
+ if (instanceHasMethod(handle, prop)) {
1352
+ return (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)));
1353
+ }
1354
+ return undefined;
1355
+ },
1356
+ set(t, prop, value) {
1357
+ if (typeof prop === 'string') {
1358
+ const up = t[USER_PROTO];
1359
+ if (up !== undefined) {
1360
+ const desc = findProtoDescriptor(up, prop);
1361
+ if (desc !== undefined && typeof desc.set === 'function') {
1362
+ desc.set.call(proxy, value);
1363
+ return true;
1364
+ }
1365
+ }
1366
+ const propName = toKebab(prop);
1367
+ if (native.hasProperty(handle, propName)) {
1368
+ native.setProperty(handle, propName, unwrapArg(value));
1369
+ return true;
1370
+ }
1371
+ }
1372
+ t[prop] = value;
1366
1373
  return true;
1367
- }
1368
- }
1369
- const propName = toKebab(prop);
1370
- if (native.hasProperty(handle, propName)) {
1371
- native.setProperty(handle, propName, unwrapArg(value));
1372
- return true;
1373
- }
1374
- }
1375
- t[prop] = value;
1376
- return true;
1377
- },
1378
- has(t, prop) {
1379
- if (prop === HANDLE || prop in t) return true;
1380
- if (typeof prop === 'string') {
1381
- // A subclass's own prototype member (method/getter) and any GObject
1382
- // property of the instance both count as `in` the wrapper. (Introspected
1383
- // GI methods are resolved dynamically and are intentionally not reported.)
1384
- const up = t[USER_PROTO];
1385
- if (up !== undefined && findProtoDescriptor(up, prop) !== undefined) return true;
1386
- if (native.hasProperty(handle, toKebab(prop))) return true;
1387
- }
1388
- return false;
1389
- },
1390
- });
1391
- instanceCache.set(handle, proxy);
1392
- return proxy;
1374
+ },
1375
+ has(t, prop) {
1376
+ if (prop === HANDLE || prop in t) return true;
1377
+ if (typeof prop === 'string') {
1378
+ // A subclass's own prototype member (method/getter) and any GObject
1379
+ // property of the instance both count as `in` the wrapper. (Introspected
1380
+ // GI methods are resolved dynamically and are intentionally not reported.)
1381
+ const up = t[USER_PROTO];
1382
+ if (up !== undefined && findProtoDescriptor(up, prop) !== undefined) return true;
1383
+ if (native.hasProperty(handle, toKebab(prop))) return true;
1384
+ }
1385
+ return false;
1386
+ },
1387
+ });
1388
+ instanceCache.set(handle, proxy);
1389
+ return proxy;
1393
1390
  }
1394
1391
 
1395
1392
  // Shared object installed at the BASE of every introspected class's prototype
@@ -1403,19 +1400,19 @@ function wrapInstance(handle, userProto) {
1403
1400
  // Non-`vfunc_` lookups fall through to Object.prototype (so the base prototype
1404
1401
  // keeps its normal object behaviour).
1405
1402
  const vfuncChainProto = new Proxy(Object.create(Object.prototype), {
1406
- get(target, prop, receiver) {
1407
- if (typeof prop === 'string' && prop.startsWith('vfunc_')) {
1408
- const vfuncName = prop.slice('vfunc_'.length);
1409
- return function chainUpToParentVfunc(...args) {
1410
- const handle = this !== null && this !== undefined ? this[HANDLE] : undefined;
1411
- if (handle === undefined) {
1412
- throw new TypeError(`super.${prop}(): chain-up requires a node-gi instance as \`this\``);
1403
+ get(target, prop, receiver) {
1404
+ if (typeof prop === 'string' && prop.startsWith('vfunc_')) {
1405
+ const vfuncName = prop.slice('vfunc_'.length);
1406
+ return function chainUpToParentVfunc(...args) {
1407
+ const handle = this !== null && this !== undefined ? this[HANDLE] : undefined;
1408
+ if (handle === undefined) {
1409
+ throw new TypeError(`super.${prop}(): chain-up requires a node-gi instance as \`this\``);
1410
+ }
1411
+ return wrapReturn(native.callParentVfunc(handle, vfuncName, unwrapArgs(args)));
1412
+ };
1413
1413
  }
1414
- return wrapReturn(native.callParentVfunc(handle, vfuncName, unwrapArgs(args)));
1415
- };
1416
- }
1417
- return Reflect.get(target, prop, receiver);
1418
- },
1414
+ return Reflect.get(target, prop, receiver);
1415
+ },
1419
1416
  });
1420
1417
 
1421
1418
  // Surface a lazy `$gtype` getter on an introspected class/struct ctor: the real
@@ -1426,144 +1423,144 @@ const vfuncChainProto = new Proxy(Object.create(Object.prototype), {
1426
1423
  // is also cached. The makeClass/makeStruct Proxy forwards `'$gtype' in t` → the
1427
1424
  // target getter (it returns `t[prop]` when `prop in t`).
1428
1425
  function defineLazyGType(ctor, namespace, typeName) {
1429
- Object.defineProperty(ctor, '$gtype', {
1430
- configurable: true,
1431
- enumerable: false,
1432
- get() {
1433
- const gt = native.getGType(namespace, typeName);
1434
- Object.defineProperty(ctor, '$gtype', {
1435
- value: gt,
1426
+ Object.defineProperty(ctor, '$gtype', {
1436
1427
  configurable: true,
1437
1428
  enumerable: false,
1438
- writable: false,
1439
- });
1440
- return gt;
1441
- },
1442
- });
1429
+ get() {
1430
+ const gt = native.getGType(namespace, typeName);
1431
+ Object.defineProperty(ctor, '$gtype', {
1432
+ value: gt,
1433
+ configurable: true,
1434
+ enumerable: false,
1435
+ writable: false,
1436
+ });
1437
+ return gt;
1438
+ },
1439
+ });
1443
1440
  }
1444
1441
 
1445
1442
  function makeClass(namespace, typeName) {
1446
- const ctor = function ctor(props) {
1447
- // G3/G2 construction routing. `new.target` is the leaf class the `new` was
1448
- // applied to and is preserved through every super() hop, so when a registerClass'd
1449
- // subclass is being constructed (directly or up a super() chain — at ANY depth,
1450
- // since the leaf's registered GType already IS the full multi-level type) we build
1451
- // the REGISTERED GType and return its canonical toggle-ref wrapper (carrying the
1452
- // leaf's prototype as USER_PROTO) — which then substitutes `this` in every derived
1453
- // ctor body. `new.target === undefined` (a call without `new`) or `=== proxy` (a
1454
- // direct `new Ns.Class(...)` on the introspected class itself) falls through to the
1455
- // unchanged introspected construction.
1456
- const nt = new.target;
1457
- if (nt !== undefined && nt !== proxy) {
1458
- const reg = registeredClasses.get(nt);
1459
- if (reg !== undefined) {
1460
- let handle;
1461
- if (adoptHandle !== undefined) {
1462
- // C-created adoption (runCtorForCObject): the engine already built the
1463
- // GObject; use its canonical handle instead of constructing another.
1464
- // Consume the latch atomically so a nested `new` in the ctor body takes
1465
- // the normal constructType path.
1466
- handle = adoptHandle;
1467
- adoptHandle = undefined;
1468
- } else {
1469
- handle = native.constructType(reg.typeHandle, props ? unwrapProps(props) : {});
1443
+ const ctor = function ctor(props) {
1444
+ // G3/G2 construction routing. `new.target` is the leaf class the `new` was
1445
+ // applied to and is preserved through every super() hop, so when a registerClass'd
1446
+ // subclass is being constructed (directly or up a super() chain — at ANY depth,
1447
+ // since the leaf's registered GType already IS the full multi-level type) we build
1448
+ // the REGISTERED GType and return its canonical toggle-ref wrapper (carrying the
1449
+ // leaf's prototype as USER_PROTO) — which then substitutes `this` in every derived
1450
+ // ctor body. `new.target === undefined` (a call without `new`) or `=== proxy` (a
1451
+ // direct `new Ns.Class(...)` on the introspected class itself) falls through to the
1452
+ // unchanged introspected construction.
1453
+ const nt = new.target;
1454
+ if (nt !== undefined && nt !== proxy) {
1455
+ const reg = registeredClasses.get(nt);
1456
+ if (reg !== undefined) {
1457
+ let handle;
1458
+ if (adoptHandle !== undefined) {
1459
+ // C-created adoption (runCtorForCObject): the engine already built the
1460
+ // GObject; use its canonical handle instead of constructing another.
1461
+ // Consume the latch atomically so a nested `new` in the ctor body takes
1462
+ // the normal constructType path.
1463
+ handle = adoptHandle;
1464
+ adoptHandle = undefined;
1465
+ } else {
1466
+ handle = native.constructType(reg.typeHandle, props ? unwrapProps(props) : {});
1467
+ }
1468
+ const instance = wrapInstance(handle, nt.prototype);
1469
+ assignTemplateChildren(instance, handle, reg);
1470
+ // Route construct-time property values through the class's JS setters now
1471
+ // that USER_PROTO is attached — before the user ctor body runs (GJS order).
1472
+ flushConstructProperties(instance, handle, reg);
1473
+ return instance;
1474
+ }
1475
+ // `nt` is a subclass of this introspected GObject class but was NEVER passed to
1476
+ // GObject.registerClass(): silently building the introspected BASE type would
1477
+ // produce the wrong GType (missing the subclass's props/signals/vfuncs). Throw
1478
+ // the GJS-shaped error instead of constructing a misleading instance — the L1
1479
+ // analogue of GJS's "are you using GObject.registerClass()?" construct guard
1480
+ // (refs/gjs/gi/object.cpp).
1481
+ throw new Error(`Object ${nt.name || '(anonymous)'} is not registered with GObject.registerClass()`);
1470
1482
  }
1471
- const instance = wrapInstance(handle, nt.prototype);
1472
- assignTemplateChildren(instance, handle, reg);
1473
- // Route construct-time property values through the class's JS setters now
1474
- // that USER_PROTO is attached — before the user ctor body runs (GJS order).
1475
- flushConstructProperties(instance, handle, reg);
1476
- return instance;
1477
- }
1478
- // `nt` is a subclass of this introspected GObject class but was NEVER passed to
1479
- // GObject.registerClass(): silently building the introspected BASE type would
1480
- // produce the wrong GType (missing the subclass's props/signals/vfuncs). Throw
1481
- // the GJS-shaped error instead of constructing a misleading instance — the L1
1482
- // analogue of GJS's "are you using GObject.registerClass()?" construct guard
1483
- // (refs/gjs/gi/object.cpp).
1484
- throw new Error(
1485
- `Object ${nt.name || '(anonymous)'} is not registered with GObject.registerClass()`,
1486
- );
1487
- }
1488
- const handle = native.newObject(namespace, typeName, props ? unwrapProps(props) : {});
1489
- return wrapInstance(handle);
1490
- };
1491
- Object.defineProperty(ctor, 'name', { value: typeName, configurable: true });
1492
- ctor.$gtypeName = `${namespace}.${typeName}`;
1493
- defineLazyGType(ctor, namespace, typeName);
1494
- // Put the vfunc chain-up Proxy beneath this base class's prototype so a
1495
- // registerClass subclass's `super.vfunc_<name>(...)` resolves (see above).
1496
- Object.setPrototypeOf(ctor.prototype, vfuncChainProto);
1497
- // Stamp the prototype with its class identity so Gio._promisify(Cls.prototype, …)
1498
- // can record which class a registration belongs to (non-enumerable).
1499
- Object.defineProperty(ctor.prototype, CLASS_INFO, {
1500
- value: { namespace, typeName },
1501
- enumerable: false,
1502
- configurable: true,
1503
- });
1504
- // `inst instanceof Ns.Class` — GJS parity for the WHOLE GObject hierarchy, not
1505
- // just the leaf class. An instance is a Proxy over a bare `{[HANDLE]}` (no live JS
1506
- // prototype chain linking Adw.ApplicationWindow → Gtk.ApplicationWindow → …), so
1507
- // the default instanceof (a prototype-chain walk) reported `false` for every base
1508
- // class. Resolve it by the GObject type system instead: `native.isInstanceOf`
1509
- // (g_type_is_a) recognises subclasses AND implemented interfaces, exactly like GJS.
1510
- // Guarded by `isGObjectHandle` first — a bare/boxed/variant handle (or any non-node-gi
1511
- // value) is NOT a GObject, so it must be `false`, never reach `isInstanceOf` (which
1512
- // throws on a non-GObject handle). The makeClass Proxy `get` trap forwards this
1513
- // symbol to the target (`typeof prop !== 'string'` → `t[prop]`), so `instanceof`
1514
- // finds it on the proxy. Overlay-built classes (GLib.Variant / GObject.Value /
1515
- // GObject.ParamSpec) bypass makeClass and keep their own dedicated hasInstance.
1516
- Object.defineProperty(ctor, Symbol.hasInstance, {
1517
- configurable: true,
1518
- value(instance) {
1519
- if (instance === null || typeof instance !== 'object') return false;
1520
- const handle = instance[HANDLE];
1521
- if (handle === undefined || !native.isGObjectHandle(handle)) return false;
1522
- // The introspected class ITSELF → resolve by name: g_type_is_a recognises every
1523
- // subclass AND implemented interface, matching GJS exactly.
1524
- if (this === proxy) return native.isInstanceOf(handle, namespace, typeName);
1525
- // A registerClass'd subclass INHERITS this method (it `extends` the proxy) but
1526
- // must match ITS OWN registered GType, not the shared introspected base — else a
1527
- // sibling subclass or a bare-base instance would wrongly test true. Resolve the
1528
- // instance's runtime GType against the subclass's own `$gtype` through the GObject
1529
- // type system (a live JS prototype chain the subclass instance lacks — the L1
1530
- // wrapper carries the user prototype as a symbol, not as [[Prototype]]).
1531
- const subGType = this.$gtype;
1532
- if (subGType !== undefined && subGType !== null) {
1533
- const G = requireGi('GObject', '2.0');
1534
- return G.type_is_a(G.type_from_name(native.getTypeName(handle)), subGType);
1535
- }
1536
- // A raw (unregistered) subclass / unrelated ctor: ordinary prototype-chain.
1537
- return Function.prototype[Symbol.hasInstance].call(this, instance);
1538
- },
1539
- });
1540
- // Expose constructor/static methods lazily: Ns.Class.new(...) /
1541
- // Ns.Class.new_for_path(...) (and the camelCase aliases). `new Ns.Class({...})`
1542
- // still goes through the target's [[Construct]] (the default Proxy behaviour).
1543
- const proxy = new Proxy(ctor, {
1544
- get(t, prop, receiver) {
1545
- // #667: a raw (UNregistered) subclass that `extends` this introspected class
1546
- // must NOT inherit-read the parent's GType. A registered subclass has its OWN
1547
- // `$gtype` (set in place by registerClass, so it shadows + never reaches this
1548
- // trap); a raw subclass is not a GType, so an inherited read (receiver is the
1549
- // subclass, not this proxy) resolves to undefined. A direct read on the
1550
- // introspected class itself (receiver === proxy) resolves the real lazy GType.
1551
- if (prop === '$gtype' && receiver !== proxy) return undefined;
1552
- if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
1553
- // GObject.Object.new(gtype, props) / .new_with_properties(gtype, names,
1554
- // values): gjs's GObject.js statics that construct a GObject from a runtime
1555
- // GType. Not introspected (g_object_new is variadic), so route them to the
1556
- // by-GType constructor (native.constructType accepts any GType handle).
1557
- if (namespace === 'GObject' && typeName === 'Object' &&
1558
- (prop === 'new' || prop === 'new_with_properties' || prop === 'newWithProperties')) {
1559
- return objectStaticConstructor(prop);
1560
- }
1561
- const giName = camelToSnake(prop);
1562
- return (...args) =>
1563
- wrapReturn(native.callStaticMethod(namespace, typeName, giName, unwrapArgs(args)));
1564
- },
1565
- });
1566
- return proxy;
1483
+ const handle = native.newObject(namespace, typeName, props ? unwrapProps(props) : {});
1484
+ return wrapInstance(handle);
1485
+ };
1486
+ Object.defineProperty(ctor, 'name', { value: typeName, configurable: true });
1487
+ ctor.$gtypeName = `${namespace}.${typeName}`;
1488
+ defineLazyGType(ctor, namespace, typeName);
1489
+ // Put the vfunc chain-up Proxy beneath this base class's prototype so a
1490
+ // registerClass subclass's `super.vfunc_<name>(...)` resolves (see above).
1491
+ Object.setPrototypeOf(ctor.prototype, vfuncChainProto);
1492
+ // Stamp the prototype with its class identity so Gio._promisify(Cls.prototype, …)
1493
+ // can record which class a registration belongs to (non-enumerable).
1494
+ Object.defineProperty(ctor.prototype, CLASS_INFO, {
1495
+ value: { namespace, typeName },
1496
+ enumerable: false,
1497
+ configurable: true,
1498
+ });
1499
+ // `inst instanceof Ns.Class` — GJS parity for the WHOLE GObject hierarchy, not
1500
+ // just the leaf class. An instance is a Proxy over a bare `{[HANDLE]}` (no live JS
1501
+ // prototype chain linking Adw.ApplicationWindow → Gtk.ApplicationWindow → …), so
1502
+ // the default instanceof (a prototype-chain walk) reported `false` for every base
1503
+ // class. Resolve it by the GObject type system instead: `native.isInstanceOf`
1504
+ // (g_type_is_a) recognises subclasses AND implemented interfaces, exactly like GJS.
1505
+ // Guarded by `isGObjectHandle` first — a bare/boxed/variant handle (or any non-node-gi
1506
+ // value) is NOT a GObject, so it must be `false`, never reach `isInstanceOf` (which
1507
+ // throws on a non-GObject handle). The makeClass Proxy `get` trap forwards this
1508
+ // symbol to the target (`typeof prop !== 'string'` → `t[prop]`), so `instanceof`
1509
+ // finds it on the proxy. Overlay-built classes (GLib.Variant / GObject.Value /
1510
+ // GObject.ParamSpec) bypass makeClass and keep their own dedicated hasInstance.
1511
+ Object.defineProperty(ctor, Symbol.hasInstance, {
1512
+ configurable: true,
1513
+ value(instance) {
1514
+ if (instance === null || typeof instance !== 'object') return false;
1515
+ const handle = instance[HANDLE];
1516
+ if (handle === undefined || !native.isGObjectHandle(handle)) return false;
1517
+ // The introspected class ITSELF → resolve by name: g_type_is_a recognises every
1518
+ // subclass AND implemented interface, matching GJS exactly.
1519
+ if (this === proxy) return native.isInstanceOf(handle, namespace, typeName);
1520
+ // A registerClass'd subclass INHERITS this method (it `extends` the proxy) but
1521
+ // must match ITS OWN registered GType, not the shared introspected base — else a
1522
+ // sibling subclass or a bare-base instance would wrongly test true. Resolve the
1523
+ // instance's runtime GType against the subclass's own `$gtype` through the GObject
1524
+ // type system (a live JS prototype chain the subclass instance lacks — the L1
1525
+ // wrapper carries the user prototype as a symbol, not as [[Prototype]]).
1526
+ const subGType = this.$gtype;
1527
+ if (subGType !== undefined && subGType !== null) {
1528
+ const G = requireGi('GObject', '2.0');
1529
+ return G.type_is_a(G.type_from_name(native.getTypeName(handle)), subGType);
1530
+ }
1531
+ // A raw (unregistered) subclass / unrelated ctor: ordinary prototype-chain.
1532
+ return Function.prototype[Symbol.hasInstance].call(this, instance);
1533
+ },
1534
+ });
1535
+ // Expose constructor/static methods lazily: Ns.Class.new(...) /
1536
+ // Ns.Class.new_for_path(...) (and the camelCase aliases). `new Ns.Class({...})`
1537
+ // still goes through the target's [[Construct]] (the default Proxy behaviour).
1538
+ const proxy = new Proxy(ctor, {
1539
+ get(t, prop, receiver) {
1540
+ // #667: a raw (UNregistered) subclass that `extends` this introspected class
1541
+ // must NOT inherit-read the parent's GType. A registered subclass has its OWN
1542
+ // `$gtype` (set in place by registerClass, so it shadows + never reaches this
1543
+ // trap); a raw subclass is not a GType, so an inherited read (receiver is the
1544
+ // subclass, not this proxy) resolves to undefined. A direct read on the
1545
+ // introspected class itself (receiver === proxy) resolves the real lazy GType.
1546
+ if (prop === '$gtype' && receiver !== proxy) return undefined;
1547
+ if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
1548
+ // GObject.Object.new(gtype, props) / .new_with_properties(gtype, names,
1549
+ // values): gjs's GObject.js statics that construct a GObject from a runtime
1550
+ // GType. Not introspected (g_object_new is variadic), so route them to the
1551
+ // by-GType constructor (native.constructType accepts any GType handle).
1552
+ if (
1553
+ namespace === 'GObject' &&
1554
+ typeName === 'Object' &&
1555
+ (prop === 'new' || prop === 'new_with_properties' || prop === 'newWithProperties')
1556
+ ) {
1557
+ return objectStaticConstructor(prop);
1558
+ }
1559
+ const giName = camelToSnake(prop);
1560
+ return (...args) => wrapReturn(native.callStaticMethod(namespace, typeName, giName, unwrapArgs(args)));
1561
+ },
1562
+ });
1563
+ return proxy;
1567
1564
  }
1568
1565
 
1569
1566
  // The GObject.Object.new / new_with_properties statics (gjs GObject.js). `new`
@@ -1573,24 +1570,22 @@ function makeClass(namespace, typeName) {
1573
1570
  // arrays into a prop dict and delegates. Mirrors GJS's `GObject.Object.new`, which
1574
1571
  // looks up the constructor for `gtype` and `new`s it with the prop dict.
1575
1572
  function objectStaticConstructor(prop) {
1576
- const objectNew = (gtype, props = {}) => {
1577
- const gt = resolveGTypeArg(gtype);
1578
- if (gt === undefined || gt === null) {
1579
- throw new TypeError('GObject.Object.new: a GType (class or Class.$gtype) is required');
1580
- }
1581
- return wrapInstance(native.constructType(gt, props ? unwrapProps(props) : {}));
1582
- };
1583
- if (prop === 'new') return objectNew;
1584
- return (gtype, names, values) => {
1585
- if (!Array.isArray(names) || !Array.isArray(values) || names.length !== values.length) {
1586
- throw new Error(
1587
- 'GObject.Object.new_with_properties takes two equal-length arrays (names, values)',
1588
- );
1589
- }
1590
- const props = {};
1591
- for (let i = 0; i < names.length; i++) props[names[i]] = values[i];
1592
- return objectNew(gtype, props);
1593
- };
1573
+ const objectNew = (gtype, props = {}) => {
1574
+ const gt = resolveGTypeArg(gtype);
1575
+ if (gt === undefined || gt === null) {
1576
+ throw new TypeError('GObject.Object.new: a GType (class or Class.$gtype) is required');
1577
+ }
1578
+ return wrapInstance(native.constructType(gt, props ? unwrapProps(props) : {}));
1579
+ };
1580
+ if (prop === 'new') return objectNew;
1581
+ return (gtype, names, values) => {
1582
+ if (!Array.isArray(names) || !Array.isArray(values) || names.length !== values.length) {
1583
+ throw new Error('GObject.Object.new_with_properties takes two equal-length arrays (names, values)');
1584
+ }
1585
+ const props = {};
1586
+ for (let i = 0; i < names.length; i++) props[names[i]] = values[i];
1587
+ return objectNew(gtype, props);
1588
+ };
1594
1589
  }
1595
1590
 
1596
1591
  // Surface a boxed/struct type (e.g. GLib.MainLoop) as a class-like object whose
@@ -1601,43 +1596,42 @@ function objectStaticConstructor(prop) {
1601
1596
  // (`new Graphene.Rect()`, `new Gdk.RGBA()` — the devtools screenshot chain).
1602
1597
  // Returned boxed instances are wrapped by {@link wrapBoxed}.
1603
1598
  function makeStruct(namespace, typeName) {
1604
- const base = function () {};
1605
- Object.defineProperty(base, 'name', { value: typeName, configurable: true });
1606
- base.$gtypeName = `${namespace}.${typeName}`;
1607
- defineLazyGType(base, namespace, typeName);
1608
- const proxy = new Proxy(base, {
1609
- get(t, prop, receiver) {
1610
- // #667: guard inherited `$gtype` reads (see makeClass) — a raw subclass of a
1611
- // boxed/struct type does not inherit the parent's GType.
1612
- if (prop === '$gtype' && receiver !== proxy) return undefined;
1613
- if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
1614
- const giName = camelToSnake(prop);
1615
- return (...args) =>
1616
- wrapReturn(native.callStaticMethod(namespace, typeName, giName, unwrapArgs(args)));
1617
- },
1618
- construct(_t, args) {
1619
- return wrapReturn(native.constructStruct(namespace, typeName, unwrapArgs(args)));
1620
- },
1621
- });
1622
- return proxy;
1599
+ const base = function () {};
1600
+ Object.defineProperty(base, 'name', { value: typeName, configurable: true });
1601
+ base.$gtypeName = `${namespace}.${typeName}`;
1602
+ defineLazyGType(base, namespace, typeName);
1603
+ const proxy = new Proxy(base, {
1604
+ get(t, prop, receiver) {
1605
+ // #667: guard inherited `$gtype` reads (see makeClass) — a raw subclass of a
1606
+ // boxed/struct type does not inherit the parent's GType.
1607
+ if (prop === '$gtype' && receiver !== proxy) return undefined;
1608
+ if (typeof prop !== 'string' || prop in t || RESERVED.has(prop)) return t[prop];
1609
+ const giName = camelToSnake(prop);
1610
+ return (...args) => wrapReturn(native.callStaticMethod(namespace, typeName, giName, unwrapArgs(args)));
1611
+ },
1612
+ construct(_t, args) {
1613
+ return wrapReturn(native.constructStruct(namespace, typeName, unwrapArgs(args)));
1614
+ },
1615
+ });
1616
+ return proxy;
1623
1617
  }
1624
1618
 
1625
1619
  // Build a frozen enum/flags object keyed GJS-style: member names UPPER_CASED
1626
1620
  // with '-' → '_' (e.g. Gio.BusType.SYSTEM, Gio.ApplicationFlags.HANDLES_OPEN).
1627
1621
  function makeEnum(namespace, typeName) {
1628
- const raw = native.getEnumValues(namespace, typeName);
1629
- const out = {};
1630
- for (const key of Object.keys(raw)) {
1631
- out[key.toUpperCase().replace(/-/g, '_')] = raw[key];
1632
- }
1633
- // If this enum is registered as a GError domain (Gio.IOErrorEnum, …), attach
1634
- // its domain descriptor (non-enumerable) so GLib.Error.matches(enum, code) can
1635
- // resolve the enum to its domain. A plain enum gets nothing (getErrorDomain → null).
1636
- const domain = native.getErrorDomain(namespace, typeName);
1637
- if (domain !== null) {
1638
- Object.defineProperty(out, ERROR_DOMAIN, { value: domain, enumerable: false });
1639
- }
1640
- return Object.freeze(out);
1622
+ const raw = native.getEnumValues(namespace, typeName);
1623
+ const out = {};
1624
+ for (const key of Object.keys(raw)) {
1625
+ out[key.toUpperCase().replace(/-/g, '_')] = raw[key];
1626
+ }
1627
+ // If this enum is registered as a GError domain (Gio.IOErrorEnum, …), attach
1628
+ // its domain descriptor (non-enumerable) so GLib.Error.matches(enum, code) can
1629
+ // resolve the enum to its domain. A plain enum gets nothing (getErrorDomain → null).
1630
+ const domain = native.getErrorDomain(namespace, typeName);
1631
+ if (domain !== null) {
1632
+ Object.defineProperty(out, ERROR_DOMAIN, { value: domain, enumerable: false });
1633
+ }
1634
+ return Object.freeze(out);
1641
1635
  }
1642
1636
 
1643
1637
  // ---- Gio._promisify (L1, GJS-shaped) ----
@@ -1658,53 +1652,53 @@ function makeEnum(namespace, typeName) {
1658
1652
 
1659
1653
  // _async / _begin → _finish; otherwise <name>_finish (GJS's convention).
1660
1654
  function deriveFinishName(asyncName) {
1661
- if (asyncName.endsWith('_begin') || asyncName.endsWith('_async')) {
1662
- return `${asyncName.slice(0, -5)}finish`;
1663
- }
1664
- return `${asyncName}_finish`;
1655
+ if (asyncName.endsWith('_begin') || asyncName.endsWith('_async')) {
1656
+ return `${asyncName.slice(0, -5)}finish`;
1657
+ }
1658
+ return `${asyncName}_finish`;
1665
1659
  }
1666
1660
 
1667
1661
  // Build the promisified wrapper for `asyncName`. Uses `this` (the instance proxy)
1668
1662
  // so it works both bound from the registry and as a prototype method.
1669
1663
  function makePromisified(asyncName, finishName) {
1670
- const giAsync = camelToSnake(asyncName);
1671
- return function promisified(...args) {
1672
- const handle = this[HANDLE];
1673
- // A trailing callback → plain async method (the caller drives the callback).
1674
- // Wrap it so its (source, result) args are marshalled to wrapped instances —
1675
- // matching the Promise path and GJS, so `source.finish(result)` works.
1676
- if (args.length > 0 && typeof args[args.length - 1] === 'function') {
1677
- const userCb = args[args.length - 1];
1678
- const forwarded = [...args.slice(0, -1), wrapSignalCallback(userCb)];
1679
- return wrapReturn(native.callMethod(handle, giAsync, unwrapArgs(forwarded)));
1680
- }
1681
- const self = this;
1682
- return new Promise((resolve, reject) => {
1683
- // wrapSignalCallback marshals each native callback arg (source, res) through
1684
- // wrapReturn → wrapped instances, so both `source[finishName](res)` and the
1685
- // captured-instance fallback round-trip cleanly.
1686
- const onReady = wrapSignalCallback((source, res) => {
1687
- try {
1688
- const finisher =
1689
- source !== null && source !== undefined && typeof source[finishName] === 'function'
1690
- ? source[finishName].bind(source)
1691
- : self[finishName].bind(self);
1692
- const value = finisher(res);
1693
- // GJS's _promisify drops a leading `true` ok-flag so the dominant
1694
- // finish shape (load_contents_finish, query_info_finish,
1695
- // communicate_utf8_finish, … → [true, …]) resolves to just the
1696
- // payload — keeping one source byte-identical on gjs + node.
1697
- if (Array.isArray(value) && value.length > 1 && value[0] === true) {
1698
- value.shift();
1699
- }
1700
- resolve(value);
1701
- } catch (error) {
1702
- reject(error);
1664
+ const giAsync = camelToSnake(asyncName);
1665
+ return function promisified(...args) {
1666
+ const handle = this[HANDLE];
1667
+ // A trailing callback → plain async method (the caller drives the callback).
1668
+ // Wrap it so its (source, result) args are marshalled to wrapped instances —
1669
+ // matching the Promise path and GJS, so `source.finish(result)` works.
1670
+ if (args.length > 0 && typeof args[args.length - 1] === 'function') {
1671
+ const userCb = args[args.length - 1];
1672
+ const forwarded = [...args.slice(0, -1), wrapSignalCallback(userCb)];
1673
+ return wrapReturn(native.callMethod(handle, giAsync, unwrapArgs(forwarded)));
1703
1674
  }
1704
- });
1705
- native.callMethod(handle, giAsync, unwrapArgs([...args, onReady]));
1706
- });
1707
- };
1675
+ const self = this;
1676
+ return new Promise((resolve, reject) => {
1677
+ // wrapSignalCallback marshals each native callback arg (source, res) through
1678
+ // wrapReturn → wrapped instances, so both `source[finishName](res)` and the
1679
+ // captured-instance fallback round-trip cleanly.
1680
+ const onReady = wrapSignalCallback((source, res) => {
1681
+ try {
1682
+ const finisher =
1683
+ source !== null && source !== undefined && typeof source[finishName] === 'function'
1684
+ ? source[finishName].bind(source)
1685
+ : self[finishName].bind(self);
1686
+ const value = finisher(res);
1687
+ // GJS's _promisify drops a leading `true` ok-flag so the dominant
1688
+ // finish shape (load_contents_finish, query_info_finish,
1689
+ // communicate_utf8_finish, … → [true, …]) resolves to just the
1690
+ // payload — keeping one source byte-identical on gjs + node.
1691
+ if (Array.isArray(value) && value.length > 1 && value[0] === true) {
1692
+ value.shift();
1693
+ }
1694
+ resolve(value);
1695
+ } catch (error) {
1696
+ reject(error);
1697
+ }
1698
+ });
1699
+ native.callMethod(handle, giAsync, unwrapArgs([...args, onReady]));
1700
+ });
1701
+ };
1708
1702
  }
1709
1703
 
1710
1704
  /**
@@ -1719,61 +1713,61 @@ function makePromisified(asyncName, finishName) {
1719
1713
  * @returns {void}
1720
1714
  */
1721
1715
  function promisify(proto, asyncName, finishName = undefined) {
1722
- if (proto === null || typeof proto !== 'object') {
1723
- throw new TypeError('Gio._promisify: prototype must be an object');
1724
- }
1725
- if (typeof asyncName !== 'string') {
1726
- throw new TypeError('Gio._promisify: asyncName must be a string');
1727
- }
1728
- const finish = finishName === undefined ? deriveFinishName(asyncName) : finishName;
1729
- const wrapper = makePromisified(asyncName, finish);
1730
- // Install on the prototype (so proto[asyncName] IS the promisified method, and a
1731
- // registerClass subclass — which resolves its userProto — picks it up) AND in
1732
- // the global registry (introspected instances resolve methods dynamically, with
1733
- // no live prototype chain to walk).
1734
- try {
1735
- proto[asyncName] = wrapper;
1736
- } catch {
1737
- // A frozen/sealed prototype — the registry still makes it work.
1738
- }
1739
- // Record the registration keyed by the GI method name, tagged with the class it
1740
- // was registered on (CLASS_INFO from makeClass) so two classes promisifying the
1741
- // same method name with different finish methods stay disambiguated per-class.
1742
- const info = proto[CLASS_INFO];
1743
- const registration = {
1744
- namespace: info ? info.namespace : undefined,
1745
- typeName: info ? info.typeName : undefined,
1746
- wrapper,
1747
- };
1748
- const key = camelToSnake(asyncName);
1749
- const existing = promisifiedMethods.get(key);
1750
- if (existing === undefined) {
1751
- promisifiedMethods.set(key, [registration]);
1752
- } else {
1753
- // Replace a same-class registration in place; otherwise append (a second class).
1754
- const i = existing.findIndex(
1755
- (r) => r.namespace === registration.namespace && r.typeName === registration.typeName,
1756
- );
1757
- if (i >= 0) existing[i] = registration;
1758
- else existing.push(registration);
1759
- }
1716
+ if (proto === null || typeof proto !== 'object') {
1717
+ throw new TypeError('Gio._promisify: prototype must be an object');
1718
+ }
1719
+ if (typeof asyncName !== 'string') {
1720
+ throw new TypeError('Gio._promisify: asyncName must be a string');
1721
+ }
1722
+ const finish = finishName === undefined ? deriveFinishName(asyncName) : finishName;
1723
+ const wrapper = makePromisified(asyncName, finish);
1724
+ // Install on the prototype (so proto[asyncName] IS the promisified method, and a
1725
+ // registerClass subclass — which resolves its userProto — picks it up) AND in
1726
+ // the global registry (introspected instances resolve methods dynamically, with
1727
+ // no live prototype chain to walk).
1728
+ try {
1729
+ proto[asyncName] = wrapper;
1730
+ } catch {
1731
+ // A frozen/sealed prototype — the registry still makes it work.
1732
+ }
1733
+ // Record the registration keyed by the GI method name, tagged with the class it
1734
+ // was registered on (CLASS_INFO from makeClass) so two classes promisifying the
1735
+ // same method name with different finish methods stay disambiguated per-class.
1736
+ const info = proto[CLASS_INFO];
1737
+ const registration = {
1738
+ namespace: info ? info.namespace : undefined,
1739
+ typeName: info ? info.typeName : undefined,
1740
+ wrapper,
1741
+ };
1742
+ const key = camelToSnake(asyncName);
1743
+ const existing = promisifiedMethods.get(key);
1744
+ if (existing === undefined) {
1745
+ promisifiedMethods.set(key, [registration]);
1746
+ } else {
1747
+ // Replace a same-class registration in place; otherwise append (a second class).
1748
+ const i = existing.findIndex(
1749
+ (r) => r.namespace === registration.namespace && r.typeName === registration.typeName,
1750
+ );
1751
+ if (i >= 0) existing[i] = registration;
1752
+ else existing.push(registration);
1753
+ }
1760
1754
  }
1761
1755
 
1762
1756
  // Resolve the promisified wrapper for `methodName` on an instance handle. The
1763
1757
  // common case is a single registration (fast path); when several classes
1764
1758
  // promisified the same method name, pick the one whose class the instance is-a.
1765
1759
  function resolvePromisified(handle, methodName) {
1766
- const registrations = promisifiedMethods.get(methodName);
1767
- if (registrations === undefined) return undefined;
1768
- if (registrations.length === 1) return registrations[0].wrapper;
1769
- for (const r of registrations) {
1770
- if (r.namespace !== undefined && native.isInstanceOf(handle, r.namespace, r.typeName)) {
1771
- return r.wrapper;
1760
+ const registrations = promisifiedMethods.get(methodName);
1761
+ if (registrations === undefined) return undefined;
1762
+ if (registrations.length === 1) return registrations[0].wrapper;
1763
+ for (const r of registrations) {
1764
+ if (r.namespace !== undefined && native.isInstanceOf(handle, r.namespace, r.typeName)) {
1765
+ return r.wrapper;
1766
+ }
1772
1767
  }
1773
- }
1774
- // No class matched (or a registerClass registration with no CLASS_INFO) — fall
1775
- // back to the last registration so a single-class misconfig still resolves.
1776
- return registrations[registrations.length - 1].wrapper;
1768
+ // No class matched (or a registerClass registration with no CLASS_INFO) — fall
1769
+ // back to the last registration so a single-class misconfig still resolves.
1770
+ return registrations[registrations.length - 1].wrapper;
1777
1771
  }
1778
1772
 
1779
1773
  // ---- GObject.registerClass decorator (L1, GJS-shaped) ----
@@ -1817,18 +1811,18 @@ function resolvePromisified(handle, methodName) {
1817
1811
 
1818
1812
  // GObject.ParamFlags — the GParamFlags bits the native property builder consumes.
1819
1813
  const ParamFlags = Object.freeze({
1820
- READABLE: 1,
1821
- WRITABLE: 2,
1822
- READWRITE: 3,
1823
- CONSTRUCT: 4,
1824
- CONSTRUCT_ONLY: 8,
1814
+ READABLE: 1,
1815
+ WRITABLE: 2,
1816
+ READWRITE: 3,
1817
+ CONSTRUCT: 4,
1818
+ CONSTRUCT_ONLY: 8,
1825
1819
  });
1826
1820
 
1827
1821
  // GObject.SignalFlags — the GSignalFlags bits (native defaults to RUN_LAST).
1828
1822
  const SignalFlags = Object.freeze({
1829
- RUN_FIRST: 1,
1830
- RUN_LAST: 2,
1831
- RUN_CLEANUP: 4,
1823
+ RUN_FIRST: 1,
1824
+ RUN_LAST: 2,
1825
+ RUN_CLEANUP: 4,
1832
1826
  });
1833
1827
 
1834
1828
  // A ParamSpec factory returns a plain descriptor the decorator maps to the
@@ -1836,29 +1830,29 @@ const SignalFlags = Object.freeze({
1836
1830
  // default); the numeric kinds capture (name, nick, blurb, flags, min, max,
1837
1831
  // default) — matching GJS's GObject.ParamSpec.* argument order.
1838
1832
  function paramSpecPlain(type) {
1839
- return (name, nick, blurb, flags, defaultValue) => ({
1840
- $paramSpec: true,
1841
- type,
1842
- name,
1843
- nick,
1844
- blurb,
1845
- flags,
1846
- default: defaultValue,
1847
- });
1833
+ return (name, nick, blurb, flags, defaultValue) => ({
1834
+ $paramSpec: true,
1835
+ type,
1836
+ name,
1837
+ nick,
1838
+ blurb,
1839
+ flags,
1840
+ default: defaultValue,
1841
+ });
1848
1842
  }
1849
1843
 
1850
1844
  function paramSpecRanged(type) {
1851
- return (name, nick, blurb, flags, minimum, maximum, defaultValue) => ({
1852
- $paramSpec: true,
1853
- type,
1854
- name,
1855
- nick,
1856
- blurb,
1857
- flags,
1858
- minimum,
1859
- maximum,
1860
- default: defaultValue,
1861
- });
1845
+ return (name, nick, blurb, flags, minimum, maximum, defaultValue) => ({
1846
+ $paramSpec: true,
1847
+ type,
1848
+ name,
1849
+ nick,
1850
+ blurb,
1851
+ flags,
1852
+ minimum,
1853
+ maximum,
1854
+ default: defaultValue,
1855
+ });
1862
1856
  }
1863
1857
 
1864
1858
  // Resolve a GObject.ParamSpec.object/.boxed `gtype` argument to a native GType
@@ -1866,12 +1860,12 @@ function paramSpecRanged(type) {
1866
1860
  // ctor (introspected `Adw.Bin` / `GObject.Object`, or a registered class) whose
1867
1861
  // real `$gtype` is the GType handle (G4), or an already-resolved GType handle.
1868
1862
  function resolveGTypeArg(gtype) {
1869
- if (gtype === null || gtype === undefined) return gtype;
1870
- if (typeof gtype === 'function' || typeof gtype === 'object') {
1871
- const gt = gtype.$gtype;
1872
- if (gt !== undefined) return gt;
1873
- }
1874
- return gtype; // assume it is already a GType handle
1863
+ if (gtype === null || gtype === undefined) return gtype;
1864
+ if (typeof gtype === 'function' || typeof gtype === 'object') {
1865
+ const gt = gtype.$gtype;
1866
+ if (gt !== undefined) return gt;
1867
+ }
1868
+ return gtype; // assume it is already a GType handle
1875
1869
  }
1876
1870
 
1877
1871
  // object/boxed ParamSpec factories: `(name, nick, blurb, flags, gtype)` — matching
@@ -1879,37 +1873,37 @@ function resolveGTypeArg(gtype) {
1879
1873
  // (its `$gtype` is read) or a GType handle. Produces a real g_param_spec_object /
1880
1874
  // g_param_spec_boxed via the engine's BuildParamSpec.
1881
1875
  function paramSpecGTyped(type) {
1882
- return (name, nick, blurb, flags, gtype) => ({
1883
- $paramSpec: true,
1884
- type,
1885
- name,
1886
- nick,
1887
- blurb,
1888
- flags,
1889
- gtype: resolveGTypeArg(gtype),
1890
- });
1876
+ return (name, nick, blurb, flags, gtype) => ({
1877
+ $paramSpec: true,
1878
+ type,
1879
+ name,
1880
+ nick,
1881
+ blurb,
1882
+ flags,
1883
+ gtype: resolveGTypeArg(gtype),
1884
+ });
1891
1885
  }
1892
1886
 
1893
1887
  const ParamSpec = Object.freeze({
1894
- string: paramSpecPlain('string'),
1895
- boolean: paramSpecPlain('boolean'),
1896
- int: paramSpecRanged('int'),
1897
- uint: paramSpecRanged('uint'),
1898
- int64: paramSpecRanged('int64'),
1899
- uint64: paramSpecRanged('uint64'),
1900
- double: paramSpecRanged('double'),
1901
- float: paramSpecRanged('float'),
1902
- object: paramSpecGTyped('object'),
1903
- boxed: paramSpecGTyped('boxed'),
1904
- // `wrappedPspec instanceof GObject.ParamSpec` — a wrapped GParamSpec (from a
1905
- // notify handler / a GParamSpec-typed value) carries [HANDLE], recognised via
1906
- // the native tag. `instanceof` honours a `[Symbol.hasInstance]` on the RHS even
1907
- // though ParamSpec is a factory object rather than a constructor.
1908
- [Symbol.hasInstance](instance) {
1909
- if (instance === null || typeof instance !== 'object') return false;
1910
- const handle = instance[HANDLE];
1911
- return handle !== undefined && native.isParamSpecHandle(handle);
1912
- },
1888
+ string: paramSpecPlain('string'),
1889
+ boolean: paramSpecPlain('boolean'),
1890
+ int: paramSpecRanged('int'),
1891
+ uint: paramSpecRanged('uint'),
1892
+ int64: paramSpecRanged('int64'),
1893
+ uint64: paramSpecRanged('uint64'),
1894
+ double: paramSpecRanged('double'),
1895
+ float: paramSpecRanged('float'),
1896
+ object: paramSpecGTyped('object'),
1897
+ boxed: paramSpecGTyped('boxed'),
1898
+ // `wrappedPspec instanceof GObject.ParamSpec` — a wrapped GParamSpec (from a
1899
+ // notify handler / a GParamSpec-typed value) carries [HANDLE], recognised via
1900
+ // the native tag. `instanceof` honours a `[Symbol.hasInstance]` on the RHS even
1901
+ // though ParamSpec is a factory object rather than a constructor.
1902
+ [Symbol.hasInstance](instance) {
1903
+ if (instance === null || typeof instance !== 'object') return false;
1904
+ const handle = instance[HANDLE];
1905
+ return handle !== undefined && native.isParamSpecHandle(handle);
1906
+ },
1913
1907
  });
1914
1908
 
1915
1909
  // Walk up the JS class's prototype chain to the nearest node-gi base and describe
@@ -1924,69 +1918,65 @@ const ParamSpec = Object.freeze({
1924
1918
  // A registered class carries BOTH a dotless `$gtypeName` AND a registeredClasses
1925
1919
  // entry, so the registry check MUST come first.
1926
1920
  function findParentGType(klass) {
1927
- for (let c = Object.getPrototypeOf(klass); typeof c === 'function'; c = Object.getPrototypeOf(c)) {
1928
- const reg = registeredClasses.get(c);
1929
- if (reg !== undefined) {
1930
- return { parentGTypeHandle: reg.typeHandle };
1931
- }
1932
- const gt = c.$gtypeName;
1933
- if (typeof gt === 'string') {
1934
- const dot = gt.indexOf('.');
1935
- if (dot > 0) {
1936
- return { parentNamespace: gt.slice(0, dot), parentType: gt.slice(dot + 1) };
1937
- }
1938
- // A dotless `$gtypeName` with no registeredClasses entry should never occur
1939
- // (only registered types are dotless, and they are in the registry); be
1940
- // explicit rather than mis-resolving the namespace split.
1941
- throw new TypeError(
1942
- "GObject.registerClass: parent '" + gt + "' has no resolvable GType",
1943
- );
1921
+ for (let c = Object.getPrototypeOf(klass); typeof c === 'function'; c = Object.getPrototypeOf(c)) {
1922
+ const reg = registeredClasses.get(c);
1923
+ if (reg !== undefined) {
1924
+ return { parentGTypeHandle: reg.typeHandle };
1925
+ }
1926
+ const gt = c.$gtypeName;
1927
+ if (typeof gt === 'string') {
1928
+ const dot = gt.indexOf('.');
1929
+ if (dot > 0) {
1930
+ return { parentNamespace: gt.slice(0, dot), parentType: gt.slice(dot + 1) };
1931
+ }
1932
+ // A dotless `$gtypeName` with no registeredClasses entry should never occur
1933
+ // (only registered types are dotless, and they are in the registry); be
1934
+ // explicit rather than mis-resolving the namespace split.
1935
+ throw new TypeError("GObject.registerClass: parent '" + gt + "' has no resolvable GType");
1936
+ }
1944
1937
  }
1945
- }
1946
- return undefined;
1938
+ return undefined;
1947
1939
  }
1948
1940
 
1949
1941
  // Map a GObject.ParamSpec.* descriptor (from meta.Properties) to a native
1950
1942
  // PropertySpec. The property name defaults to the Properties key.
1951
1943
  function paramSpecToNative(desc, key) {
1952
- if (desc === null || typeof desc !== 'object') {
1953
- throw new TypeError(
1954
- "GObject.registerClass: Properties['" + key + "'] must be a GObject.ParamSpec descriptor",
1955
- );
1956
- }
1957
- const spec = { name: typeof desc.name === 'string' && desc.name.length > 0 ? desc.name : key };
1958
- spec.type = desc.type;
1959
- if (typeof desc.flags === 'number') spec.flags = desc.flags;
1960
- if (desc.default !== undefined) spec.default = desc.default;
1961
- if (typeof desc.minimum === 'number') spec.minimum = desc.minimum;
1962
- if (typeof desc.maximum === 'number') spec.maximum = desc.maximum;
1963
- // object/boxed ParamSpecs carry a value GType handle (passed straight to the
1964
- // engine's BuildParamSpec → g_param_spec_object / g_param_spec_boxed).
1965
- if (desc.gtype !== undefined) spec.gtype = desc.gtype;
1966
- return spec;
1944
+ if (desc === null || typeof desc !== 'object') {
1945
+ throw new TypeError("GObject.registerClass: Properties['" + key + "'] must be a GObject.ParamSpec descriptor");
1946
+ }
1947
+ const spec = { name: typeof desc.name === 'string' && desc.name.length > 0 ? desc.name : key };
1948
+ spec.type = desc.type;
1949
+ if (typeof desc.flags === 'number') spec.flags = desc.flags;
1950
+ if (desc.default !== undefined) spec.default = desc.default;
1951
+ if (typeof desc.minimum === 'number') spec.minimum = desc.minimum;
1952
+ if (typeof desc.maximum === 'number') spec.maximum = desc.maximum;
1953
+ // object/boxed ParamSpecs carry a value GType handle (passed straight to the
1954
+ // engine's BuildParamSpec → g_param_spec_object / g_param_spec_boxed).
1955
+ if (desc.gtype !== undefined) spec.gtype = desc.gtype;
1956
+ return spec;
1967
1957
  }
1968
1958
 
1969
1959
  // Normalise a signal param/return type to the native type-name vocabulary: a
1970
1960
  // string passes through; a ParamSpec descriptor contributes its `.type`.
1971
1961
  function normalizeSignalType(t) {
1972
- if (typeof t === 'string') return t;
1973
- if (t !== null && typeof t === 'object' && typeof t.type === 'string') return t.type;
1974
- return undefined;
1962
+ if (typeof t === 'string') return t;
1963
+ if (t !== null && typeof t === 'object' && typeof t.type === 'string') return t.type;
1964
+ return undefined;
1975
1965
  }
1976
1966
 
1977
1967
  // Map a meta.Signals entry (`{ param_types?, return_type?, flags? }`, GJS keys,
1978
1968
  // camelCase also accepted) to a native SignalSpec; the signal name is the key.
1979
1969
  function signalSpecToNative(spec, key) {
1980
- const s = spec !== null && typeof spec === 'object' ? spec : {};
1981
- const out = { name: key };
1982
- const params = s.param_types !== undefined ? s.param_types : s.paramTypes;
1983
- if (Array.isArray(params)) {
1984
- out.paramTypes = params.map(normalizeSignalType).filter((t) => t !== undefined);
1985
- }
1986
- const ret = normalizeSignalType(s.return_type !== undefined ? s.return_type : s.returnType);
1987
- if (ret !== undefined) out.returnType = ret;
1988
- if (typeof s.flags === 'number') out.flags = s.flags;
1989
- return out;
1970
+ const s = spec !== null && typeof spec === 'object' ? spec : {};
1971
+ const out = { name: key };
1972
+ const params = s.param_types !== undefined ? s.param_types : s.paramTypes;
1973
+ if (Array.isArray(params)) {
1974
+ out.paramTypes = params.map(normalizeSignalType).filter((t) => t !== undefined);
1975
+ }
1976
+ const ret = normalizeSignalType(s.return_type !== undefined ? s.return_type : s.returnType);
1977
+ if (ret !== undefined) out.returnType = ret;
1978
+ if (typeof s.flags === 'number') out.flags = s.flags;
1979
+ return out;
1990
1980
  }
1991
1981
 
1992
1982
  // Collect this class's OWN newly-declared `vfunc_<name>` methods into the native
@@ -2008,23 +1998,23 @@ function signalSpecToNative(spec, key) {
2008
1998
  // the leaf and the nearest registered ancestor ARE still collected — they have no
2009
1999
  // GType of their own, so their vfuncs only reach the vtable through the leaf.
2010
2000
  function collectVfuncs(klass) {
2011
- const vfuncs = {};
2012
- const seen = new Set();
2013
- for (let p = klass.prototype; p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
2014
- const owner = Object.getOwnPropertyDescriptor(p, 'constructor')?.value;
2015
- if (owner !== undefined && owner !== klass && registeredClasses.has(owner)) break;
2016
- for (const key of Object.getOwnPropertyNames(p)) {
2017
- if (!key.startsWith('vfunc_') || seen.has(key)) continue;
2018
- const desc = Object.getOwnPropertyDescriptor(p, key);
2019
- if (desc === undefined || typeof desc.value !== 'function') continue;
2020
- seen.add(key);
2021
- const userFn = desc.value;
2022
- vfuncs[key.slice('vfunc_'.length)] = function (...args) {
2023
- return userFn.apply(wrapInstance(this, klass.prototype), args);
2024
- };
2001
+ const vfuncs = {};
2002
+ const seen = new Set();
2003
+ for (let p = klass.prototype; p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
2004
+ const owner = Object.getOwnPropertyDescriptor(p, 'constructor')?.value;
2005
+ if (owner !== undefined && owner !== klass && registeredClasses.has(owner)) break;
2006
+ for (const key of Object.getOwnPropertyNames(p)) {
2007
+ if (!key.startsWith('vfunc_') || seen.has(key)) continue;
2008
+ const desc = Object.getOwnPropertyDescriptor(p, key);
2009
+ if (desc === undefined || typeof desc.value !== 'function') continue;
2010
+ seen.add(key);
2011
+ const userFn = desc.value;
2012
+ vfuncs[key.slice('vfunc_'.length)] = function (...args) {
2013
+ return userFn.apply(wrapInstance(this, klass.prototype), args);
2014
+ };
2015
+ }
2025
2016
  }
2026
- }
2027
- return vfuncs;
2017
+ return vfuncs;
2028
2018
  }
2029
2019
 
2030
2020
  /**
@@ -2042,120 +2032,119 @@ function collectVfuncs(klass) {
2042
2032
  * @returns {Function} the same `klass` (now registered)
2043
2033
  */
2044
2034
  function registerClass(metaOrClass, maybeClass) {
2045
- let meta;
2046
- let klass;
2047
- if (typeof metaOrClass === 'function') {
2048
- klass = metaOrClass;
2049
- meta = {};
2050
- } else {
2051
- meta = metaOrClass !== null && typeof metaOrClass === 'object' ? metaOrClass : {};
2052
- klass = maybeClass;
2053
- }
2054
- if (typeof klass !== 'function') {
2055
- throw new TypeError('GObject.registerClass: expected a class to register');
2056
- }
2057
-
2058
- const parent = findParentGType(klass);
2059
- if (parent === undefined) {
2060
- throw new TypeError(
2061
- 'GObject.registerClass: ' +
2062
- (klass.name || 'the class') +
2063
- ' must extend a node-gi GObject class (e.g. GObject.Object)',
2064
- );
2065
- }
2035
+ let meta;
2036
+ let klass;
2037
+ if (typeof metaOrClass === 'function') {
2038
+ klass = metaOrClass;
2039
+ meta = {};
2040
+ } else {
2041
+ meta = metaOrClass !== null && typeof metaOrClass === 'object' ? metaOrClass : {};
2042
+ klass = maybeClass;
2043
+ }
2044
+ if (typeof klass !== 'function') {
2045
+ throw new TypeError('GObject.registerClass: expected a class to register');
2046
+ }
2047
+
2048
+ const parent = findParentGType(klass);
2049
+ if (parent === undefined) {
2050
+ throw new TypeError(
2051
+ 'GObject.registerClass: ' +
2052
+ (klass.name || 'the class') +
2053
+ ' must extend a node-gi GObject class (e.g. GObject.Object)',
2054
+ );
2055
+ }
2066
2056
 
2067
- const properties = [];
2068
- if (meta.Properties !== null && typeof meta.Properties === 'object') {
2069
- for (const key of Object.keys(meta.Properties)) {
2070
- properties.push(paramSpecToNative(meta.Properties[key], key));
2057
+ const properties = [];
2058
+ if (meta.Properties !== null && typeof meta.Properties === 'object') {
2059
+ for (const key of Object.keys(meta.Properties)) {
2060
+ properties.push(paramSpecToNative(meta.Properties[key], key));
2061
+ }
2071
2062
  }
2072
- }
2073
2063
 
2074
- const signals = [];
2075
- if (meta.Signals !== null && typeof meta.Signals === 'object') {
2076
- for (const key of Object.keys(meta.Signals)) {
2077
- signals.push(signalSpecToNative(meta.Signals[key], key));
2064
+ const signals = [];
2065
+ if (meta.Signals !== null && typeof meta.Signals === 'object') {
2066
+ for (const key of Object.keys(meta.Signals)) {
2067
+ signals.push(signalSpecToNative(meta.Signals[key], key));
2068
+ }
2078
2069
  }
2079
- }
2080
-
2081
- const vfuncs = collectVfuncs(klass);
2082
-
2083
- const gtypeName =
2084
- typeof meta.GTypeName === 'string' && meta.GTypeName.length > 0 ? meta.GTypeName : klass.name;
2085
- if (!gtypeName) {
2086
- throw new TypeError('GObject.registerClass: a GTypeName is required for an anonymous class');
2087
- }
2088
-
2089
- // Gtk.Widget composite template (GJS-shaped meta: Template / Children /
2090
- // InternalChildren / CssName). Template is a Uint8Array/Buffer of inline UI-XML,
2091
- // a "resource:///…" path string, or an inline UI-XML string — passed through to
2092
- // the engine, which installs it in class_init (set_template[_from_resource] +
2093
- // bind_template_child_full) and runs gtk_widget_init_template at construction.
2094
- const options = { properties, signals, vfuncs };
2095
- if (meta.Template !== undefined && meta.Template !== null) options.template = meta.Template;
2096
- if (typeof meta.CssName === 'string' && meta.CssName.length > 0) options.cssName = meta.CssName;
2097
- const children = Array.isArray(meta.Children) ? meta.Children.filter((c) => typeof c === 'string') : [];
2098
- const internalChildren = Array.isArray(meta.InternalChildren)
2099
- ? meta.InternalChildren.filter((c) => typeof c === 'string')
2100
- : [];
2101
- if (children.length > 0) options.children = children;
2102
- if (internalChildren.length > 0) options.internalChildren = internalChildren;
2103
-
2104
- // Branch on the parent shape (findParentGType): a REGISTERED parent is subclassed
2105
- // from its runtime GType handle (it has no introspection entry); an INTROSPECTED
2106
- // parent is resolved by namespace+type. Construction (the leaf's registered GType
2107
- // via the makeClass new.target routing) is already multi-level-correct either way.
2108
- const typeHandle =
2109
- parent.parentGTypeHandle !== undefined
2110
- ? native.registerClassFromGType(gtypeName, parent.parentGTypeHandle, options)
2111
- : native.registerClass(gtypeName, parent.parentNamespace, parent.parentType, options);
2112
-
2113
- // G3: register the GType for the GIVEN class IN PLACE (no throwaway Subclass) and
2114
- // return that same class. `$gtypeName`/`$gtype` are defined as OWN properties:
2115
- // `klass` inherits a getter-only `$gtype` accessor from the introspected base ctor
2116
- // (via the makeClass Proxy), so a plain `klass.$gtype = …` assignment would throw
2117
- // in strict mode — defineProperty installs an own data property that shadows it.
2118
- Object.defineProperty(klass, '$gtypeName', {
2119
- value: gtypeName,
2120
- configurable: true,
2121
- writable: true,
2122
- });
2123
- // native.registerClass returns the registered GType as a tag-distinct node-gi
2124
- // GType handle (G4) — surface it as `$gtype` (GObject.type_ensure(X.$gtype),
2125
- // ParamSpec.object(..., X) all read it). It is the SAME handle constructType
2126
- // consumes, so registration and `$gtype` share one carrier.
2127
- Object.defineProperty(klass, '$gtype', {
2128
- value: typeHandle,
2129
- configurable: true,
2130
- enumerable: false,
2131
- writable: false,
2132
- });
2133
- // Record the registration so the introspected base ctor (makeClass) routes
2134
- // `new X(args)` (where new.target === klass) to constructType(typeHandle, …) +
2135
- // the canonical wrapper. Template children move here from the old Subclass.
2136
- // The names of CONSTRUCT / CONSTRUCT_ONLY properties — the ONLY ones GObject
2137
- // applies at construction. The flush (flushConstructProperties) must be limited to
2138
- // these: a plain READWRITE property is NOT set during construction, so running its
2139
- // JS setter early (GObject never would) fires the setter's side effects against
2140
- // still-uninitialised instance state (the SourceView `selectable` → `_signalHandlers`
2141
- // forEach crash). Matches GJS, which only runs setters for props GObject applies at
2142
- // construct. Flags are the ABI-stable G_PARAM_CONSTRUCT (1<<2) | CONSTRUCT_ONLY (1<<3).
2143
- const PARAM_CONSTRUCT_MASK = 0x4 | 0x8;
2144
- const constructPropertyNames = properties
2145
- .filter((p) => typeof p.flags === 'number' && (p.flags & PARAM_CONSTRUCT_MASK) !== 0)
2146
- .map((p) => p.name)
2147
- .filter((n) => typeof n === 'string');
2148
- registeredClasses.set(klass, {
2149
- typeHandle,
2150
- children: children.length > 0 ? children : undefined,
2151
- internalChildren: internalChildren.length > 0 ? internalChildren : undefined,
2152
- // CONSTRUCT-property names, for the construct-property flush (below).
2153
- constructPropertyNames: constructPropertyNames.length > 0 ? constructPropertyNames : undefined,
2154
- });
2155
- // Reverse index for runCtorForCObject: the engine identifies a C-created instance
2156
- // by its GType name (= gtypeName, what native.registerClass registered).
2157
- classesByGType.set(gtypeName, klass);
2158
- return klass;
2070
+
2071
+ const vfuncs = collectVfuncs(klass);
2072
+
2073
+ const gtypeName = typeof meta.GTypeName === 'string' && meta.GTypeName.length > 0 ? meta.GTypeName : klass.name;
2074
+ if (!gtypeName) {
2075
+ throw new TypeError('GObject.registerClass: a GTypeName is required for an anonymous class');
2076
+ }
2077
+
2078
+ // Gtk.Widget composite template (GJS-shaped meta: Template / Children /
2079
+ // InternalChildren / CssName). Template is a Uint8Array/Buffer of inline UI-XML,
2080
+ // a "resource:///…" path string, or an inline UI-XML string — passed through to
2081
+ // the engine, which installs it in class_init (set_template[_from_resource] +
2082
+ // bind_template_child_full) and runs gtk_widget_init_template at construction.
2083
+ const options = { properties, signals, vfuncs };
2084
+ if (meta.Template !== undefined && meta.Template !== null) options.template = meta.Template;
2085
+ if (typeof meta.CssName === 'string' && meta.CssName.length > 0) options.cssName = meta.CssName;
2086
+ const children = Array.isArray(meta.Children) ? meta.Children.filter((c) => typeof c === 'string') : [];
2087
+ const internalChildren = Array.isArray(meta.InternalChildren)
2088
+ ? meta.InternalChildren.filter((c) => typeof c === 'string')
2089
+ : [];
2090
+ if (children.length > 0) options.children = children;
2091
+ if (internalChildren.length > 0) options.internalChildren = internalChildren;
2092
+
2093
+ // Branch on the parent shape (findParentGType): a REGISTERED parent is subclassed
2094
+ // from its runtime GType handle (it has no introspection entry); an INTROSPECTED
2095
+ // parent is resolved by namespace+type. Construction (the leaf's registered GType
2096
+ // via the makeClass new.target routing) is already multi-level-correct either way.
2097
+ const typeHandle =
2098
+ parent.parentGTypeHandle !== undefined
2099
+ ? native.registerClassFromGType(gtypeName, parent.parentGTypeHandle, options)
2100
+ : native.registerClass(gtypeName, parent.parentNamespace, parent.parentType, options);
2101
+
2102
+ // G3: register the GType for the GIVEN class IN PLACE (no throwaway Subclass) and
2103
+ // return that same class. `$gtypeName`/`$gtype` are defined as OWN properties:
2104
+ // `klass` inherits a getter-only `$gtype` accessor from the introspected base ctor
2105
+ // (via the makeClass Proxy), so a plain `klass.$gtype = …` assignment would throw
2106
+ // in strict mode — defineProperty installs an own data property that shadows it.
2107
+ Object.defineProperty(klass, '$gtypeName', {
2108
+ value: gtypeName,
2109
+ configurable: true,
2110
+ writable: true,
2111
+ });
2112
+ // native.registerClass returns the registered GType as a tag-distinct node-gi
2113
+ // GType handle (G4) — surface it as `$gtype` (GObject.type_ensure(X.$gtype),
2114
+ // ParamSpec.object(..., X) all read it). It is the SAME handle constructType
2115
+ // consumes, so registration and `$gtype` share one carrier.
2116
+ Object.defineProperty(klass, '$gtype', {
2117
+ value: typeHandle,
2118
+ configurable: true,
2119
+ enumerable: false,
2120
+ writable: false,
2121
+ });
2122
+ // Record the registration so the introspected base ctor (makeClass) routes
2123
+ // `new X(args)` (where new.target === klass) to constructType(typeHandle, …) +
2124
+ // the canonical wrapper. Template children move here from the old Subclass.
2125
+ // The names of CONSTRUCT / CONSTRUCT_ONLY properties — the ONLY ones GObject
2126
+ // applies at construction. The flush (flushConstructProperties) must be limited to
2127
+ // these: a plain READWRITE property is NOT set during construction, so running its
2128
+ // JS setter early (GObject never would) fires the setter's side effects against
2129
+ // still-uninitialised instance state (the SourceView `selectable` → `_signalHandlers`
2130
+ // forEach crash). Matches GJS, which only runs setters for props GObject applies at
2131
+ // construct. Flags are the ABI-stable G_PARAM_CONSTRUCT (1<<2) | CONSTRUCT_ONLY (1<<3).
2132
+ const PARAM_CONSTRUCT_MASK = 0x4 | 0x8;
2133
+ const constructPropertyNames = properties
2134
+ .filter((p) => typeof p.flags === 'number' && (p.flags & PARAM_CONSTRUCT_MASK) !== 0)
2135
+ .map((p) => p.name)
2136
+ .filter((n) => typeof n === 'string');
2137
+ registeredClasses.set(klass, {
2138
+ typeHandle,
2139
+ children: children.length > 0 ? children : undefined,
2140
+ internalChildren: internalChildren.length > 0 ? internalChildren : undefined,
2141
+ // CONSTRUCT-property names, for the construct-property flush (below).
2142
+ constructPropertyNames: constructPropertyNames.length > 0 ? constructPropertyNames : undefined,
2143
+ });
2144
+ // Reverse index for runCtorForCObject: the engine identifies a C-created instance
2145
+ // by its GType name (= gtypeName, what native.registerClass registered).
2146
+ classesByGType.set(gtypeName, klass);
2147
+ return klass;
2159
2148
  }
2160
2149
 
2161
2150
  // Merge convenience flag names UNDER an introspected enum: the introspected
@@ -2165,8 +2154,8 @@ function registerClass(metaOrClass, maybeClass) {
2165
2154
  // (e.g. READWRITE). Returns the convenience table verbatim if introspection
2166
2155
  // yielded no usable enum.
2167
2156
  function mergeFlags(introspected, convenience) {
2168
- if (introspected === null || typeof introspected !== 'object') return convenience;
2169
- return Object.freeze({ ...convenience, ...introspected });
2157
+ if (introspected === null || typeof introspected !== 'object') return convenience;
2158
+ return Object.freeze({ ...convenience, ...introspected });
2170
2159
  }
2171
2160
 
2172
2161
  // GObject.AccumulatorType — gjs's fake enum for signal accumulators (GObject.js,
@@ -2181,32 +2170,32 @@ const AccumulatorType = Object.freeze({ NONE: 0, FIRST_WINS: 1, TRUE_HANDLED: 2
2181
2170
  // native.disconnectSignal. Returns the count of handlers matched, exactly as gjs's
2182
2171
  // g_signal_handlers_*_matched-based helpers do.
2183
2172
  function signalHandlersBlockByFunc(instance, fn) {
2184
- const handle = unwrapArg(instance);
2185
- const ids = handlerIdsForFunc(handle, fn);
2186
- for (const id of ids) native.callFunction('GObject', 'signal_handler_block', [handle, id]);
2187
- return ids.length;
2173
+ const handle = unwrapArg(instance);
2174
+ const ids = handlerIdsForFunc(handle, fn);
2175
+ for (const id of ids) native.callFunction('GObject', 'signal_handler_block', [handle, id]);
2176
+ return ids.length;
2188
2177
  }
2189
2178
  function signalHandlersUnblockByFunc(instance, fn) {
2190
- const handle = unwrapArg(instance);
2191
- const ids = handlerIdsForFunc(handle, fn);
2192
- for (const id of ids) native.callFunction('GObject', 'signal_handler_unblock', [handle, id]);
2193
- return ids.length;
2179
+ const handle = unwrapArg(instance);
2180
+ const ids = handlerIdsForFunc(handle, fn);
2181
+ for (const id of ids) native.callFunction('GObject', 'signal_handler_unblock', [handle, id]);
2182
+ return ids.length;
2194
2183
  }
2195
2184
  function signalHandlersDisconnectByFunc(instance, fn) {
2196
- const handle = unwrapArg(instance);
2197
- const ids = handlerIdsForFunc(handle, fn);
2198
- for (const id of ids) {
2199
- native.disconnectSignal(handle, id);
2200
- forgetSignalHandlerId(handle, id);
2201
- }
2202
- return ids.length;
2185
+ const handle = unwrapArg(instance);
2186
+ const ids = handlerIdsForFunc(handle, fn);
2187
+ for (const id of ids) {
2188
+ native.disconnectSignal(handle, id);
2189
+ forgetSignalHandlerId(handle, id);
2190
+ }
2191
+ return ids.length;
2203
2192
  }
2204
2193
  // Matches gjs's non-introspectable guard (GObject.js) — there is no func→data map.
2205
2194
  function signalHandlersDisconnectByData() {
2206
- throw new Error(
2207
- 'GObject.signal_handlers_disconnect_by_data() is not introspectable. Use ' +
2208
- 'GObject.signal_handlers_disconnect_by_func() instead.',
2209
- );
2195
+ throw new Error(
2196
+ 'GObject.signal_handlers_disconnect_by_data() is not introspectable. Use ' +
2197
+ 'GObject.signal_handlers_disconnect_by_func() instead.',
2198
+ );
2210
2199
  }
2211
2200
 
2212
2201
  // GObject.signal_connect / signal_connect_after / signal_emit_by_name — gjs's
@@ -2214,13 +2203,13 @@ function signalHandlersDisconnectByData() {
2214
2203
  // workaround for classes that shadow those names with their own methods). The
2215
2204
  // instance is the L1 wrapper, whose .connect records the id in the by-func registry.
2216
2205
  function signalConnect(object, name, handler) {
2217
- return object.connect(name, handler);
2206
+ return object.connect(name, handler);
2218
2207
  }
2219
2208
  function signalConnectAfter(object, name, handler) {
2220
- return object.connect_after(name, handler);
2209
+ return object.connect_after(name, handler);
2221
2210
  }
2222
2211
  function signalEmitByName(object, ...nameAndArgs) {
2223
- return object.emit(...nameAndArgs);
2212
+ return object.emit(...nameAndArgs);
2224
2213
  }
2225
2214
 
2226
2215
  // Overlay the GJS `GObject` runtime statics on top of the introspected GObject
@@ -2234,19 +2223,19 @@ function signalEmitByName(object, ...nameAndArgs) {
2234
2223
  // `GObject.Object` etc. keep resolving from introspection (with .new added in
2235
2224
  // makeClass). Merged enums are cached so identity is stable.
2236
2225
  const OVERLAY_NAMES = new Set([
2237
- 'registerClass',
2238
- 'ParamSpec',
2239
- 'ParamFlags',
2240
- 'SignalFlags',
2241
- 'Value',
2242
- 'AccumulatorType',
2243
- 'signal_handlers_block_by_func',
2244
- 'signal_handlers_unblock_by_func',
2245
- 'signal_handlers_disconnect_by_func',
2246
- 'signal_handlers_disconnect_by_data',
2247
- 'signal_connect',
2248
- 'signal_connect_after',
2249
- 'signal_emit_by_name',
2226
+ 'registerClass',
2227
+ 'ParamSpec',
2228
+ 'ParamFlags',
2229
+ 'SignalFlags',
2230
+ 'Value',
2231
+ 'AccumulatorType',
2232
+ 'signal_handlers_block_by_func',
2233
+ 'signal_handlers_unblock_by_func',
2234
+ 'signal_handlers_disconnect_by_func',
2235
+ 'signal_handlers_disconnect_by_data',
2236
+ 'signal_connect',
2237
+ 'signal_connect_after',
2238
+ 'signal_emit_by_name',
2250
2239
  ]);
2251
2240
 
2252
2241
  // The fundamental GObject.TYPE_* constants. GJS defines these in its GObject
@@ -2263,109 +2252,109 @@ const OVERLAY_NAMES = new Set([
2263
2252
  // that libgobject does not register on a plain node-gi host, so it has no correct
2264
2253
  // value here.)
2265
2254
  const GTYPE_CONSTANT_NAMES = {
2266
- TYPE_NONE: 'void',
2267
- TYPE_INTERFACE: 'GInterface',
2268
- TYPE_CHAR: 'gchar',
2269
- TYPE_UCHAR: 'guchar',
2270
- TYPE_BOOLEAN: 'gboolean',
2271
- TYPE_INT: 'gint',
2272
- TYPE_UINT: 'guint',
2273
- TYPE_LONG: 'glong',
2274
- TYPE_ULONG: 'gulong',
2275
- TYPE_INT64: 'gint64',
2276
- TYPE_UINT64: 'guint64',
2277
- TYPE_ENUM: 'GEnum',
2278
- TYPE_FLAGS: 'GFlags',
2279
- TYPE_FLOAT: 'gfloat',
2280
- TYPE_DOUBLE: 'gdouble',
2281
- TYPE_STRING: 'gchararray',
2282
- TYPE_POINTER: 'gpointer',
2283
- TYPE_BOXED: 'GBoxed',
2284
- TYPE_PARAM: 'GParam',
2285
- TYPE_OBJECT: 'GObject',
2286
- TYPE_VARIANT: 'GVariant',
2287
- TYPE_GTYPE: 'GType',
2288
- TYPE_UNICHAR: 'gint',
2255
+ TYPE_NONE: 'void',
2256
+ TYPE_INTERFACE: 'GInterface',
2257
+ TYPE_CHAR: 'gchar',
2258
+ TYPE_UCHAR: 'guchar',
2259
+ TYPE_BOOLEAN: 'gboolean',
2260
+ TYPE_INT: 'gint',
2261
+ TYPE_UINT: 'guint',
2262
+ TYPE_LONG: 'glong',
2263
+ TYPE_ULONG: 'gulong',
2264
+ TYPE_INT64: 'gint64',
2265
+ TYPE_UINT64: 'guint64',
2266
+ TYPE_ENUM: 'GEnum',
2267
+ TYPE_FLAGS: 'GFlags',
2268
+ TYPE_FLOAT: 'gfloat',
2269
+ TYPE_DOUBLE: 'gdouble',
2270
+ TYPE_STRING: 'gchararray',
2271
+ TYPE_POINTER: 'gpointer',
2272
+ TYPE_BOXED: 'GBoxed',
2273
+ TYPE_PARAM: 'GParam',
2274
+ TYPE_OBJECT: 'GObject',
2275
+ TYPE_VARIANT: 'GVariant',
2276
+ TYPE_GTYPE: 'GType',
2277
+ TYPE_UNICHAR: 'gint',
2289
2278
  };
2290
2279
 
2291
2280
  function isGObjectOverlayName(prop) {
2292
- return (
2293
- typeof prop === 'string' &&
2294
- (OVERLAY_NAMES.has(prop) || Object.prototype.hasOwnProperty.call(GTYPE_CONSTANT_NAMES, prop))
2295
- );
2281
+ return (
2282
+ typeof prop === 'string' &&
2283
+ (OVERLAY_NAMES.has(prop) || Object.prototype.hasOwnProperty.call(GTYPE_CONSTANT_NAMES, prop))
2284
+ );
2296
2285
  }
2297
2286
 
2298
2287
  function decorateGObjectNamespace(baseNs) {
2299
- const cache = new Map();
2300
- const resolve = (prop) => {
2301
- if (cache.has(prop)) return cache.get(prop);
2302
- let value;
2303
- if (prop === 'registerClass') value = registerClass;
2304
- else if (prop === 'ParamSpec') value = ParamSpec;
2305
- else if (prop === 'ParamFlags') value = mergeFlags(baseNs.ParamFlags, ParamFlags);
2306
- else if (prop === 'SignalFlags') value = mergeFlags(baseNs.SignalFlags, SignalFlags);
2307
- else if (prop === 'Value') value = valueClass;
2308
- else if (prop === 'AccumulatorType') value = AccumulatorType;
2309
- else if (prop === 'signal_handlers_block_by_func') value = signalHandlersBlockByFunc;
2310
- else if (prop === 'signal_handlers_unblock_by_func') value = signalHandlersUnblockByFunc;
2311
- else if (prop === 'signal_handlers_disconnect_by_func') value = signalHandlersDisconnectByFunc;
2312
- else if (prop === 'signal_handlers_disconnect_by_data') value = signalHandlersDisconnectByData;
2313
- else if (prop === 'signal_connect') value = signalConnect;
2314
- else if (prop === 'signal_connect_after') value = signalConnectAfter;
2315
- else if (prop === 'signal_emit_by_name') value = signalEmitByName;
2316
- else if (Object.prototype.hasOwnProperty.call(GTYPE_CONSTANT_NAMES, prop)) {
2317
- // Resolve the real GType from libgobject via the introspected type_from_name
2318
- // (0 / unregistered → null, matching object.cc's GType marshalling).
2319
- value = baseNs.type_from_name(GTYPE_CONSTANT_NAMES[prop]);
2320
- }
2321
- cache.set(prop, value);
2322
- return value;
2323
- };
2324
- return new Proxy(baseNs, {
2325
- get(t, prop) {
2326
- return isGObjectOverlayName(prop) ? resolve(prop) : t[prop];
2327
- },
2328
- has(t, prop) {
2329
- return isGObjectOverlayName(prop) || prop in t;
2330
- },
2331
- });
2288
+ const cache = new Map();
2289
+ const resolve = (prop) => {
2290
+ if (cache.has(prop)) return cache.get(prop);
2291
+ let value;
2292
+ if (prop === 'registerClass') value = registerClass;
2293
+ else if (prop === 'ParamSpec') value = ParamSpec;
2294
+ else if (prop === 'ParamFlags') value = mergeFlags(baseNs.ParamFlags, ParamFlags);
2295
+ else if (prop === 'SignalFlags') value = mergeFlags(baseNs.SignalFlags, SignalFlags);
2296
+ else if (prop === 'Value') value = valueClass;
2297
+ else if (prop === 'AccumulatorType') value = AccumulatorType;
2298
+ else if (prop === 'signal_handlers_block_by_func') value = signalHandlersBlockByFunc;
2299
+ else if (prop === 'signal_handlers_unblock_by_func') value = signalHandlersUnblockByFunc;
2300
+ else if (prop === 'signal_handlers_disconnect_by_func') value = signalHandlersDisconnectByFunc;
2301
+ else if (prop === 'signal_handlers_disconnect_by_data') value = signalHandlersDisconnectByData;
2302
+ else if (prop === 'signal_connect') value = signalConnect;
2303
+ else if (prop === 'signal_connect_after') value = signalConnectAfter;
2304
+ else if (prop === 'signal_emit_by_name') value = signalEmitByName;
2305
+ else if (Object.prototype.hasOwnProperty.call(GTYPE_CONSTANT_NAMES, prop)) {
2306
+ // Resolve the real GType from libgobject via the introspected type_from_name
2307
+ // (0 / unregistered → null, matching object.cc's GType marshalling).
2308
+ value = baseNs.type_from_name(GTYPE_CONSTANT_NAMES[prop]);
2309
+ }
2310
+ cache.set(prop, value);
2311
+ return value;
2312
+ };
2313
+ return new Proxy(baseNs, {
2314
+ get(t, prop) {
2315
+ return isGObjectOverlayName(prop) ? resolve(prop) : t[prop];
2316
+ },
2317
+ has(t, prop) {
2318
+ return isGObjectOverlayName(prop) || prop in t;
2319
+ },
2320
+ });
2332
2321
  }
2333
2322
 
2334
2323
  function createNamespace(namespace) {
2335
- const cache = new Map();
2336
- return new Proxy(Object.create(null), {
2337
- get(_t, prop) {
2338
- if (typeof prop !== 'string' || RESERVED.has(prop)) return undefined;
2339
- if (cache.has(prop)) return cache.get(prop);
2340
- const info = native.findInfo(namespace, prop);
2341
- let value;
2342
- if (info === null) {
2343
- value = undefined;
2344
- } else if (info.kind === 'function') {
2345
- value = (...args) => wrapReturn(native.callFunction(namespace, prop, unwrapArgs(args)));
2346
- } else if (info.kind === 'object' || info.kind === 'interface') {
2347
- // Interfaces are not constructible (`new` throws via newObject) but
2348
- // carry static/constructor methods, e.g. Gio.File.new_for_path.
2349
- value = makeClass(namespace, prop);
2350
- } else if (info.kind === 'enum' || info.kind === 'flags') {
2351
- value = makeEnum(namespace, prop);
2352
- } else if (info.kind === 'constant') {
2353
- value = native.getConstantValue(namespace, prop);
2354
- } else if (info.kind === 'struct' || info.kind === 'union') {
2355
- // Boxed/struct types: static/constructor methods + boxed instances
2356
- // (GLib.MainLoop, …). Field access lands with the broader structs drop.
2357
- value = makeStruct(namespace, prop);
2358
- } else {
2359
- // interface / callback: surfaced in a later drop.
2360
- value = undefined;
2361
- }
2362
- cache.set(prop, value);
2363
- return value;
2364
- },
2365
- has(_t, prop) {
2366
- return typeof prop === 'string' && native.findInfo(namespace, prop) !== null;
2367
- },
2368
- });
2324
+ const cache = new Map();
2325
+ return new Proxy(Object.create(null), {
2326
+ get(_t, prop) {
2327
+ if (typeof prop !== 'string' || RESERVED.has(prop)) return undefined;
2328
+ if (cache.has(prop)) return cache.get(prop);
2329
+ const info = native.findInfo(namespace, prop);
2330
+ let value;
2331
+ if (info === null) {
2332
+ value = undefined;
2333
+ } else if (info.kind === 'function') {
2334
+ value = (...args) => wrapReturn(native.callFunction(namespace, prop, unwrapArgs(args)));
2335
+ } else if (info.kind === 'object' || info.kind === 'interface') {
2336
+ // Interfaces are not constructible (`new` throws via newObject) but
2337
+ // carry static/constructor methods, e.g. Gio.File.new_for_path.
2338
+ value = makeClass(namespace, prop);
2339
+ } else if (info.kind === 'enum' || info.kind === 'flags') {
2340
+ value = makeEnum(namespace, prop);
2341
+ } else if (info.kind === 'constant') {
2342
+ value = native.getConstantValue(namespace, prop);
2343
+ } else if (info.kind === 'struct' || info.kind === 'union') {
2344
+ // Boxed/struct types: static/constructor methods + boxed instances
2345
+ // (GLib.MainLoop, …). Field access lands with the broader structs drop.
2346
+ value = makeStruct(namespace, prop);
2347
+ } else {
2348
+ // interface / callback: surfaced in a later drop.
2349
+ value = undefined;
2350
+ }
2351
+ cache.set(prop, value);
2352
+ return value;
2353
+ },
2354
+ has(_t, prop) {
2355
+ return typeof prop === 'string' && native.findInfo(namespace, prop) !== null;
2356
+ },
2357
+ });
2369
2358
  }
2370
2359
 
2371
2360
  const namespaceCache = new Map();
@@ -2383,66 +2372,66 @@ let loopAttached = false;
2383
2372
  * @returns {Record<string, unknown>}
2384
2373
  */
2385
2374
  export function requireGi(namespace, version) {
2386
- native.requireNamespace(namespace, version);
2387
- // Attach the libuv↔GLib integration once. On Node this arms BOTH directions:
2388
- // the uv-in-GLib bridge (a blocking GLib.MainLoop.run / Gio.Application.run
2389
- // keeps Node's event loop alive) AND the uv-driven auto-pump (pending GLib
2390
- // sources — Gio async completions, GLib timeouts/idles, DBus — dispatch from
2391
- // Node's own loop, so async gi:// code works with NO explicit mainloop). This
2392
- // is Node-only: Bun/Deno have no usable libuv (Deno exports no libuv symbols;
2393
- // Bun panics on uv_backend_fd). There, GLib async is co-pumped from the runtime
2394
- // loop via the portable GLib-iteration pump instead (see startMainContextPump /
2395
- // runAsync).
2396
- if (!loopAttached) {
2397
- if (native.isNodeRuntime) {
2398
- native.startMainLoop();
2399
- // Bootstrap kick for the auto-pump: with ONLY unref'd handles active,
2400
- // uv_run exits without running the pump's prepare/check phase at all, so
2401
- // an otherwise-empty loop would never arm GLib's timer/fd wake-ups (a
2402
- // pending top-level await on a GLib timeout would exit-13 unsettled).
2403
- // 'beforeExit' fires exactly when the loop runs empty: drain what is
2404
- // ready and re-arm — if GLib still has scheduled work, the armed (ref'd)
2405
- // uv timer revives the loop; otherwise the process exits normally.
2406
- runtimeProcess.on('beforeExit', () => native.pumpKick());
2407
- } else if (native.RUNTIME !== 'gjs') {
2408
- // Bun/Deno: no libuv to hook, so the portable timer pump IS the auto-pump.
2409
- // Armed HERE, not left to the caller, so that `bun bundle.mjs` /
2410
- // `deno run bundle.mjs` behaves like `node bundle.mjs` and `gjs -m`:
2411
- // GLib sources dispatch with no explicit loop anywhere, and the pump's
2412
- // keep-alive accounting decides when the process may exit. Leaving it
2413
- // opt-in is what made the same bundle exit 0 MID-suite on bun/deno while
2414
- // node ran it to completion.
2415
- startMainContextPump(); // permanent +1 — the auto-pump is never disposed
2416
- // Tag that hold as the AUTO pump's: pumpBeat dispatches for it only while
2417
- // a JS-armed GI callback is outstanding, so a finished program produces
2418
- // no teardown churn (the deno #47 window) — explicit callers keep
2419
- // unconditional dispatch.
2420
- pumpAutoRefs++;
2421
- // The portable analogue of the Node pump's beforeExit kick. The runtime's
2422
- // loop just ran empty, which is exactly the moment an unref'd pump would
2423
- // let the process die with GLib work still outstanding: run one beat —
2424
- // if the program still owns GLib work it drains + re-refs (reviving the
2425
- // loop); if not, the beat stays silent and the process exits.
2426
- runtimeProcess.on('beforeExit', pumpBeat);
2375
+ native.requireNamespace(namespace, version);
2376
+ // Attach the libuv↔GLib integration once. On Node this arms BOTH directions:
2377
+ // the uv-in-GLib bridge (a blocking GLib.MainLoop.run / Gio.Application.run
2378
+ // keeps Node's event loop alive) AND the uv-driven auto-pump (pending GLib
2379
+ // sources — Gio async completions, GLib timeouts/idles, DBus — dispatch from
2380
+ // Node's own loop, so async gi:// code works with NO explicit mainloop). This
2381
+ // is Node-only: Bun/Deno have no usable libuv (Deno exports no libuv symbols;
2382
+ // Bun panics on uv_backend_fd). There, GLib async is co-pumped from the runtime
2383
+ // loop via the portable GLib-iteration pump instead (see startMainContextPump /
2384
+ // runAsync).
2385
+ if (!loopAttached) {
2386
+ if (native.isNodeRuntime) {
2387
+ native.startMainLoop();
2388
+ // Bootstrap kick for the auto-pump: with ONLY unref'd handles active,
2389
+ // uv_run exits without running the pump's prepare/check phase at all, so
2390
+ // an otherwise-empty loop would never arm GLib's timer/fd wake-ups (a
2391
+ // pending top-level await on a GLib timeout would exit-13 unsettled).
2392
+ // 'beforeExit' fires exactly when the loop runs empty: drain what is
2393
+ // ready and re-arm — if GLib still has scheduled work, the armed (ref'd)
2394
+ // uv timer revives the loop; otherwise the process exits normally.
2395
+ runtimeProcess.on('beforeExit', () => native.pumpKick());
2396
+ } else if (native.RUNTIME !== 'gjs') {
2397
+ // Bun/Deno: no libuv to hook, so the portable timer pump IS the auto-pump.
2398
+ // Armed HERE, not left to the caller, so that `bun bundle.mjs` /
2399
+ // `deno run bundle.mjs` behaves like `node bundle.mjs` and `gjs -m`:
2400
+ // GLib sources dispatch with no explicit loop anywhere, and the pump's
2401
+ // keep-alive accounting decides when the process may exit. Leaving it
2402
+ // opt-in is what made the same bundle exit 0 MID-suite on bun/deno while
2403
+ // node ran it to completion.
2404
+ startMainContextPump(); // permanent +1 — the auto-pump is never disposed
2405
+ // Tag that hold as the AUTO pump's: pumpBeat dispatches for it only while
2406
+ // a JS-armed GI callback is outstanding, so a finished program produces
2407
+ // no teardown churn (the deno #47 window) — explicit callers keep
2408
+ // unconditional dispatch.
2409
+ pumpAutoRefs++;
2410
+ // The portable analogue of the Node pump's beforeExit kick. The runtime's
2411
+ // loop just ran empty, which is exactly the moment an unref'd pump would
2412
+ // let the process die with GLib work still outstanding: run one beat —
2413
+ // if the program still owns GLib work it drains + re-refs (reviving the
2414
+ // loop); if not, the beat stays silent and the process exits.
2415
+ runtimeProcess.on('beforeExit', pumpBeat);
2416
+ }
2417
+ loopAttached = true;
2418
+ }
2419
+ const key = version ? `${namespace}@${version}` : namespace;
2420
+ let ns = namespaceCache.get(key);
2421
+ if (ns === undefined) {
2422
+ ns = createNamespace(namespace);
2423
+ // The GObject namespace also carries the GJS runtime statics (registerClass +
2424
+ // ParamSpec/ParamFlags/SignalFlags) layered over its introspected members.
2425
+ if (namespace === 'GObject') ns = decorateGObjectNamespace(ns);
2426
+ // The GLib namespace carries the GJS-shaped GLib.Variant ergonomics
2427
+ // (new GLib.Variant(sig, value) + deepUnpack/unpack/recursiveUnpack) and the
2428
+ // GLib.Error class.
2429
+ else if (namespace === 'GLib') ns = decorateGLibNamespace(ns);
2430
+ // The Gio namespace carries Gio._promisify (async → Promise).
2431
+ else if (namespace === 'Gio') ns = decorateGioNamespace(ns);
2432
+ namespaceCache.set(key, ns);
2427
2433
  }
2428
- loopAttached = true;
2429
- }
2430
- const key = version ? `${namespace}@${version}` : namespace;
2431
- let ns = namespaceCache.get(key);
2432
- if (ns === undefined) {
2433
- ns = createNamespace(namespace);
2434
- // The GObject namespace also carries the GJS runtime statics (registerClass +
2435
- // ParamSpec/ParamFlags/SignalFlags) layered over its introspected members.
2436
- if (namespace === 'GObject') ns = decorateGObjectNamespace(ns);
2437
- // The GLib namespace carries the GJS-shaped GLib.Variant ergonomics
2438
- // (new GLib.Variant(sig, value) + deepUnpack/unpack/recursiveUnpack) and the
2439
- // GLib.Error class.
2440
- else if (namespace === 'GLib') ns = decorateGLibNamespace(ns);
2441
- // The Gio namespace carries Gio._promisify (async → Promise).
2442
- else if (namespace === 'Gio') ns = decorateGioNamespace(ns);
2443
- namespaceCache.set(key, ns);
2444
- }
2445
- return ns;
2434
+ return ns;
2446
2435
  }
2447
2436
 
2448
2437
  /**
@@ -2452,12 +2441,12 @@ export function requireGi(namespace, version) {
2452
2441
  * @returns {unknown}
2453
2442
  */
2454
2443
  export function unwrap(value) {
2455
- // NOT unwrapArg: the public unwrap only extracts handles — it must never
2456
- // replace a function with the engine-facing callback shim.
2457
- if (value !== null && typeof value === 'object' && value[HANDLE] !== undefined) {
2458
- return value[HANDLE];
2459
- }
2460
- return value;
2444
+ // NOT unwrapArg: the public unwrap only extracts handles — it must never
2445
+ // replace a function with the engine-facing callback shim.
2446
+ if (value !== null && typeof value === 'object' && value[HANDLE] !== undefined) {
2447
+ return value[HANDLE];
2448
+ }
2449
+ return value;
2461
2450
  }
2462
2451
 
2463
2452
  export default requireGi;