@gjsify/node-gi 0.38.1 → 0.40.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/README.md +41 -0
- package/gi.js +184 -57
- package/index.d.ts +20 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/addon.cc +2 -0
- package/src/calls.cc +69 -3
- package/src/common.h +6 -0
- package/src/loop.cc +29 -5
- package/src/object.cc +33 -0
package/README.md
CHANGED
|
@@ -479,6 +479,47 @@ A *passive* fd source with no pending op (e.g. only a listening
|
|
|
479
479
|
`Gio.SocketService`) does not keep the process alive on its own, and a
|
|
480
480
|
purely-sync program still exits immediately.
|
|
481
481
|
|
|
482
|
+
#### Class prototypes and the lookup order
|
|
483
|
+
|
|
484
|
+
`Ns.Class.prototype` is a real prototype: reading a name on it MATERIALIZES the
|
|
485
|
+
introspected method behind that name (the JS twin of the `resolve` hook GJS
|
|
486
|
+
installs on every GObject prototype), and every wrapper resolves its members
|
|
487
|
+
through the prototype of its runtime GType — a `registerClass` subclass's own
|
|
488
|
+
prototype, otherwise the introspected class's — before any native fallback. So
|
|
489
|
+
instrumenting a method works the way it does on GJS:
|
|
490
|
+
|
|
491
|
+
```js
|
|
492
|
+
const proto = Gio.ZlibDecompressor.prototype;
|
|
493
|
+
const original = proto.convert; // the introspected method
|
|
494
|
+
proto.convert = function (...args) { // instrument it
|
|
495
|
+
calls++;
|
|
496
|
+
return original.apply(this, args);
|
|
497
|
+
};
|
|
498
|
+
const d = new Gio.ZlibDecompressor({ format: Gio.ZlibCompressorFormat.GZIP });
|
|
499
|
+
d.convert === proto.convert; // true — and the wrapper IS called
|
|
500
|
+
proto.convert = original; // put it back
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
Until 0.39 an instance ignored its prototype: the assignment stuck, `d.convert`
|
|
504
|
+
still reached the native method, and a spy-based test reported itself installed
|
|
505
|
+
while measuring nothing. Byte-compared against GJS by the `prototype-chain`
|
|
506
|
+
conformance program.
|
|
507
|
+
|
|
508
|
+
Lookup order on a wrapper: own JS field → the class prototype chain →
|
|
509
|
+
`runAsync` / the GObject.js shims → GObject property → inherited `Object.prototype`
|
|
510
|
+
member → a `Gio._promisify` registration → introspected method → `undefined`.
|
|
511
|
+
|
|
512
|
+
Deliberate divergences from GJS, all measured:
|
|
513
|
+
|
|
514
|
+
- class prototypes are NOT chained to their base classes, so `instanceof` goes
|
|
515
|
+
through `g_type_is_a` (see below) and an interface method materializes once per
|
|
516
|
+
implementing class instead of being one shared function;
|
|
517
|
+
- GObject **properties** are not surfaced as prototype accessors — they resolve on
|
|
518
|
+
the instance only;
|
|
519
|
+
- a private concrete GType (`GLocalFile`) resolves to its nearest INTROSPECTABLE
|
|
520
|
+
ancestor rather than to the interface GJS would use, which is why
|
|
521
|
+
`Gio._promisify` also keeps a per-class registry.
|
|
522
|
+
|
|
482
523
|
#### `GLib.Variant` (build + unpack, GJS semantics)
|
|
483
524
|
|
|
484
525
|
`new GLib.Variant(signature, value)` recursively builds a GVariant from a type
|
package/gi.js
CHANGED
|
@@ -149,16 +149,16 @@ function buildGError(domainName, domainQuark, code, message) {
|
|
|
149
149
|
native.setErrorBuilder(buildGError);
|
|
150
150
|
|
|
151
151
|
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
152
|
-
// record WHICH introspected class a registration belongs to —
|
|
153
|
-
//
|
|
154
|
-
//
|
|
152
|
+
// record WHICH introspected class a registration belongs to — matched against the
|
|
153
|
+
// instance's GType via native.isInstanceOf when the promisified prototype is not the
|
|
154
|
+
// one the instance resolves through.
|
|
155
155
|
const CLASS_INFO = Symbol('nodeGiClassInfo');
|
|
156
156
|
|
|
157
157
|
// Promisified async methods: GI method name (snake_case) → registrations
|
|
158
|
-
// `{ namespace?, typeName?, wrapper }`.
|
|
159
|
-
//
|
|
160
|
-
//
|
|
161
|
-
// class the instance is-a.
|
|
158
|
+
// `{ namespace?, typeName?, wrapper }`. An instance whose concrete GType resolves to a
|
|
159
|
+
// DIFFERENT prototype than the promisified one (a private GLocalFile against
|
|
160
|
+
// Gio.File.prototype) reaches the wrapper only through this registry; when two classes
|
|
161
|
+
// promisify the SAME method name it picks the registration whose class the instance is-a.
|
|
162
162
|
const promisifiedMethods = new Map();
|
|
163
163
|
|
|
164
164
|
function wrapReturn(value) {
|
|
@@ -1113,11 +1113,42 @@ function instanceHasMethod(handle, name) {
|
|
|
1113
1113
|
return present;
|
|
1114
1114
|
}
|
|
1115
1115
|
|
|
1116
|
-
//
|
|
1117
|
-
//
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
//
|
|
1116
|
+
// The JS prototype a wrapper resolves its members through, by runtime GType name.
|
|
1117
|
+
// Stable for the process — a GType neither gains ancestors nor changes class.
|
|
1118
|
+
const protoByGTypeName = new Map();
|
|
1119
|
+
|
|
1120
|
+
// EVERY wrapper gets a prototype, as in GJS where an instance's [[Prototype]] IS
|
|
1121
|
+
// its class's prototype: without one, a member assigned to `Ns.Class.prototype` was
|
|
1122
|
+
// accepted and observable there yet invisible to the instance, which kept reaching
|
|
1123
|
+
// the native method underneath (#1175) — a spy that reports itself installed and
|
|
1124
|
+
// then measures nothing. A registerClass subclass's own prototype when the GType is
|
|
1125
|
+
// one we registered, else the introspected class's.
|
|
1126
|
+
function protoForInstance(handle) {
|
|
1127
|
+
const gtypeName = native.getTypeName(handle);
|
|
1128
|
+
if (gtypeName === null) return undefined;
|
|
1129
|
+
const cached = protoByGTypeName.get(gtypeName);
|
|
1130
|
+
if (cached !== undefined) return cached === null ? undefined : cached;
|
|
1131
|
+
const registered = classesByGType.get(gtypeName);
|
|
1132
|
+
let proto = registered !== undefined ? registered.prototype : null;
|
|
1133
|
+
if (proto === null) {
|
|
1134
|
+
// Only LOADED namespaces are searched, and a namespace object is reached
|
|
1135
|
+
// through the same per-namespace cache `requireGi` fills, so the prototype
|
|
1136
|
+
// resolved here is the very object a program can patch.
|
|
1137
|
+
const info = native.classInfoForTypeName(gtypeName);
|
|
1138
|
+
const cls = info === null ? undefined : namespaceObject(info.namespace)[info.name];
|
|
1139
|
+
if (cls !== undefined && cls !== null) proto = cls.prototype ?? null;
|
|
1140
|
+
}
|
|
1141
|
+
protoByGTypeName.set(gtypeName, proto);
|
|
1142
|
+
return proto === null ? undefined : proto;
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// Wrap a live GObject handle as a GJS-shaped instance. The wrapper resolves its
|
|
1146
|
+
// class prototype chain FIRST (a registerClass subclass's own methods, anything
|
|
1147
|
+
// the program put on the introspected prototype) — so `inst.myMethod()` runs the
|
|
1148
|
+
// JS method with the wrapper as `this` — then falls back to GObject property
|
|
1149
|
+
// get/set and GI method routing. `userProto` overrides the prototype derived from
|
|
1150
|
+
// the handle's GType, for the construction paths that already know the leaf class.
|
|
1151
|
+
// `.connect()/.emit()/.disconnect()` work in both modes.
|
|
1121
1152
|
function wrapInstance(handle, userProto) {
|
|
1122
1153
|
const cached = instanceCache.get(handle);
|
|
1123
1154
|
if (cached !== undefined) {
|
|
@@ -1139,7 +1170,8 @@ function wrapInstance(handle, userProto) {
|
|
|
1139
1170
|
return cached;
|
|
1140
1171
|
}
|
|
1141
1172
|
const target = { [HANDLE]: handle };
|
|
1142
|
-
|
|
1173
|
+
const proto = userProto ?? protoForInstance(handle);
|
|
1174
|
+
if (proto !== undefined) target[USER_PROTO] = proto;
|
|
1143
1175
|
const proxy = new Proxy(target, {
|
|
1144
1176
|
get(t, prop) {
|
|
1145
1177
|
if (prop === HANDLE) return handle;
|
|
@@ -1181,12 +1213,22 @@ function wrapInstance(handle, userProto) {
|
|
|
1181
1213
|
default:
|
|
1182
1214
|
break;
|
|
1183
1215
|
}
|
|
1216
|
+
// A plain JS field previously written on THIS wrapper. With the toggle-ref
|
|
1217
|
+
// bridge the wrapper is CANONICAL (one proxy per GObject, cached by the
|
|
1218
|
+
// canonical native handle), so a plain field IS shared across the
|
|
1219
|
+
// vfunc<->instance boundary and survives a round-trip + GC while C owns the
|
|
1220
|
+
// object. Read FIRST, before the class prototype — ordinary JS lookup order.
|
|
1221
|
+
// (GObject PROPERTIES remain the right choice for state C must also see; the
|
|
1222
|
+
// `set` trap routes those to GObject, so no expando competes with one.)
|
|
1223
|
+
if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
|
|
1184
1224
|
const up = t[USER_PROTO];
|
|
1185
1225
|
if (up !== undefined) {
|
|
1186
1226
|
const desc = findProtoDescriptor(up, prop);
|
|
1187
1227
|
if (desc !== undefined) {
|
|
1188
|
-
if (typeof desc.value === 'function') return (...args) => desc.value.apply(proxy, args);
|
|
1189
1228
|
if (typeof desc.get === 'function') return desc.get.call(proxy);
|
|
1229
|
+
// The prototype's OWN function, never a re-bound thunk: `this` comes
|
|
1230
|
+
// from the call site anyway, and re-binding breaks the identity a
|
|
1231
|
+
// spy is recognised by — `inst.m === Cls.prototype.m` (#1175).
|
|
1190
1232
|
return desc.value;
|
|
1191
1233
|
}
|
|
1192
1234
|
}
|
|
@@ -1210,25 +1252,16 @@ function wrapInstance(handle, userProto) {
|
|
|
1210
1252
|
if (native.hasProperty(handle, propName)) {
|
|
1211
1253
|
return wrapReturn(native.getProperty(handle, propName));
|
|
1212
1254
|
}
|
|
1213
|
-
// Surface a plain JS field previously written on THIS wrapper. With the
|
|
1214
|
-
// toggle-ref bridge the wrapper is now CANONICAL (one proxy per GObject,
|
|
1215
|
-
// cached by the canonical native handle), so a plain field IS shared across
|
|
1216
|
-
// the vfunc<->instance boundary and survives a round-trip + GC while C owns
|
|
1217
|
-
// the object — a vfunc's `this` resolves to the same cached proxy as
|
|
1218
|
-
// construct, and `store.get_item(x)` returns the same proxy a setter wrote
|
|
1219
|
-
// to. Own-property only (set via the `set` trap), so an introspected GI
|
|
1220
|
-
// method of the same name is never shadowed unless the user explicitly
|
|
1221
|
-
// assigned an expando. (GObject PROPERTIES remain the right choice for state
|
|
1222
|
-
// that must also be visible to C / other language bindings.)
|
|
1223
|
-
if (Object.prototype.hasOwnProperty.call(t, prop)) return t[prop];
|
|
1224
1255
|
// LAST resort before treating an unknown name as a GI method: an INHERITED
|
|
1225
1256
|
// member (Object.prototype.hasOwnProperty / isPrototypeOf / … — RESERVED
|
|
1226
1257
|
// already covers toString/valueOf/etc.) must resolve to the real function,
|
|
1227
1258
|
// not a GI callMethod thunk that would throw on `inst.hasOwnProperty('x')`.
|
|
1228
1259
|
if (prop in t) return t[prop];
|
|
1229
|
-
// A Gio._promisify'd async method
|
|
1230
|
-
//
|
|
1231
|
-
//
|
|
1260
|
+
// A Gio._promisify'd async method. The prototype lookup above already finds
|
|
1261
|
+
// it whenever the promisified prototype is on the instance's chain; the
|
|
1262
|
+
// registry covers what a chain cannot reach — a private concrete type
|
|
1263
|
+
// (GLocalFile) resolves to its nearest INTROSPECTABLE ancestor, so an
|
|
1264
|
+
// instance of it never sees `Gio.File.prototype`. Bound to this instance.
|
|
1232
1265
|
const promisified = resolvePromisified(handle, camelToSnake(prop));
|
|
1233
1266
|
if (promisified !== undefined) return (...args) => promisified.apply(proxy, args);
|
|
1234
1267
|
// GJS parity: an UNKNOWN member is `undefined`, never a throw-on-call
|
|
@@ -1335,6 +1368,91 @@ function defineLazyGType(ctor, namespace, typeName) {
|
|
|
1335
1368
|
});
|
|
1336
1369
|
}
|
|
1337
1370
|
|
|
1371
|
+
// The GObject behind a prototype method's `this`. A prototype function is
|
|
1372
|
+
// reachable without an instance (`Cls.prototype.m()`, a detached reference), so
|
|
1373
|
+
// the engine must never be handed `undefined` for the instance argument.
|
|
1374
|
+
function prototypeMethodHandle(self, where) {
|
|
1375
|
+
const handle = self === null || self === undefined ? undefined : self[HANDLE];
|
|
1376
|
+
if (handle === undefined) throw new TypeError(`${where}: \`this\` is not a node-gi instance`);
|
|
1377
|
+
return handle;
|
|
1378
|
+
}
|
|
1379
|
+
|
|
1380
|
+
// The invocable form of an introspected instance method, as it sits on a class
|
|
1381
|
+
// prototype: `this` supplies the GObject, so ONE function serves every instance of
|
|
1382
|
+
// the class and `inst.m === Cls.prototype.m` holds (gjs parity).
|
|
1383
|
+
function makeGiMethod(where, method) {
|
|
1384
|
+
const fn = function (...args) {
|
|
1385
|
+
return wrapReturn(native.callMethod(prototypeMethodHandle(this, where), method, unwrapArgs(args)));
|
|
1386
|
+
};
|
|
1387
|
+
Object.defineProperty(fn, 'name', { value: method, configurable: true });
|
|
1388
|
+
return fn;
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1391
|
+
// A class prototype that MATERIALIZES its introspected methods on first look-up —
|
|
1392
|
+
// the JS twin of the SpiderMonkey `resolve` hook gjs installs on every GObject
|
|
1393
|
+
// prototype (refs/gjs gi/object.cpp). Without it a node-gi prototype stayed
|
|
1394
|
+
// permanently empty, so `Cls.prototype.method` read `undefined` and wrapping a
|
|
1395
|
+
// method had nothing to wrap (#1175). Lazy, not eager: a class like Gtk.Widget
|
|
1396
|
+
// carries hundreds of inherited methods and a program touches a handful.
|
|
1397
|
+
function makeClassPrototype(namespace, typeName) {
|
|
1398
|
+
const target = {};
|
|
1399
|
+
// Put the vfunc chain-up Proxy beneath the prototype so a registerClass
|
|
1400
|
+
// subclass's `super.vfunc_<name>(...)` resolves (see vfuncChainProto).
|
|
1401
|
+
Object.setPrototypeOf(target, vfuncChainProto);
|
|
1402
|
+
// Stamp the prototype with its class identity so Gio._promisify(Cls.prototype, …)
|
|
1403
|
+
// can record which class a registration belongs to (non-enumerable).
|
|
1404
|
+
Object.defineProperty(target, CLASS_INFO, {
|
|
1405
|
+
value: { namespace, typeName },
|
|
1406
|
+
enumerable: false,
|
|
1407
|
+
configurable: true,
|
|
1408
|
+
});
|
|
1409
|
+
const where = `${namespace}.${typeName}.prototype`;
|
|
1410
|
+
const resolved = new Map(); // accessor name -> is an introspected method of this class
|
|
1411
|
+
// Writable + configurable: a program REPLACES a method to instrument it and puts
|
|
1412
|
+
// the original back afterwards, which is the whole point of the prototype.
|
|
1413
|
+
// Enumerable, like the methods gjs's resolve hook defines.
|
|
1414
|
+
const define = (prop, value) => {
|
|
1415
|
+
Object.defineProperty(target, prop, { value, writable: true, enumerable: true, configurable: true });
|
|
1416
|
+
return true;
|
|
1417
|
+
};
|
|
1418
|
+
// Defines the member on the target and reports whether the name resolved, so the
|
|
1419
|
+
// traps answer from the target itself and stay within the Proxy invariants (a
|
|
1420
|
+
// reported own property must really exist on an extensible target).
|
|
1421
|
+
const materialize = (prop) => {
|
|
1422
|
+
if (typeof prop !== 'string' || RESERVED.has(prop)) return false;
|
|
1423
|
+
if (Object.prototype.hasOwnProperty.call(target, prop)) return true;
|
|
1424
|
+
// gjs's GObject.Object.prototype members are JS shims over namespace-level
|
|
1425
|
+
// functions (see objectPrototypeShim), and `bind_property_full` even HAS an
|
|
1426
|
+
// introspected GIR shadow — bind_with_closures, whose GValue write-back a
|
|
1427
|
+
// marshaled GClosure cannot satisfy — so the shim must win here too.
|
|
1428
|
+
const shim = objectPrototypeShim(prop);
|
|
1429
|
+
if (shim !== undefined) {
|
|
1430
|
+
return define(prop, function (...args) {
|
|
1431
|
+
return shim(prototypeMethodHandle(this, `${where}.${prop}`), args);
|
|
1432
|
+
});
|
|
1433
|
+
}
|
|
1434
|
+
let present = resolved.get(prop);
|
|
1435
|
+
if (present === undefined) {
|
|
1436
|
+
present = native.hasClassMethod(namespace, typeName, prop);
|
|
1437
|
+
resolved.set(prop, present);
|
|
1438
|
+
}
|
|
1439
|
+
return present && define(prop, makeGiMethod(where, prop));
|
|
1440
|
+
};
|
|
1441
|
+
return new Proxy(target, {
|
|
1442
|
+
get(t, prop, receiver) {
|
|
1443
|
+
materialize(prop);
|
|
1444
|
+
return Reflect.get(t, prop, receiver);
|
|
1445
|
+
},
|
|
1446
|
+
getOwnPropertyDescriptor(t, prop) {
|
|
1447
|
+
materialize(prop);
|
|
1448
|
+
return Reflect.getOwnPropertyDescriptor(t, prop);
|
|
1449
|
+
},
|
|
1450
|
+
has(t, prop) {
|
|
1451
|
+
return materialize(prop) || Reflect.has(t, prop);
|
|
1452
|
+
},
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1338
1456
|
function makeClass(namespace, typeName) {
|
|
1339
1457
|
const ctor = function ctor(props) {
|
|
1340
1458
|
// G3/G2 construction routing. `new.target` is the leaf class the `new` was
|
|
@@ -1382,20 +1500,12 @@ function makeClass(namespace, typeName) {
|
|
|
1382
1500
|
Object.defineProperty(ctor, 'name', { value: typeName, configurable: true });
|
|
1383
1501
|
ctor.$gtypeName = `${namespace}.${typeName}`;
|
|
1384
1502
|
defineLazyGType(ctor, namespace, typeName);
|
|
1385
|
-
|
|
1386
|
-
// registerClass subclass's `super.vfunc_<name>(...)` resolves (see above).
|
|
1387
|
-
Object.setPrototypeOf(ctor.prototype, vfuncChainProto);
|
|
1388
|
-
// Stamp the prototype with its class identity so Gio._promisify(Cls.prototype, …)
|
|
1389
|
-
// can record which class a registration belongs to (non-enumerable).
|
|
1390
|
-
Object.defineProperty(ctor.prototype, CLASS_INFO, {
|
|
1391
|
-
value: { namespace, typeName },
|
|
1392
|
-
enumerable: false,
|
|
1393
|
-
configurable: true,
|
|
1394
|
-
});
|
|
1503
|
+
ctor.prototype = makeClassPrototype(namespace, typeName);
|
|
1395
1504
|
// `inst instanceof Ns.Class` — GJS parity for the WHOLE GObject hierarchy, not
|
|
1396
|
-
// just the leaf class. An instance is a Proxy
|
|
1397
|
-
//
|
|
1398
|
-
//
|
|
1505
|
+
// just the leaf class. An instance is a Proxy carrying its class prototype as a
|
|
1506
|
+
// SYMBOL rather than as [[Prototype]], and class prototypes are not linked to their
|
|
1507
|
+
// bases (no Adw.ApplicationWindow → Gtk.ApplicationWindow → … chain), so the
|
|
1508
|
+
// default instanceof (a prototype-chain walk) reported `false` for every base
|
|
1399
1509
|
// class. Resolve it by the GObject type system instead: `native.isInstanceOf`
|
|
1400
1510
|
// (g_type_is_a) recognises subclasses AND implemented interfaces, exactly like GJS.
|
|
1401
1511
|
// Guarded by `isGObjectHandle` first — a bare/boxed/variant handle (or any non-node-gi
|
|
@@ -1456,6 +1566,14 @@ function makeClass(namespace, typeName) {
|
|
|
1456
1566
|
return (...args) => wrapReturn(native.callStaticMethod(namespace, typeName, giName, unwrapArgs(args)));
|
|
1457
1567
|
},
|
|
1458
1568
|
});
|
|
1569
|
+
// Replacing ctor.prototype dropped the automatic back-link; restore it pointing at
|
|
1570
|
+
// the class the user actually holds (the Proxy), not the raw ctor behind it.
|
|
1571
|
+
Object.defineProperty(ctor.prototype, 'constructor', {
|
|
1572
|
+
value: proxy,
|
|
1573
|
+
writable: true,
|
|
1574
|
+
enumerable: false,
|
|
1575
|
+
configurable: true,
|
|
1576
|
+
});
|
|
1459
1577
|
return proxy;
|
|
1460
1578
|
}
|
|
1461
1579
|
|
|
@@ -2316,6 +2434,30 @@ function createNamespace(namespace) {
|
|
|
2316
2434
|
|
|
2317
2435
|
const namespaceCache = new Map();
|
|
2318
2436
|
|
|
2437
|
+
// The ONE namespace object per GI namespace, as in gjs where `imports.gi.Ns` is a
|
|
2438
|
+
// singleton. Keyed by NAMESPACE alone: a repository cannot hold two versions at
|
|
2439
|
+
// once, and keying by `Ns@version` handed `requireGi('Gio','2.0')` and
|
|
2440
|
+
// `requireGi('Gio')` two independent objects — `Gio.File !== Gio.File` between the
|
|
2441
|
+
// spellings, a prototype patched through one invisible through the other. Does NOT
|
|
2442
|
+
// load the typelib (requireGi does that first).
|
|
2443
|
+
function namespaceObject(namespace) {
|
|
2444
|
+
let ns = namespaceCache.get(namespace);
|
|
2445
|
+
if (ns === undefined) {
|
|
2446
|
+
ns = createNamespace(namespace);
|
|
2447
|
+
// The GObject namespace also carries the GJS runtime statics (registerClass +
|
|
2448
|
+
// ParamSpec/ParamFlags/SignalFlags) layered over its introspected members.
|
|
2449
|
+
if (namespace === 'GObject') ns = decorateGObjectNamespace(ns);
|
|
2450
|
+
// The GLib namespace carries the GJS-shaped GLib.Variant ergonomics
|
|
2451
|
+
// (new GLib.Variant(sig, value) + deepUnpack/unpack/recursiveUnpack) and the
|
|
2452
|
+
// GLib.Error class.
|
|
2453
|
+
else if (namespace === 'GLib') ns = decorateGLibNamespace(ns);
|
|
2454
|
+
// The Gio namespace carries Gio._promisify (async → Promise).
|
|
2455
|
+
else if (namespace === 'Gio') ns = decorateGioNamespace(ns);
|
|
2456
|
+
namespaceCache.set(namespace, ns);
|
|
2457
|
+
}
|
|
2458
|
+
return ns;
|
|
2459
|
+
}
|
|
2460
|
+
|
|
2319
2461
|
// Whether the libuv↔GLib bridge has been attached this process. The native
|
|
2320
2462
|
// startMainLoop is itself idempotent; this just avoids the extra call.
|
|
2321
2463
|
let loopAttached = false;
|
|
@@ -2373,22 +2515,7 @@ export function requireGi(namespace, version) {
|
|
|
2373
2515
|
}
|
|
2374
2516
|
loopAttached = true;
|
|
2375
2517
|
}
|
|
2376
|
-
|
|
2377
|
-
let ns = namespaceCache.get(key);
|
|
2378
|
-
if (ns === undefined) {
|
|
2379
|
-
ns = createNamespace(namespace);
|
|
2380
|
-
// The GObject namespace also carries the GJS runtime statics (registerClass +
|
|
2381
|
-
// ParamSpec/ParamFlags/SignalFlags) layered over its introspected members.
|
|
2382
|
-
if (namespace === 'GObject') ns = decorateGObjectNamespace(ns);
|
|
2383
|
-
// The GLib namespace carries the GJS-shaped GLib.Variant ergonomics
|
|
2384
|
-
// (new GLib.Variant(sig, value) + deepUnpack/unpack/recursiveUnpack) and the
|
|
2385
|
-
// GLib.Error class.
|
|
2386
|
-
else if (namespace === 'GLib') ns = decorateGLibNamespace(ns);
|
|
2387
|
-
// The Gio namespace carries Gio._promisify (async → Promise).
|
|
2388
|
-
else if (namespace === 'Gio') ns = decorateGioNamespace(ns);
|
|
2389
|
-
namespaceCache.set(key, ns);
|
|
2390
|
-
}
|
|
2391
|
-
return ns;
|
|
2518
|
+
return namespaceObject(namespace);
|
|
2392
2519
|
}
|
|
2393
2520
|
|
|
2394
2521
|
/**
|
package/index.d.ts
CHANGED
|
@@ -99,6 +99,15 @@ export function callMethod(handle: GObjectHandle, methodName: string, args?: unk
|
|
|
99
99
|
*/
|
|
100
100
|
export function hasMethod(handle: GObjectHandle, methodName: string): boolean;
|
|
101
101
|
|
|
102
|
+
/**
|
|
103
|
+
* The CLASS-level twin of {@link hasMethod}: `true` iff `Ns.Type` declares an
|
|
104
|
+
* invocable instance method by that name (literal introspected name or
|
|
105
|
+
* snake_case alias), across own, implemented-interface and inherited methods.
|
|
106
|
+
* Answers by TYPE rather than by instance handle, which is what lets a class
|
|
107
|
+
* prototype resolve its methods before any instance of it exists.
|
|
108
|
+
*/
|
|
109
|
+
export function hasClassMethod(namespace: string, typeName: string, methodName: string): boolean;
|
|
110
|
+
|
|
102
111
|
/**
|
|
103
112
|
* Invoke a type-level constructor/static function (e.g. `Gio.File.new_for_path`,
|
|
104
113
|
* `Gtk.Label.new`) — a function found on a type but taking no instance. The Node
|
|
@@ -254,6 +263,15 @@ export function hasProperty(handle: GObjectHandle, name: string): boolean;
|
|
|
254
263
|
/** The runtime GType name of a GObject handle (e.g. "GSimpleAction"). */
|
|
255
264
|
export function getTypeName(handle: GObjectHandle): string;
|
|
256
265
|
|
|
266
|
+
/**
|
|
267
|
+
* The introspected type that OWNS a runtime GType name — the reverse of
|
|
268
|
+
* {@link getGType}, walking up to the nearest ancestor carrying object info (a
|
|
269
|
+
* private concrete type such as `GLocalFile` has none of its own). Searches only
|
|
270
|
+
* LOADED namespaces, so it never pulls in a typelib as a side effect. `null` when
|
|
271
|
+
* the GType is unknown or no ancestor is introspectable.
|
|
272
|
+
*/
|
|
273
|
+
export function classInfoForTypeName(gtypeName: string): { namespace: string; name: string } | null;
|
|
274
|
+
|
|
257
275
|
/**
|
|
258
276
|
* The runtime GType of an introspected registered type, as an opaque node-gi
|
|
259
277
|
* GType handle ({@link TypeHandle} — a tag-distinct External carrying the GType,
|
|
@@ -525,6 +543,7 @@ declare const native: {
|
|
|
525
543
|
callFunction: typeof callFunction;
|
|
526
544
|
callMethod: typeof callMethod;
|
|
527
545
|
hasMethod: typeof hasMethod;
|
|
546
|
+
hasClassMethod: typeof hasClassMethod;
|
|
528
547
|
callStaticMethod: typeof callStaticMethod;
|
|
529
548
|
constructStruct: typeof constructStruct;
|
|
530
549
|
newObject: typeof newObject;
|
|
@@ -535,6 +554,7 @@ declare const native: {
|
|
|
535
554
|
setProperty: typeof setProperty;
|
|
536
555
|
hasProperty: typeof hasProperty;
|
|
537
556
|
getTypeName: typeof getTypeName;
|
|
557
|
+
classInfoForTypeName: typeof classInfoForTypeName;
|
|
538
558
|
getGType: typeof getGType;
|
|
539
559
|
isInstanceOf: typeof isInstanceOf;
|
|
540
560
|
isGObjectHandle: typeof isGObjectHandle;
|
package/index.js
CHANGED
|
@@ -197,6 +197,7 @@ export const prependLibraryPath = native.prependLibraryPath;
|
|
|
197
197
|
export const callFunction = native.callFunction;
|
|
198
198
|
export const callMethod = native.callMethod;
|
|
199
199
|
export const hasMethod = native.hasMethod;
|
|
200
|
+
export const hasClassMethod = native.hasClassMethod;
|
|
200
201
|
export const callStaticMethod = native.callStaticMethod;
|
|
201
202
|
export const constructStruct = native.constructStruct;
|
|
202
203
|
export const newObject = native.newObject;
|
|
@@ -216,6 +217,7 @@ export const getProperty = native.getProperty;
|
|
|
216
217
|
export const setProperty = native.setProperty;
|
|
217
218
|
export const hasProperty = native.hasProperty;
|
|
218
219
|
export const getTypeName = native.getTypeName;
|
|
220
|
+
export const classInfoForTypeName = native.classInfoForTypeName;
|
|
219
221
|
export const getGType = native.getGType;
|
|
220
222
|
export const isInstanceOf = native.isInstanceOf;
|
|
221
223
|
export const isGObjectHandle = native.isGObjectHandle;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.40.0",
|
|
4
4
|
"description": "GObject-Introspection runtime for Node.js — load gi:// namespaces (GLib/GObject/Gio/…) with GJS-compatible semantics. Vendored + rewritten from node-gtk under MIT.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/addon.cc
CHANGED
|
@@ -78,6 +78,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
78
78
|
exports.Set("callFunction", Napi::Function::New(env, CallFunction));
|
|
79
79
|
exports.Set("callMethod", Napi::Function::New(env, CallMethod));
|
|
80
80
|
exports.Set("hasMethod", Napi::Function::New(env, HasMethod));
|
|
81
|
+
exports.Set("hasClassMethod", Napi::Function::New(env, HasClassMethod));
|
|
81
82
|
exports.Set("callStaticMethod", Napi::Function::New(env, CallStaticMethod));
|
|
82
83
|
exports.Set("constructStruct", Napi::Function::New(env, ConstructStruct));
|
|
83
84
|
exports.Set("newObject", Napi::Function::New(env, NewObject));
|
|
@@ -90,6 +91,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
90
91
|
exports.Set("setProperty", Napi::Function::New(env, SetProperty));
|
|
91
92
|
exports.Set("hasProperty", Napi::Function::New(env, HasProperty));
|
|
92
93
|
exports.Set("getTypeName", Napi::Function::New(env, GetTypeName));
|
|
94
|
+
exports.Set("classInfoForTypeName", Napi::Function::New(env, ClassInfoForTypeName));
|
|
93
95
|
exports.Set("getGType", Napi::Function::New(env, GetGType));
|
|
94
96
|
exports.Set("isInstanceOf", Napi::Function::New(env, IsInstanceOf));
|
|
95
97
|
exports.Set("isGObjectHandle", Napi::Function::New(env, IsGObjectHandle));
|
package/src/calls.cc
CHANGED
|
@@ -673,6 +673,23 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
673
673
|
// don't. The out-container is freed (or borrowed) by ReadOutOrReturn per the
|
|
674
674
|
// SAME (OUT) transfer. Verified vs gjs 1.88 (GIMarshallingTests.array_inout,
|
|
675
675
|
// garray/glist/gslist/gptrarray/ghashtable_utf8_none_inout, bytearray_full_inout).
|
|
676
|
+
//
|
|
677
|
+
// THAT ABI IS TAKEN FROM THE ANNOTATION AND CANNOT BE VERIFIED HERE. `c:type`
|
|
678
|
+
// — the only place `gchar*` and `gchar**` differ — lives in the GIR XML and is
|
|
679
|
+
// NOT compiled into the typelib. `GLib.base64_decode_inplace` is annotated
|
|
680
|
+
// `direction="inout"` over a `c:type="gchar*"`, one star; compared field by
|
|
681
|
+
// field against a genuine `gchar***` INOUT (`GLib.OptionContext.parse`'s
|
|
682
|
+
// `argv`) its metadata is identical — direction INOUT, transfer EVERYTHING,
|
|
683
|
+
// tag ARRAY, is_pointer true, caller-allocates false. A mis-annotated callee
|
|
684
|
+
// therefore receives `&slots[i]` and writes its output OVER the GIArgument
|
|
685
|
+
// union, and the read-back below marshals and frees a clobbered pointer.
|
|
686
|
+
// Measured: five runs of that one call, five different return values; gjs
|
|
687
|
+
// corrupts identically, so there is no oracle to compare against either.
|
|
688
|
+
//
|
|
689
|
+
// Nothing to detect on this side — the discriminator is data the runtime does
|
|
690
|
+
// not have — and a per-function denylist ossifies. Upstream annotation bug:
|
|
691
|
+
// `status/upstream-patch-candidates.md`. `test/arrays.test.mjs` records why
|
|
692
|
+
// that call was removed from the suite and must not come back.
|
|
676
693
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
677
694
|
jsCursor++;
|
|
678
695
|
if (!IsSupportedContainerType(ti, &why)) {
|
|
@@ -1066,9 +1083,10 @@ static std::string CamelToSnake(const std::string& name) {
|
|
|
1066
1083
|
|
|
1067
1084
|
// The instance's concrete GType may lack introspection info (e.g. a private
|
|
1068
1085
|
// GLocalFile); walk up to the nearest ancestor GType that has an object info.
|
|
1069
|
-
// Returns a new ref or nullptr. Shared by CallMethod (invoke)
|
|
1070
|
-
// (feature detection)
|
|
1071
|
-
|
|
1086
|
+
// Returns a new ref or nullptr. Shared by CallMethod (invoke), HasMethod
|
|
1087
|
+
// (feature detection) and object.cc's ClassInfoForTypeName (the L1 prototype
|
|
1088
|
+
// lookup) so all three resolve the same way.
|
|
1089
|
+
GIObjectInfo* FindNearestObjectInfo(GIRepository* repo, GType gtype) {
|
|
1072
1090
|
for (GType t = gtype; t != 0; t = g_type_parent(t)) {
|
|
1073
1091
|
GIBaseInfo* bi = gi_repository_find_by_gtype(repo, t);
|
|
1074
1092
|
if (bi != nullptr) {
|
|
@@ -1225,6 +1243,54 @@ Napi::Value HasMethod(const Napi::CallbackInfo& info) {
|
|
|
1225
1243
|
return Napi::Boolean::New(env, invocable);
|
|
1226
1244
|
}
|
|
1227
1245
|
|
|
1246
|
+
// hasClassMethod(namespace, typeName, methodName) -> boolean
|
|
1247
|
+
// The CLASS-level twin of hasMethod: TRUE iff `Ns.Type` declares an invocable
|
|
1248
|
+
// instance method by that name (literal introspected name first, snake_case
|
|
1249
|
+
// alias second — the resolution callMethod itself performs), across own,
|
|
1250
|
+
// implemented-interface and inherited methods. Keyed by TYPE rather than by an
|
|
1251
|
+
// instance handle because it backs the lazy method resolution on the class
|
|
1252
|
+
// PROTOTYPE (#1175), which must answer before any instance exists.
|
|
1253
|
+
Napi::Value HasClassMethod(const Napi::CallbackInfo& info) {
|
|
1254
|
+
Napi::Env env = info.Env();
|
|
1255
|
+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
1256
|
+
Napi::TypeError::New(env,
|
|
1257
|
+
"hasClassMethod(namespace: string, typeName: string, methodName: string)")
|
|
1258
|
+
.ThrowAsJavaScriptException();
|
|
1259
|
+
return env.Null();
|
|
1260
|
+
}
|
|
1261
|
+
std::string ns = info[0].As<Napi::String>().Utf8Value();
|
|
1262
|
+
std::string tn = info[1].As<Napi::String>().Utf8Value();
|
|
1263
|
+
std::string method = info[2].As<Napi::String>().Utf8Value();
|
|
1264
|
+
|
|
1265
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1266
|
+
GIBaseInfo* base = gi_repository_find_by_name(repo, ns.c_str(), tn.c_str());
|
|
1267
|
+
GIFunctionInfo* func = nullptr;
|
|
1268
|
+
if (base != nullptr) {
|
|
1269
|
+
if (GI_IS_OBJECT_INFO(base)) {
|
|
1270
|
+
GType gtype =
|
|
1271
|
+
gi_registered_type_info_get_g_type(reinterpret_cast<GIRegisteredTypeInfo*>(base));
|
|
1272
|
+
// An object info always carries a registered GType; the guard is for the
|
|
1273
|
+
// interface-list step, which would warn on G_TYPE_INVALID.
|
|
1274
|
+
if (gtype != G_TYPE_INVALID) {
|
|
1275
|
+
func = ResolveInstanceMethod(repo, gtype, reinterpret_cast<GIObjectInfo*>(base), method);
|
|
1276
|
+
}
|
|
1277
|
+
} else if (GI_IS_INTERFACE_INFO(base)) {
|
|
1278
|
+
GIInterfaceInfo* iface = reinterpret_cast<GIInterfaceInfo*>(base);
|
|
1279
|
+
func = gi_interface_info_find_method(iface, method.c_str());
|
|
1280
|
+
if (func == nullptr) {
|
|
1281
|
+
const std::string snake = CamelToSnake(method);
|
|
1282
|
+
if (snake != method) func = gi_interface_info_find_method(iface, snake.c_str());
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
gi_base_info_unref(base);
|
|
1286
|
+
}
|
|
1287
|
+
g_object_unref(repo);
|
|
1288
|
+
if (func == nullptr) return Napi::Boolean::New(env, false);
|
|
1289
|
+
const bool invocable = gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func));
|
|
1290
|
+
gi_base_info_unref(func);
|
|
1291
|
+
return Napi::Boolean::New(env, invocable);
|
|
1292
|
+
}
|
|
1293
|
+
|
|
1228
1294
|
// callStaticMethod(namespace, typeName, methodName, args?) -> unknown
|
|
1229
1295
|
// Invoke a type-level constructor/static function (e.g. Gio.File.new_for_path,
|
|
1230
1296
|
// Gtk.Label.new) — a function found ON a type but taking no instance. The Node
|
package/src/common.h
CHANGED
|
@@ -556,6 +556,11 @@ Napi::Value PrependLibraryPath(const Napi::CallbackInfo& info);
|
|
|
556
556
|
Napi::Value CallFunction(const Napi::CallbackInfo& info);
|
|
557
557
|
Napi::Value CallMethod(const Napi::CallbackInfo& info);
|
|
558
558
|
Napi::Value HasMethod(const Napi::CallbackInfo& info);
|
|
559
|
+
Napi::Value HasClassMethod(const Napi::CallbackInfo& info);
|
|
560
|
+
// The nearest ancestor GType carrying object introspection info (a concrete type
|
|
561
|
+
// may be private — GLocalFile); a new ref, or nullptr. Shared with object.cc so
|
|
562
|
+
// method dispatch and prototype lookup resolve to the same class.
|
|
563
|
+
GIObjectInfo* FindNearestObjectInfo(GIRepository* repo, GType gtype);
|
|
559
564
|
Napi::Value CallStaticMethod(const Napi::CallbackInfo& info);
|
|
560
565
|
// The `new <Struct>()` [[Construct]] path: route to the struct's 'new'
|
|
561
566
|
// constructor when it has one, else zero-allocate (GJS gi/boxed.cpp parity).
|
|
@@ -588,6 +593,7 @@ Napi::Value GetProperty(const Napi::CallbackInfo& info);
|
|
|
588
593
|
Napi::Value SetProperty(const Napi::CallbackInfo& info);
|
|
589
594
|
Napi::Value HasProperty(const Napi::CallbackInfo& info);
|
|
590
595
|
Napi::Value GetTypeName(const Napi::CallbackInfo& info);
|
|
596
|
+
Napi::Value ClassInfoForTypeName(const Napi::CallbackInfo& info);
|
|
591
597
|
Napi::Value GetGType(const Napi::CallbackInfo& info);
|
|
592
598
|
Napi::Value IsInstanceOf(const Napi::CallbackInfo& info);
|
|
593
599
|
Napi::Value IsGObjectHandle(const Napi::CallbackInfo& info);
|
package/src/loop.cc
CHANGED
|
@@ -509,14 +509,30 @@ static void PumpDrainContext() {
|
|
|
509
509
|
if (scope != nullptr) napi_close_handle_scope(g_loop_env, scope);
|
|
510
510
|
}
|
|
511
511
|
|
|
512
|
-
// The prepare+query HINT pass: learn GLib's earliest-timer deadline + poll
|
|
513
|
-
// and mirror them into the uv timer / uv_poll watchers, so libuv's poll sleep
|
|
514
|
-
// ends exactly when GLib next has work.
|
|
515
|
-
// matching check/dispatch — safe: the next g_main_context_iteration() (ours or a
|
|
516
|
-
// blocking loop's) simply re-prepares. Runs only at g_main_depth() == 0; the
|
|
512
|
+
// The prepare+query+check HINT pass: learn GLib's earliest-timer deadline + poll
|
|
513
|
+
// fds and mirror them into the uv timer / uv_poll watchers, so libuv's poll sleep
|
|
514
|
+
// ends exactly when GLib next has work. Runs only at g_main_depth() == 0; the
|
|
517
515
|
// g_in_uv_pump flag is held across it so UvLoopSource stays parked (masking uv's
|
|
518
516
|
// backend fd out of the queried set — it must not be uv_poll'd, and uv's own
|
|
519
517
|
// backend timeout must not feed back into the timer hint).
|
|
518
|
+
//
|
|
519
|
+
// The CHECK is not cosmetic and must never be dropped again: a GSource may hold a
|
|
520
|
+
// LOCK between its prepare() and its check(), and leaving the context prepared
|
|
521
|
+
// deadlocks the process on the next call into that library. GDK's Wayland event
|
|
522
|
+
// source is exactly that shape — its prepare() takes libwayland's designated-reader
|
|
523
|
+
// slot (`wl_display_prepare_read_queue`) and only its check() releases it
|
|
524
|
+
// (`wl_display_read_events` / `wl_display_cancel_read`). Measured (gjsify #1145,
|
|
525
|
+
// gtk 4.22 / Fedora 44 / Mesa Vulkan): with the hint pass ending at query, the very
|
|
526
|
+
// next `gtk_window_present()` blocked forever inside `wl_display_roundtrip_queue`
|
|
527
|
+
// under `gsk_renderer_new_for_surface_full`, so the app never finished showing its
|
|
528
|
+
// window while its GDBus worker thread kept answering Peer.Ping and Introspect —
|
|
529
|
+
// a live process at ~0.5 % CPU that read as "devtools hangs". Only Wayland shows
|
|
530
|
+
// it (GdkX11's source holds nothing across the pair), only node (bun/deno pump
|
|
531
|
+
// through complete `g_main_context_iteration`s), and only once GTK was initialised
|
|
532
|
+
// at g_main_depth() == 0 — `Gio.Application.register()` before `runAsync()` does
|
|
533
|
+
// exactly that, which is why an app that only ever calls `run()` never saw it.
|
|
534
|
+
// Passing revents = 0 is the right reading: this pass did NOT poll, so no fd is
|
|
535
|
+
// reported ready and every source that took something in prepare() gives it back.
|
|
520
536
|
static void PumpArmWakeups() {
|
|
521
537
|
if (!g_pump_inited || g_main_depth() > 0 || g_in_uv_pump) return;
|
|
522
538
|
GMainContext* ctx = g_main_context_default();
|
|
@@ -531,6 +547,14 @@ static void PumpArmWakeups() {
|
|
|
531
547
|
nfds = g_main_context_query(ctx, prio, &timeout, g_pump_fds->data(),
|
|
532
548
|
static_cast<gint>(g_pump_fds->size()));
|
|
533
549
|
}
|
|
550
|
+
// g_main_context_query fills fd + events and leaves revents untouched, so the
|
|
551
|
+
// previous pass's values would otherwise be replayed as fresh readiness.
|
|
552
|
+
for (int i = 0; i < nfds; i++) (*g_pump_fds)[i].revents = 0;
|
|
553
|
+
// No dispatch pairs with this check: g_main_context_prepare() clears
|
|
554
|
+
// pending_dispatches on entry (glib gmain.c), and PumpDrainContext() — a full
|
|
555
|
+
// iteration — runs on the same libuv turn, so anything this marks ready is
|
|
556
|
+
// dispatched there rather than being stranded.
|
|
557
|
+
g_main_context_check(ctx, prio, g_pump_fds->data(), nfds);
|
|
534
558
|
g_in_uv_pump = FALSE;
|
|
535
559
|
|
|
536
560
|
if (!SyncPumpPolls(nfds)) {
|
package/src/object.cc
CHANGED
|
@@ -745,6 +745,39 @@ Napi::Value GetTypeName(const Napi::CallbackInfo& info) {
|
|
|
745
745
|
return Napi::String::New(env, G_OBJECT_TYPE_NAME(obj));
|
|
746
746
|
}
|
|
747
747
|
|
|
748
|
+
// classInfoForTypeName(gtypeName) -> { namespace, name } | null
|
|
749
|
+
// The introspected type that OWNS a runtime GType name — the reverse of
|
|
750
|
+
// getGType, over the same nearest-ancestor walk method resolution uses (a
|
|
751
|
+
// private concrete type such as GLocalFile carries no info of its own). L1 needs
|
|
752
|
+
// it to hand a wrapper the JS prototype of its class, so that a member the
|
|
753
|
+
// program put on `Ns.Class.prototype` is what the instance resolves (#1175).
|
|
754
|
+
// Searches only LOADED namespaces (gi_repository_find_by_gtype), so it never
|
|
755
|
+
// pulls in a typelib as a side effect of wrapping an object.
|
|
756
|
+
Napi::Value ClassInfoForTypeName(const Napi::CallbackInfo& info) {
|
|
757
|
+
Napi::Env env = info.Env();
|
|
758
|
+
if (info.Length() < 1 || !info[0].IsString()) {
|
|
759
|
+
Napi::TypeError::New(env, "classInfoForTypeName(gtypeName: string)")
|
|
760
|
+
.ThrowAsJavaScriptException();
|
|
761
|
+
return env.Null();
|
|
762
|
+
}
|
|
763
|
+
std::string typeName = info[0].As<Napi::String>().Utf8Value();
|
|
764
|
+
GType gtype = g_type_from_name(typeName.c_str());
|
|
765
|
+
if (gtype == G_TYPE_INVALID) return env.Null();
|
|
766
|
+
GIRepository* repo = DupDefaultRepository();
|
|
767
|
+
GIObjectInfo* objInfo = FindNearestObjectInfo(repo, gtype);
|
|
768
|
+
Napi::Value result = env.Null();
|
|
769
|
+
if (objInfo != nullptr) {
|
|
770
|
+
GIBaseInfo* base = reinterpret_cast<GIBaseInfo*>(objInfo);
|
|
771
|
+
Napi::Object out = Napi::Object::New(env);
|
|
772
|
+
out.Set("namespace", Napi::String::New(env, gi_base_info_get_namespace(base)));
|
|
773
|
+
out.Set("name", Napi::String::New(env, gi_base_info_get_name(base)));
|
|
774
|
+
gi_base_info_unref(base);
|
|
775
|
+
result = out;
|
|
776
|
+
}
|
|
777
|
+
g_object_unref(repo);
|
|
778
|
+
return result;
|
|
779
|
+
}
|
|
780
|
+
|
|
748
781
|
// getGType(namespace, name) -> GType handle | null
|
|
749
782
|
// The runtime GType of an introspected registered type (object/interface/struct/
|
|
750
783
|
// union/enum/flags), as a node-gi GType handle. The L1 layer surfaces it as a
|