@gjsify/node-gi 0.51.1 → 0.53.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 +2 -1
- package/binding.gyp +1 -0
- package/gi.js +83 -1
- package/gtk-runtime.d.ts +31 -0
- package/gtk-runtime.js +288 -21
- package/index.d.ts +14 -0
- package/index.js +18 -3
- package/package.json +13 -13
- 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 +1 -0
- package/src/calls.cc +10 -34
- package/src/class.cc +14 -37
- package/src/common.h +38 -0
- package/src/loop.cc +28 -4
- package/src/marshal.cc +371 -9
- package/src/opengl-win32.cc +234 -0
- package/src/toggle.cc +1 -1
- package/system-gi.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.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",
|
|
@@ -88,23 +88,23 @@
|
|
|
88
88
|
"build:prebuild": "node-gyp rebuild && node scripts/stage-prebuild.mjs",
|
|
89
89
|
"rebuild": "node-gyp rebuild",
|
|
90
90
|
"clean": "node-gyp clean",
|
|
91
|
-
"test": "
|
|
92
|
-
"test:node": "
|
|
93
|
-
"test:gc": "
|
|
94
|
-
"test:dbus": "dbus-run-session --config-file=test/session.conf -- env
|
|
91
|
+
"test": "node scripts/pinned-env.mjs --test",
|
|
92
|
+
"test:node": "node scripts/pinned-env.mjs --test",
|
|
93
|
+
"test:gc": "node scripts/pinned-env.mjs --test --expose-gc",
|
|
94
|
+
"test:dbus": "dbus-run-session --config-file=test/session.conf -- node scripts/pinned-env.mjs --test --expose-gc test/dbus.test.mjs test/dbus-async.test.mjs",
|
|
95
95
|
"test:bun": "node scripts/cross-runtime.mjs bun",
|
|
96
96
|
"test:conformance": "node scripts/conformance.mjs",
|
|
97
97
|
"test:conformance:gjs-napi": "node scripts/conformance.mjs --runtimes=gjs-napi",
|
|
98
98
|
"test:gimarshalling": "node scripts/gimarshalling.mjs",
|
|
99
99
|
"test:deno": "node scripts/cross-runtime.mjs deno",
|
|
100
|
-
"test:cairo": "
|
|
101
|
-
"test:cairo-canvas2d": "
|
|
102
|
-
"test:gtk": "
|
|
103
|
-
"test:gtk-cairo": "
|
|
104
|
-
"test:adw": "
|
|
105
|
-
"test:gtk-template": "
|
|
106
|
-
"test:gtk-template-callbacks": "
|
|
107
|
-
"test:gtk-template-signals": "
|
|
100
|
+
"test:cairo": "node scripts/pinned-env.mjs --test test/cairo.test.mjs",
|
|
101
|
+
"test:cairo-canvas2d": "node scripts/pinned-env.mjs --test test/cairo-canvas2d.test.mjs",
|
|
102
|
+
"test:gtk": "node scripts/pinned-env.mjs --test test/gtk-smoke.test.mjs",
|
|
103
|
+
"test:gtk-cairo": "node scripts/pinned-env.mjs --test test/cairo-drawfunc.test.mjs",
|
|
104
|
+
"test:adw": "node scripts/pinned-env.mjs --test test/adw-smoke.test.mjs",
|
|
105
|
+
"test:gtk-template": "node scripts/pinned-env.mjs --test test/gtk-template.test.mjs",
|
|
106
|
+
"test:gtk-template-callbacks": "node scripts/pinned-env.mjs --test test/gtk-template-callbacks.test.mjs",
|
|
107
|
+
"test:gtk-template-signals": "node scripts/pinned-env.mjs --test test/gtk-template-signals.test.mjs"
|
|
108
108
|
},
|
|
109
109
|
"dependencies": {
|
|
110
110
|
"node-addon-api": "^8.0.0"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/addon.cc
CHANGED
|
@@ -155,6 +155,7 @@ static Napi::Object Init(Napi::Env env, Napi::Object exports) {
|
|
|
155
155
|
exports.Set("localeCategories", Napi::Function::New(env, LocaleCategories));
|
|
156
156
|
// The native cairo binding + foreign-struct registration (the `__cairo` export).
|
|
157
157
|
InitCairo(env, exports);
|
|
158
|
+
InitOpenGLWin32(env, exports);
|
|
158
159
|
return exports;
|
|
159
160
|
}
|
|
160
161
|
|
package/src/calls.cc
CHANGED
|
@@ -223,58 +223,34 @@ static void NodeGiCallbackTrampoline(ffi_cif* /*cif*/, void* result, void** args
|
|
|
223
223
|
if (cb->scope != GI_SCOPE_TYPE_CALL) depthReset = std::make_unique<SyncEmitDepthReset>();
|
|
224
224
|
|
|
225
225
|
GICallableInfo* ci = cb->info;
|
|
226
|
-
|
|
226
|
+
// Arguments and the answer in gjs's OUT/INOUT shape, shared with the vfunc
|
|
227
|
+
// trampoline (CToJsCall, marshal.cc): a pure OUT is not a JS argument and is
|
|
228
|
+
// written back from the return value. ffi hands each argument's storage as
|
|
229
|
+
// args[i] (a user_data/void IN arg marshals to undefined, which JS ignores).
|
|
230
|
+
CToJsCall call(napiEnv, ci, std::string("callback '") +
|
|
231
|
+
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ci)) + "'");
|
|
227
232
|
std::vector<napi_value> jsArgs;
|
|
228
|
-
|
|
229
|
-
bool ok = true;
|
|
230
|
-
for (unsigned int i = 0; i < n; i++) {
|
|
231
|
-
GIArgInfo* ai = gi_callable_info_get_arg(ci, i);
|
|
232
|
-
GITypeInfo* ti = gi_arg_info_get_type_info(ai);
|
|
233
|
-
// ffi hands each argument's storage as args[i]; reinterpret as a GIArgument
|
|
234
|
-
// union (a user_data/void arg marshals to undefined, which JS ignores).
|
|
235
|
-
Napi::Value v = GIArgumentToJs(napiEnv, ti, static_cast<GIArgument*>(args[i]), GI_TRANSFER_NOTHING);
|
|
236
|
-
gi_base_info_unref(ti);
|
|
237
|
-
gi_base_info_unref(ai);
|
|
238
|
-
if (napiEnv.IsExceptionPending()) {
|
|
239
|
-
ok = false;
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
242
|
-
jsArgs.push_back(v);
|
|
243
|
-
}
|
|
233
|
+
bool ok = call.MarshalArgs(args, 0, &jsArgs);
|
|
244
234
|
|
|
245
235
|
// Zero the result slot first (it is >= ffi_arg wide; narrow returns leave the
|
|
246
236
|
// upper bytes indeterminate otherwise).
|
|
247
237
|
if (result != nullptr) static_cast<GIArgument*>(result)->v_uint64 = 0;
|
|
248
238
|
|
|
249
|
-
|
|
239
|
+
napi_value ret = nullptr;
|
|
250
240
|
if (ok) {
|
|
251
241
|
napi_value fn = nullptr;
|
|
252
242
|
if (napi_get_reference_value(env, cb->jsFn, &fn) == napi_ok && fn != nullptr) {
|
|
253
243
|
napi_value global = nullptr;
|
|
254
244
|
napi_get_global(env, &global);
|
|
255
|
-
napi_value ret = nullptr;
|
|
256
245
|
// napi_make_callback drains nextTick/microtasks around the call (Node; on
|
|
257
246
|
// Bun/Deno the checkpoint is run by NodeGiMaybeDrainMicrotasks below).
|
|
258
247
|
g_loopDispatchDepth++;
|
|
259
248
|
napi_status st = napi_make_callback(env, nullptr, global, fn, jsArgs.size(), jsArgs.data(), &ret);
|
|
260
249
|
g_loopDispatchDepth--;
|
|
261
|
-
if (st
|
|
262
|
-
GITypeTag rtag = gi_type_info_get_tag(retType);
|
|
263
|
-
if (rtag == GI_TYPE_TAG_UTF8 || rtag == GI_TYPE_TAG_FILENAME) {
|
|
264
|
-
// Hand the caller an owned copy — a JsToGIArgument string would point
|
|
265
|
-
// into a std::string that dies with this frame.
|
|
266
|
-
Napi::Value rv(env, ret);
|
|
267
|
-
static_cast<GIArgument*>(result)->v_string =
|
|
268
|
-
rv.IsString() ? g_strdup(rv.As<Napi::String>().Utf8Value().c_str()) : nullptr;
|
|
269
|
-
} else if (rtag != GI_TYPE_TAG_VOID) {
|
|
270
|
-
std::string held;
|
|
271
|
-
JsToGIArgument(napiEnv, Napi::Value(env, ret), retType, static_cast<GIArgument*>(result),
|
|
272
|
-
&held);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
250
|
+
if (st != napi_ok) ret = nullptr;
|
|
275
251
|
}
|
|
276
252
|
}
|
|
277
|
-
|
|
253
|
+
call.WriteAnswer(ret, result);
|
|
278
254
|
// A CALL-scope callback (e.g. a list `foreach`) runs synchronously inside a
|
|
279
255
|
// JS-initiated GI invoke, so a pending exception must propagate to that JS
|
|
280
256
|
// caller — leave it to surface at the invoke's N-API boundary. A NOTIFIED/ASYNC
|
package/src/class.cc
CHANGED
|
@@ -223,37 +223,27 @@ static void NodeGiVFuncTrampoline(ffi_cif* /*cif*/, void* result, void** args,
|
|
|
223
223
|
|
|
224
224
|
GICallableInfo* ci = reinterpret_cast<GICallableInfo*>(vf->info);
|
|
225
225
|
// args[0] is the instance; declared args follow at args[1..].
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
226
|
+
GObject* instance = static_cast<GObject*>(static_cast<GIArgument*>(args[0])->v_pointer);
|
|
227
|
+
Napi::Value recv = WrapGObject(napiEnv, instance, GI_TRANSFER_NOTHING);
|
|
228
|
+
|
|
229
|
+
// Arguments and the answer in gjs's OUT/INOUT shape, shared with the GI-callback
|
|
230
|
+
// trampoline (CToJsCall, marshal.cc). args[0] is the instance, so the declared
|
|
231
|
+
// arguments start at offset 1. `instance` is also the owner a transfer-none string
|
|
232
|
+
// answer ties its lifetime to (StringForC, marshal.cc) instead of interning it
|
|
233
|
+
// forever — a real vfunc can answer a fresh string every call (e.g.
|
|
234
|
+
// Gtk.Editable's vfunc_get_text on a live-updating buffer).
|
|
235
|
+
CToJsCall call(napiEnv, ci, "vfunc '" + vf->name + "'", instance);
|
|
231
236
|
std::vector<napi_value> jsArgs;
|
|
232
|
-
|
|
233
|
-
bool ok = true;
|
|
234
|
-
for (unsigned int i = 0; i < n; i++) {
|
|
235
|
-
GIArgInfo* ai = gi_callable_info_get_arg(ci, i);
|
|
236
|
-
GITypeInfo* ti = gi_arg_info_get_type_info(ai);
|
|
237
|
-
Napi::Value v =
|
|
238
|
-
GIArgumentToJs(napiEnv, ti, static_cast<GIArgument*>(args[i + 1]), GI_TRANSFER_NOTHING);
|
|
239
|
-
gi_base_info_unref(ti);
|
|
240
|
-
gi_base_info_unref(ai);
|
|
241
|
-
if (napiEnv.IsExceptionPending()) {
|
|
242
|
-
ok = false;
|
|
243
|
-
break;
|
|
244
|
-
}
|
|
245
|
-
jsArgs.push_back(v);
|
|
246
|
-
}
|
|
237
|
+
bool ok = call.MarshalArgs(args, 1, &jsArgs);
|
|
247
238
|
|
|
248
239
|
// Zero the result slot first (it is >= ffi_arg wide; narrow returns leave the
|
|
249
240
|
// upper bytes indeterminate otherwise).
|
|
250
241
|
if (result != nullptr) static_cast<GIArgument*>(result)->v_uint64 = 0;
|
|
251
242
|
|
|
252
|
-
|
|
243
|
+
napi_value ret = nullptr;
|
|
253
244
|
if (ok) {
|
|
254
245
|
napi_value fn = nullptr;
|
|
255
246
|
if (napi_get_reference_value(env, vf->fn, &fn) == napi_ok && fn != nullptr) {
|
|
256
|
-
napi_value ret = nullptr;
|
|
257
247
|
// napi_make_callback drains nextTick/microtasks around the call (Node; on
|
|
258
248
|
// Bun/Deno the checkpoint is run by NodeGiMaybeDrainMicrotasks below); the
|
|
259
249
|
// wrapped instance is the receiver (`this`).
|
|
@@ -261,23 +251,10 @@ static void NodeGiVFuncTrampoline(ffi_cif* /*cif*/, void* result, void** args,
|
|
|
261
251
|
napi_status st =
|
|
262
252
|
napi_make_callback(env, nullptr, recv, fn, jsArgs.size(), jsArgs.data(), &ret);
|
|
263
253
|
g_loopDispatchDepth--;
|
|
264
|
-
if (st
|
|
265
|
-
GITypeTag rtag = gi_type_info_get_tag(retType);
|
|
266
|
-
if (rtag == GI_TYPE_TAG_UTF8 || rtag == GI_TYPE_TAG_FILENAME) {
|
|
267
|
-
// Hand the caller an owned copy — a JsToGIArgument string would point
|
|
268
|
-
// into a std::string that dies with this frame.
|
|
269
|
-
Napi::Value rv(env, ret);
|
|
270
|
-
static_cast<GIArgument*>(result)->v_string =
|
|
271
|
-
rv.IsString() ? g_strdup(rv.As<Napi::String>().Utf8Value().c_str()) : nullptr;
|
|
272
|
-
} else if (rtag != GI_TYPE_TAG_VOID) {
|
|
273
|
-
std::string held;
|
|
274
|
-
JsToGIArgument(napiEnv, Napi::Value(env, ret), retType, static_cast<GIArgument*>(result),
|
|
275
|
-
&held);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
254
|
+
if (st != napi_ok) ret = nullptr;
|
|
278
255
|
}
|
|
279
256
|
}
|
|
280
|
-
|
|
257
|
+
call.WriteAnswer(ret, result);
|
|
281
258
|
// A pending JS exception surfaces at the next N-API boundary (e.g. when the
|
|
282
259
|
// constructType / method call that triggered this vfunc returns).
|
|
283
260
|
//
|
package/src/common.h
CHANGED
|
@@ -176,6 +176,8 @@ const ForeignStructOps* ForeignOpsForInfo(GIBaseInfo* iface);
|
|
|
176
176
|
// from addon.cc's Init; sets the `__cairo` export + registers Context/Surface/
|
|
177
177
|
// Pattern foreign converters.
|
|
178
178
|
void InitCairo(Napi::Env env, Napi::Object exports);
|
|
179
|
+
// win32: probeHostOpenGL / preloadOpenGL / currentGLStrings (opengl-win32.cc); a no-op elsewhere.
|
|
180
|
+
void InitOpenGLWin32(Napi::Env env, Napi::Object exports);
|
|
179
181
|
|
|
180
182
|
// ---- GJS-exact 64-bit integer marshalling helpers (shared) -------------------
|
|
181
183
|
//
|
|
@@ -413,6 +415,42 @@ bool IsSupportedContainerType(GITypeInfo* type, std::string* why, ContainerUse u
|
|
|
413
415
|
// function-invoke path (calls.cc) and the vfunc chain-up path (class.cc).
|
|
414
416
|
bool IsSupportedOutType(GITypeInfo* type, std::string* why);
|
|
415
417
|
size_t CElementSize(GITypeInfo* elem);
|
|
418
|
+
// One call C makes INTO JS — a JS vfunc override (class.cc) or a GI callback
|
|
419
|
+
// (calls.cc) — marshalled in gjs's OUT/INOUT shape (see marshal.cc). Lives on the
|
|
420
|
+
// trampoline's stack under its HandleScope; `ci` is borrowed.
|
|
421
|
+
class CToJsCall {
|
|
422
|
+
public:
|
|
423
|
+
// `owner` is the vfunc's instance (class.cc passes args[0]'s GObject*); nullptr for a
|
|
424
|
+
// plain GI callback (calls.cc), which has no natural object. Only used to tie a
|
|
425
|
+
// transfer-none string ANSWER's lifetime to something bounded (StringForC,
|
|
426
|
+
// marshal.cc) — gjs's own fallback (g_intern_string, unbounded) when there is none.
|
|
427
|
+
CToJsCall(Napi::Env env, GICallableInfo* ci, std::string label, GObject* owner = nullptr);
|
|
428
|
+
~CToJsCall();
|
|
429
|
+
CToJsCall(const CToJsCall&) = delete;
|
|
430
|
+
CToJsCall& operator=(const CToJsCall&) = delete;
|
|
431
|
+
// The JS arguments from the ffi args (`args[offset + i]` is declared arg i): IN as
|
|
432
|
+
// values, INOUT as their current value, pure OUT omitted. False = exception pending.
|
|
433
|
+
bool MarshalArgs(void** args, unsigned int offset, std::vector<napi_value>* jsArgs);
|
|
434
|
+
// Write JS's answer back: the return value into `result` (already zeroed by the
|
|
435
|
+
// caller), the OUT/INOUT through their pointers. A wrong shape or a failed
|
|
436
|
+
// conversion throws; every pure OUT left unanswered is zeroed, never uninitialised.
|
|
437
|
+
// `ret` is nullptr when the JS call itself failed.
|
|
438
|
+
void WriteAnswer(napi_value ret, void* result);
|
|
439
|
+
|
|
440
|
+
private:
|
|
441
|
+
struct Slot {
|
|
442
|
+
GIArgInfo* ai; // owned
|
|
443
|
+
GITypeInfo* ti; // owned
|
|
444
|
+
gpointer dest; // the caller's variable (or caller-allocated record); may be NULL
|
|
445
|
+
};
|
|
446
|
+
void ThrowUnsupported(const Slot& s) const;
|
|
447
|
+
bool WriteOut(const Slot& s, Napi::Value v);
|
|
448
|
+
Napi::Env env_;
|
|
449
|
+
GICallableInfo* ci_;
|
|
450
|
+
std::string label_;
|
|
451
|
+
GObject* owner_; // borrowed; see the constructor comment
|
|
452
|
+
std::vector<Slot> outs_;
|
|
453
|
+
};
|
|
416
454
|
void WriteLengthValue(GITypeInfo* lenType, GIArgument* slot, long n);
|
|
417
455
|
|
|
418
456
|
// One IN length-argument autofill, remembered so a SECOND array naming the same length
|
package/src/loop.cc
CHANGED
|
@@ -379,8 +379,9 @@ Napi::Value IterateMainContext(const Napi::CallbackInfo& info) {
|
|
|
379
379
|
struct PumpPoll {
|
|
380
380
|
uv_poll_t handle;
|
|
381
381
|
int fd;
|
|
382
|
-
int events;
|
|
383
|
-
gboolean seen;
|
|
382
|
+
int events; // currently-subscribed uv event mask
|
|
383
|
+
gboolean seen; // mark/sweep flag for SyncPumpPolls
|
|
384
|
+
gboolean started; // whether the watcher is currently armed (see PumpPollCb)
|
|
384
385
|
};
|
|
385
386
|
|
|
386
387
|
static gboolean g_pump_inited = FALSE;
|
|
@@ -398,7 +399,26 @@ static gboolean g_pump_poll_warned = FALSE;
|
|
|
398
399
|
// Wake-only callbacks: readiness/expiry just ends libuv's poll sleep; the actual
|
|
399
400
|
// GLib dispatch happens in the check phase of the same loop turn (PumpCheckCb).
|
|
400
401
|
static void PumpTimerCb(uv_timer_t* /*t*/) {}
|
|
401
|
-
|
|
402
|
+
|
|
403
|
+
// A wake-up is an EDGE, so the watcher DISARMS itself on the first fire and
|
|
404
|
+
// PumpArmWakeups re-arms it on the next libuv turn. uv_poll is level-triggered
|
|
405
|
+
// and this callback reads nothing, so a GLib fd that stays ready — a dead D-Bus
|
|
406
|
+
// socket stuck at POLLHUP, or the context's own wakeup eventfd, which
|
|
407
|
+
// `block_source()` re-signals on every dispatch — leaves uv's backend fd
|
|
408
|
+
// PERPETUALLY readable. That fd is embedded in UvLoopSource, so GLib then reads
|
|
409
|
+
// "Node has work" on every prepare, and any foreign drain loop of the shape
|
|
410
|
+
// `while (g_main_context_iteration(ctx, FALSE));` never terminates: measured in
|
|
411
|
+
// `e_source_registry_new_sync`'s failure path (no D-Bus reachable), 6.9 M
|
|
412
|
+
// dispatches in 20 s at 100 % CPU with `uv_backend_timeout()` sitting at ~4.6 s,
|
|
413
|
+
// i.e. never once because Node actually had work. The pump's own drain would
|
|
414
|
+
// have cleared the GLib side, but it no-ops at `g_main_depth() > 0` — exactly
|
|
415
|
+
// where a foreign nested iteration runs. Regression
|
|
416
|
+
// `test/foreign-context-drain.test.mjs`.
|
|
417
|
+
static void PumpPollCb(uv_poll_t* p, int /*status*/, int /*events*/) {
|
|
418
|
+
PumpPoll* pp = reinterpret_cast<PumpPoll*>(p->data);
|
|
419
|
+
uv_poll_stop(p);
|
|
420
|
+
pp->started = FALSE;
|
|
421
|
+
}
|
|
402
422
|
|
|
403
423
|
static void PumpPollCloseCb(uv_handle_t* h) {
|
|
404
424
|
delete reinterpret_cast<PumpPoll*>(h->data);
|
|
@@ -440,9 +460,12 @@ static gboolean SyncPumpPolls(int nfds) {
|
|
|
440
460
|
if (it != g_pump_polls->end()) {
|
|
441
461
|
PumpPoll* pp = it->second;
|
|
442
462
|
pp->seen = TRUE;
|
|
443
|
-
|
|
463
|
+
// Re-arm a watcher that disarmed itself on its last fire, as well as one
|
|
464
|
+
// whose event mask changed.
|
|
465
|
+
if (pp->events != ev || !pp->started) {
|
|
444
466
|
if (uv_poll_start(&pp->handle, ev, PumpPollCb) == 0) {
|
|
445
467
|
pp->events = ev;
|
|
468
|
+
pp->started = TRUE;
|
|
446
469
|
uv_unref(reinterpret_cast<uv_handle_t*>(&pp->handle));
|
|
447
470
|
} else {
|
|
448
471
|
all_ok = FALSE;
|
|
@@ -466,6 +489,7 @@ static gboolean SyncPumpPolls(int nfds) {
|
|
|
466
489
|
all_ok = FALSE;
|
|
467
490
|
continue;
|
|
468
491
|
}
|
|
492
|
+
pp->started = TRUE;
|
|
469
493
|
uv_unref(reinterpret_cast<uv_handle_t*>(&pp->handle));
|
|
470
494
|
(*g_pump_polls)[fd] = pp;
|
|
471
495
|
}
|