@gjsify/node-gi 0.42.0 → 0.44.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 +112 -1091
- package/gi.js +81 -9
- package/index.d.ts +39 -0
- package/index.js +3 -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 +3 -0
- package/src/calls.cc +202 -66
- package/src/class.cc +240 -64
- package/src/common.h +11 -1
- package/src/marshal.cc +68 -1
package/gi.js
CHANGED
|
@@ -1142,6 +1142,26 @@ function protoForInstance(handle) {
|
|
|
1142
1142
|
return proto === null ? undefined : proto;
|
|
1143
1143
|
}
|
|
1144
1144
|
|
|
1145
|
+
// Attach a class prototype to a wrapper — as its [[Prototype]] AND as the USER_PROTO
|
|
1146
|
+
// symbol the traps resolve members through.
|
|
1147
|
+
//
|
|
1148
|
+
// The symbol alone is not the link. `constructor` is RESERVED, so the get trap hands
|
|
1149
|
+
// it straight to the bare target, whose [[Prototype]] was Object.prototype: every
|
|
1150
|
+
// introspected instance answered `Object` — carrying no `$gtype`, so
|
|
1151
|
+
// `GObject.type_name(inst.constructor.$gtype)` was `null` and @gjsify/gtk-host's
|
|
1152
|
+
// adopt() / nearestRegistered() / every descriptor lookup failed with
|
|
1153
|
+
// "No descriptor registered for null" (99 rows of its node leg). On gjs an instance's
|
|
1154
|
+
// [[Prototype]] IS its class's prototype and `constructor` is the class object itself
|
|
1155
|
+
// (measured on gjs 1.88.1), which is what the class prototype's own `constructor`
|
|
1156
|
+
// back-link — the makeClass Proxy, the object `requireGi` returns — now supplies.
|
|
1157
|
+
// It is also what puts a class prototype on the chain findProtoDescriptor walks for
|
|
1158
|
+
// `vfunc_*` (see makeClassPrototype). Works on the wrapper Proxy too: neither
|
|
1159
|
+
// setPrototypeOf nor the symbol write is trapped, so both reach the target.
|
|
1160
|
+
function linkInstanceProto(objOrProxy, proto) {
|
|
1161
|
+
objOrProxy[USER_PROTO] = proto;
|
|
1162
|
+
Object.setPrototypeOf(objOrProxy, proto);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1145
1165
|
// Wrap a live GObject handle as a GJS-shaped instance. The wrapper resolves its
|
|
1146
1166
|
// class prototype chain FIRST (a registerClass subclass's own methods, anything
|
|
1147
1167
|
// the program put on the introspected prototype) — so `inst.myMethod()` runs the
|
|
@@ -1164,14 +1184,14 @@ function wrapInstance(handle, userProto) {
|
|
|
1164
1184
|
if (userProto !== undefined && userProto !== cached[USER_PROTO]) {
|
|
1165
1185
|
const current = cached[USER_PROTO];
|
|
1166
1186
|
if (current === undefined || isProtoSameOrDescendantOf(userProto, current)) {
|
|
1167
|
-
cached
|
|
1187
|
+
linkInstanceProto(cached, userProto);
|
|
1168
1188
|
}
|
|
1169
1189
|
}
|
|
1170
1190
|
return cached;
|
|
1171
1191
|
}
|
|
1172
1192
|
const target = { [HANDLE]: handle };
|
|
1173
1193
|
const proto = userProto ?? protoForInstance(handle);
|
|
1174
|
-
if (proto !== undefined) target
|
|
1194
|
+
if (proto !== undefined) linkInstanceProto(target, proto);
|
|
1175
1195
|
const proxy = new Proxy(target, {
|
|
1176
1196
|
get(t, prop) {
|
|
1177
1197
|
if (prop === HANDLE) return handle;
|
|
@@ -1380,11 +1400,33 @@ function prototypeMethodHandle(self, where) {
|
|
|
1380
1400
|
// The invocable form of an introspected instance method, as it sits on a class
|
|
1381
1401
|
// prototype: `this` supplies the GObject, so ONE function serves every instance of
|
|
1382
1402
|
// the class and `inst.m === Cls.prototype.m` holds (gjs parity).
|
|
1383
|
-
|
|
1403
|
+
//
|
|
1404
|
+
// `arity` becomes the function's `length`, as on gjs (m_js_in_argc — the
|
|
1405
|
+
// JS-visible IN args, not the C arg count). A rest-args thunk reports 0, and
|
|
1406
|
+
// @gjsify/gtk-host's descriptor conformance reads `.length` to hold the widget
|
|
1407
|
+
// table against the installed GTK — `add_titled() takes 3 argument(s), but
|
|
1408
|
+
// GtkStack's takes 0` was this thunk, not the typelib.
|
|
1409
|
+
function makeGiMethod(where, method, arity) {
|
|
1384
1410
|
const fn = function (...args) {
|
|
1385
1411
|
return wrapReturn(native.callMethod(prototypeMethodHandle(this, where), method, unwrapArgs(args)));
|
|
1386
1412
|
};
|
|
1387
1413
|
Object.defineProperty(fn, 'name', { value: method, configurable: true });
|
|
1414
|
+
Object.defineProperty(fn, 'length', { value: arity, configurable: true });
|
|
1415
|
+
return fn;
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
// The invocable form of an introspected VFUNC as it sits on a class prototype. Like
|
|
1419
|
+
// makeGiMethod, but the engine resolves the vtable slot of THIS class (not a captured
|
|
1420
|
+
// parent pointer), so it works on a plain `new Ns.Class()` — which is what gjs does
|
|
1421
|
+
// and what `vfunc_add_child` (a GtkBuildable slot with no introspected method form)
|
|
1422
|
+
// has no other route to.
|
|
1423
|
+
function makeGiVFunc(namespace, typeName, vfuncName, where) {
|
|
1424
|
+
const fn = function (...args) {
|
|
1425
|
+
return wrapReturn(
|
|
1426
|
+
native.callClassVfunc(prototypeMethodHandle(this, where), namespace, typeName, vfuncName, unwrapArgs(args)),
|
|
1427
|
+
);
|
|
1428
|
+
};
|
|
1429
|
+
Object.defineProperty(fn, 'name', { value: `vfunc_${vfuncName}`, configurable: true });
|
|
1388
1430
|
return fn;
|
|
1389
1431
|
}
|
|
1390
1432
|
|
|
@@ -1407,7 +1449,7 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1407
1449
|
configurable: true,
|
|
1408
1450
|
});
|
|
1409
1451
|
const where = `${namespace}.${typeName}.prototype`;
|
|
1410
|
-
const resolved = new Map(); // accessor name ->
|
|
1452
|
+
const resolved = new Map(); // accessor name -> method arity (-1 = absent) or vfunc addressability
|
|
1411
1453
|
// Writable + configurable: a program REPLACES a method to instrument it and puts
|
|
1412
1454
|
// the original back afterwards, which is the whole point of the prototype.
|
|
1413
1455
|
// Enumerable, like the methods gjs's resolve hook defines.
|
|
@@ -1421,6 +1463,25 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1421
1463
|
const materialize = (prop) => {
|
|
1422
1464
|
if (typeof prop !== 'string' || RESERVED.has(prop)) return false;
|
|
1423
1465
|
if (Object.prototype.hasOwnProperty.call(target, prop)) return true;
|
|
1466
|
+
// A vfunc slot. Defining it HERE — rather than leaving it to the vfuncChainProto
|
|
1467
|
+
// Proxy below the prototype — is what makes it reachable from an INSTANCE: the
|
|
1468
|
+
// wrapper resolves members by descriptor (findProtoDescriptor), and a Proxy that
|
|
1469
|
+
// traps only `get` reports no descriptor and no `in`, so `new Gtk.Box()
|
|
1470
|
+
// .vfunc_add_child` was `undefined` while `Gtk.Box.prototype.vfunc_add_child` was
|
|
1471
|
+
// a function. Gated on the engine being able to ADDRESS the slot, which gives gjs
|
|
1472
|
+
// parity two ways: an unknown vfunc name stays `undefined` instead of becoming a
|
|
1473
|
+
// throw-on-call thunk, and a name the engine cannot address (GObject's own vfuncs
|
|
1474
|
+
// report GI_UNKNOWN as their struct offset) still falls through to the chain-up
|
|
1475
|
+
// thunk, so `super.vfunc_dispose()` keeps working.
|
|
1476
|
+
if (prop.startsWith('vfunc_')) {
|
|
1477
|
+
const vfuncName = prop.slice('vfunc_'.length);
|
|
1478
|
+
let addressable = resolved.get(prop);
|
|
1479
|
+
if (addressable === undefined) {
|
|
1480
|
+
addressable = native.hasClassVfunc(namespace, typeName, vfuncName);
|
|
1481
|
+
resolved.set(prop, addressable);
|
|
1482
|
+
}
|
|
1483
|
+
return addressable && define(prop, makeGiVFunc(namespace, typeName, vfuncName, `${where}.${prop}`));
|
|
1484
|
+
}
|
|
1424
1485
|
// gjs's GObject.Object.prototype members are JS shims over namespace-level
|
|
1425
1486
|
// functions (see objectPrototypeShim), and `bind_property_full` even HAS an
|
|
1426
1487
|
// introspected GIR shadow — bind_with_closures, whose GValue write-back a
|
|
@@ -1431,12 +1492,14 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1431
1492
|
return shim(prototypeMethodHandle(this, `${where}.${prop}`), args);
|
|
1432
1493
|
});
|
|
1433
1494
|
}
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1495
|
+
// ONE native call answers both presence (arity >= 0) and the function's
|
|
1496
|
+
// `length` — see classMethodArity in calls.cc.
|
|
1497
|
+
let arity = resolved.get(prop);
|
|
1498
|
+
if (arity === undefined) {
|
|
1499
|
+
arity = native.classMethodArity(namespace, typeName, prop);
|
|
1500
|
+
resolved.set(prop, arity);
|
|
1438
1501
|
}
|
|
1439
|
-
return
|
|
1502
|
+
return arity >= 0 && define(prop, makeGiMethod(where, prop, arity));
|
|
1440
1503
|
};
|
|
1441
1504
|
return new Proxy(target, {
|
|
1442
1505
|
get(t, prop, receiver) {
|
|
@@ -2064,6 +2127,15 @@ function collectVfuncs(klass) {
|
|
|
2064
2127
|
const vfuncs = {};
|
|
2065
2128
|
const seen = new Set();
|
|
2066
2129
|
for (let p = klass.prototype; p !== null && p !== Object.prototype; p = Object.getPrototypeOf(p)) {
|
|
2130
|
+
// The INTROSPECTED base's prototype declares no user override, and since it
|
|
2131
|
+
// materializes its own `vfunc_*` members on first look-up (makeClassPrototype)
|
|
2132
|
+
// it now CARRIES such names: without this boundary a program that read
|
|
2133
|
+
// `base.vfunc_activate` before registering a subclass had that vfunc collected
|
|
2134
|
+
// as the subclass's own override, installing a trampoline for a slot the user
|
|
2135
|
+
// never wrote ("registerClass vfunc 'activate' not found on any ancestor").
|
|
2136
|
+
// CLASS_INFO is stamped only by makeClassPrototype, and only OWN counts — a
|
|
2137
|
+
// registered subclass's prototype inherits it from its base.
|
|
2138
|
+
if (p !== klass.prototype && Object.prototype.hasOwnProperty.call(p, CLASS_INFO)) break;
|
|
2067
2139
|
const owner = Object.getOwnPropertyDescriptor(p, 'constructor')?.value;
|
|
2068
2140
|
if (owner !== undefined && owner !== klass && registeredClasses.has(owner)) break;
|
|
2069
2141
|
for (const key of Object.getOwnPropertyNames(p)) {
|
package/index.d.ts
CHANGED
|
@@ -108,6 +108,16 @@ export function hasMethod(handle: GObjectHandle, methodName: string): boolean;
|
|
|
108
108
|
*/
|
|
109
109
|
export function hasClassMethod(namespace: string, typeName: string, methodName: string): boolean;
|
|
110
110
|
|
|
111
|
+
/**
|
|
112
|
+
* The JS-visible IN-arg count of an instance method — what gjs reports as the
|
|
113
|
+
* materialized function's `length` (IN/INOUT args minus array-length and
|
|
114
|
+
* callback user_data/destroy-notify slots) — or -1 when the name does not
|
|
115
|
+
* resolve to an invocable instance method. Resolution is shared with
|
|
116
|
+
* {@link hasClassMethod}, so `classMethodArity(...) >= 0` answers both
|
|
117
|
+
* questions in one call.
|
|
118
|
+
*/
|
|
119
|
+
export function classMethodArity(namespace: string, typeName: string, methodName: string): number;
|
|
120
|
+
|
|
111
121
|
/**
|
|
112
122
|
* Invoke a type-level constructor/static function (e.g. `Gio.File.new_for_path`,
|
|
113
123
|
* `Gtk.Label.new`) — a function found on a type but taking no instance. The Node
|
|
@@ -232,6 +242,34 @@ export function registerClass(
|
|
|
232
242
|
*/
|
|
233
243
|
export function callParentVfunc(handle: GObjectHandle, vfuncName: string, args?: unknown[]): unknown;
|
|
234
244
|
|
|
245
|
+
/**
|
|
246
|
+
* Whether `Ns.Type.prototype.vfunc_<vfuncName>` can be dispatched directly — i.e.
|
|
247
|
+
* an ancestor of `typeName` (or one of the interfaces it implements) declares that
|
|
248
|
+
* virtual function AND girepository can locate its slot in the class's vtable.
|
|
249
|
+
* The gate the L1 class prototype asks before materializing a vfunc member: `false`
|
|
250
|
+
* leaves the name `undefined` (gjs parity for an unknown vfunc) or, for a declared
|
|
251
|
+
* one whose struct offset girepository reports as unknown — GObject's own vfuncs —
|
|
252
|
+
* lets it fall through to the {@link callParentVfunc} chain-up thunk.
|
|
253
|
+
*/
|
|
254
|
+
export function hasClassVfunc(namespace: string, typeName: string, vfuncName: string): boolean;
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Invoke the virtual function `Ns.Type` carries in its vtable, with `handle` as the
|
|
258
|
+
* instance — what `new Gtk.Box().vfunc_add_child(builder, child, null)` does on gjs,
|
|
259
|
+
* and the ONLY route to a `Gtk.Buildable` adder (`add_child` is introspected as a
|
|
260
|
+
* vfunc, never as a method). Unlike {@link callParentVfunc} this needs no
|
|
261
|
+
* {@link registerClass} override: the slot comes from the class itself. Same
|
|
262
|
+
* marshalling and same `[returnValue?, ...outArgs]` return convention. Throws if the
|
|
263
|
+
* vfunc is not addressable on that class, or if `handle` is not an instance of it.
|
|
264
|
+
*/
|
|
265
|
+
export function callClassVfunc(
|
|
266
|
+
handle: GObjectHandle,
|
|
267
|
+
namespace: string,
|
|
268
|
+
typeName: string,
|
|
269
|
+
vfuncName: string,
|
|
270
|
+
args?: unknown[],
|
|
271
|
+
): unknown;
|
|
272
|
+
|
|
235
273
|
/**
|
|
236
274
|
* Construct a GObject of a registered type handle (from {@link registerClass})
|
|
237
275
|
* with optional construct/settable properties. For a templated type the engine
|
|
@@ -544,6 +582,7 @@ declare const native: {
|
|
|
544
582
|
callMethod: typeof callMethod;
|
|
545
583
|
hasMethod: typeof hasMethod;
|
|
546
584
|
hasClassMethod: typeof hasClassMethod;
|
|
585
|
+
classMethodArity: typeof classMethodArity;
|
|
547
586
|
callStaticMethod: typeof callStaticMethod;
|
|
548
587
|
constructStruct: typeof constructStruct;
|
|
549
588
|
newObject: typeof newObject;
|
package/index.js
CHANGED
|
@@ -198,6 +198,7 @@ export const callFunction = native.callFunction;
|
|
|
198
198
|
export const callMethod = native.callMethod;
|
|
199
199
|
export const hasMethod = native.hasMethod;
|
|
200
200
|
export const hasClassMethod = native.hasClassMethod;
|
|
201
|
+
export const classMethodArity = native.classMethodArity;
|
|
201
202
|
export const callStaticMethod = native.callStaticMethod;
|
|
202
203
|
export const constructStruct = native.constructStruct;
|
|
203
204
|
export const newObject = native.newObject;
|
|
@@ -212,6 +213,8 @@ export const registerClassFromGType = native.registerClassFromGType;
|
|
|
212
213
|
|
|
213
214
|
export const constructType = native.constructType;
|
|
214
215
|
export const callParentVfunc = native.callParentVfunc;
|
|
216
|
+
export const hasClassVfunc = native.hasClassVfunc;
|
|
217
|
+
export const callClassVfunc = native.callClassVfunc;
|
|
215
218
|
export const getTemplateChild = native.getTemplateChild;
|
|
216
219
|
export const getProperty = native.getProperty;
|
|
217
220
|
export const setProperty = native.setProperty;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.44.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
|
@@ -79,6 +79,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
79
79
|
exports.Set("callMethod", Napi::Function::New(env, CallMethod));
|
|
80
80
|
exports.Set("hasMethod", Napi::Function::New(env, HasMethod));
|
|
81
81
|
exports.Set("hasClassMethod", Napi::Function::New(env, HasClassMethod));
|
|
82
|
+
exports.Set("classMethodArity", Napi::Function::New(env, ClassMethodArity));
|
|
82
83
|
exports.Set("callStaticMethod", Napi::Function::New(env, CallStaticMethod));
|
|
83
84
|
exports.Set("constructStruct", Napi::Function::New(env, ConstructStruct));
|
|
84
85
|
exports.Set("newObject", Napi::Function::New(env, NewObject));
|
|
@@ -86,6 +87,8 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
86
87
|
exports.Set("registerClassFromGType", Napi::Function::New(env, RegisterClassFromGType));
|
|
87
88
|
exports.Set("constructType", Napi::Function::New(env, ConstructType));
|
|
88
89
|
exports.Set("callParentVfunc", Napi::Function::New(env, CallParentVfunc));
|
|
90
|
+
exports.Set("hasClassVfunc", Napi::Function::New(env, HasClassVfunc));
|
|
91
|
+
exports.Set("callClassVfunc", Napi::Function::New(env, CallClassVfunc));
|
|
89
92
|
exports.Set("getTemplateChild", Napi::Function::New(env, GetTemplateChild));
|
|
90
93
|
exports.Set("getProperty", Napi::Function::New(env, GetProperty));
|
|
91
94
|
exports.Set("setProperty", Napi::Function::New(env, SetProperty));
|
package/src/calls.cc
CHANGED
|
@@ -432,6 +432,70 @@ void ThrowGError(Napi::Env env, GError* error, const std::string& context) {
|
|
|
432
432
|
Napi::Error::New(env, "Calling " + context + ": " + message).ThrowAsJavaScriptException();
|
|
433
433
|
}
|
|
434
434
|
|
|
435
|
+
// Which of a callable's args are NOT consumed from the JS argument list:
|
|
436
|
+
// - the user_data + destroy-notify slots associated with a callback arg
|
|
437
|
+
// (filled from the callback itself);
|
|
438
|
+
// - array-length args of an array IN/OUT arg or of the return value
|
|
439
|
+
// (autofilled from the array's JS length / wired as an OUT slot; flagged
|
|
440
|
+
// in `isLenArg` so the invoke loop can tell the two skip kinds apart).
|
|
441
|
+
// SHARED between the invoke loop and JsInArgCount below so the arity a
|
|
442
|
+
// function REPORTS is by construction the arity the invoke CONSUMES.
|
|
443
|
+
static void ComputeSkippedArgs(GICallableInfo* callable, unsigned int n_args,
|
|
444
|
+
std::vector<bool>* skip, std::vector<bool>* isLenArg) {
|
|
445
|
+
for (unsigned int i = 0; i < n_args; i++) {
|
|
446
|
+
GIArgInfo* ai = gi_callable_info_get_arg(callable, i);
|
|
447
|
+
GITypeInfo* ti = gi_arg_info_get_type_info(ai);
|
|
448
|
+
if (gi_type_info_get_tag(ti) == GI_TYPE_TAG_INTERFACE) {
|
|
449
|
+
GIBaseInfo* iface = gi_type_info_get_interface(ti);
|
|
450
|
+
if (iface != nullptr && GI_IS_CALLBACK_INFO(iface)) {
|
|
451
|
+
int ci = ArgClosureIndex(ai);
|
|
452
|
+
int di = ArgDestroyIndex(ai);
|
|
453
|
+
if (ci >= 0 && static_cast<unsigned int>(ci) < n_args) (*skip)[ci] = true;
|
|
454
|
+
if (di >= 0 && static_cast<unsigned int>(di) < n_args) (*skip)[di] = true;
|
|
455
|
+
}
|
|
456
|
+
if (iface != nullptr) gi_base_info_unref(iface);
|
|
457
|
+
} else if (gi_type_info_get_tag(ti) == GI_TYPE_TAG_ARRAY) {
|
|
458
|
+
unsigned int L = 0;
|
|
459
|
+
if (gi_type_info_get_array_length_index(ti, &L) && L < n_args) {
|
|
460
|
+
(*skip)[L] = true;
|
|
461
|
+
(*isLenArg)[L] = true;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
gi_base_info_unref(ti);
|
|
465
|
+
gi_base_info_unref(ai);
|
|
466
|
+
}
|
|
467
|
+
GITypeInfo* rt = gi_callable_info_get_return_type(callable);
|
|
468
|
+
if (gi_type_info_get_tag(rt) == GI_TYPE_TAG_ARRAY) {
|
|
469
|
+
unsigned int L = 0;
|
|
470
|
+
if (gi_type_info_get_array_length_index(rt, &L) && L < n_args) {
|
|
471
|
+
(*skip)[L] = true;
|
|
472
|
+
(*isLenArg)[L] = true;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
gi_base_info_unref(rt);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// The number of JS arguments a callable consumes: IN/INOUT args minus the
|
|
479
|
+
// skipped ones. This is what gjs reports as `Function.length` (m_js_in_argc,
|
|
480
|
+
// refs/gjs/gi/function.cpp Function::init counts exactly the same set), and it
|
|
481
|
+
// is what @gjsify/gtk-host's descriptor conformance reads to hold the widget
|
|
482
|
+
// table against the installed typelib.
|
|
483
|
+
int JsInArgCount(GICallableInfo* callable) {
|
|
484
|
+
unsigned int n_args = gi_callable_info_get_n_args(callable);
|
|
485
|
+
std::vector<bool> skip(n_args, false);
|
|
486
|
+
std::vector<bool> isLenArg(n_args, false);
|
|
487
|
+
ComputeSkippedArgs(callable, n_args, &skip, &isLenArg);
|
|
488
|
+
int count = 0;
|
|
489
|
+
for (unsigned int i = 0; i < n_args; i++) {
|
|
490
|
+
if (skip[i]) continue;
|
|
491
|
+
GIArgInfo* ai = gi_callable_info_get_arg(callable, i);
|
|
492
|
+
GIDirection d = gi_arg_info_get_direction(ai);
|
|
493
|
+
gi_base_info_unref(ai);
|
|
494
|
+
if (d == GI_DIRECTION_IN || d == GI_DIRECTION_INOUT) count++;
|
|
495
|
+
}
|
|
496
|
+
return count;
|
|
497
|
+
}
|
|
498
|
+
|
|
435
499
|
static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpointer instance,
|
|
436
500
|
Napi::Array args, const std::string& displayName) {
|
|
437
501
|
GICallableInfo* callable = reinterpret_cast<GICallableInfo*>(func);
|
|
@@ -488,56 +552,10 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
488
552
|
}
|
|
489
553
|
}
|
|
490
554
|
|
|
491
|
-
// Pre-scan:
|
|
492
|
-
// arg are filled from the callback, not consumed from the JS argument list.
|
|
555
|
+
// Pre-scan: which args are NOT JS-consumed (see ComputeSkippedArgs).
|
|
493
556
|
std::vector<bool> skip(n_args, false);
|
|
494
|
-
for (unsigned int i = 0; i < n_args; i++) {
|
|
495
|
-
GIArgInfo* ai = gi_callable_info_get_arg(callable, i);
|
|
496
|
-
GITypeInfo* ti = gi_arg_info_get_type_info(ai);
|
|
497
|
-
if (gi_type_info_get_tag(ti) == GI_TYPE_TAG_INTERFACE) {
|
|
498
|
-
GIBaseInfo* iface = gi_type_info_get_interface(ti);
|
|
499
|
-
if (iface != nullptr && GI_IS_CALLBACK_INFO(iface)) {
|
|
500
|
-
int ci = ArgClosureIndex(ai);
|
|
501
|
-
int di = ArgDestroyIndex(ai);
|
|
502
|
-
if (ci >= 0 && static_cast<unsigned int>(ci) < n_args) skip[ci] = true;
|
|
503
|
-
if (di >= 0 && static_cast<unsigned int>(di) < n_args) skip[di] = true;
|
|
504
|
-
}
|
|
505
|
-
if (iface != nullptr) gi_base_info_unref(iface);
|
|
506
|
-
}
|
|
507
|
-
gi_base_info_unref(ti);
|
|
508
|
-
gi_base_info_unref(ai);
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
// Pre-scan: array-length args. An array IN/OUT arg — or the return value — may
|
|
512
|
-
// carry a separate introspectable length arg. Mark it skip (not JS-consumed)
|
|
513
|
-
// and flag it so its OUT slot is wired below / its IN value is autofilled from
|
|
514
|
-
// the array's JS length when the array arg is marshalled. This extends the same
|
|
515
|
-
// skip mechanism the callback closure/destroy slots already use.
|
|
516
557
|
std::vector<bool> isLenArg(n_args, false);
|
|
517
|
-
|
|
518
|
-
GIArgInfo* ai = gi_callable_info_get_arg(callable, i);
|
|
519
|
-
GITypeInfo* ti = gi_arg_info_get_type_info(ai);
|
|
520
|
-
if (gi_type_info_get_tag(ti) == GI_TYPE_TAG_ARRAY) {
|
|
521
|
-
unsigned int L = 0;
|
|
522
|
-
if (gi_type_info_get_array_length_index(ti, &L) && L < n_args) {
|
|
523
|
-
skip[L] = true;
|
|
524
|
-
isLenArg[L] = true;
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
gi_base_info_unref(ti);
|
|
528
|
-
gi_base_info_unref(ai);
|
|
529
|
-
}
|
|
530
|
-
{
|
|
531
|
-
GITypeInfo* rt = gi_callable_info_get_return_type(callable);
|
|
532
|
-
if (gi_type_info_get_tag(rt) == GI_TYPE_TAG_ARRAY) {
|
|
533
|
-
unsigned int L = 0;
|
|
534
|
-
if (gi_type_info_get_array_length_index(rt, &L) && L < n_args) {
|
|
535
|
-
skip[L] = true;
|
|
536
|
-
isLenArg[L] = true;
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
gi_base_info_unref(rt);
|
|
540
|
-
}
|
|
558
|
+
ComputeSkippedArgs(callable, n_args, &skip, &isLenArg);
|
|
541
559
|
|
|
542
560
|
// Caller-allocates OUT storage (fixed C array or boxed struct): blob != nullptr
|
|
543
561
|
// marks the arg; boxedGType != 0 selects the boxed copy/free path on read-back.
|
|
@@ -739,7 +757,8 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
739
757
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
740
758
|
jsCursor++;
|
|
741
759
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
742
|
-
if (JsToGIArgument(env, v, ti, &slots[i], &held[i], tr, &ownedInStrings
|
|
760
|
+
if (JsToGIArgument(env, v, ti, &slots[i], &held[i], tr, &ownedInStrings, nullptr,
|
|
761
|
+
nullptr, nullptr, gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)))) {
|
|
743
762
|
in_args[inPos[i]].v_pointer = &slots[i];
|
|
744
763
|
out_args[outPos[i]].v_pointer = &slots[i];
|
|
745
764
|
} else {
|
|
@@ -835,7 +854,8 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
835
854
|
} else {
|
|
836
855
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
837
856
|
ok = JsToGIArgument(env, v, ti, &in_args[inPos[i]], &held[i], tr, &ownedInStrings,
|
|
838
|
-
&createdClosures, &createdBytes, &createdValues
|
|
857
|
+
&createdClosures, &createdBytes, &createdValues,
|
|
858
|
+
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
839
859
|
}
|
|
840
860
|
}
|
|
841
861
|
|
|
@@ -1250,19 +1270,12 @@ Napi::Value HasMethod(const Napi::CallbackInfo& info) {
|
|
|
1250
1270
|
// implemented-interface and inherited methods. Keyed by TYPE rather than by an
|
|
1251
1271
|
// instance handle because it backs the lazy method resolution on the class
|
|
1252
1272
|
// PROTOTYPE (#1175), which must answer before any instance exists.
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
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();
|
|
1273
|
+
// The class-level resolution HasClassMethod and ClassMethodArity share: the
|
|
1274
|
+
// literal-then-snake_case walk over own, implemented-interface and inherited
|
|
1275
|
+
// methods for an object type, or the interface's own methods for an interface
|
|
1276
|
+
// type. Returns a new ref or nullptr.
|
|
1277
|
+
static GIFunctionInfo* ResolveClassLevelMethod(GIRepository* repo, const std::string& ns,
|
|
1278
|
+
const std::string& tn, const std::string& method) {
|
|
1266
1279
|
GIBaseInfo* base = gi_repository_find_by_name(repo, ns.c_str(), tn.c_str());
|
|
1267
1280
|
GIFunctionInfo* func = nullptr;
|
|
1268
1281
|
if (base != nullptr) {
|
|
@@ -1284,6 +1297,23 @@ Napi::Value HasClassMethod(const Napi::CallbackInfo& info) {
|
|
|
1284
1297
|
}
|
|
1285
1298
|
gi_base_info_unref(base);
|
|
1286
1299
|
}
|
|
1300
|
+
return func;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
Napi::Value HasClassMethod(const Napi::CallbackInfo& info) {
|
|
1304
|
+
Napi::Env env = info.Env();
|
|
1305
|
+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
1306
|
+
Napi::TypeError::New(env,
|
|
1307
|
+
"hasClassMethod(namespace: string, typeName: string, methodName: string)")
|
|
1308
|
+
.ThrowAsJavaScriptException();
|
|
1309
|
+
return env.Null();
|
|
1310
|
+
}
|
|
1311
|
+
std::string ns = info[0].As<Napi::String>().Utf8Value();
|
|
1312
|
+
std::string tn = info[1].As<Napi::String>().Utf8Value();
|
|
1313
|
+
std::string method = info[2].As<Napi::String>().Utf8Value();
|
|
1314
|
+
|
|
1315
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1316
|
+
GIFunctionInfo* func = ResolveClassLevelMethod(repo, ns, tn, method);
|
|
1287
1317
|
g_object_unref(repo);
|
|
1288
1318
|
if (func == nullptr) return Napi::Boolean::New(env, false);
|
|
1289
1319
|
const bool invocable = gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func));
|
|
@@ -1291,6 +1321,89 @@ Napi::Value HasClassMethod(const Napi::CallbackInfo& info) {
|
|
|
1291
1321
|
return Napi::Boolean::New(env, invocable);
|
|
1292
1322
|
}
|
|
1293
1323
|
|
|
1324
|
+
// classMethodArity(namespace, typeName, methodName) -> number
|
|
1325
|
+
// The JS-visible IN-arg count of the method — what gjs reports as the
|
|
1326
|
+
// materialized function's `length` — or -1 when the name does not resolve to an
|
|
1327
|
+
// invocable instance method. Resolution is byte-identical to hasClassMethod
|
|
1328
|
+
// (shared ResolveClassLevelMethod), so `arity >= 0` IS the hasClassMethod
|
|
1329
|
+
// answer and the prototype materializer needs a single native call for both.
|
|
1330
|
+
// Arity 0 is a real answer (`Gtk.Widget.show()`), which is why absence is -1.
|
|
1331
|
+
Napi::Value ClassMethodArity(const Napi::CallbackInfo& info) {
|
|
1332
|
+
Napi::Env env = info.Env();
|
|
1333
|
+
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
1334
|
+
Napi::TypeError::New(
|
|
1335
|
+
env, "classMethodArity(namespace: string, typeName: string, methodName: string)")
|
|
1336
|
+
.ThrowAsJavaScriptException();
|
|
1337
|
+
return env.Null();
|
|
1338
|
+
}
|
|
1339
|
+
std::string ns = info[0].As<Napi::String>().Utf8Value();
|
|
1340
|
+
std::string tn = info[1].As<Napi::String>().Utf8Value();
|
|
1341
|
+
std::string method = info[2].As<Napi::String>().Utf8Value();
|
|
1342
|
+
|
|
1343
|
+
GIRepository* repo = DupDefaultRepository();
|
|
1344
|
+
GIFunctionInfo* func = ResolveClassLevelMethod(repo, ns, tn, method);
|
|
1345
|
+
g_object_unref(repo);
|
|
1346
|
+
if (func == nullptr) return Napi::Number::New(env, -1);
|
|
1347
|
+
const bool invocable = gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func));
|
|
1348
|
+
const int arity = invocable ? JsInArgCount(reinterpret_cast<GICallableInfo*>(func)) : -1;
|
|
1349
|
+
gi_base_info_unref(func);
|
|
1350
|
+
return Napi::Number::New(env, arity);
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// Resolve a CLASS-STRUCT method — one of the GObjectClass functions gjs exposes as a
|
|
1354
|
+
// STATIC on the constructor (`Gtk.Button.list_properties()`, `.find_property()`).
|
|
1355
|
+
// Absent from the object's own GIObjectInfo, which is why CallStaticMethod's three
|
|
1356
|
+
// find_method arms missed it and every such call threw "no static method '…' on
|
|
1357
|
+
// Ns.Class" — including @gjsify/gtk-host's paramSpecs(), on every element-creation
|
|
1358
|
+
// path. `list_properties` is declared on GObject.ObjectClass and NOWHERE else
|
|
1359
|
+
// (Gio.SimpleActionClass has no GIR record at all) and class structs carry no GI
|
|
1360
|
+
// inheritance, so the walk is over the object-info PARENT chain, each level's class
|
|
1361
|
+
// struct in turn — reproducing what gjs reaches through the CONSTRUCTOR PROTOTYPE
|
|
1362
|
+
// CHAIN, where gjs_define_static_methods put GObjectClass's methods on
|
|
1363
|
+
// GObject.Object's constructor and Gtk.Button inherits them. Returns a new ref or
|
|
1364
|
+
// nullptr; same shape as ResolveMethodByName's parent walk above.
|
|
1365
|
+
static GIFunctionInfo* ResolveClassStructMethod(GIObjectInfo* objInfo, const char* name) {
|
|
1366
|
+
GIObjectInfo* walk =
|
|
1367
|
+
reinterpret_cast<GIObjectInfo*>(gi_base_info_ref(reinterpret_cast<GIBaseInfo*>(objInfo)));
|
|
1368
|
+
GIFunctionInfo* found = nullptr;
|
|
1369
|
+
while (walk != nullptr) {
|
|
1370
|
+
GIStructInfo* cs = gi_object_info_get_class_struct(walk);
|
|
1371
|
+
if (cs != nullptr) {
|
|
1372
|
+
if (gi_struct_info_is_gtype_struct(cs)) found = gi_struct_info_find_method(cs, name);
|
|
1373
|
+
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(cs));
|
|
1374
|
+
}
|
|
1375
|
+
if (found != nullptr) break;
|
|
1376
|
+
GIObjectInfo* parent = gi_object_info_get_parent(walk);
|
|
1377
|
+
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(walk));
|
|
1378
|
+
walk = parent;
|
|
1379
|
+
}
|
|
1380
|
+
if (walk != nullptr) gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(walk));
|
|
1381
|
+
return found;
|
|
1382
|
+
}
|
|
1383
|
+
|
|
1384
|
+
// The GTypeClass a class-struct method runs ON: the LEAF type's class, never the
|
|
1385
|
+
// declarer's — list_properties() on GtkButton's class must return the button's 44
|
|
1386
|
+
// pspecs, not GObject's zero. gjs resolves the same way, reading the gtype off the
|
|
1387
|
+
// constructor the call went THROUGH (`this`), which is the leaf.
|
|
1388
|
+
//
|
|
1389
|
+
// gjs's GTypeStructInstanceIn uses g_type_class_peek and states its reason: "we use
|
|
1390
|
+
// peek here to simplify reference counting … GType classes are never really freed …
|
|
1391
|
+
// we know that the GType class is referenced at least once when the JS constructor
|
|
1392
|
+
// is initialized". node-gi's class proxy takes no such ref, so peek alone is nullptr
|
|
1393
|
+
// for a type nothing has instantiated yet — hence g_type_class_ref, to REALISE the
|
|
1394
|
+
// class (the same reason ConstructGObject refs it before reading its pspecs). That
|
|
1395
|
+
// ref is KEPT: it establishes gjs's invariant once per GType (bounded — every later
|
|
1396
|
+
// call is served by the peek), while unrefing is the one call that could invalidate
|
|
1397
|
+
// the GParamSpecs list_properties just handed out, which the class owns. Since GLib
|
|
1398
|
+
// 2.84 g_type_class_unref is a documented no-op, so pairing buys nothing anyway.
|
|
1399
|
+
static gpointer ClassStructInstance(GIObjectInfo* objInfo) {
|
|
1400
|
+
GType gtype =
|
|
1401
|
+
gi_registered_type_info_get_g_type(reinterpret_cast<GIRegisteredTypeInfo*>(objInfo));
|
|
1402
|
+
if (gtype == G_TYPE_INVALID) return nullptr;
|
|
1403
|
+
gpointer klass = g_type_class_peek(gtype);
|
|
1404
|
+
return klass != nullptr ? klass : g_type_class_ref(gtype);
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1294
1407
|
// callStaticMethod(namespace, typeName, methodName, args?) -> unknown
|
|
1295
1408
|
// Invoke a type-level constructor/static function (e.g. Gio.File.new_for_path,
|
|
1296
1409
|
// Gtk.Label.new) — a function found ON a type but taking no instance. The Node
|
|
@@ -1317,8 +1430,27 @@ Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
|
1317
1430
|
return env.Null();
|
|
1318
1431
|
}
|
|
1319
1432
|
GIFunctionInfo* func = nullptr;
|
|
1433
|
+
// Non-null ONLY when the class-struct fallback is what resolved `func`: the
|
|
1434
|
+
// GTypeClass to invoke it on. It doubles as the flag that the "is an instance
|
|
1435
|
+
// method" rejection below must not fire — a class-struct method IS is_method,
|
|
1436
|
+
// its instance is the class rather than an object.
|
|
1437
|
+
gpointer classInstance = nullptr;
|
|
1320
1438
|
if (GI_IS_OBJECT_INFO(typeInfo)) {
|
|
1321
|
-
|
|
1439
|
+
GIObjectInfo* objInfo = reinterpret_cast<GIObjectInfo*>(typeInfo);
|
|
1440
|
+
func = gi_object_info_find_method(objInfo, method.c_str());
|
|
1441
|
+
if (func == nullptr) {
|
|
1442
|
+
func = ResolveClassStructMethod(objInfo, method.c_str());
|
|
1443
|
+
if (func != nullptr) {
|
|
1444
|
+
classInstance = ClassStructInstance(objInfo);
|
|
1445
|
+
if (classInstance == nullptr) {
|
|
1446
|
+
// No GType behind the info, so there is no class to call it on. Drop back
|
|
1447
|
+
// to the plain "no static method" error rather than invoking with a null
|
|
1448
|
+
// instance, which would hand the C function a null GObjectClass*.
|
|
1449
|
+
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(func));
|
|
1450
|
+
func = nullptr;
|
|
1451
|
+
}
|
|
1452
|
+
}
|
|
1453
|
+
}
|
|
1322
1454
|
} else if (GI_IS_INTERFACE_INFO(typeInfo)) {
|
|
1323
1455
|
func = gi_interface_info_find_method(reinterpret_cast<GIInterfaceInfo*>(typeInfo), method.c_str());
|
|
1324
1456
|
} else if (GI_IS_STRUCT_INFO(typeInfo)) {
|
|
@@ -1331,7 +1463,8 @@ Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
|
1331
1463
|
.ThrowAsJavaScriptException();
|
|
1332
1464
|
return env.Null();
|
|
1333
1465
|
}
|
|
1334
|
-
|
|
1466
|
+
const bool isMethod = gi_callable_info_is_method(reinterpret_cast<GICallableInfo*>(func));
|
|
1467
|
+
if (isMethod && classInstance == nullptr) {
|
|
1335
1468
|
gi_base_info_unref(func);
|
|
1336
1469
|
g_object_unref(repo);
|
|
1337
1470
|
Napi::TypeError::New(env, ns + "." + tn + "." + method +
|
|
@@ -1339,7 +1472,10 @@ Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
|
1339
1472
|
.ThrowAsJavaScriptException();
|
|
1340
1473
|
return env.Null();
|
|
1341
1474
|
}
|
|
1342
|
-
|
|
1475
|
+
// A class struct may also declare plain functions (no instance); only the
|
|
1476
|
+
// is_method ones take the class.
|
|
1477
|
+
Napi::Value result = InvokeFunctionInfo(env, func, isMethod ? classInstance : nullptr, args,
|
|
1478
|
+
ns + "." + tn + "." + method);
|
|
1343
1479
|
gi_base_info_unref(func);
|
|
1344
1480
|
g_object_unref(repo);
|
|
1345
1481
|
return result;
|