@gjsify/node-gi 0.24.2 → 0.25.1
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/index.js +25 -4
- package/package.json +1 -1
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/src/calls.cc +10 -1
- package/src/common.h +32 -1
- package/src/loop.cc +8 -2
- package/src/marshal.cc +146 -2
package/index.js
CHANGED
|
@@ -179,11 +179,32 @@ if (!isNodeRuntime && RUNTIME !== 'gjs') {
|
|
|
179
179
|
// (Bun's own processTicksAndRejections calls it from JS).
|
|
180
180
|
({ drainMicrotasks: drain } = require('bun:jsc'));
|
|
181
181
|
} else if (typeof globalThis.Deno?.internal === 'symbol') {
|
|
182
|
-
// V8: core.runMicrotasks
|
|
183
|
-
// Isolate::PerformMicrotaskCheckpoint
|
|
184
|
-
//
|
|
182
|
+
// V8: the checkpoint needs BOTH queues. core.runMicrotasks is only
|
|
183
|
+
// Isolate::PerformMicrotaskCheckpoint; Deno's node-compat
|
|
184
|
+
// process.nextTick queue is a SEPARATE deno_core queue
|
|
185
|
+
// (ext:deno_node/_next_tick.ts → core.queueNextTick) that only the
|
|
186
|
+
// runtime's own event loop drains — and that loop is paused for the
|
|
187
|
+
// whole lifetime of a blocking run(). node:stream delivers stream
|
|
188
|
+
// 'end' via process.nextTick (endReadableNT), so with a
|
|
189
|
+
// microtask-only drain any body consumption over a Readable
|
|
190
|
+
// (@gjsify/fetch consumeBody → XHR arrayBuffer/text) got its chunks
|
|
191
|
+
// (microtask-delivered) but never the end: every asset load hung at
|
|
192
|
+
// readyState 3 forever on Deno while the SAME bundle settled on Bun,
|
|
193
|
+
// whose nextTick rides JSC's microtask queue (bun:jsc
|
|
194
|
+
// drainMicrotasks covers it). core.runNextTicks mirrors node's
|
|
195
|
+
// task_queues.js runNextTicks: microtask checkpoint when no ticks
|
|
196
|
+
// are scheduled, full processTicksAndRejections (ticks + microtasks
|
|
197
|
+
// interleaved) when there are — one call, both queues. Regression:
|
|
198
|
+
// test/blocking-run-checkpoint.test.mjs.
|
|
185
199
|
const core = globalThis.Deno[globalThis.Deno.internal]?.core;
|
|
186
|
-
if (typeof core?.
|
|
200
|
+
if (typeof core?.runNextTicks === 'function') {
|
|
201
|
+
drain = () => core.runNextTicks();
|
|
202
|
+
} else if (typeof core?.runMicrotasks === 'function') {
|
|
203
|
+
// Older deno_core without runNextTicks: microtask-only drain
|
|
204
|
+
// (pre-fix behaviour — promise continuations settle, nextTick
|
|
205
|
+
// consumers like node:stream 'end' still starve).
|
|
206
|
+
drain = () => core.runMicrotasks();
|
|
207
|
+
}
|
|
187
208
|
}
|
|
188
209
|
if (typeof drain === 'function') native.setMicrotaskDrain(drain);
|
|
189
210
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.25.1",
|
|
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
|
package/src/calls.cc
CHANGED
|
@@ -545,6 +545,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
545
545
|
// the invoke (the callee ref'd the GBytes if it kept it), transfer-full refs
|
|
546
546
|
// belong to the callee (dropped only when the callee never ran).
|
|
547
547
|
CreatedBytes createdBytes;
|
|
548
|
+
CreatedValues createdValues;
|
|
548
549
|
bool ok = true;
|
|
549
550
|
size_t jsCursor = 0;
|
|
550
551
|
for (unsigned int i = 0; i < n_args && ok; i++) {
|
|
@@ -800,7 +801,7 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
800
801
|
} else {
|
|
801
802
|
GITransfer tr = gi_arg_info_get_ownership_transfer(ai);
|
|
802
803
|
ok = JsToGIArgument(env, v, ti, &in_args[inPos[i]], &held[i], tr, &ownedInStrings,
|
|
803
|
-
&createdClosures, &createdBytes);
|
|
804
|
+
&createdClosures, &createdBytes, &createdValues);
|
|
804
805
|
}
|
|
805
806
|
}
|
|
806
807
|
|
|
@@ -818,6 +819,9 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
818
819
|
// Likewise every fresh GBytes built from a JS typed array — never adopted.
|
|
819
820
|
for (GBytes* b : createdBytes.transferNone) g_bytes_unref(b);
|
|
820
821
|
for (GBytes* b : createdBytes.transferFull) g_bytes_unref(b);
|
|
822
|
+
// Likewise every GValue boxed from a plain JS value — never adopted.
|
|
823
|
+
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
824
|
+
for (GValue* gv : createdValues.transferFull) NodeGiFreeBoxedGValue(gv);
|
|
821
825
|
// Likewise the transfer-full-instance ref: the callee never consumed it.
|
|
822
826
|
if (instanceRefTaken) g_object_unref(static_cast<GObject*>(instance));
|
|
823
827
|
for (gpointer s : ownedInStrings) g_free(s); // never reached the callee (#658)
|
|
@@ -857,6 +861,11 @@ static Napi::Value InvokeFunctionInfo(Napi::Env env, GIFunctionInfo* func, gpoin
|
|
|
857
861
|
// paths). A callee that kept the bytes (GdkPixbuf.Pixbuf.new_from_bytes) holds
|
|
858
862
|
// its own ref. Transfer-full GBytes were adopted by the callee — no release.
|
|
859
863
|
for (GBytes* b : createdBytes.transferNone) g_bytes_unref(b);
|
|
864
|
+
// Transfer-none GValue IN-args boxed from plain JS values: the callee copied
|
|
865
|
+
// whatever it needed (g_object_set_property copies into the property), so the
|
|
866
|
+
// box is ours to free — success AND error paths, mirroring the GBytes rule.
|
|
867
|
+
// Transfer-full GValues were adopted by the callee — no release.
|
|
868
|
+
for (GValue* gv : createdValues.transferNone) NodeGiFreeBoxedGValue(gv);
|
|
860
869
|
if (!success) {
|
|
861
870
|
for (const InContainer& c : inContainers) FreeInContainer(c);
|
|
862
871
|
// A failed invoke did not adopt the transfer-full IN/INOUT strings we g_strdup'd
|
package/src/common.h
CHANGED
|
@@ -295,6 +295,33 @@ struct CreatedBytes {
|
|
|
295
295
|
std::vector<GBytes*> transferFull; // callee adopts; unref only when the callee never ran
|
|
296
296
|
};
|
|
297
297
|
|
|
298
|
+
// A plain JS value handed to a `GObject.Value` IN-arg is BOXED into a fresh
|
|
299
|
+
// GValue whose GType is guessed from the JS value — exactly what GJS does
|
|
300
|
+
// (refs/gjs/gi/arg-cache.cpp GValueIn::in → gjs_value_to_g_value, whose
|
|
301
|
+
// gjs_value_guess_g_type supplies the type for an uninitialized GValue). Without
|
|
302
|
+
// it, the single most common GObject call in existence —
|
|
303
|
+
// `obj.set_property('name', 0.5)`, whose second GI argument is a GValue — fails
|
|
304
|
+
// with "Unsupported interface IN argument", because a JS number is not a boxed
|
|
305
|
+
// handle. An existing GObject.Value boxed handle still routes through the
|
|
306
|
+
// boxed-handle path and is passed through untouched.
|
|
307
|
+
//
|
|
308
|
+
// Lifetime mirrors CreatedBytes: the box is freed after the invoke for
|
|
309
|
+
// transfer-none (the callee copied whatever it needed — g_object_set_property
|
|
310
|
+
// copies into the property), and left with the callee for transfer-full.
|
|
311
|
+
struct CreatedValues {
|
|
312
|
+
std::vector<GValue*> transferNone; // free after the invoke (callee borrowed)
|
|
313
|
+
std::vector<GValue*> transferFull; // callee adopts; free only when it never ran
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Release a GValue this layer boxed: unset the held value (dropping the ref an
|
|
317
|
+
// object/boxed/string GValue took) and free the struct. A plain `g_free` would
|
|
318
|
+
// leak whatever the GValue owns.
|
|
319
|
+
inline void NodeGiFreeBoxedGValue(GValue* gv) {
|
|
320
|
+
if (gv == nullptr) return;
|
|
321
|
+
if (G_IS_VALUE(gv)) g_value_unset(gv);
|
|
322
|
+
g_free(gv);
|
|
323
|
+
}
|
|
324
|
+
|
|
298
325
|
// Build the (floating) marshaled GClosure for a JS function (signals.cc — shares
|
|
299
326
|
// the battle-tested JsClosure marshal/finalize machinery with `.connect()`).
|
|
300
327
|
GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
@@ -309,12 +336,16 @@ GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
|
309
336
|
// `bytes` (optional): enables the JS-bytes→GBytes IN-arg marshalling above;
|
|
310
337
|
// nullptr (the default) keeps the previous behaviour (typed array → TypeError) on
|
|
311
338
|
// paths that cannot release a created GBytes.
|
|
339
|
+
// `values` (optional): enables the JS-value→GValue IN-arg boxing above; nullptr
|
|
340
|
+
// (the default) keeps the previous behaviour (plain value → TypeError) on paths
|
|
341
|
+
// that cannot release a created GValue.
|
|
312
342
|
bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument* out,
|
|
313
343
|
std::string* heldString,
|
|
314
344
|
GITransfer transfer = GI_TRANSFER_NOTHING,
|
|
315
345
|
std::vector<gpointer>* ownedStrings = nullptr,
|
|
316
346
|
CreatedClosures* closures = nullptr,
|
|
317
|
-
CreatedBytes* bytes = nullptr
|
|
347
|
+
CreatedBytes* bytes = nullptr,
|
|
348
|
+
CreatedValues* values = nullptr);
|
|
318
349
|
|
|
319
350
|
// ---- IN container building -----------------------------------------
|
|
320
351
|
//
|
package/src/loop.cc
CHANGED
|
@@ -108,8 +108,14 @@ static void DrainMicrotasks() {
|
|
|
108
108
|
//
|
|
109
109
|
// Portable fix: N-API exposes no engine microtask checkpoint, but both runtimes
|
|
110
110
|
// expose their OWN drain primitive to JS (Bun: `bun:jsc` drainMicrotasks —
|
|
111
|
-
// JSC's VM drain, callable mid-stack by design
|
|
112
|
-
//
|
|
111
|
+
// JSC's VM drain, callable mid-stack by design, and Bun's process.nextTick
|
|
112
|
+
// rides that same queue; Deno: core.runNextTicks — node's task_queues.js
|
|
113
|
+
// runNextTicks shape, draining the node-compat process.nextTick queue AND the
|
|
114
|
+
// V8 microtask checkpoint. core.runMicrotasks alone is NOT enough on Deno:
|
|
115
|
+
// its nextTick queue is separate from V8's microtasks, and node:stream's
|
|
116
|
+
// 'end' is nextTick-delivered (endReadableNT), so a microtask-only drain hung
|
|
117
|
+
// every fetch/XHR body consumption at readyState 3 during a blocking run —
|
|
118
|
+
// see test/blocking-run-checkpoint.test.mjs). index.js registers
|
|
113
119
|
// it here on non-Node runtimes only; the loop-dispatched trampolines
|
|
114
120
|
// (signals.cc / calls.cc / class.cc) invoke NodeGiMaybeDrainMicrotasks at their
|
|
115
121
|
// OUTERMOST boundary (g_loopDispatchDepth == 0). Node never registers — its
|
package/src/marshal.cc
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
#include "common.h"
|
|
5
5
|
|
|
6
|
+
#include <cmath>
|
|
6
7
|
#include <unordered_map>
|
|
7
8
|
#include <utility>
|
|
8
9
|
|
|
@@ -190,6 +191,101 @@ gpointer TransferBoxedIn(BoxedHandle* h, GITransfer transfer) {
|
|
|
190
191
|
return h->ptr;
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
// Whether `v` is a boxed handle that ALREADY holds a GValue — the one shape a
|
|
195
|
+
// `GObject.Value` IN-arg takes as-is (gjs GValueIn::in passes it through when the
|
|
196
|
+
// JS object's own gtype is G_TYPE_VALUE, and boxes EVERYTHING else, including
|
|
197
|
+
// other boxed handles: a GVariant handed to `set_property('state', …)` becomes a
|
|
198
|
+
// G_TYPE_VARIANT GValue, it does not become the GValue).
|
|
199
|
+
static bool NodeGiIsGValueHandle(Napi::Value v) {
|
|
200
|
+
BoxedHandle* h = TryGetBoxedHandle(v);
|
|
201
|
+
return h != nullptr && h->gtype != G_TYPE_INVALID && g_type_is_a(h->gtype, G_TYPE_VALUE);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Initialise `out` from a plain JS value, guessing the GType the way gjs does
|
|
205
|
+
// for an uninitialized GValue (refs/gjs/gi/value.cpp gjs_value_guess_g_type +
|
|
206
|
+
// gjs_value_to_g_value_internal). Returns false with a pending JS exception when
|
|
207
|
+
// the value has no sensible GValue representation.
|
|
208
|
+
//
|
|
209
|
+
// The int32-vs-double split matches gjs exactly (`value.isInt32()` →
|
|
210
|
+
// `G_TYPE_INT`, `value.isDouble()` → `G_TYPE_DOUBLE`); JS has no such tag at the
|
|
211
|
+
// N-API boundary, so an integral number in int32 range stands in for isInt32.
|
|
212
|
+
// That distinction is not load-bearing for correctness — `g_object_set_property`
|
|
213
|
+
// transforms between the numeric fundamentals — but staying bit-for-bit with gjs
|
|
214
|
+
// keeps a property that reads the GValue's own type behaving identically.
|
|
215
|
+
static bool JsToFreshGValue(Napi::Env env, Napi::Value v, GValue* out) {
|
|
216
|
+
if (v.IsNull() || v.IsUndefined()) {
|
|
217
|
+
// gjs guesses G_TYPE_POINTER for null. A real GValue holding NULL is also
|
|
218
|
+
// the only thing a callee can safely dereference — passing a NULL GValue*
|
|
219
|
+
// (the plain boxed-null path) would crash g_object_set_property.
|
|
220
|
+
g_value_init(out, G_TYPE_POINTER);
|
|
221
|
+
g_value_set_pointer(out, nullptr);
|
|
222
|
+
return true;
|
|
223
|
+
}
|
|
224
|
+
if (v.IsBoolean()) {
|
|
225
|
+
g_value_init(out, G_TYPE_BOOLEAN);
|
|
226
|
+
g_value_set_boolean(out, v.As<Napi::Boolean>().Value() ? TRUE : FALSE);
|
|
227
|
+
return true;
|
|
228
|
+
}
|
|
229
|
+
if (v.IsString()) {
|
|
230
|
+
g_value_init(out, G_TYPE_STRING);
|
|
231
|
+
g_value_set_string(out, v.As<Napi::String>().Utf8Value().c_str());
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
if (v.IsBigInt()) {
|
|
235
|
+
bool lossless = false;
|
|
236
|
+
int64_t i64 = v.As<Napi::BigInt>().Int64Value(&lossless);
|
|
237
|
+
if (lossless || i64 < 0) {
|
|
238
|
+
g_value_init(out, G_TYPE_INT64);
|
|
239
|
+
g_value_set_int64(out, i64);
|
|
240
|
+
} else {
|
|
241
|
+
bool ulossless = false;
|
|
242
|
+
g_value_init(out, G_TYPE_UINT64);
|
|
243
|
+
g_value_set_uint64(out, v.As<Napi::BigInt>().Uint64Value(&ulossless));
|
|
244
|
+
}
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
if (v.IsNumber()) {
|
|
248
|
+
double d = v.As<Napi::Number>().DoubleValue();
|
|
249
|
+
if (d == std::trunc(d) && d >= static_cast<double>(G_MININT32) &&
|
|
250
|
+
d <= static_cast<double>(G_MAXINT32)) {
|
|
251
|
+
g_value_init(out, G_TYPE_INT);
|
|
252
|
+
g_value_set_int(out, static_cast<gint>(d));
|
|
253
|
+
} else {
|
|
254
|
+
g_value_init(out, G_TYPE_DOUBLE);
|
|
255
|
+
g_value_set_double(out, d);
|
|
256
|
+
}
|
|
257
|
+
return true;
|
|
258
|
+
}
|
|
259
|
+
if (v.IsExternal()) {
|
|
260
|
+
// A GObject handle → a G_TYPE_OBJECT GValue carrying its own GType, so a
|
|
261
|
+
// property typed against a subclass still accepts it.
|
|
262
|
+
Napi::External<GObject> objExt = v.As<Napi::External<GObject>>();
|
|
263
|
+
if (objExt.CheckTypeTag(&kGObjectHandleTag)) {
|
|
264
|
+
GObject* obj = objExt.Data();
|
|
265
|
+
GType gt = obj != nullptr ? G_OBJECT_TYPE(obj) : G_TYPE_OBJECT;
|
|
266
|
+
g_value_init(out, gt);
|
|
267
|
+
g_value_set_object(out, obj);
|
|
268
|
+
return true;
|
|
269
|
+
}
|
|
270
|
+
// A boxed handle → its own registered GType (GVariant has its own
|
|
271
|
+
// fundamental; a non-registered C struct has no GType to name and is
|
|
272
|
+
// rejected rather than guessed at).
|
|
273
|
+
BoxedHandle* h = TryGetBoxedHandle(v);
|
|
274
|
+
if (h != nullptr && h->gtype != G_TYPE_INVALID) {
|
|
275
|
+
g_value_init(out, h->gtype);
|
|
276
|
+
if (h->gtype == G_TYPE_VARIANT) g_value_set_variant(out, static_cast<GVariant*>(h->ptr));
|
|
277
|
+
else if (G_TYPE_IS_BOXED(h->gtype)) g_value_set_boxed(out, h->ptr);
|
|
278
|
+
else g_value_set_pointer(out, h->ptr);
|
|
279
|
+
return true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
Napi::TypeError::New(
|
|
283
|
+
env, "Cannot convert this value to a GObject.Value (expected a boolean, number, bigint, "
|
|
284
|
+
"string, null, or a GObject/boxed handle)")
|
|
285
|
+
.ThrowAsJavaScriptException();
|
|
286
|
+
return false;
|
|
287
|
+
}
|
|
288
|
+
|
|
193
289
|
// Read a boxed handle's pointer if `v` is one (tag-checked; no deref of ptr).
|
|
194
290
|
bool TryGetBoxedPtr(Napi::Value v, gpointer* out) {
|
|
195
291
|
if (!v.IsExternal()) return false;
|
|
@@ -337,7 +433,8 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
337
433
|
GITransfer transfer,
|
|
338
434
|
std::vector<gpointer>* ownedStrings,
|
|
339
435
|
CreatedClosures* closures,
|
|
340
|
-
CreatedBytes* bytes
|
|
436
|
+
CreatedBytes* bytes,
|
|
437
|
+
CreatedValues* values) {
|
|
341
438
|
if (v.IsEmpty()) {
|
|
342
439
|
// Residue of a swallowed napi failure (a fallible Get()/coercion upstream
|
|
343
440
|
// failed on a terminating env, or a throwing getter left the exception
|
|
@@ -399,7 +496,31 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
399
496
|
out->v_pointer = nullptr;
|
|
400
497
|
handled = true;
|
|
401
498
|
} else if (v.IsExternal()) {
|
|
402
|
-
|
|
499
|
+
GObject* obj = v.As<Napi::External<GObject>>().Data();
|
|
500
|
+
// A (transfer full) GObject IN arg CONSUMES a reference: the callee
|
|
501
|
+
// takes over the one it is handed. Passing the handle's own ref
|
|
502
|
+
// makes the callee and the JS wrapper share a single ref that BOTH
|
|
503
|
+
// release — the wrapper's finalize then frees an object the callee
|
|
504
|
+
// still owns, and the callee is left dereferencing freed memory.
|
|
505
|
+
//
|
|
506
|
+
// `gtk_widget_add_controller()` is the canonical case and the one
|
|
507
|
+
// that surfaced it: GTK keeps the controller in the widget's list,
|
|
508
|
+
// the JS wrapper for it goes out of scope, and the next pointer
|
|
509
|
+
// motion over the widget hits
|
|
510
|
+
//
|
|
511
|
+
// Gtk-CRITICAL **: gtk_event_controller_handle_crossing:
|
|
512
|
+
// assertion 'GTK_IS_EVENT_CONTROLLER (controller)' failed
|
|
513
|
+
//
|
|
514
|
+
// NONDETERMINISTICALLY — it needs a GC between the add and the
|
|
515
|
+
// event, which is why it looked unrelated to anything nearby.
|
|
516
|
+
//
|
|
517
|
+
// gjs takes the ref here (wrapperutils.h
|
|
518
|
+
// `transfer_to_gi_argument`: `GI_DIRECTION_IN && transfer !=
|
|
519
|
+
// GI_TRANSFER_NOTHING` → `copy_ptr` → `g_object_ref`), and this
|
|
520
|
+
// file already does the equivalent for BOXED args via
|
|
521
|
+
// `TransferBoxedIn`. Objects were simply never given the same rule.
|
|
522
|
+
if (transfer != GI_TRANSFER_NOTHING && obj != nullptr) g_object_ref(obj);
|
|
523
|
+
out->v_pointer = obj;
|
|
403
524
|
handled = true;
|
|
404
525
|
}
|
|
405
526
|
} else if (GI_IS_ENUM_INFO(iface) || GI_IS_FLAGS_INFO(iface)) {
|
|
@@ -454,6 +575,29 @@ bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument*
|
|
|
454
575
|
if (transfer == GI_TRANSFER_NOTHING) bytes->transferNone.push_back(b);
|
|
455
576
|
else bytes->transferFull.push_back(b);
|
|
456
577
|
handled = true;
|
|
578
|
+
} else if (values != nullptr && !NodeGiIsGValueHandle(v) &&
|
|
579
|
+
gi_registered_type_info_get_g_type(
|
|
580
|
+
reinterpret_cast<GIRegisteredTypeInfo*>(iface)) == G_TYPE_VALUE) {
|
|
581
|
+
// A plain JS value for a `GObject.Value` IN-arg → a fresh GValue whose
|
|
582
|
+
// GType is guessed from the value, exactly as gjs (refs/gjs
|
|
583
|
+
// gi/arg-cache.cpp GValueIn::in → gjs_value_to_g_value). This is what
|
|
584
|
+
// makes `obj.set_property('volume', 0.5)` — whose GI signature takes a
|
|
585
|
+
// GValue — work at all; a JS number is not a boxed handle, so it used
|
|
586
|
+
// to fall through to the "Unsupported interface IN argument" throw.
|
|
587
|
+
//
|
|
588
|
+
// An EXISTING GObject.Value handle is excluded here and routes through
|
|
589
|
+
// the boxed-handle branch below untouched, matching gjs's pass-through
|
|
590
|
+
// for an already-typed GValue.
|
|
591
|
+
GValue* boxed = g_new0(GValue, 1);
|
|
592
|
+
if (!JsToFreshGValue(env, v, boxed)) {
|
|
593
|
+
g_free(boxed);
|
|
594
|
+
gi_base_info_unref(iface);
|
|
595
|
+
return false; // JsToFreshGValue already threw
|
|
596
|
+
}
|
|
597
|
+
out->v_pointer = boxed;
|
|
598
|
+
if (transfer == GI_TRANSFER_NOTHING) values->transferNone.push_back(boxed);
|
|
599
|
+
else values->transferFull.push_back(boxed);
|
|
600
|
+
handled = true;
|
|
457
601
|
} else if (v.IsNull() || v.IsUndefined()) {
|
|
458
602
|
// Boxed/struct IN args arrive as boxed handles; null/undefined maps to
|
|
459
603
|
// a NULL pointer (e.g. GLib.MainLoop.new(null, false)).
|