@gjsify/node-gi 0.13.0 → 0.20.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 +818 -5
- package/binding.gyp +55 -19
- package/cairo.d.ts +187 -0
- package/cairo.js +570 -0
- package/gettext.d.ts +65 -0
- package/gettext.js +128 -0
- package/gi.d.ts +178 -0
- package/gi.js +2018 -23
- package/globals.d.ts +7 -33
- package/globals.js +40 -51
- package/gtk-runtime.d.ts +17 -0
- package/gtk-runtime.js +245 -0
- package/index.d.ts +306 -12
- package/index.js +447 -14
- package/overrides/_signals.js +167 -0
- package/overrides/gio-dbus.js +749 -0
- package/overrides/mainloop.js +60 -0
- package/package.json +45 -4
- package/src/addon.cc +98 -2074
- package/src/cairo.cc +926 -0
- package/src/calls.cc +1425 -0
- package/src/class.cc +1170 -0
- package/src/common.h +624 -0
- package/src/loop.cc +766 -0
- package/src/marshal.cc +1738 -0
- package/src/object.cc +814 -0
- package/src/private.cc +351 -0
- package/src/repo.cc +297 -0
- package/src/signals.cc +535 -0
- package/src/template.cc +116 -0
- package/src/toggle.cc +718 -0
- package/src/variant.cc +587 -0
- package/system.d.ts +49 -0
- package/system.js +116 -0
package/src/common.h
ADDED
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/node-gi — GObject-Introspection runtime for Node.js.
|
|
3
|
+
//
|
|
4
|
+
// Reference: refs/node-gtk (romgrk and node-gtk contributors, MIT). The design
|
|
5
|
+
// of this binding derives from node-gtk; this file is an original N-API
|
|
6
|
+
// implementation retargeted to the modern girepository-2.0 API (the
|
|
7
|
+
// GLib-integrated `gi_*` GIRepository merged into GLib >= 2.80; the standalone
|
|
8
|
+
// libgirepository-1.0 node-gtk linked no longer ships). GJS's gi/repo.cpp is
|
|
9
|
+
// the reference for the girepository-2.0 API surface.
|
|
10
|
+
//
|
|
11
|
+
// Milestone 1 (headless core): the modern GIRepository API end to end — resolve
|
|
12
|
+
// the default repository, require namespaces, marshal functions/methods/props/
|
|
13
|
+
// signals/callbacks/variants/containers, the libuv↔GLib mainloop bridge, and the
|
|
14
|
+
// toggle-ref instance GC bridge (single canonical wrapper per GObject; toggle ref
|
|
15
|
+
// flips strong↔weak so wrappers are collectable yet identity-stable + rooted
|
|
16
|
+
// while C owns them; idle-deferred teardown; resurrection; thread-marshalled
|
|
17
|
+
// toggles). GTK/Adwaita layering lands on top.
|
|
18
|
+
|
|
19
|
+
#ifndef NODE_GI_SRC_COMMON_H_
|
|
20
|
+
#define NODE_GI_SRC_COMMON_H_
|
|
21
|
+
|
|
22
|
+
#ifndef _WIN32
|
|
23
|
+
#include <dlfcn.h> // dlopen/dlsym the GtkWidgetClass template API (no GTK link)
|
|
24
|
+
#endif
|
|
25
|
+
#include <napi.h>
|
|
26
|
+
#include <uv.h>
|
|
27
|
+
|
|
28
|
+
#include <girepository/girepository.h>
|
|
29
|
+
#include <girepository/girffi.h> // gi_callable_info_create_closure + ffi_cif
|
|
30
|
+
#include <glib-object.h>
|
|
31
|
+
#include <glib.h>
|
|
32
|
+
|
|
33
|
+
#include <atomic>
|
|
34
|
+
#include <deque>
|
|
35
|
+
#include <memory>
|
|
36
|
+
#include <mutex>
|
|
37
|
+
#include <string>
|
|
38
|
+
#include <thread>
|
|
39
|
+
#include <vector>
|
|
40
|
+
|
|
41
|
+
#ifdef _WIN32
|
|
42
|
+
// libuv's uv.h transitively includes <windows.h>, whose <winuser.h> defines the
|
|
43
|
+
// object-like macro `RegisterClass` (→ RegisterClassW, the Win32 window-class API).
|
|
44
|
+
// It clobbers our identically-named N-API export `RegisterClass`. We never call the
|
|
45
|
+
// Win32 windowing API, so undefine it (it is the only Win32 UI macro that collides
|
|
46
|
+
// with a node-gi identifier — `RegisterClassFromGType` is a distinct token).
|
|
47
|
+
#undef RegisterClass
|
|
48
|
+
#endif
|
|
49
|
+
|
|
50
|
+
namespace nodegi {
|
|
51
|
+
|
|
52
|
+
// Forward declaration: GetConstantValue (defined early, near the namespace
|
|
53
|
+
// helpers) marshals through GIArgumentToJs, whose definition lives further down
|
|
54
|
+
// with the rest of the value-marshalling boundary.
|
|
55
|
+
Napi::Value GIArgumentToJs(Napi::Env env, GITypeInfo* type, GIArgument* arg,
|
|
56
|
+
GITransfer transfer);
|
|
57
|
+
|
|
58
|
+
GIRepository* DupDefaultRepository();
|
|
59
|
+
|
|
60
|
+
// Per-env state, stored in N-API instance data so each env (incl. a
|
|
61
|
+
// worker_threads worker) holds + derefs its OWN GLib.Error builder ref. A napi_ref
|
|
62
|
+
// is env-specific: keying it per-env (not a file-static slot shared across every
|
|
63
|
+
// env) removes both the cross-env clobber (last loader wins, prior envs lose the
|
|
64
|
+
// builder) AND the cross-env deref (env A reading env B's ref = UB). Created in
|
|
65
|
+
// Init, freed by the instance-data finalizer at env teardown.
|
|
66
|
+
struct NodeGiEnvData {
|
|
67
|
+
napi_ref errorBuilder = nullptr;
|
|
68
|
+
// L1-registered resolver for Gtk.Template `<signal handler="…">` callbacks: given
|
|
69
|
+
// (instanceHandle, handlerName) it returns the instance's bound JS method (or
|
|
70
|
+
// undefined). Stored per-env for the same reason as errorBuilder (a napi_ref is
|
|
71
|
+
// env-specific). See the Gtk.Widget composite-template scope further down.
|
|
72
|
+
napi_ref templateCallbackResolver = nullptr;
|
|
73
|
+
// The cairo L1 module's JS wrapper factories ({context, surface, pattern}), set
|
|
74
|
+
// by the native cairo `setup()` when `import 'cairo'` first loads. The
|
|
75
|
+
// foreign-struct from_func calls them to wrap a returned/callback cairo pointer
|
|
76
|
+
// into the JS Context/Surface(/ImageSurface)/Pattern class instance. Per-env for
|
|
77
|
+
// the same reason as errorBuilder (a napi_ref is env-specific). See cairo.cc.
|
|
78
|
+
napi_ref cairoWrappers = nullptr;
|
|
79
|
+
// The runtime-native microtask drain (Bun: `bun:jsc` drainMicrotasks; Deno:
|
|
80
|
+
// core.runMicrotasks), registered by index.js on non-Node runtimes only — see
|
|
81
|
+
// setMicrotaskDrain / NodeGiMaybeDrainMicrotasks in loop.cc. Never set on Node
|
|
82
|
+
// (napi_make_callback already runs the checkpoint there). Per-env for the same
|
|
83
|
+
// reason as errorBuilder (a napi_ref is env-specific).
|
|
84
|
+
napi_ref microtaskDrain = nullptr;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
void NodeGiEnvDataFinalize(napi_env env, void* data, void* hint);
|
|
88
|
+
NodeGiEnvData* EnvData(napi_env env);
|
|
89
|
+
|
|
90
|
+
// ---- boxed / struct handles (milestone: mainloop) ----
|
|
91
|
+
//
|
|
92
|
+
// Boxed/struct instances (e.g. GMainLoop) are wrapped as type-tagged Externals
|
|
93
|
+
// over a small heap record carrying the pointer + its boxed GType, so the
|
|
94
|
+
// finalizer can g_boxed_free a fully-owned boxed and method resolution can find
|
|
95
|
+
// the struct's GIStructInfo by GType. Distinct tag from the GObject handle so
|
|
96
|
+
// the two never cross-dereference. Full general struct support (field access,
|
|
97
|
+
// copy semantics for non-registered C structs) lands with the broader
|
|
98
|
+
// structs/boxed drop; this is the slice the GLib main loop needs.
|
|
99
|
+
struct BoxedHandle {
|
|
100
|
+
gpointer ptr;
|
|
101
|
+
GType gtype; // boxed GType, or G_TYPE_INVALID when unknown/non-registered
|
|
102
|
+
bool owns; // g_boxed_free(gtype, ptr) on finalize when true
|
|
103
|
+
// The struct/union GIBaseInfo backing this handle (a held ref, unref'd on
|
|
104
|
+
// finalize), or nullptr. Carried so field + method resolution works for an
|
|
105
|
+
// UNREGISTERED struct whose runtime GType is G_TYPE_NONE (e.g. GIMarshalling
|
|
106
|
+
// SimpleStruct) — find_by_gtype can't recover the info there, so the static
|
|
107
|
+
// info from the wrap site is stored. A registered type may leave this nullptr
|
|
108
|
+
// and be re-resolved via find_by_gtype(gtype).
|
|
109
|
+
GIBaseInfo* info;
|
|
110
|
+
// g_free(ptr) on finalize. For a NON-boxed plain C struct whose storage the
|
|
111
|
+
// engine g_malloc0'd itself (a caller-allocates OUT param, e.g. a filled
|
|
112
|
+
// PangoRectangle): there is no boxed free-func, the handle simply owns the
|
|
113
|
+
// malloc'd block. Mutually exclusive with `owns` (boxed ownership).
|
|
114
|
+
bool rawOwned = false;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
extern const napi_type_tag kBoxedHandleTag;
|
|
118
|
+
extern const napi_type_tag kGObjectHandleTag;
|
|
119
|
+
|
|
120
|
+
// `info` (optional): the struct/union GIBaseInfo backing the handle. When passed,
|
|
121
|
+
// MakeBoxedHandle takes its OWN ref (the caller keeps its own), unref'd on finalize.
|
|
122
|
+
// `rawOwned` (optional): the handle owns a plain g_malloc'd block — g_free(ptr) on
|
|
123
|
+
// finalize (the non-boxed caller-allocates OUT case; see BoxedHandle::rawOwned).
|
|
124
|
+
Napi::Value MakeBoxedHandle(Napi::Env env, gpointer ptr, GType gtype, bool owns,
|
|
125
|
+
GIBaseInfo* info = nullptr, bool rawOwned = false);
|
|
126
|
+
Napi::Value WrapVariant(Napi::Env env, GVariant* var, GITransfer transfer);
|
|
127
|
+
BoxedHandle* TryGetBoxedHandle(Napi::Value v);
|
|
128
|
+
bool TryGetBoxedPtr(Napi::Value v, gpointer* out);
|
|
129
|
+
Napi::Value MakeGTypeHandle(Napi::Env env, GType gtype);
|
|
130
|
+
GType ReadGTypeHandle(Napi::Value v);
|
|
131
|
+
bool UnwrapGTypeArg(Napi::Env env, Napi::Value v, GType* out);
|
|
132
|
+
|
|
133
|
+
// ---- foreign-struct seam (cairo) --------------------------------------------
|
|
134
|
+
//
|
|
135
|
+
// A "foreign" struct (gi_struct_info_is_foreign) is one whose layout GI does not
|
|
136
|
+
// know — it delegates marshalling to a module. cairo's Context/Surface/Pattern are
|
|
137
|
+
// the canonical case: a GI function taking/returning a cairo_t*/cairo_surface_t*/
|
|
138
|
+
// cairo_pattern_t* (e.g. a Gtk.DrawingArea draw-func's cairo_t) round-trips through
|
|
139
|
+
// the cairo module's converters, NOT the generic boxed path. Mirrors GJS's
|
|
140
|
+
// gi/foreign.cpp seam (gjs_struct_foreign_register / lookup / to/from_gi_argument).
|
|
141
|
+
//
|
|
142
|
+
// A registered module (cairo.cc) supplies `to` (JS wrapper -> GIArgument.v_pointer)
|
|
143
|
+
// and `from` (GIArgument.v_pointer -> a fresh JS wrapper). Ownership follows the
|
|
144
|
+
// node-gi adopt-or-ref model (no separate release call, matching WrapBoxed).
|
|
145
|
+
struct ForeignStructOps {
|
|
146
|
+
bool (*to)(Napi::Env, Napi::Value, GITransfer, GIArgument*);
|
|
147
|
+
Napi::Value (*from)(Napi::Env, gpointer, GITransfer);
|
|
148
|
+
};
|
|
149
|
+
void RegisterForeignStruct(const char* ns, const char* type_name, const ForeignStructOps* ops);
|
|
150
|
+
const ForeignStructOps* LookupForeignStruct(const char* ns, const char* type_name);
|
|
151
|
+
// The foreign ops for a struct/union GIBaseInfo, or null when it is not a
|
|
152
|
+
// registered foreign type — used by the INTERFACE marshalling branches to route a
|
|
153
|
+
// cairo-typed arg/return/callback-arg to the module instead of the boxed path.
|
|
154
|
+
const ForeignStructOps* ForeignOpsForInfo(GIBaseInfo* iface);
|
|
155
|
+
|
|
156
|
+
// cairo.cc — the native cairo binding + foreign-struct registration. Called once
|
|
157
|
+
// from addon.cc's Init; sets the `__cairo` export + registers Context/Surface/
|
|
158
|
+
// Pattern foreign converters.
|
|
159
|
+
void InitCairo(Napi::Env env, Napi::Object exports);
|
|
160
|
+
|
|
161
|
+
// ---- GJS-exact 64-bit integer marshalling helpers (shared) -------------------
|
|
162
|
+
//
|
|
163
|
+
// Single source of truth for BigInt/Number ⇄ 64-bit C int, used by the GI scalar
|
|
164
|
+
// + array-element marshaller (marshal.cc), the GValue property marshaller
|
|
165
|
+
// (object.cc), and the GVariant packer/unpacker (variant.cc).
|
|
166
|
+
//
|
|
167
|
+
// IN (JsValueTo{Int,Uint}64): a BigInt is read LOSSLESSLY — GJS uses
|
|
168
|
+
// JS::ToBigInt64 / JS::ToBigUint64 (refs/gjs/gi/js-value-inl.h:126-146): a BigInt
|
|
169
|
+
// is exact by construction, a wrapping conversion, NOT an error, so `lossless` is
|
|
170
|
+
// intentionally ignored. A plain Number is truncated via node-addon-api's
|
|
171
|
+
// Int64Value (GJS truncates a Number via JS::ToInt64 — also not an error). The
|
|
172
|
+
// BigInt branch is load-bearing: `ToNumber()` on a BigInt sets a pending N-API
|
|
173
|
+
// error under NAPI_DISABLE_CPP_EXCEPTIONS, and the follow-up `Napi::Error::New`
|
|
174
|
+
// would fatally abort the process (exit 134) instead of marshalling — a crash the
|
|
175
|
+
// repo invariant forbids.
|
|
176
|
+
|
|
177
|
+
// ---- terminate-safe coercion helpers ----------------------------------------
|
|
178
|
+
//
|
|
179
|
+
// Under NODE_API_SWALLOW_UNTHROWABLE_EXCEPTIONS a fallible node-addon-api call on
|
|
180
|
+
// an env that can no longer run JS (worker.terminate() landing while a JS→GI call
|
|
181
|
+
// is inside this addon, or env teardown) returns a DEFAULT-CONSTRUCTED value
|
|
182
|
+
// (`_env == nullptr`) instead of aborting — the throw is swallowed, the value is
|
|
183
|
+
// empty. Chaining the NEXT fallible wrapper call onto that empty value funnels
|
|
184
|
+
// into node-addon-api's Error::New(napi_get_last_error_info) NAPI_FATAL_IF_FAILED
|
|
185
|
+
// sites — which are OUTSIDE the swallow valve — and aborts the process
|
|
186
|
+
// ("FATAL ERROR: Error::New napi_get_last_error_info"). Every coercion chain
|
|
187
|
+
// (`v.ToNumber().Int32Value()`, `v.ToString().Utf8Value()`, …) on a path
|
|
188
|
+
// reachable mid-terminate must go through these helpers: they check the input AND
|
|
189
|
+
// the coercion result and degrade to a zero value instead. On a LIVE env a
|
|
190
|
+
// successful napi call never yields an empty value, so behaviour is unchanged;
|
|
191
|
+
// the guards trip only on the swallowed-failure residue.
|
|
192
|
+
inline bool NodeGiToBool(Napi::Value v) {
|
|
193
|
+
if (v.IsEmpty()) return false;
|
|
194
|
+
Napi::Boolean b = v.ToBoolean();
|
|
195
|
+
return b.IsEmpty() ? false : b.Value();
|
|
196
|
+
}
|
|
197
|
+
inline int32_t NodeGiToInt32(Napi::Value v) {
|
|
198
|
+
if (v.IsEmpty()) return 0;
|
|
199
|
+
Napi::Number n = v.ToNumber();
|
|
200
|
+
return n.IsEmpty() ? 0 : n.Int32Value();
|
|
201
|
+
}
|
|
202
|
+
inline uint32_t NodeGiToUint32(Napi::Value v) {
|
|
203
|
+
if (v.IsEmpty()) return 0;
|
|
204
|
+
Napi::Number n = v.ToNumber();
|
|
205
|
+
return n.IsEmpty() ? 0 : n.Uint32Value();
|
|
206
|
+
}
|
|
207
|
+
inline double NodeGiToDouble(Napi::Value v) {
|
|
208
|
+
if (v.IsEmpty()) return 0;
|
|
209
|
+
Napi::Number n = v.ToNumber();
|
|
210
|
+
return n.IsEmpty() ? 0 : n.DoubleValue();
|
|
211
|
+
}
|
|
212
|
+
inline std::string NodeGiToUtf8(Napi::Value v) {
|
|
213
|
+
if (v.IsEmpty()) return std::string();
|
|
214
|
+
Napi::String s = v.ToString();
|
|
215
|
+
return s.IsEmpty() ? std::string() : s.Utf8Value();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
inline int64_t JsValueToInt64(Napi::Value v) {
|
|
219
|
+
if (v.IsEmpty()) return 0; // swallowed-failure residue (see helpers above)
|
|
220
|
+
if (v.IsBigInt()) {
|
|
221
|
+
bool lossless = false;
|
|
222
|
+
return v.As<Napi::BigInt>().Int64Value(&lossless);
|
|
223
|
+
}
|
|
224
|
+
Napi::Number n = v.ToNumber();
|
|
225
|
+
return n.IsEmpty() ? 0 : n.Int64Value();
|
|
226
|
+
}
|
|
227
|
+
inline uint64_t JsValueToUint64(Napi::Value v) {
|
|
228
|
+
if (v.IsEmpty()) return 0; // swallowed-failure residue (see helpers above)
|
|
229
|
+
if (v.IsBigInt()) {
|
|
230
|
+
bool lossless = false;
|
|
231
|
+
return v.As<Napi::BigInt>().Uint64Value(&lossless);
|
|
232
|
+
}
|
|
233
|
+
Napi::Number n = v.ToNumber();
|
|
234
|
+
return n.IsEmpty() ? 0 : static_cast<uint64_t>(n.Int64Value());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// OUT (WarnIfUnsafe{Int,Uint}64): GJS ALWAYS returns a JS Number for a 64-bit int
|
|
238
|
+
// (never a BigInt) and emits this g_warning when the value falls outside the range
|
|
239
|
+
// a double represents exactly (|v| > 2^53 - 1 == Number.MAX_SAFE_INTEGER), because
|
|
240
|
+
// the returned double may be rounded. Mirrors refs/gjs/gi/arg-inl.h:222-228 +
|
|
241
|
+
// js-value-inl.h:223-236, where max_safe_big_number == (1 << DBL_MANT_DIG) - 1 and
|
|
242
|
+
// DBL_MANT_DIG (std::numeric_limits<double>::digits) == 53. The message text is
|
|
243
|
+
// byte-for-byte GJS's so a warning-capturing consumer sees the identical string.
|
|
244
|
+
constexpr int64_t kMaxSafeJsInteger = (int64_t{1} << 53) - 1; // 9007199254740991
|
|
245
|
+
inline void WarnIfUnsafeInt64(int64_t v) {
|
|
246
|
+
if (v > kMaxSafeJsInteger || v < -kMaxSafeJsInteger)
|
|
247
|
+
g_warning("Value %s cannot be safely stored in a JS Number and may be rounded",
|
|
248
|
+
std::to_string(v).c_str());
|
|
249
|
+
}
|
|
250
|
+
inline void WarnIfUnsafeUint64(uint64_t v) {
|
|
251
|
+
if (v > static_cast<uint64_t>(kMaxSafeJsInteger))
|
|
252
|
+
g_warning("Value %s cannot be safely stored in a JS Number and may be rounded",
|
|
253
|
+
std::to_string(v).c_str());
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// ---- JS function -> GClosure IN-args ----------------------------------------
|
|
257
|
+
//
|
|
258
|
+
// A JS function passed for a `GObject.Closure`-typed IN parameter (e.g.
|
|
259
|
+
// g_signal_connect_closure, g_source_set_closure, GIMarshallingTests.gclosure_in,
|
|
260
|
+
// g_dbus_connection_register_object_with_closures2) is marshalled as a REAL
|
|
261
|
+
// GClosure whose marshal converts the invocation's GValue params to JS, calls the
|
|
262
|
+
// function, and writes the JS return into the return GValue — mirroring GJS's
|
|
263
|
+
// Gjs::Closure::create_marshaled "boxed" closures (refs/gjs/gi/arg-cache.cpp
|
|
264
|
+
// GClosureInTransferNone::in + gi/closure.cpp). Lifetime follows gjs exactly:
|
|
265
|
+
// the closure is ref'd + sunk at marshal time; a transfer-none arg drops that ref
|
|
266
|
+
// after the invoke (the callee keeps its own if it stored the closure), a
|
|
267
|
+
// transfer-full arg leaves it with the callee. Closures created during an invoke
|
|
268
|
+
// are recorded here so calls.cc can release them on the right path.
|
|
269
|
+
struct CreatedClosures {
|
|
270
|
+
std::vector<GClosure*> transferNone; // unref after the invoke (gjs BoxedInTransferNone::release)
|
|
271
|
+
std::vector<GClosure*> transferFull; // callee adopts; unref only when the callee never ran
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
// ---- JS bytes -> GBytes IN-args ----------------------------------------------
|
|
275
|
+
//
|
|
276
|
+
// A JS Uint8Array (or any TypedArray/DataView/ArrayBuffer) passed for a
|
|
277
|
+
// `GLib.Bytes`-typed IN parameter (e.g. GdkPixbuf.Pixbuf.new_from_bytes,
|
|
278
|
+
// g_compute_checksum_for_bytes) is COPIED into a fresh GBytes — exactly what GJS
|
|
279
|
+
// does (refs/gjs/gi/arg-cache.cpp GBytesIn::in → gjs_byte_array_get_bytes →
|
|
280
|
+
// g_bytes_new). Lifetime follows gjs: a transfer-none arg drops the fresh ref
|
|
281
|
+
// after the invoke (GBytesInTransferNone::release → g_boxed_free; a callee that
|
|
282
|
+
// kept the bytes holds its own ref), a transfer-full arg leaves the ref with the
|
|
283
|
+
// callee (GBytesIn::release skips via the ignore_release mark). GBytes created
|
|
284
|
+
// during an invoke are recorded here so calls.cc can release them on the right
|
|
285
|
+
// path (mirrors CreatedClosures).
|
|
286
|
+
struct CreatedBytes {
|
|
287
|
+
std::vector<GBytes*> transferNone; // unref after the invoke (callee borrowed/ref'd)
|
|
288
|
+
std::vector<GBytes*> transferFull; // callee adopts; unref only when the callee never ran
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Build the (floating) marshaled GClosure for a JS function (signals.cc — shares
|
|
292
|
+
// the battle-tested JsClosure marshal/finalize machinery with `.connect()`).
|
|
293
|
+
GClosure* NodeGiMakeGenericJsClosure(Napi::Env env, Napi::Value fn);
|
|
294
|
+
|
|
295
|
+
// `ownedStrings` (optional): when a transfer-full string IN/INOUT arg is g_strdup'd
|
|
296
|
+
// here, the freshly-allocated pointer is appended so the caller can g_free it if the
|
|
297
|
+
// invoke never adopts it (an arg-marshal error before the call, or a failed invoke).
|
|
298
|
+
// nullptr (the default) → no tracking, for the vfunc-return / signal-arg callers.
|
|
299
|
+
// `closures` (optional): enables the JS-function→GClosure IN-arg marshalling above;
|
|
300
|
+
// nullptr (the default) keeps the previous behaviour (function → TypeError) on
|
|
301
|
+
// paths that cannot release a created closure.
|
|
302
|
+
// `bytes` (optional): enables the JS-bytes→GBytes IN-arg marshalling above;
|
|
303
|
+
// nullptr (the default) keeps the previous behaviour (typed array → TypeError) on
|
|
304
|
+
// paths that cannot release a created GBytes.
|
|
305
|
+
bool JsToGIArgument(Napi::Env env, Napi::Value v, GITypeInfo* type, GIArgument* out,
|
|
306
|
+
std::string* heldString,
|
|
307
|
+
GITransfer transfer = GI_TRANSFER_NOTHING,
|
|
308
|
+
std::vector<gpointer>* ownedStrings = nullptr,
|
|
309
|
+
CreatedClosures* closures = nullptr,
|
|
310
|
+
CreatedBytes* bytes = nullptr);
|
|
311
|
+
|
|
312
|
+
// ---- IN container building -----------------------------------------
|
|
313
|
+
//
|
|
314
|
+
// Each built container is recorded so it can be freed after the invoke for
|
|
315
|
+
// TRANSFER_NOTHING (the callee borrowed it). TRANSFER_EVERYTHING/CONTAINER are
|
|
316
|
+
// adopted by the callee → never freed here.
|
|
317
|
+
struct InContainer {
|
|
318
|
+
GITypeInfo* type; // ref'd; unref'd in FreeInContainer
|
|
319
|
+
gpointer ptr;
|
|
320
|
+
GITransfer transfer;
|
|
321
|
+
long count; // element count (to free C-array strings)
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
bool IsSupportedContainerType(GITypeInfo* type, std::string* why);
|
|
325
|
+
// Whether `type` is a supported OUT/INOUT marshalling type (fundamentals, strings,
|
|
326
|
+
// object/interface/enum/flags, struct/boxed/union, and containers). Shared by the
|
|
327
|
+
// function-invoke path (calls.cc) and the vfunc chain-up path (class.cc).
|
|
328
|
+
bool IsSupportedOutType(GITypeInfo* type, std::string* why);
|
|
329
|
+
size_t CElementSize(GITypeInfo* elem);
|
|
330
|
+
void WriteLengthValue(GITypeInfo* lenType, GIArgument* slot, long n);
|
|
331
|
+
Napi::Value ReadOutOrReturn(Napi::Env env, GICallableInfo* callable, GITypeInfo* ti,
|
|
332
|
+
GIArgument* arg, GITransfer transfer,
|
|
333
|
+
std::vector<GIArgument>* slots);
|
|
334
|
+
void FreeInContainer(const InContainer& c);
|
|
335
|
+
bool JsToInContainer(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfer transfer,
|
|
336
|
+
gpointer* outPtr, long* outCount,
|
|
337
|
+
std::vector<InContainer>* containers);
|
|
338
|
+
|
|
339
|
+
extern std::atomic<napi_env> g_owner_env;
|
|
340
|
+
extern std::atomic<bool> g_toggle_shutdown;
|
|
341
|
+
extern std::recursive_mutex g_queue_mutex;
|
|
342
|
+
extern napi_threadsafe_function g_drain_tsfn;
|
|
343
|
+
extern bool g_drain_async_inited;
|
|
344
|
+
|
|
345
|
+
// ---- env-teardown safety (toggle.cc) ----------------------------------------
|
|
346
|
+
//
|
|
347
|
+
// True iff `env` may enter JS right now. Probes napi_strict_equals(undef, undef)
|
|
348
|
+
// — NAPI_PREAMBLE-gated upstream (refs/node src/js_native_api_v8.cc), so it fails
|
|
349
|
+
// with napi_pending_exception / napi_cannot_run_js exactly when the env must not
|
|
350
|
+
// run JS: after node::FreeEnvironment / Environment::ExitEnv set
|
|
351
|
+
// can_call_into_js=false (env teardown, worker.terminate()), or while a JS
|
|
352
|
+
// exception is pending. Side-effect-free and pure N-API (portable to Bun/Deno).
|
|
353
|
+
//
|
|
354
|
+
// Every C->JS re-entry that can fire OUTSIDE a JS-initiated frame (the drain
|
|
355
|
+
// TSFN callback, the vfunc/callback ffi trampolines, the signal closure marshal)
|
|
356
|
+
// MUST check this first and degrade to a no-op when false: a node-addon-api call
|
|
357
|
+
// that fails on a dead env escalates to Error::ThrowAsJavaScriptException, whose
|
|
358
|
+
// napi_throw fails the same way -> NAPI_FATAL_IF_FAILED -> process abort
|
|
359
|
+
// ("FATAL ERROR: Error::ThrowAsJavaScriptException napi_throw").
|
|
360
|
+
bool NodeGiJsAvailable(napi_env env);
|
|
361
|
+
|
|
362
|
+
// Opt-in stderr tracing of the toggle/teardown machinery (env NODE_GI_TOGGLE_DEBUG,
|
|
363
|
+
// parsed once). Call sites guard with NodeGiToggleDebugEnabled() so argument
|
|
364
|
+
// evaluation costs nothing when off.
|
|
365
|
+
bool NodeGiToggleDebugEnabled();
|
|
366
|
+
void NodeGiToggleDebugLog(const char* fmt, ...) G_GNUC_PRINTF(1, 2);
|
|
367
|
+
|
|
368
|
+
Napi::Value MakeGObjectHandle(Napi::Env env, GObject* obj);
|
|
369
|
+
Napi::Value WrapGObject(Napi::Env env, GObject* obj, GITransfer transfer);
|
|
370
|
+
|
|
371
|
+
extern int g_syncEmitDepth;
|
|
372
|
+
|
|
373
|
+
// Nesting depth of loop-dispatched C->JS callbacks (the napi_make_callback sites
|
|
374
|
+
// in signals.cc / calls.cc / class.cc). Used to run the cross-runtime microtask
|
|
375
|
+
// checkpoint (NodeGiMaybeDrainMicrotasks) only at the OUTERMOST callback
|
|
376
|
+
// boundary — a nested dispatch (a handler re-entering the loop via a nested
|
|
377
|
+
// run()) defers draining to the outer boundary, exactly as Node's nested
|
|
378
|
+
// InternalCallbackScopes and GJS's "drain when the last JS frame exits" do.
|
|
379
|
+
extern int g_loopDispatchDepth;
|
|
380
|
+
|
|
381
|
+
// Cross-runtime microtask checkpoint (Bun/Deno — see loop.cc). Invokes the
|
|
382
|
+
// runtime-native drain registered via setMicrotaskDrain after an outermost
|
|
383
|
+
// loop-dispatched callback returns; a no-op on Node (never registered), with a
|
|
384
|
+
// pending JS exception, or when re-entered.
|
|
385
|
+
void NodeGiMaybeDrainMicrotasks(napi_env env);
|
|
386
|
+
|
|
387
|
+
bool SurfacePendingException(napi_env env, const char* context);
|
|
388
|
+
void ThrowGError(Napi::Env env, GError* error, const std::string& context);
|
|
389
|
+
|
|
390
|
+
// Forward declaration: JsToGValue (below) marshals object/boxed-typed property
|
|
391
|
+
// values, which need to unwrap a node-gi GObject handle; UnwrapGObject is defined
|
|
392
|
+
// further down (it shares the validation logic with the property/method paths).
|
|
393
|
+
GObject* UnwrapGObject(Napi::Env env, Napi::Value handle);
|
|
394
|
+
|
|
395
|
+
Napi::Value GValueToJs(Napi::Env env, const GValue* v);
|
|
396
|
+
bool JsToGValue(Napi::Env env, Napi::Value js, GValue* v);
|
|
397
|
+
Napi::Value ConstructGObject(Napi::Env env, GType gtype, Napi::Object props,
|
|
398
|
+
const std::string& displayName);
|
|
399
|
+
|
|
400
|
+
struct NodeGiSignalDef {
|
|
401
|
+
std::string name;
|
|
402
|
+
std::vector<GType> paramTypes;
|
|
403
|
+
GType returnType;
|
|
404
|
+
GSignalFlags flags;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
struct NodeGiVFunc; // defined in class.cc (registerClass vfunc overrides)
|
|
408
|
+
|
|
409
|
+
// ---- Gtk.Widget composite-template API (resolved via dlsym, no GTK link) ----
|
|
410
|
+
//
|
|
411
|
+
// The engine links only girepository-2.0; GTK is dlopen'd at runtime by the
|
|
412
|
+
// typelib. The composite-template entry points are GtkWidgetClass / GtkWidget
|
|
413
|
+
// calls that take the klass pointer class_init already holds (set_template,
|
|
414
|
+
// bind_template_child_full) or a constructed instance (init_template,
|
|
415
|
+
// get_template_child) — they are not naturally reachable through the introspected
|
|
416
|
+
// method-invoke paths, so they are resolved by symbol from the already-loaded
|
|
417
|
+
// libgtk-4. All argument types are plain GLib/GObject types (GBytes, GType,
|
|
418
|
+
// GObject, gpointer for the opaque GtkWidgetClass/GtkWidget), so no GTK headers
|
|
419
|
+
// are needed. Self-contained to the template feature; nothing else dlopens GTK.
|
|
420
|
+
struct GtkTemplateApi {
|
|
421
|
+
void (*set_template)(gpointer widget_class, GBytes* template_bytes);
|
|
422
|
+
void (*set_template_from_resource)(gpointer widget_class, const char* resource_name);
|
|
423
|
+
void (*bind_template_child_full)(gpointer widget_class, const char* name, gboolean internal,
|
|
424
|
+
gssize struct_offset);
|
|
425
|
+
void (*set_css_name)(gpointer widget_class, const char* name);
|
|
426
|
+
void (*init_template)(gpointer widget);
|
|
427
|
+
GObject* (*get_template_child)(gpointer widget, GType widget_type, const char* name);
|
|
428
|
+
// Template-callback dispatch (`<signal handler="…">`): a custom GtkBuilderScope
|
|
429
|
+
// is set on the class so GtkBuilder resolves any handler name to the instance's
|
|
430
|
+
// JS method (mirrors GJS's TemplateBuilderScope). set_template_scope installs it;
|
|
431
|
+
// builder_get_current_object yields the widget being built (the handler `this`);
|
|
432
|
+
// builder_scope_get_type is the interface GType our scope subtype implements.
|
|
433
|
+
void (*set_template_scope)(gpointer widget_class, gpointer scope);
|
|
434
|
+
GObject* (*builder_get_current_object)(gpointer builder);
|
|
435
|
+
GType (*builder_scope_get_type)(void);
|
|
436
|
+
bool ok;
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
const GtkTemplateApi* GetGtkTemplateApi();
|
|
440
|
+
|
|
441
|
+
// Per-registered-type metadata, passed as GTypeInfo.class_data → class_init.
|
|
442
|
+
// Heap-allocated and intentionally never freed (a GType is process-permanent).
|
|
443
|
+
struct NodeGiClassData {
|
|
444
|
+
std::vector<GParamSpec*> properties; // ownership transfers to the class on install
|
|
445
|
+
std::vector<NodeGiSignalDef> signals;
|
|
446
|
+
std::vector<NodeGiVFunc*> vfuncs; // class-lifetime vfunc overrides (never freed)
|
|
447
|
+
void (*parentGet)(GObject*, guint, GValue*, GParamSpec*);
|
|
448
|
+
void (*parentSet)(GObject*, guint, const GValue*, GParamSpec*);
|
|
449
|
+
// Gtk.Widget composite template (when registerClass meta carried a Template).
|
|
450
|
+
bool hasTemplate = false;
|
|
451
|
+
GBytes* templateBytes = nullptr; // owned inline UI-XML (g_bytes_new copy)
|
|
452
|
+
std::string templateResource; // resource path (e.g. "/eu/app/win.ui")
|
|
453
|
+
std::string cssName; // gtk_widget_class_set_css_name (optional)
|
|
454
|
+
std::vector<std::string> children; // public Children ids
|
|
455
|
+
std::vector<std::string> internalChildren; // InternalChildren ids
|
|
456
|
+
// The generic template-callback scope set on the class (set_template_scope). Owned
|
|
457
|
+
// for the class lifetime (process-permanent, like cd itself); its JS env qdata is
|
|
458
|
+
// refreshed per construction so create_closure resolves handlers in the live env.
|
|
459
|
+
GObject* templateScope = nullptr;
|
|
460
|
+
|
|
461
|
+
// The cold `delete cd` failure paths (a bad GParamSpec, or g_type_register_static
|
|
462
|
+
// returning 0) must not leak the owned inline-template GBytes. A destructor frees
|
|
463
|
+
// it by construction → every `delete cd` path is covered. On the SUCCESS path cd
|
|
464
|
+
// is stored as the type's qdata for the class lifetime and is NEVER deleted, so
|
|
465
|
+
// this destructor does not run there and templateBytes stays alive for the
|
|
466
|
+
// template install. (properties/vfuncs are released explicitly at the delete
|
|
467
|
+
// sites — kept out of here to avoid double-freeing them.)
|
|
468
|
+
~NodeGiClassData() {
|
|
469
|
+
if (templateBytes != nullptr) g_bytes_unref(templateBytes);
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
|
|
473
|
+
NodeGiClassData* FindClassData(GType type);
|
|
474
|
+
GQuark NodeGiScopeEnvQuark();
|
|
475
|
+
|
|
476
|
+
// Forward declaration: ConstructGObject calls gtk_widget_init_template on a
|
|
477
|
+
// freshly-built widget whose registered type carries a Gtk.Widget template. The
|
|
478
|
+
// helper (and the NodeGiClassData/GtkTemplateApi it reads) is defined with the
|
|
479
|
+
// registerClass machinery further down; a no-op for any non-templated type. The
|
|
480
|
+
// env is threaded through so the per-construction JS env can be stamped on the
|
|
481
|
+
// template-callback scope before init_template builds (and connects) the tree.
|
|
482
|
+
struct NodeGiClassData;
|
|
483
|
+
void MaybeInitTemplate(Napi::Env env, GObject* obj);
|
|
484
|
+
|
|
485
|
+
// Forward declarations for the Gtk.Widget composite-template callback scope. The
|
|
486
|
+
// implementation lives further down (alongside the JsClosure signal machinery it
|
|
487
|
+
// reuses), but NodeGiClassInit installs the scope and MaybeInitTemplate refreshes
|
|
488
|
+
// its env, both above that point.
|
|
489
|
+
void NodeGiInstallTemplateScopeOnClass(NodeGiClassData* cd, gpointer g_class);
|
|
490
|
+
|
|
491
|
+
// ---- N-API entry points (registered in addon.cc's Init) ----
|
|
492
|
+
|
|
493
|
+
// repo.cc
|
|
494
|
+
Napi::Value RequireNamespace(const Napi::CallbackInfo& info);
|
|
495
|
+
Napi::Value ListInfoNames(const Napi::CallbackInfo& info);
|
|
496
|
+
Napi::Value FindInfo(const Napi::CallbackInfo& info);
|
|
497
|
+
Napi::Value GetConstantValue(const Napi::CallbackInfo& info);
|
|
498
|
+
Napi::Value GetEnumValues(const Napi::CallbackInfo& info);
|
|
499
|
+
Napi::Value GetErrorDomain(const Napi::CallbackInfo& info);
|
|
500
|
+
Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info);
|
|
501
|
+
Napi::Value PrependSearchPath(const Napi::CallbackInfo& info);
|
|
502
|
+
|
|
503
|
+
// calls.cc
|
|
504
|
+
Napi::Value CallFunction(const Napi::CallbackInfo& info);
|
|
505
|
+
Napi::Value CallMethod(const Napi::CallbackInfo& info);
|
|
506
|
+
Napi::Value HasMethod(const Napi::CallbackInfo& info);
|
|
507
|
+
Napi::Value CallStaticMethod(const Napi::CallbackInfo& info);
|
|
508
|
+
// The `new <Struct>()` [[Construct]] path: route to the struct's 'new'
|
|
509
|
+
// constructor when it has one, else zero-allocate (GJS gi/boxed.cpp parity).
|
|
510
|
+
Napi::Value ConstructStruct(const Napi::CallbackInfo& info);
|
|
511
|
+
Napi::Value CallBoxedMethod(const Napi::CallbackInfo& info);
|
|
512
|
+
Napi::Value IsBoxedHandle(const Napi::CallbackInfo& info);
|
|
513
|
+
// Struct/boxed/union FIELD access (marshal.cc). boxedMemberKind(handle, name) →
|
|
514
|
+
// 0 (neither) | 1 (method) | 2 (field); getBoxedField(handle, name) → the field
|
|
515
|
+
// value; setBoxedField(handle, name, value) writes it.
|
|
516
|
+
Napi::Value BoxedMemberKind(const Napi::CallbackInfo& info);
|
|
517
|
+
Napi::Value GetBoxedField(const Napi::CallbackInfo& info);
|
|
518
|
+
Napi::Value SetBoxedField(const Napi::CallbackInfo& info);
|
|
519
|
+
Napi::Value BoxedTypeName(const Napi::CallbackInfo& info);
|
|
520
|
+
// GParamSpec wrapping (object.cc): a tagged GObject-fundamental handle plus its
|
|
521
|
+
// name/nick/blurb/flags/value_type/owner_type/default_value accessors.
|
|
522
|
+
Napi::Value MakeParamSpecHandle(Napi::Env env, GParamSpec* pspec, GITransfer transfer);
|
|
523
|
+
Napi::Value IsParamSpecHandle(const Napi::CallbackInfo& info);
|
|
524
|
+
Napi::Value ParamSpecProp(const Napi::CallbackInfo& info);
|
|
525
|
+
|
|
526
|
+
// Non-GObject GObject-fundamental wrapping (object.cc): a tagged External carrying
|
|
527
|
+
// the raw pointer + the introspected unref func, for fundamentals like
|
|
528
|
+
// GskRenderNode / GdkEvent that are introspected as object info but are NOT
|
|
529
|
+
// GObjects (their own ref/unref, G_IS_OBJECT false — see the object.cc comment).
|
|
530
|
+
Napi::Value MakeFundamentalHandle(Napi::Env env, gpointer ptr, GIObjectInfo* info, GITransfer transfer);
|
|
531
|
+
Napi::Value IsFundamentalHandle(const Napi::CallbackInfo& info);
|
|
532
|
+
|
|
533
|
+
// object.cc
|
|
534
|
+
Napi::Value NewObject(const Napi::CallbackInfo& info);
|
|
535
|
+
Napi::Value GetProperty(const Napi::CallbackInfo& info);
|
|
536
|
+
Napi::Value SetProperty(const Napi::CallbackInfo& info);
|
|
537
|
+
Napi::Value HasProperty(const Napi::CallbackInfo& info);
|
|
538
|
+
Napi::Value GetTypeName(const Napi::CallbackInfo& info);
|
|
539
|
+
Napi::Value GetGType(const Napi::CallbackInfo& info);
|
|
540
|
+
Napi::Value IsInstanceOf(const Napi::CallbackInfo& info);
|
|
541
|
+
Napi::Value IsGObjectHandle(const Napi::CallbackInfo& info);
|
|
542
|
+
Napi::Value NewGValue(const Napi::CallbackInfo& info);
|
|
543
|
+
|
|
544
|
+
// class.cc
|
|
545
|
+
Napi::Value RegisterClass(const Napi::CallbackInfo& info);
|
|
546
|
+
Napi::Value RegisterClassFromGType(const Napi::CallbackInfo& info);
|
|
547
|
+
Napi::Value ConstructType(const Napi::CallbackInfo& info);
|
|
548
|
+
Napi::Value CallParentVfunc(const Napi::CallbackInfo& info);
|
|
549
|
+
|
|
550
|
+
// template.cc
|
|
551
|
+
Napi::Value GetTemplateChild(const Napi::CallbackInfo& info);
|
|
552
|
+
|
|
553
|
+
// toggle.cc (TEST-ONLY cross-thread GC stress)
|
|
554
|
+
Napi::Value StressRefUnrefOffThread(const Napi::CallbackInfo& info);
|
|
555
|
+
Napi::Value StressRefUnrefRunning(const Napi::CallbackInfo& info);
|
|
556
|
+
Napi::Value StressRefUnrefProgress(const Napi::CallbackInfo& info);
|
|
557
|
+
Napi::Value StressRefUnrefStop(const Napi::CallbackInfo& info);
|
|
558
|
+
|
|
559
|
+
// variant.cc
|
|
560
|
+
Napi::Value VariantNew(const Napi::CallbackInfo& info);
|
|
561
|
+
Napi::Value VariantUnpack(const Napi::CallbackInfo& info);
|
|
562
|
+
Napi::Value VariantGetTypeString(const Napi::CallbackInfo& info);
|
|
563
|
+
Napi::Value IsVariantHandle(const Napi::CallbackInfo& info);
|
|
564
|
+
|
|
565
|
+
// signals.cc
|
|
566
|
+
Napi::Value ConnectSignal(const Napi::CallbackInfo& info);
|
|
567
|
+
Napi::Value EmitSignal(const Napi::CallbackInfo& info);
|
|
568
|
+
Napi::Value DisconnectSignal(const Napi::CallbackInfo& info);
|
|
569
|
+
Napi::Value SetTemplateCallbackResolver(const Napi::CallbackInfo& info);
|
|
570
|
+
|
|
571
|
+
// private.cc — GjsPrivate-mirroring helpers (refs/gjs/libgjs-private/gjs-util.c).
|
|
572
|
+
// The structured-log writer func + the bind_property_full/bind_full transform
|
|
573
|
+
// trampolines, which need a C wrapper for the same reasons GJS uses GjsPrivate:
|
|
574
|
+
// the GLogWriterFunc can fire on ANY thread (JS only runs on the registration
|
|
575
|
+
// thread; other threads fall back to the default writer) and the GLogField array
|
|
576
|
+
// is not generically introspectable; the binding transform's `to_value` is a
|
|
577
|
+
// write-back OUT GValue no marshaled GClosure can reach.
|
|
578
|
+
Napi::Value LogSetWriterFunc(const Napi::CallbackInfo& info);
|
|
579
|
+
Napi::Value LogSetWriterDefault(const Napi::CallbackInfo& info);
|
|
580
|
+
Napi::Value BindPropertyFull(const Napi::CallbackInfo& info);
|
|
581
|
+
Napi::Value BindingGroupBindFull(const Napi::CallbackInfo& info);
|
|
582
|
+
|
|
583
|
+
// loop.cc
|
|
584
|
+
Napi::Value StartMainLoop(const Napi::CallbackInfo& info);
|
|
585
|
+
Napi::Value IterateMainContext(const Napi::CallbackInfo& info);
|
|
586
|
+
Napi::Value PumpKick(const Napi::CallbackInfo& info);
|
|
587
|
+
Napi::Value SetMicrotaskDrain(const Napi::CallbackInfo& info);
|
|
588
|
+
|
|
589
|
+
// ---- uv-driven GLib auto-pump (loop.cc) -------------------------------------
|
|
590
|
+
//
|
|
591
|
+
// In-flight scope=async keep-alive: while a GI scope=async callback (e.g. a
|
|
592
|
+
// GAsyncReadyCallback) is pending, the pump holds a libuv ref so a plain Node
|
|
593
|
+
// script survives until the completion dispatches (the top-level-await case).
|
|
594
|
+
// Begin returns whether the callback was counted (only on the pump-owning env,
|
|
595
|
+
// i.e. Node's main env after startMainLoop); the caller must call End exactly
|
|
596
|
+
// once for each counted callback. Main-thread only.
|
|
597
|
+
bool NodeGiPumpAsyncBegin(napi_env env);
|
|
598
|
+
void NodeGiPumpAsyncEnd();
|
|
599
|
+
// Close the pump's uv handles at env teardown (no-op for non-owner envs).
|
|
600
|
+
void NodeGiPumpShutdown(napi_env env);
|
|
601
|
+
|
|
602
|
+
// RAII window for every C→JS dispatch (the GI callback trampoline, the signal
|
|
603
|
+
// closure marshal, a vfunc invocation). The pump holds g_in_uv_pump while it
|
|
604
|
+
// iterates the context so the uv-in-GLib UvLoopSource stays parked (no reentrant
|
|
605
|
+
// uv_run out of the pump's own uv callbacks) — but JS dispatched FROM that
|
|
606
|
+
// iteration, including every microtask continuation napi_make_callback runs at
|
|
607
|
+
// its boundary, must not inherit the flag: user code there may legitimately
|
|
608
|
+
// start a blocking GLib.MainLoop.run(), which needs the UvLoopSource co-pump
|
|
609
|
+
// live (Node timers keep firing during the run). Clears the flag on entry,
|
|
610
|
+
// restores it on scope exit — mirroring SyncEmitDepthReset.
|
|
611
|
+
class NodeGiPumpJsDispatchScope {
|
|
612
|
+
public:
|
|
613
|
+
NodeGiPumpJsDispatchScope();
|
|
614
|
+
~NodeGiPumpJsDispatchScope();
|
|
615
|
+
NodeGiPumpJsDispatchScope(const NodeGiPumpJsDispatchScope&) = delete;
|
|
616
|
+
NodeGiPumpJsDispatchScope& operator=(const NodeGiPumpJsDispatchScope&) = delete;
|
|
617
|
+
|
|
618
|
+
private:
|
|
619
|
+
gboolean saved_;
|
|
620
|
+
};
|
|
621
|
+
|
|
622
|
+
} // namespace nodegi
|
|
623
|
+
|
|
624
|
+
#endif // NODE_GI_SRC_COMMON_H_
|