@gjsify/node-gi 0.64.1 → 0.64.4
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 +9 -0
- package/cairo.d.ts +38 -0
- package/cairo.js +95 -17
- package/gettext.js +29 -7
- package/gi.js +514 -34
- package/host.nativescript.js +19 -0
- package/host.node.js +16 -0
- package/index.d.ts +12 -0
- package/index.js +245 -76
- 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-arm64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/cairo.cc +96 -0
- package/src/class.cc +15 -0
- package/src/common.h +21 -3
- package/src/marshal.cc +39 -17
- package/src/signals.cc +45 -22
package/src/common.h
CHANGED
|
@@ -363,7 +363,7 @@ inline NodeGiIntResult NodeGiJsToInteger(Napi::Value v, GITypeTag tag, GIArgumen
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
enum class NodeGiIntContext { kArgument, kField, kReturnValue };
|
|
366
|
+
enum class NodeGiIntContext { kArgument, kField, kReturnValue, kListElement, kHashElement };
|
|
367
367
|
inline const char* NodeGiIntTagName(GITypeTag tag) {
|
|
368
368
|
switch (tag) {
|
|
369
369
|
case GI_TYPE_TAG_INT8: return "int8";
|
|
@@ -385,13 +385,31 @@ inline void NodeGiThrowIntOutOfRange(Napi::Env env, GITypeTag tag, NodeGiIntCont
|
|
|
385
385
|
msg = std::string("Argument ") + (name != nullptr ? name : "") + ": value is out of range for " + t;
|
|
386
386
|
} else {
|
|
387
387
|
msg = "value " + shown + " is out of range for " +
|
|
388
|
-
(ctx == NodeGiIntContext::kField
|
|
389
|
-
|
|
388
|
+
(ctx == NodeGiIntContext::kField
|
|
389
|
+
? std::string("Field '") + (name ? name : "") + "'"
|
|
390
|
+
: ctx == NodeGiIntContext::kListElement ? std::string("List element")
|
|
391
|
+
: ctx == NodeGiIntContext::kHashElement ? std::string("Hash element")
|
|
392
|
+
: std::string("Return value")) +
|
|
390
393
|
" (type " + t + ")";
|
|
391
394
|
}
|
|
392
395
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
// The text gjs_debug_value (refs/gjs/gi/../jsapi-util-string.cpp) prints for the offending
|
|
399
|
+
// value in a list/hash element error: a Number goes through C++ ostream (%g, 6 digits), a
|
|
400
|
+
// BigInt is shown as its signed 64-bit value with a "(modulo 2^64)" suffix.
|
|
401
|
+
inline std::string NodeGiDebugValue(Napi::Value v) {
|
|
402
|
+
if (v.IsBigInt()) {
|
|
403
|
+
bool lossless = false;
|
|
404
|
+
return std::to_string(v.As<Napi::BigInt>().Int64Value(&lossless)) + "n (modulo 2^64)";
|
|
405
|
+
}
|
|
406
|
+
Napi::Number n = v.ToNumber();
|
|
407
|
+
if (n.IsEmpty()) return std::string();
|
|
408
|
+
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
409
|
+
g_ascii_formatd(buf, sizeof(buf), "%g", n.DoubleValue()); // locale-independent, like ostream
|
|
410
|
+
return buf;
|
|
411
|
+
}
|
|
412
|
+
|
|
395
413
|
// IN array elements and GValue int/uint, GJS-exact: these WRAP instead of refusing.
|
|
396
414
|
// An array element goes through js_value_to_c_strict (refs/gjs/gi/arg.cpp:543), which
|
|
397
415
|
// is ToInt32/ToUint32 truncated to the tag for 8/16/32 bits and ToInt64/ToUint64 —
|
package/src/marshal.cc
CHANGED
|
@@ -1060,6 +1060,8 @@ static bool IsSupportedElementType(GITypeInfo* elem, std::string* why, bool byVa
|
|
|
1060
1060
|
case GI_TYPE_TAG_UTF8:
|
|
1061
1061
|
case GI_TYPE_TAG_FILENAME:
|
|
1062
1062
|
return true;
|
|
1063
|
+
// A gunichar is a 4-byte cell; only a C array writes one (no list/hash node).
|
|
1064
|
+
case GI_TYPE_TAG_UNICHAR: return cArrayCell;
|
|
1063
1065
|
case GI_TYPE_TAG_INTERFACE: {
|
|
1064
1066
|
GIBaseInfo* iface = gi_type_info_get_interface(elem);
|
|
1065
1067
|
bool ok = iface != nullptr && (GI_IS_OBJECT_INFO(iface) || GI_IS_INTERFACE_INFO(iface));
|
|
@@ -1178,7 +1180,8 @@ size_t CElementSize(GITypeInfo* elem) {
|
|
|
1178
1180
|
case GI_TYPE_TAG_INT16:
|
|
1179
1181
|
case GI_TYPE_TAG_UINT16: return 2;
|
|
1180
1182
|
case GI_TYPE_TAG_INT32:
|
|
1181
|
-
case GI_TYPE_TAG_UINT32:
|
|
1183
|
+
case GI_TYPE_TAG_UINT32:
|
|
1184
|
+
case GI_TYPE_TAG_UNICHAR: return 4;
|
|
1182
1185
|
case GI_TYPE_TAG_INT64:
|
|
1183
1186
|
case GI_TYPE_TAG_UINT64: return 8;
|
|
1184
1187
|
case GI_TYPE_TAG_FLOAT: return sizeof(gfloat);
|
|
@@ -1601,16 +1604,16 @@ Napi::Value ReadOutOrReturn(Napi::Env env, GICallableInfo* callable, GITypeInfo*
|
|
|
1601
1604
|
// the element, so it gets a ref (objects) or an independent copy (boxed) instead.
|
|
1602
1605
|
// Throws + returns false on an unsupported element.
|
|
1603
1606
|
static bool ElementToGIArgument(Napi::Env env, GITypeInfo* elem, Napi::Value v, GIArgument* a,
|
|
1604
|
-
GITransfer elemTransfer,
|
|
1607
|
+
GITransfer elemTransfer, NodeGiIntContext intContext = NodeGiIntContext::kListElement,
|
|
1608
|
+
bool wrapIntegers = false) {
|
|
1605
1609
|
memset(a, 0, sizeof(*a));
|
|
1606
1610
|
switch (gi_type_info_get_tag(elem)) {
|
|
1607
1611
|
// Scalar coercions via the terminate-safe helpers (common.h): a swallowed
|
|
1608
1612
|
// napi failure mid worker.terminate() must degrade to a zero, not cascade
|
|
1609
1613
|
// into Error::New(nullptr)'s fatal sites.
|
|
1610
1614
|
case GI_TYPE_TAG_BOOLEAN: a->v_boolean = NodeGiToBool(v); return true;
|
|
1611
|
-
// Integers WRAP
|
|
1612
|
-
//
|
|
1613
|
-
// coercion: gjs's rule for those is not probed yet.
|
|
1615
|
+
// Integers WRAP in an array (arg.cpp gjs_array_to_auto_array, NodeGiJsToIntegerWrap in
|
|
1616
|
+
// common.h) and are REFUSED out of range in a GList/GHashTable node.
|
|
1614
1617
|
case GI_TYPE_TAG_INT8:
|
|
1615
1618
|
case GI_TYPE_TAG_UINT8:
|
|
1616
1619
|
case GI_TYPE_TAG_INT16:
|
|
@@ -1621,16 +1624,20 @@ static bool ElementToGIArgument(Napi::Env env, GITypeInfo* elem, Napi::Value v,
|
|
|
1621
1624
|
case GI_TYPE_TAG_UINT64:
|
|
1622
1625
|
if (wrapIntegers)
|
|
1623
1626
|
return NodeGiJsToIntegerWrap(v, gi_type_info_get_tag(elem), a) == NodeGiIntResult::kOk;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
case
|
|
1628
|
-
case
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1627
|
+
// A list/hash node is checked, not wrapped (arg.cpp gjs_value_to_basic_gi_argument
|
|
1628
|
+
// -> gjs_arg_set_from_js_value): out of range is a plain Error.
|
|
1629
|
+
switch (NodeGiJsToInteger(v, gi_type_info_get_tag(elem), a, nullptr)) {
|
|
1630
|
+
case NodeGiIntResult::kPending: return false;
|
|
1631
|
+
case NodeGiIntResult::kOutOfRange:
|
|
1632
|
+
NodeGiThrowIntOutOfRange(env, gi_type_info_get_tag(elem), intContext, nullptr,
|
|
1633
|
+
NodeGiDebugValue(v));
|
|
1634
|
+
return false;
|
|
1635
|
+
case NodeGiIntResult::kOk: return true;
|
|
1633
1636
|
}
|
|
1637
|
+
return true;
|
|
1638
|
+
// gjs_array_to_auto_array<char32_t> wraps like uint32 (arg.cpp js_value_to_c_strict).
|
|
1639
|
+
case GI_TYPE_TAG_UNICHAR:
|
|
1640
|
+
return NodeGiJsToIntegerWrap(v, GI_TYPE_TAG_UINT32, a) == NodeGiIntResult::kOk;
|
|
1634
1641
|
case GI_TYPE_TAG_FLOAT: a->v_float = static_cast<gfloat>(NodeGiToDouble(v)); return true;
|
|
1635
1642
|
case GI_TYPE_TAG_DOUBLE: a->v_double = NodeGiToDouble(v); return true;
|
|
1636
1643
|
case GI_TYPE_TAG_GTYPE: {
|
|
@@ -2267,6 +2274,20 @@ static bool JsToCArray(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfe
|
|
|
2267
2274
|
return true;
|
|
2268
2275
|
}
|
|
2269
2276
|
|
|
2277
|
+
// A JS string as a gunichar C array → its code points (arg.cpp:584, gjs_string_to_ucs4).
|
|
2278
|
+
if (at == GI_ARRAY_TYPE_C && etag == GI_TYPE_TAG_UNICHAR && v.IsString()) {
|
|
2279
|
+
std::string s = v.As<Napi::String>().Utf8Value();
|
|
2280
|
+
glong n = 0;
|
|
2281
|
+
gunichar* ucs = g_utf8_to_ucs4_fast(s.c_str(), static_cast<glong>(s.size()), &n);
|
|
2282
|
+
void* buf = g_malloc0(elemSize * (n + (zt ? 1 : 0)));
|
|
2283
|
+
if (n > 0) memcpy(buf, ucs, elemSize * n);
|
|
2284
|
+
g_free(ucs);
|
|
2285
|
+
*outPtr = buf;
|
|
2286
|
+
*outCount = static_cast<long>(n);
|
|
2287
|
+
gi_base_info_unref(elem);
|
|
2288
|
+
return true;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2270
2291
|
if (!v.IsArray()) {
|
|
2271
2292
|
gi_base_info_unref(elem);
|
|
2272
2293
|
Napi::TypeError::New(env, "expected an array for the array argument")
|
|
@@ -2301,7 +2322,7 @@ static bool JsToCArray(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfe
|
|
|
2301
2322
|
}
|
|
2302
2323
|
GIArgument a;
|
|
2303
2324
|
ok = ElementToGIArgument(env, elem, arr.Get(static_cast<uint32_t>(i)), &a, elemTransfer,
|
|
2304
|
-
/*wrapIntegers=*/true);
|
|
2325
|
+
NodeGiIntContext::kListElement, /*wrapIntegers=*/true);
|
|
2305
2326
|
if (!ok) break;
|
|
2306
2327
|
// Copy the element's storage bytes (LE: the low elemSize bytes of the union
|
|
2307
2328
|
// alias the active field — v_string/v_pointer for pointers, the scalar
|
|
@@ -2448,7 +2469,8 @@ static bool JsToGHashIn(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransf
|
|
|
2448
2469
|
} else {
|
|
2449
2470
|
GIArgument ka;
|
|
2450
2471
|
Napi::Value keyNum = Napi::Number::New(env, static_cast<double>(g_ascii_strtoll(ks.c_str(), nullptr, 10)));
|
|
2451
|
-
ok = ElementToGIArgument(env, kt, keyNum, &ka, GI_TRANSFER_NOTHING
|
|
2472
|
+
ok = ElementToGIArgument(env, kt, keyNum, &ka, GI_TRANSFER_NOTHING,
|
|
2473
|
+
NodeGiIntContext::kHashElement, /*wrapIntegers=*/true);
|
|
2452
2474
|
if (!ok) break;
|
|
2453
2475
|
kp = gi_type_info_hash_pointer_from_argument(kt, &ka);
|
|
2454
2476
|
}
|
|
@@ -2459,7 +2481,7 @@ static bool JsToGHashIn(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransf
|
|
|
2459
2481
|
Napi::Value val = obj.Get(key);
|
|
2460
2482
|
gpointer vp = nullptr;
|
|
2461
2483
|
GIArgument va;
|
|
2462
|
-
ok = ElementToGIArgument(env, vt, val, &va, elemTransfer);
|
|
2484
|
+
ok = ElementToGIArgument(env, vt, val, &va, elemTransfer, NodeGiIntContext::kHashElement);
|
|
2463
2485
|
if (!ok) {
|
|
2464
2486
|
g_free(kp);
|
|
2465
2487
|
break;
|
package/src/signals.cc
CHANGED
|
@@ -7,22 +7,36 @@ namespace nodegi {
|
|
|
7
7
|
|
|
8
8
|
// ---- signals (milestone 1) ----
|
|
9
9
|
//
|
|
10
|
-
// A GClosure that wraps a JS callback. The
|
|
11
|
-
// napi_ref
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
// into the signal return GValue.
|
|
10
|
+
// A GClosure that wraps a JS callback. The closure's finalize notifier drops the
|
|
11
|
+
// napi_ref. The generic marshal converts the signal's GValue params to JS — the
|
|
12
|
+
// EMITTER instance (param 0) first, then the signal's own params (1..n), matching
|
|
13
|
+
// GJS — and the JS return into the signal return GValue.
|
|
15
14
|
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
// the
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
//
|
|
25
|
-
//
|
|
15
|
+
// REF STRENGTH IS THE WHOLE CONTRACT, and it differs per closure KIND, exactly as
|
|
16
|
+
// in GJS (refs/gjs/gi/closure.h: `create_marshaled` roots, `create_for_signal`
|
|
17
|
+
// does not):
|
|
18
|
+
//
|
|
19
|
+
// * GENERIC closure (a JS function marshalled as a GClosure IN-arg, invoked by
|
|
20
|
+
// arbitrary C) — STRONG. Nothing on the JS side owns it; the ref IS the owner.
|
|
21
|
+
// * SIGNAL closure (`.connect()`) and TEMPLATE-CALLBACK closure — WEAK. A
|
|
22
|
+
// handler that captures its own emitter otherwise forms a
|
|
23
|
+
// GObject → GClosure → napi_ref → callback → wrapper → toggle ref → GObject
|
|
24
|
+
// cycle that spans C, and no GC can break it: `obj.connect('x', () => obj.y)`
|
|
25
|
+
// made `obj` immortal. GJS has the identical cycle on paper and collects it,
|
|
26
|
+
// because its signal closures keep the callable UNROOTED and trace it from
|
|
27
|
+
// the owning wrapper (ObjectInstance::m_closures / trace_impl), which puts
|
|
28
|
+
// the whole cycle inside one heap the GC can reason about.
|
|
29
|
+
//
|
|
30
|
+
// node-gi has no tracing hook, so the equivalent edge is built in JS: index.js
|
|
31
|
+
// retains the callback in a WeakMap keyed by the canonical handle — the only
|
|
32
|
+
// ephemeron V8 offers over a non-extensible External, and the same shape
|
|
33
|
+
// `associate_closure` has. The handle is reachable exactly when GJS would trace
|
|
34
|
+
// the closure (JS holds the wrapper, or C owns the object and toggle.cc roots
|
|
35
|
+
// it), so a connected handler still fires for as long as anything can emit to it,
|
|
36
|
+
// and the self-referential cycle collects. A collected callback leaves the weak
|
|
37
|
+
// ref empty and the marshal returns early (it already tolerates a null value).
|
|
38
|
+
// Measured against gjs 1.88.1 with a FinalizationRegistry witness; verified by the
|
|
39
|
+
// signal-cycle cases in test/gc-identity.test.mjs.
|
|
26
40
|
|
|
27
41
|
struct JsClosureData {
|
|
28
42
|
napi_env env;
|
|
@@ -191,8 +205,11 @@ static void JsGenericClosureMarshal(GClosure* closure, GValue* return_value, gui
|
|
|
191
205
|
|
|
192
206
|
// JS function → a floating marshaled GClosure (the engine's twin of GJS's
|
|
193
207
|
// Gjs::Closure::create_marshaled for "boxed" closure args — refs/gjs/gi/
|
|
194
|
-
// arg-cache.cpp GClosureInTransferNone::in). The closure holds a
|
|
195
|
-
// the function
|
|
208
|
+
// arg-cache.cpp GClosureInTransferNone::in). The closure holds a STRONG ref to
|
|
209
|
+
// the function — deliberately, and unlike the signal/template closures above:
|
|
210
|
+
// `create_marshaled` defaults `root = true` (refs/gjs/gi/closure.h), because no
|
|
211
|
+
// JS-side owner exists for a closure handed to arbitrary C. JsClosureFinalize
|
|
212
|
+
// drops it when the closure is invalidated /
|
|
196
213
|
// finalized (the callee released its last ref, or the invoke-site released the
|
|
197
214
|
// only ref because the callee never kept one). Ref/sink + release policy lives at
|
|
198
215
|
// the marshalling site (marshal.cc / calls.cc CreatedClosures).
|
|
@@ -231,9 +248,11 @@ Napi::Value ConnectSignal(const Napi::CallbackInfo& info) {
|
|
|
231
248
|
return env.Null();
|
|
232
249
|
}
|
|
233
250
|
|
|
251
|
+
// WEAK (refcount 0) — the keep-alive is the JS-side handle→callback edge
|
|
252
|
+
// index.js installs; see the ref-strength contract at the top of this file.
|
|
234
253
|
JsClosureData* jc = g_new0(JsClosureData, 1);
|
|
235
254
|
jc->env = env;
|
|
236
|
-
napi_create_reference(env, info[2],
|
|
255
|
+
napi_create_reference(env, info[2], 0, &jc->callback);
|
|
237
256
|
|
|
238
257
|
GClosure* closure = g_closure_new_simple(sizeof(GClosure), jc);
|
|
239
258
|
g_closure_set_marshal(closure, JsClosureMarshal);
|
|
@@ -468,13 +487,17 @@ static GClosure* NodeGiScopeCreateClosure(void* selfScope, void* builder,
|
|
|
468
487
|
}
|
|
469
488
|
|
|
470
489
|
// Wrap the resolved (instance-bound, arg-wrapping) JS function in a JsClosure —
|
|
471
|
-
// the same closure type a `.connect()` produces
|
|
472
|
-
// the
|
|
473
|
-
//
|
|
490
|
+
// the same closure type a `.connect()` produces, and WEAK for the same reason:
|
|
491
|
+
// the resolver's dispatcher captures the widget handle, so a strong ref made
|
|
492
|
+
// every templated widget immortal. gi.js's resolveTemplateCallback retains the
|
|
493
|
+
// dispatcher on the widget handle before returning it, which is what GJS's Gtk
|
|
494
|
+
// override does with `Gi.associateClosure(lifetimeObject, …)`
|
|
495
|
+
// (refs/gjs/modules/core/overrides/Gtk.js). g_object_watch_closure additionally
|
|
496
|
+
// ties the closure to the widget and invalidates it on finalize, exactly like
|
|
474
497
|
// GtkBuilderCScope's own create_closure.
|
|
475
498
|
JsClosureData* jc = g_new0(JsClosureData, 1);
|
|
476
499
|
jc->env = rawEnv;
|
|
477
|
-
napi_create_reference(rawEnv, resolved,
|
|
500
|
+
napi_create_reference(rawEnv, resolved, 0, &jc->callback);
|
|
478
501
|
GClosure* closure = g_closure_new_simple(sizeof(GClosure), jc);
|
|
479
502
|
g_closure_set_marshal(closure, JsClosureMarshal);
|
|
480
503
|
g_closure_add_finalize_notifier(closure, jc, JsClosureFinalize);
|