@gjsify/node-gi 0.45.0 → 0.47.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 +23 -9
- package/gi.js +65 -2
- package/gtk-runtime.js +14 -0
- package/index.d.ts +15 -1
- package/index.js +16 -0
- package/native-prebuilds.js +510 -0
- package/package.json +2 -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 +1 -0
- package/src/calls.cc +102 -26
- package/src/class.cc +22 -9
- package/src/common.h +70 -20
- package/src/marshal.cc +561 -28
- package/src/object.cc +26 -0
- package/src/repo.cc +15 -4
- package/src/toggle.cc +208 -53
- package/system-gi.d.ts +4 -0
- package/system-gi.js +65 -11
package/src/calls.cc
CHANGED
|
@@ -331,9 +331,8 @@ static NodeGiCallback* CreateCallback(Napi::Env env, Napi::Function fn, GICallab
|
|
|
331
331
|
|
|
332
332
|
// Whether `type` is a supported OUT/INOUT marshalling type for this milestone:
|
|
333
333
|
// fundamentals (numbers/bool), strings (utf8/filename), GObject/interface +
|
|
334
|
-
// enums/flags, struct/boxed/union (wrapped as boxed handles on return),
|
|
335
|
-
// containers (arrays, GList/GSList/GHashTable).
|
|
336
|
-
// deferred: a clear error rather than silent mis-handling. *why receives a short
|
|
334
|
+
// enums/flags, struct/boxed/union (wrapped as boxed handles on return), GError,
|
|
335
|
+
// and the containers (arrays, GList/GSList/GHashTable). *why receives a short
|
|
337
336
|
// type label on refusal. Declared in common.h — shared with the vfunc chain-up
|
|
338
337
|
// path (class.cc CallParentVfunc), which routes each OUT/INOUT vfunc arg the same
|
|
339
338
|
// way this function-invoke path does.
|
|
@@ -365,6 +364,24 @@ bool IsSupportedOutType(GITypeInfo* type, std::string* why) {
|
|
|
365
364
|
if (iface != nullptr) gi_base_info_unref(iface);
|
|
366
365
|
return ok;
|
|
367
366
|
}
|
|
367
|
+
case GI_TYPE_TAG_ERROR:
|
|
368
|
+
// A plain `GError**` OUT slot — `Gst.Message.parse_error()` and the other bus
|
|
369
|
+
// accessors of the GStreamer message APIs, `GLib.set_error_literal`,
|
|
370
|
+
// `GLib.propagate_error`. Swept over the installed typelibs, EVERY GError
|
|
371
|
+
// OUT/INOUT parameter is caller_allocates=false + transfer=EVERYTHING, so
|
|
372
|
+
// this tag never reaches the caller-allocates branch above and the callee
|
|
373
|
+
// always writes a GError the caller then owns.
|
|
374
|
+
//
|
|
375
|
+
// Ownership needs nothing new: ReadOutOrReturn falls through to
|
|
376
|
+
// GIArgumentToJs's GI_TYPE_TAG_ERROR arm (marshal.cc), and G_TYPE_ERROR is a
|
|
377
|
+
// BOXED type — glib gobject/gboxed.c,
|
|
378
|
+
// `G_DEFINE_BOXED_TYPE(GError, g_error, g_error_copy, g_error_free)` — so the
|
|
379
|
+
// handle adopts a transfer-full pointer and its finalizer g_boxed_free's it
|
|
380
|
+
// (= g_error_free) exactly like any other EVERYTHING boxed OUT. A callee that
|
|
381
|
+
// leaves the slot untouched (`parse_info()` on an ERROR message) reads back as
|
|
382
|
+
// `null` and not an empty error, because `slots` is value-initialised and that
|
|
383
|
+
// arm null-guards the pointer — matching gjs.
|
|
384
|
+
return true;
|
|
368
385
|
case GI_TYPE_TAG_ARRAY:
|
|
369
386
|
case GI_TYPE_TAG_GLIST:
|
|
370
387
|
case GI_TYPE_TAG_GSLIST:
|
|
@@ -372,7 +389,7 @@ bool IsSupportedOutType(GITypeInfo* type, std::string* why) {
|
|
|
372
389
|
// Container OUT/return — arrays (incl. GStrv / byte arrays), GList/GSList,
|
|
373
390
|
// and string-keyed GHashTable. Element-type support is checked here so an
|
|
374
391
|
// exotic combo defers cleanly before the invoke.
|
|
375
|
-
return IsSupportedContainerType(type, why);
|
|
392
|
+
return IsSupportedContainerType(type, why, ContainerUse::kReadBack);
|
|
376
393
|
default:
|
|
377
394
|
if (why != nullptr)
|
|
378
395
|
*why = "type tag " + std::to_string(static_cast<int>(gi_type_info_get_tag(type)));
|
|
@@ -581,8 +598,10 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
581
598
|
// belong to the callee (dropped only when the callee never ran).
|
|
582
599
|
CreatedBytes createdBytes;
|
|
583
600
|
CreatedValues createdValues;
|
|
601
|
+
CreatedErrors createdErrors;
|
|
584
602
|
bool ok = true;
|
|
585
603
|
size_t jsCursor = 0;
|
|
604
|
+
std::vector<LengthWrite> lengthWrites;
|
|
586
605
|
for (unsigned int i = 0; i < n_args && ok; i++) {
|
|
587
606
|
if (skip[i]) {
|
|
588
607
|
// A length arg that is OUT/INOUT: the callee writes the array length into
|
|
@@ -710,7 +729,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
710
729
|
// that call was removed from the suite and must not come back.
|
|
711
730
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
712
731
|
jsCursor++;
|
|
713
|
-
if (!IsSupportedContainerType(ti, &why)) {
|
|
732
|
+
if (!IsSupportedContainerType(ti, &why, ContainerUse::kReadBack)) {
|
|
714
733
|
Napi::TypeError::New(env, displayName + ": INOUT " + why +
|
|
715
734
|
" parameters are not yet supported")
|
|
716
735
|
.ThrowAsJavaScriptException();
|
|
@@ -758,7 +777,8 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
758
777
|
jsCursor++;
|
|
759
778
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
760
779
|
if (JsToGIArgument(env, v, ti, &slots[i], &held[i], tr, &ownedInStrings, nullptr,
|
|
761
|
-
nullptr, nullptr,
|
|
780
|
+
nullptr, nullptr, &createdErrors,
|
|
781
|
+
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)))) {
|
|
762
782
|
in_args[inPos[i]].v_pointer = &slots[i];
|
|
763
783
|
out_args[outPos[i]].v_pointer = &slots[i];
|
|
764
784
|
} else {
|
|
@@ -825,7 +845,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
825
845
|
if (tg == GI_TYPE_TAG_ARRAY || tg == GI_TYPE_TAG_GLIST || tg == GI_TYPE_TAG_GSLIST ||
|
|
826
846
|
tg == GI_TYPE_TAG_GHASH) {
|
|
827
847
|
std::string why;
|
|
828
|
-
if (!IsSupportedContainerType(ti, &why)) {
|
|
848
|
+
if (!IsSupportedContainerType(ti, &why, ContainerUse::kIn)) {
|
|
829
849
|
Napi::TypeError::New(env, displayName + ": IN " + why +
|
|
830
850
|
" parameters are not yet supported")
|
|
831
851
|
.ThrowAsJavaScriptException();
|
|
@@ -842,11 +862,21 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
842
862
|
unsigned int L = 0;
|
|
843
863
|
if (gi_type_info_get_array_length_index(ti, &L) && L < n_args &&
|
|
844
864
|
dirs[L] == GI_DIRECTION_IN && inPos[L] >= 0) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
865
|
+
long other = 0;
|
|
866
|
+
if (!RecordLengthWrite(&lengthWrites, L, ccount, &other)) {
|
|
867
|
+
Napi::TypeError::New(
|
|
868
|
+
env, displayName + ": two arrays share length argument " +
|
|
869
|
+
std::to_string(L) + " but have different lengths (" +
|
|
870
|
+
std::to_string(other) + " and " + std::to_string(ccount) + ")")
|
|
871
|
+
.ThrowAsJavaScriptException();
|
|
872
|
+
ok = false;
|
|
873
|
+
} else {
|
|
874
|
+
GIArgInfo* la = gi_callable_info_get_arg(callable, L);
|
|
875
|
+
GITypeInfo* lt = gi_arg_info_get_type_info(la);
|
|
876
|
+
WriteLengthValue(lt, &in_args[inPos[L]], ccount);
|
|
877
|
+
gi_base_info_unref(lt);
|
|
878
|
+
gi_base_info_unref(la);
|
|
879
|
+
}
|
|
850
880
|
}
|
|
851
881
|
}
|
|
852
882
|
}
|
|
@@ -854,7 +884,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
854
884
|
} else {
|
|
855
885
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
856
886
|
ok = JsToGIArgument(env, v, ti, &in_args[inPos[i]], &held[i], tr, &ownedInStrings,
|
|
857
|
-
&createdClosures, &createdBytes, &createdValues,
|
|
887
|
+
&createdClosures, &createdBytes, &createdValues, &createdErrors,
|
|
858
888
|
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
859
889
|
}
|
|
860
890
|
}
|
|
@@ -876,6 +906,9 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
876
906
|
// Likewise every GValue boxed from a plain JS value — never adopted.
|
|
877
907
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
878
908
|
for (GValue* gv : createdValues.transferFull) NodeGiFreeBoxedGValue(gv);
|
|
909
|
+
// Likewise every GError built from an L1 GLib.Error — never adopted.
|
|
910
|
+
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
911
|
+
for (GError* e : createdErrors.transferFull) g_error_free(e);
|
|
879
912
|
// Likewise the transfer-full-instance ref: the callee never consumed it.
|
|
880
913
|
if (instanceRefTaken) g_object_unref(static_cast<GObject*>(instance));
|
|
881
914
|
for (gpointer s : ownedInStrings) g_free(s); // never reached the callee (#658)
|
|
@@ -920,6 +953,11 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
920
953
|
// box is ours to free — success AND error paths, mirroring the GBytes rule.
|
|
921
954
|
// Transfer-full GValues were adopted by the callee — no release.
|
|
922
955
|
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
956
|
+
// Transfer-none GError IN-args built from an L1 GLib.Error: the callee copied
|
|
957
|
+
// what it kept (gst_message_new_error copies the GError into the message), so
|
|
958
|
+
// the build is ours to free — success AND error paths, mirroring the two rules
|
|
959
|
+
// above. Transfer-full GErrors were adopted by the callee — no release.
|
|
960
|
+
for (GError* e : createdErrors.transferNone) g_error_free(e);
|
|
923
961
|
if (!success) {
|
|
924
962
|
for (const InContainer& c : inContainers) FreeInContainer(c);
|
|
925
963
|
// A failed invoke did not adopt the transfer-full IN/INOUT strings we g_strdup'd
|
|
@@ -1362,7 +1400,15 @@ Napi::Value ClassMethodArity(const Napi::CallbackInfo& info) {
|
|
|
1362
1400
|
// CHAIN, where gjs_define_static_methods put GObjectClass's methods on
|
|
1363
1401
|
// GObject.Object's constructor and Gtk.Button inherits them. Returns a new ref or
|
|
1364
1402
|
// nullptr; same shape as ResolveMethodByName's parent walk above.
|
|
1365
|
-
|
|
1403
|
+
//
|
|
1404
|
+
// `declaringGType` receives the GType whose class struct DECLARED the method —
|
|
1405
|
+
// G_TYPE_OBJECT for `list_properties` — and is left untouched on a miss. That is the
|
|
1406
|
+
// type a receiver has to be compatible with, exactly as in gjs, where the method is
|
|
1407
|
+
// one function on GObject.Object's constructor and every class inherits the same one:
|
|
1408
|
+
// measured, `Gtk.ListItem.list_properties.call(Gtk.Widget)` answers GtkWidget's 36
|
|
1409
|
+
// there. Checking against the type the name was READ from would refuse that.
|
|
1410
|
+
static GIFunctionInfo* ResolveClassStructMethod(GIObjectInfo* objInfo, const char* name,
|
|
1411
|
+
GType* declaringGType) {
|
|
1366
1412
|
GIObjectInfo* walk =
|
|
1367
1413
|
reinterpret_cast<GIObjectInfo*>(gi_base_info_ref(reinterpret_cast<GIBaseInfo*>(objInfo)));
|
|
1368
1414
|
GIFunctionInfo* found = nullptr;
|
|
@@ -1372,7 +1418,11 @@ static GIFunctionInfo* ResolveClassStructMethod(GIObjectInfo* objInfo, const cha
|
|
|
1372
1418
|
if (gi_struct_info_is_gtype_struct(cs)) found = gi_struct_info_find_method(cs, name);
|
|
1373
1419
|
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(cs));
|
|
1374
1420
|
}
|
|
1375
|
-
if (found != nullptr)
|
|
1421
|
+
if (found != nullptr) {
|
|
1422
|
+
*declaringGType =
|
|
1423
|
+
gi_registered_type_info_get_g_type(reinterpret_cast<GIRegisteredTypeInfo*>(walk));
|
|
1424
|
+
break;
|
|
1425
|
+
}
|
|
1376
1426
|
GIObjectInfo* parent = gi_object_info_get_parent(walk);
|
|
1377
1427
|
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(walk));
|
|
1378
1428
|
walk = parent;
|
|
@@ -1396,23 +1446,39 @@ static GIFunctionInfo* ResolveClassStructMethod(GIObjectInfo* objInfo, const cha
|
|
|
1396
1446
|
// call is served by the peek), while unrefing is the one call that could invalidate
|
|
1397
1447
|
// the GParamSpecs list_properties just handed out, which the class owns. Since GLib
|
|
1398
1448
|
// 2.84 g_type_class_unref is a documented no-op, so pairing buys nothing anyway.
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1449
|
+
//
|
|
1450
|
+
// nullptr for an UNCLASSED GType (boxed/struct, interface) as well as for an absent
|
|
1451
|
+
// one — `g_type_class_ref` has no class to hand back there and answers nullptr after
|
|
1452
|
+
// a CRITICAL. It is what makes the receiver retarget below refuse safely.
|
|
1453
|
+
static gpointer ClassStructInstanceForGType(GType gtype) {
|
|
1454
|
+
if (gtype == G_TYPE_INVALID || !G_TYPE_IS_CLASSED(gtype)) return nullptr;
|
|
1403
1455
|
gpointer klass = g_type_class_peek(gtype);
|
|
1404
1456
|
return klass != nullptr ? klass : g_type_class_ref(gtype);
|
|
1405
1457
|
}
|
|
1406
1458
|
|
|
1407
|
-
// callStaticMethod(namespace, typeName, methodName, args?) -> unknown
|
|
1459
|
+
// callStaticMethod(namespace, typeName, methodName, args?, receiverGType?) -> unknown
|
|
1408
1460
|
// Invoke a type-level constructor/static function (e.g. Gio.File.new_for_path,
|
|
1409
1461
|
// Gtk.Label.new) — a function found ON a type but taking no instance. The Node
|
|
1410
1462
|
// twin of `Ns.Class.method(...)`.
|
|
1463
|
+
//
|
|
1464
|
+
// `receiverGType` is the GType of the object the call went THROUGH (`this`), and it
|
|
1465
|
+
// is honoured for CLASS-STRUCT methods only. gjs defines those on the constructor
|
|
1466
|
+
// and marshals `this` as the GTypeClass, so the very same function object answers
|
|
1467
|
+
// for every class it is applied to: `GObject.Object.list_properties.call(Gtk.ListItem)`
|
|
1468
|
+
// reads GtkListItem's 9 pspecs, and an inherited `Subclass.list_properties()` reads the
|
|
1469
|
+
// SUBCLASS's. node-gi bound the name to the type it was READ from, so both spellings
|
|
1470
|
+
// answered the declarer's pspecs — GObject's zero — with nothing thrown (#1438). A
|
|
1471
|
+
// plain static (Gio.File.new_for_path) ignores `this` in gjs too, so it ignores this
|
|
1472
|
+
// argument here; and a receiver that is not `g_type_is_a` the DECLARING type (see
|
|
1473
|
+
// ResolveClassStructMethod) is ignored rather than trusted, so no C function can be
|
|
1474
|
+
// handed the class pointer of an unrelated type.
|
|
1411
1475
|
Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
1412
1476
|
Napi::Env env = info.Env();
|
|
1413
1477
|
if (info.Length() < 3 || !info[0].IsString() || !info[1].IsString() || !info[2].IsString()) {
|
|
1414
1478
|
Napi::TypeError::New(
|
|
1415
|
-
env,
|
|
1479
|
+
env,
|
|
1480
|
+
"callStaticMethod(namespace: string, typeName: string, methodName: string, args?: unknown[], "
|
|
1481
|
+
"receiverGType?: GType)")
|
|
1416
1482
|
.ThrowAsJavaScriptException();
|
|
1417
1483
|
return env.Null();
|
|
1418
1484
|
}
|
|
@@ -1421,6 +1487,10 @@ Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
|
1421
1487
|
std::string method = info[2].As<Napi::String>().Utf8Value();
|
|
1422
1488
|
Napi::Array args = (info.Length() >= 4 && info[3].IsArray()) ? info[3].As<Napi::Array>()
|
|
1423
1489
|
: Napi::Array::New(env, 0);
|
|
1490
|
+
// 0 for every shape that is not a GType handle (absent, null, an ordinary object),
|
|
1491
|
+
// which is the "no receiver given" case — never an error, since `this` is whatever
|
|
1492
|
+
// the caller's call site happened to be.
|
|
1493
|
+
GType receiverGType = info.Length() >= 5 ? ReadGTypeHandle(info[4]) : G_TYPE_INVALID;
|
|
1424
1494
|
|
|
1425
1495
|
GIRepository* repo = DupDefaultRepository();
|
|
1426
1496
|
GIBaseInfo* typeInfo = gi_repository_find_by_name(repo, ns.c_str(), tn.c_str());
|
|
@@ -1439,13 +1509,19 @@ Napi::Value CallStaticMethod(const Napi::CallbackInfo& info) {
|
|
|
1439
1509
|
GIObjectInfo* objInfo = reinterpret_cast<GIObjectInfo*>(typeInfo);
|
|
1440
1510
|
func = gi_object_info_find_method(objInfo, method.c_str());
|
|
1441
1511
|
if (func == nullptr) {
|
|
1442
|
-
|
|
1512
|
+
GType declaringGType = G_TYPE_INVALID;
|
|
1513
|
+
func = ResolveClassStructMethod(objInfo, method.c_str(), &declaringGType);
|
|
1443
1514
|
if (func != nullptr) {
|
|
1444
|
-
|
|
1515
|
+
GType ownGType =
|
|
1516
|
+
gi_registered_type_info_get_g_type(reinterpret_cast<GIRegisteredTypeInfo*>(objInfo));
|
|
1517
|
+
const bool retarget = receiverGType != G_TYPE_INVALID && receiverGType != ownGType &&
|
|
1518
|
+
declaringGType != G_TYPE_INVALID &&
|
|
1519
|
+
g_type_is_a(receiverGType, declaringGType);
|
|
1520
|
+
classInstance = ClassStructInstanceForGType(retarget ? receiverGType : ownGType);
|
|
1445
1521
|
if (classInstance == nullptr) {
|
|
1446
|
-
// No
|
|
1447
|
-
// to the plain "no static method" error rather than invoking with
|
|
1448
|
-
// instance, which would hand the C function a null GObjectClass*.
|
|
1522
|
+
// No class to call it on — no GType behind the info, or an unclassed one.
|
|
1523
|
+
// Drop back to the plain "no static method" error rather than invoking with
|
|
1524
|
+
// a null instance, which would hand the C function a null GObjectClass*.
|
|
1449
1525
|
gi_base_info_unref(reinterpret_cast<GIBaseInfo*>(func));
|
|
1450
1526
|
func = nullptr;
|
|
1451
1527
|
}
|
package/src/class.cc
CHANGED
|
@@ -723,6 +723,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
723
723
|
// an all-IN vfunc jsCursor == i, so the IN-only path is byte-identical to before.
|
|
724
724
|
bool ok = true;
|
|
725
725
|
size_t jsCursor = 0;
|
|
726
|
+
std::vector<LengthWrite> lengthWrites;
|
|
726
727
|
for (unsigned int i = 0; i < nDeclared && ok; i++) {
|
|
727
728
|
if (skip[i]) {
|
|
728
729
|
// An OUT/INOUT array-length arg: wire its slot so the callee writes the count.
|
|
@@ -796,7 +797,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
796
797
|
// transfer by ReadOutOrReturn below.
|
|
797
798
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
798
799
|
jsCursor++;
|
|
799
|
-
if (!IsSupportedContainerType(ti, &why)) {
|
|
800
|
+
if (!IsSupportedContainerType(ti, &why, ContainerUse::kReadBack)) {
|
|
800
801
|
Napi::TypeError::New(env, where + ": INOUT " + why +
|
|
801
802
|
" parameters are not yet supported")
|
|
802
803
|
.ThrowAsJavaScriptException();
|
|
@@ -836,7 +837,8 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
836
837
|
jsCursor++;
|
|
837
838
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
838
839
|
if (JsToGIArgument(env, v, ti, &slots[i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
839
|
-
nullptr, nullptr,
|
|
840
|
+
nullptr, nullptr, nullptr,
|
|
841
|
+
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai))))
|
|
840
842
|
giArgs[1 + i].v_pointer = &slots[i];
|
|
841
843
|
else
|
|
842
844
|
ok = false; // JsToGIArgument already threw
|
|
@@ -850,7 +852,7 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
850
852
|
std::string why;
|
|
851
853
|
Napi::Value v = jsCursor < args.Length() ? args.Get(jsCursor) : env.Undefined();
|
|
852
854
|
jsCursor++;
|
|
853
|
-
if (!IsSupportedContainerType(ti, &why)) {
|
|
855
|
+
if (!IsSupportedContainerType(ti, &why, ContainerUse::kIn)) {
|
|
854
856
|
Napi::TypeError::New(env, where + ": IN " + why +
|
|
855
857
|
" parameters are not yet supported")
|
|
856
858
|
.ThrowAsJavaScriptException();
|
|
@@ -866,11 +868,21 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
866
868
|
unsigned int L = 0;
|
|
867
869
|
if (gi_type_info_get_array_length_index(ti, &L) && L < nDeclared &&
|
|
868
870
|
dirs[L] == GI_DIRECTION_IN) {
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
871
|
+
long other = 0;
|
|
872
|
+
if (!RecordLengthWrite(&lengthWrites, L, ccount, &other)) {
|
|
873
|
+
Napi::TypeError::New(
|
|
874
|
+
env, where + ": two arrays share length argument " + std::to_string(L) +
|
|
875
|
+
" but have different lengths (" + std::to_string(other) + " and " +
|
|
876
|
+
std::to_string(ccount) + ")")
|
|
877
|
+
.ThrowAsJavaScriptException();
|
|
878
|
+
ok = false;
|
|
879
|
+
} else {
|
|
880
|
+
GIArgInfo* la = gi_callable_info_get_arg(ci, L);
|
|
881
|
+
GITypeInfo* lt = gi_arg_info_get_type_info(la);
|
|
882
|
+
WriteLengthValue(lt, &giArgs[1 + L], ccount);
|
|
883
|
+
gi_base_info_unref(lt);
|
|
884
|
+
gi_base_info_unref(la);
|
|
885
|
+
}
|
|
874
886
|
}
|
|
875
887
|
}
|
|
876
888
|
}
|
|
@@ -881,7 +893,8 @@ static Napi::Value InvokeVFuncPointer(Napi::Env env, GObject* obj, GICallableInf
|
|
|
881
893
|
jsCursor++;
|
|
882
894
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
883
895
|
ok = JsToGIArgument(env, v, ti, &giArgs[1 + i], &holds[i], tr, &ownedInStrings, nullptr,
|
|
884
|
-
nullptr, nullptr,
|
|
896
|
+
nullptr, nullptr, nullptr,
|
|
897
|
+
gi_base_info_get_name(reinterpret_cast<GIBaseInfo*>(ai)));
|
|
885
898
|
}
|
|
886
899
|
|
|
887
900
|
avalue.push_back(&giArgs[1 + i]);
|
package/src/common.h
CHANGED
|
@@ -66,6 +66,11 @@ GIRepository* DupDefaultRepository();
|
|
|
66
66
|
// Init, freed by the instance-data finalizer at env teardown.
|
|
67
67
|
struct NodeGiEnvData {
|
|
68
68
|
napi_ref errorBuilder = nullptr;
|
|
69
|
+
// The CLASS the builder above constructs — L1's `GLib.Error`. Registered with
|
|
70
|
+
// the builder (one call, so the two cannot drift apart) and read the other way
|
|
71
|
+
// round: a GError-typed IN arg uses it to recognise an L1 error and rebuild a
|
|
72
|
+
// real GError from its fields. Per-env for the same reason as errorBuilder.
|
|
73
|
+
napi_ref errorClass = nullptr;
|
|
69
74
|
// L1-registered resolver for Gtk.Template `<signal handler="…">` callbacks: given
|
|
70
75
|
// (instanceHandle, handlerName) it returns the instance's bound JS method (or
|
|
71
76
|
// undefined). Stored per-env for the same reason as errorBuilder (a napi_ref is
|
|
@@ -279,22 +284,32 @@ struct CreatedClosures {
|
|
|
279
284
|
std::vector<GClosure*> transferFull; // callee adopts; unref only when the callee never ran
|
|
280
285
|
};
|
|
281
286
|
|
|
282
|
-
// ----
|
|
287
|
+
// ---- C objects this layer SYNTHESISES for one IN argument --------------------
|
|
283
288
|
//
|
|
289
|
+
// Three IN arms build a throw-away C object out of a plain JS value, because the
|
|
290
|
+
// JS side has no handle to hand over: a Uint8Array for a `GLib.Bytes` arg, a
|
|
291
|
+
// number/string for a `GObject.Value` arg, an L1 `GLib.Error` for a `GError` arg.
|
|
292
|
+
// All three carry the SAME lifetime rule, which is why they share one type
|
|
293
|
+
// instead of a third near-identical struct: transfer-none → the callee borrowed
|
|
294
|
+
// (or copied) it, so we release it after the invoke; transfer-full → the callee
|
|
295
|
+
// adopted it, so it is released only when the invoke never ran.
|
|
296
|
+
template <typename T>
|
|
297
|
+
struct CreatedIn {
|
|
298
|
+
std::vector<T*> transferNone; // release after the invoke (callee borrowed/copied)
|
|
299
|
+
std::vector<T*> transferFull; // callee adopts; release only when the callee never ran
|
|
300
|
+
void Record(T* p, GITransfer transfer) {
|
|
301
|
+
(transfer == GI_TRANSFER_NOTHING ? transferNone : transferFull).push_back(p);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
284
305
|
// A JS Uint8Array (or any TypedArray/DataView/ArrayBuffer) passed for a
|
|
285
306
|
// `GLib.Bytes`-typed IN parameter (e.g. GdkPixbuf.Pixbuf.new_from_bytes,
|
|
286
307
|
// g_compute_checksum_for_bytes) is COPIED into a fresh GBytes — exactly what GJS
|
|
287
308
|
// does (refs/gjs/gi/arg-cache.cpp GBytesIn::in → gjs_byte_array_get_bytes →
|
|
288
|
-
// g_bytes_new)
|
|
289
|
-
//
|
|
290
|
-
//
|
|
291
|
-
|
|
292
|
-
// during an invoke are recorded here so calls.cc can release them on the right
|
|
293
|
-
// path (mirrors CreatedClosures).
|
|
294
|
-
struct CreatedBytes {
|
|
295
|
-
std::vector<GBytes*> transferNone; // unref after the invoke (callee borrowed/ref'd)
|
|
296
|
-
std::vector<GBytes*> transferFull; // callee adopts; unref only when the callee never ran
|
|
297
|
-
};
|
|
309
|
+
// g_bytes_new), and released per the rule above (gjs
|
|
310
|
+
// GBytesInTransferNone::release → g_boxed_free; GBytesIn::release skips for
|
|
311
|
+
// transfer-full via the ignore_release mark).
|
|
312
|
+
using CreatedBytes = CreatedIn<GBytes>;
|
|
298
313
|
|
|
299
314
|
// A plain JS value handed to a `GObject.Value` IN-arg is BOXED into a fresh
|
|
300
315
|
// GValue whose GType is guessed from the JS value — exactly what GJS does
|
|
@@ -305,14 +320,18 @@ struct CreatedBytes {
|
|
|
305
320
|
// with "Unsupported interface IN argument", because a JS number is not a boxed
|
|
306
321
|
// handle. An existing GObject.Value boxed handle still routes through the
|
|
307
322
|
// boxed-handle path and is passed through untouched.
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
323
|
+
using CreatedValues = CreatedIn<GValue>;
|
|
324
|
+
|
|
325
|
+
// An L1 `GLib.Error` (what a `catch` hands you, and what `new GLib.Error(…)`
|
|
326
|
+
// builds) has no `GError*` behind it — it is a JS class, not a boxed handle — so
|
|
327
|
+
// a `GError`-typed IN arg gets a fresh `g_error_new_literal` built from its
|
|
328
|
+
// domain/code/message. Without this, the hand-back direction the OUT direction
|
|
329
|
+
// exists for is only open to errors that came OUT of GI in the first place:
|
|
330
|
+
// `GLib.propagate_error(caught)` and `Gst.Message.new_error(src, caught, dbg)`
|
|
331
|
+
// both refused where gjs accepts, which is the shape an application actually
|
|
332
|
+
// writes. A marshalled GError (a boxed handle) still routes through
|
|
333
|
+
// TransferBoxedIn and is not copied here.
|
|
334
|
+
using CreatedErrors = CreatedIn<GError>;
|
|
316
335
|
|
|
317
336
|
// Release a GValue this layer boxed: unset the held value (dropping the ref an
|
|
318
337
|
// object/boxed/string GValue took) and free the struct. A plain `g_free` would
|
|
@@ -340,6 +359,9 @@ GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
|
340
359
|
// `values` (optional): enables the JS-value→GValue IN-arg boxing above; nullptr
|
|
341
360
|
// (the default) keeps the previous behaviour (plain value → TypeError) on paths
|
|
342
361
|
// that cannot release a created GValue.
|
|
362
|
+
// `errors` (optional): enables the L1-GLib.Error→GError IN-arg build above;
|
|
363
|
+
// nullptr (the default) keeps a marshalled boxed handle working and refuses the
|
|
364
|
+
// L1 class, on paths that cannot release a created GError.
|
|
343
365
|
// `argName` (optional): the introspected argument name, used ONLY in error
|
|
344
366
|
// messages so a refusal reads like gjs's ("Expected type string for argument
|
|
345
367
|
// 'name' but got type number"); nullptr degrades to a message without the name.
|
|
@@ -350,6 +372,7 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
350
372
|
CreatedClosures* closures = nullptr,
|
|
351
373
|
CreatedBytes* bytes = nullptr,
|
|
352
374
|
CreatedValues* values = nullptr,
|
|
375
|
+
CreatedErrors* errors = nullptr,
|
|
353
376
|
const char* argName = nullptr);
|
|
354
377
|
|
|
355
378
|
// ---- IN container building -----------------------------------------
|
|
@@ -362,15 +385,40 @@ struct InContainer {
|
|
|
362
385
|
gpointer ptr;
|
|
363
386
|
GITransfer transfer;
|
|
364
387
|
long count; // element count (to free C-array strings)
|
|
388
|
+
// For a by-value GValue array: the caller's own GValue per element, or nullptr where
|
|
389
|
+
// the element came from a plain JS value and there is nothing to write back to. See
|
|
390
|
+
// the write-back note in FreeInContainer — some callees FILL the cells, and the
|
|
391
|
+
// typelib does not say which.
|
|
392
|
+
std::vector<gpointer> writeBack;
|
|
365
393
|
};
|
|
366
394
|
|
|
367
|
-
|
|
395
|
+
// How a container argument is used, which decides which ELEMENT kinds are
|
|
396
|
+
// marshallable. The two directions are not symmetric: the IN path builds a C buffer
|
|
397
|
+
// and can therefore lay out a BY-VALUE element (an enum's int, a record's own bytes),
|
|
398
|
+
// while a read-back — an OUT/INOUT parameter or a return — hands the buffer to
|
|
399
|
+
// ReadCElement, which still dereferences an interface element as a pointer. Passing
|
|
400
|
+
// kIn where the value is later read would admit a shape that marshals in and misreads
|
|
401
|
+
// out, so the caller states the direction rather than the predicate guessing it.
|
|
402
|
+
enum class ContainerUse { kIn, kReadBack };
|
|
403
|
+
|
|
404
|
+
bool IsSupportedContainerType(GITypeInfo* type, std::string* why, ContainerUse use);
|
|
368
405
|
// Whether `type` is a supported OUT/INOUT marshalling type (fundamentals, strings,
|
|
369
406
|
// object/interface/enum/flags, struct/boxed/union, and containers). Shared by the
|
|
370
407
|
// function-invoke path (calls.cc) and the vfunc chain-up path (class.cc).
|
|
371
408
|
bool IsSupportedOutType(GITypeInfo* type, std::string* why);
|
|
372
409
|
size_t CElementSize(GITypeInfo* elem);
|
|
373
410
|
void WriteLengthValue(GITypeInfo* lenType, GIArgument* slot, long n);
|
|
411
|
+
|
|
412
|
+
// One IN length-argument autofill, remembered so a SECOND array naming the same length
|
|
413
|
+
// argument can be checked against it rather than silently overwriting it.
|
|
414
|
+
struct LengthWrite {
|
|
415
|
+
unsigned int index;
|
|
416
|
+
long count;
|
|
417
|
+
};
|
|
418
|
+
// Record `count` for length argument `index`. Returns false when that argument was
|
|
419
|
+
// already filled with a DIFFERENT count, and reports the earlier one in *other.
|
|
420
|
+
bool RecordLengthWrite(std::vector<LengthWrite>* seen, unsigned int index, long count,
|
|
421
|
+
long* other);
|
|
374
422
|
Napi::Value ReadOutOrReturn(Napi::Env env, GICallableInfo* callable, GITypeInfo* ti,
|
|
375
423
|
GIArgument* arg, GITransfer transfer,
|
|
376
424
|
std::vector<GIArgument>* slots);
|
|
@@ -582,6 +630,8 @@ Napi::Value BoxedMemberKind(const Napi::CallbackInfo& info);
|
|
|
582
630
|
Napi::Value GetBoxedField(const Napi::CallbackInfo& info);
|
|
583
631
|
Napi::Value SetBoxedField(const Napi::CallbackInfo& info);
|
|
584
632
|
Napi::Value BoxedTypeName(const Napi::CallbackInfo& info);
|
|
633
|
+
// TEST-ONLY (see marshal.cc): the address a boxed handle wraps, as hex.
|
|
634
|
+
Napi::Value BoxedAddress(const Napi::CallbackInfo& info);
|
|
585
635
|
// GParamSpec wrapping (object.cc): a tagged GObject-fundamental handle plus its
|
|
586
636
|
// name/nick/blurb/flags/value_type/owner_type/default_value accessors.
|
|
587
637
|
Napi::Value MakeParamSpecHandle(Napi::Env env, GParamSpec* pspec, GITransfer transfer);
|