@gjsify/node-gi 0.35.0 → 0.37.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/binding.gyp +10 -0
- package/gi.js +143 -247
- package/index.js +81 -617
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/scripts/install.mjs +68 -97
- package/src/class.cc +15 -0
package/binding.gyp
CHANGED
|
@@ -41,6 +41,16 @@
|
|
|
41
41
|
"OTHER_CFLAGS": [
|
|
42
42
|
"<!@(pkg-config --cflags girepository-2.0 cairo)"
|
|
43
43
|
],
|
|
44
|
+
# scripts/stage-prebuild.mjs rewrites this addon's Homebrew-absolute
|
|
45
|
+
# dependencies to @rpath and gives it a four-entry LC_RPATH list (#1120).
|
|
46
|
+
# A Mach-O's load commands live in a header pad fixed at LINK time, and
|
|
47
|
+
# the default pad is too small for that: install_name_tool then refuses
|
|
48
|
+
# with "larger updated load commands do not fit (the program must be
|
|
49
|
+
# relinked)". Not repairable after the fact, which is why it is a link
|
|
50
|
+
# flag and not a staging step.
|
|
51
|
+
"OTHER_LDFLAGS": [
|
|
52
|
+
"-Wl,-headerpad_max_install_names"
|
|
53
|
+
],
|
|
44
54
|
"CLANG_CXX_LANGUAGE_STANDARD": "c++17",
|
|
45
55
|
"GCC_ENABLE_CPP_EXCEPTIONS": "NO"
|
|
46
56
|
}
|
package/gi.js
CHANGED
|
@@ -1,58 +1,41 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
|
-
// @gjsify/node-gi/gi — the L1 GJS-compatibility layer over the native engine
|
|
2
|
+
// @gjsify/node-gi/gi — the L1 GJS-compatibility layer over the native engine: it turns
|
|
3
|
+
// the engine primitives into a GJS-shaped namespace object, so `requireGi('Gio','2.0')`
|
|
4
|
+
// gives the same surface as `import Gio from 'gi://Gio?version=2.0'` under GJS. This is
|
|
5
|
+
// the seam the bundler's --app node target rewrites `gi://Ns?version=X` onto.
|
|
3
6
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// a GJS-shaped namespace object: `requireGi('Gio', '2.0')` returns an object
|
|
7
|
-
// where `Gio.SomeFunction(...)` calls a namespace function, `new Gio.SomeClass({
|
|
8
|
-
// prop: value })` constructs a GObject, and the instance exposes `.method(...)`,
|
|
9
|
-
// `.prop` (property get/set) and `.connect()/.emit()/.disconnect()` — the same
|
|
10
|
-
// surface `import Gio from 'gi://Gio?version=2.0'` gives under GJS.
|
|
11
|
-
//
|
|
12
|
-
// This is the seam the bundler's --app node target rewrites `gi://Ns?version=X`
|
|
13
|
-
// onto. Reference: GJS's gi module (gjs/modules/esm/gi.js) for the require shape;
|
|
14
|
-
// node-gtk (romgrk, MIT) for the binding lineage. Hand-authored JS (the package
|
|
15
|
-
// ships no build step). Milestone 1 surfaces functions + GObject classes; enums,
|
|
16
|
-
// flags, constants, structs/boxed, interface static methods and camelCase
|
|
17
|
-
// aliases land in subsequent drops.
|
|
7
|
+
// Reference: gjs/modules/esm/gi.js for the require shape; node-gtk (romgrk, MIT) for the
|
|
8
|
+
// binding lineage. Hand-authored JS — the package ships no build step.
|
|
18
9
|
import * as native from './index.js';
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// the RUNTIME's timers, never `globalThis.setInterval`. A GJS source running
|
|
25
|
-
// through the bridge routinely replaces the global timers with GLib-backed ones
|
|
26
|
-
// (`@gjsify/node-globals/register` swaps them for `GLib.timeout_add` wrappers) —
|
|
27
|
-
// driving the GLib pump off a GLib timeout is a deadlock: the timer that is
|
|
28
|
-
// supposed to dispatch GLib sources is itself a GLib source nobody dispatches.
|
|
10
|
+
// From node:timers, not the globals, for two reasons: setImmediate is a Node/Bun global
|
|
11
|
+
// but Deno only exposes it here; and a GJS source running through the bridge routinely
|
|
12
|
+
// replaces the global timers with GLib-backed ones (`@gjsify/node-globals/register`
|
|
13
|
+
// swaps them for `GLib.timeout_add`), which deadlocks the pump — the timer meant to
|
|
14
|
+
// dispatch GLib sources would itself be a GLib source nobody dispatches.
|
|
29
15
|
import { setImmediate, setInterval, clearInterval } from 'node:timers';
|
|
30
16
|
// Same reasoning for `process`: `globalThis.process` may be the `@gjsify/process`
|
|
31
|
-
// polyfill, whose EventEmitter never emits the runtime's 'beforeExit'
|
|
32
|
-
// loop-liveness hooks below
|
|
17
|
+
// polyfill, whose EventEmitter never emits the runtime's 'beforeExit', so the
|
|
18
|
+
// loop-liveness hooks below would silently never fire.
|
|
33
19
|
import runtimeProcess from 'node:process';
|
|
34
|
-
// The GJS Gio DBus surface (Gio.DBus.session/system, own_name/watch_name,
|
|
35
|
-
// Gio.DBusProxy.makeProxyWrapper, Gio.DBusExportedObject) — ported to the L1 model.
|
|
36
20
|
import { createGioDBus } from './overrides/gio-dbus.js';
|
|
37
21
|
|
|
38
|
-
//
|
|
39
|
-
//
|
|
22
|
+
// The raw native GObject handle on a wrapped instance, so it can be unwrapped again
|
|
23
|
+
// when passed back into the engine as a GI argument.
|
|
40
24
|
const HANDLE = Symbol('nodeGiHandle');
|
|
41
25
|
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
// place (preserving identity + expandos) — see wrapInstance.
|
|
26
|
+
// The user-class prototype (a registerClass subclass) on a wrapped instance. On the
|
|
27
|
+
// target rather than in the proxy closure, so a later wrap carrying a userProto can
|
|
28
|
+
// UPGRADE an already-cached generic wrapper in place, preserving identity + expandos.
|
|
46
29
|
const USER_PROTO = Symbol('nodeGiUserProto');
|
|
47
30
|
|
|
48
|
-
//
|
|
49
|
-
//
|
|
50
|
-
//
|
|
31
|
+
// Names that must NEVER be treated as a GI method or property — otherwise awaiting,
|
|
32
|
+
// printing or inspecting a wrapper would call into GObject (a stray `then` alone would
|
|
33
|
+
// make every wrapper look thenable).
|
|
51
34
|
const RESERVED = new Set(['then', 'toString', 'valueOf', 'constructor', 'inspect']);
|
|
52
35
|
|
|
53
|
-
// GJS accepts both snake_case and camelCase for methods/properties
|
|
54
|
-
//
|
|
55
|
-
//
|
|
36
|
+
// GJS accepts both snake_case and camelCase for methods/properties: map a JS accessor
|
|
37
|
+
// to the GI method name (snake_case) and to a GObject property name (kebab-case); a
|
|
38
|
+
// name already in the target case passes through.
|
|
56
39
|
function camelToSnake(name) {
|
|
57
40
|
return name.replace(/([A-Z])/g, '_$1').toLowerCase();
|
|
58
41
|
}
|
|
@@ -64,20 +47,16 @@ function toKebab(name) {
|
|
|
64
47
|
.toLowerCase();
|
|
65
48
|
}
|
|
66
49
|
|
|
67
|
-
//
|
|
68
|
-
//
|
|
69
|
-
// user fn per call site pattern).
|
|
50
|
+
// Repeated passes of the same callback must marshal to the SAME native-facing function:
|
|
51
|
+
// stable identity, one ffi/GClosure wrapper per user fn.
|
|
70
52
|
const callbackShims = new WeakMap();
|
|
71
53
|
|
|
72
|
-
// A JS function crossing INTO the engine (a GI callback arg, or a JS fn
|
|
73
|
-
//
|
|
74
|
-
//
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
// wrapper returned from e.g. a DBus get-property closure must reach the
|
|
79
|
-
// engine as the raw variant handle).
|
|
80
|
-
// Primitives and plain objects pass through both directions untouched.
|
|
54
|
+
// A JS function crossing INTO the engine (a GI callback arg, or a JS fn marshalled as a
|
|
55
|
+
// GClosure IN-arg) is wrapped so C sees GJS's calling convention: each native arg goes
|
|
56
|
+
// through wrapReturn (a GObject handle becomes the cached chainable instance, a GVariant
|
|
57
|
+
// a GLib.Variant wrapper, …) and the JS return is unwrapped back to its native handle —
|
|
58
|
+
// a GLib.Variant wrapper returned from e.g. a DBus get-property closure must reach the
|
|
59
|
+
// engine as the raw variant handle.
|
|
81
60
|
function wrapCallbackFn(fn) {
|
|
82
61
|
let shim = callbackShims.get(fn);
|
|
83
62
|
if (shim === undefined) {
|
|
@@ -99,45 +78,30 @@ function unwrapArgs(args) {
|
|
|
99
78
|
return args.map(unwrapArg);
|
|
100
79
|
}
|
|
101
80
|
|
|
102
|
-
// Normalize a construct-property dict:
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
//
|
|
106
|
-
//
|
|
107
|
-
//
|
|
108
|
-
// property getter/setter accessor path already applies (lines below: `x.maximumSize`
|
|
109
|
-
// reads/writes `maximum-size`) — keeping a single source of truth, so construction
|
|
110
|
-
// and accessor agree. Idempotent: an already-dashed (`maximum-size`) or snake_case
|
|
111
|
-
// (`maximum_size`) key passes through unchanged (GObject canonicalizes `_`↔`-`).
|
|
112
|
-
// This is the ONLY caller-facing construct path — newObject for an introspected
|
|
113
|
-
// `new Ns.Class({...})`, and constructType for a registerClass'd subclass plus its
|
|
114
|
-
// `super({...})` chain-up — so all three accept camelCase identically to GJS.
|
|
81
|
+
// Normalize a construct-property dict: KEY to the GObject canonical (kebab) property
|
|
82
|
+
// name, VALUE unwrapped to its native handle. GJS accepts camelCase, snake_case or
|
|
83
|
+
// already-dashed keys, while the native layer looks each key up against the GParamSpec
|
|
84
|
+
// by its canonical dashed name — `{maximumSize:400}` would otherwise miss
|
|
85
|
+
// `maximum-size`. Reusing `toKebab` keeps construction and property accessors on one
|
|
86
|
+
// normalization. Idempotent, since GObject canonicalizes `_`↔`-`.
|
|
115
87
|
function unwrapProps(props) {
|
|
116
88
|
const out = {};
|
|
117
89
|
for (const key of Object.keys(props)) out[toKebab(key)] = unwrapArg(props[key]);
|
|
118
90
|
return out;
|
|
119
91
|
}
|
|
120
92
|
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
//
|
|
124
|
-
// as a GError domain; read by errorDomainOf.
|
|
93
|
+
// An enum object's GError-domain descriptor ({ name, quark }), so GLib.Error.matches
|
|
94
|
+
// can resolve an error-enum (Gio.IOErrorEnum) to the domain it represents. Attached by
|
|
95
|
+
// makeEnum when the enum is registered as a GError domain.
|
|
125
96
|
const ERROR_DOMAIN = Symbol('nodeGiErrorDomain');
|
|
126
97
|
|
|
127
|
-
//
|
|
98
|
+
// GLib.Error, L1: the engine throws one of these on a failed sync GI invoke (via the
|
|
99
|
+
// builder registered below) and `requireGi('GLib').Error` is this constructor, so a
|
|
100
|
+
// caught error is `instanceof GLib.Error` exactly as under GJS.
|
|
128
101
|
//
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
// this on a failed sync GI invoke (via the builder registered below), and
|
|
133
|
-
// `requireGi('GLib').Error` is this constructor, so a caught error is
|
|
134
|
-
// `instanceof GLib.Error` exactly as under GJS.
|
|
135
|
-
//
|
|
136
|
-
// Divergence from GJS (documented): GJS's `.domain` is the numeric GQuark; here
|
|
137
|
-
// `.domain` is the quark NAME string (more useful in a Node context, and the task
|
|
138
|
-
// permits either). The numeric quark is kept on `.domainQuark`. `matches()`
|
|
139
|
-
// accepts the domain as an error-enum object (Gio.IOErrorEnum), a name string, or
|
|
140
|
-
// a numeric quark, so all the idiomatic call shapes work.
|
|
102
|
+
// Deliberate divergence from GJS: `.domain` is the quark NAME string (GJS's is the
|
|
103
|
+
// numeric GQuark), with the numeric quark on `.domainQuark`. `matches()` accepts the
|
|
104
|
+
// domain as an error-enum object, a name string or a numeric quark.
|
|
141
105
|
|
|
142
106
|
// Resolve a `matches` domain argument to a { name?, quark? } descriptor.
|
|
143
107
|
function errorDomainOf(domain) {
|
|
@@ -149,8 +113,8 @@ function errorDomainOf(domain) {
|
|
|
149
113
|
}
|
|
150
114
|
|
|
151
115
|
class GLibError extends Error {
|
|
152
|
-
//
|
|
153
|
-
//
|
|
116
|
+
// `new GLib.Error(domain, code, message)`, where `domain` may be an error-enum
|
|
117
|
+
// object, a quark name string, or a numeric quark.
|
|
154
118
|
constructor(domain, code, message) {
|
|
155
119
|
super(typeof message === 'string' ? message : '');
|
|
156
120
|
this.name = 'GLib.Error';
|
|
@@ -161,8 +125,7 @@ class GLibError extends Error {
|
|
|
161
125
|
this.code = code;
|
|
162
126
|
}
|
|
163
127
|
|
|
164
|
-
// g_error_matches: true when the error's domain
|
|
165
|
-
// domain as an error-enum object / name string / numeric quark (errorDomainOf).
|
|
128
|
+
// g_error_matches: true when the error's domain AND code both match.
|
|
166
129
|
matches(domain, code) {
|
|
167
130
|
const d = errorDomainOf(domain);
|
|
168
131
|
if (d === null) return false;
|
|
@@ -176,8 +139,7 @@ class GLibError extends Error {
|
|
|
176
139
|
}
|
|
177
140
|
}
|
|
178
141
|
|
|
179
|
-
//
|
|
180
|
-
// fields (quark name + numeric quark + code + message) to build the thrown error.
|
|
142
|
+
// Called by the engine on a failed GI invoke, with the GError's authoritative fields.
|
|
181
143
|
function buildGError(domainName, domainQuark, code, message) {
|
|
182
144
|
const error = new GLibError(domainName, code, message);
|
|
183
145
|
error.domain = domainName;
|
|
@@ -186,74 +148,53 @@ function buildGError(domainName, domainQuark, code, message) {
|
|
|
186
148
|
}
|
|
187
149
|
native.setErrorBuilder(buildGError);
|
|
188
150
|
|
|
189
|
-
//
|
|
190
|
-
//
|
|
191
|
-
//
|
|
192
|
-
//
|
|
151
|
+
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
152
|
+
// record WHICH introspected class a registration belongs to — introspected instances
|
|
153
|
+
// have no live prototype chain to resolve by, so it is matched against the instance's
|
|
154
|
+
// GType via native.isInstanceOf.
|
|
193
155
|
const CLASS_INFO = Symbol('nodeGiClassInfo');
|
|
194
156
|
|
|
195
|
-
// Promisified async methods
|
|
196
|
-
//
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
// class the instance is-a (native.isInstanceOf). See _promisify / wrapInstance.
|
|
157
|
+
// Promisified async methods: GI method name (snake_case) → registrations
|
|
158
|
+
// `{ namespace?, typeName?, wrapper }`. Introspected-class instances resolve methods
|
|
159
|
+
// dynamically (no prototype chain), so the instance proxy consults this registry;
|
|
160
|
+
// when two classes promisify the SAME method name it picks the registration whose
|
|
161
|
+
// class the instance is-a.
|
|
201
162
|
const promisifiedMethods = new Map();
|
|
202
163
|
|
|
203
|
-
// Object-typed return values become chainable instance proxies; boxed/struct
|
|
204
|
-
// handles (e.g. GMainLoop) become method-routing proxies; everything else
|
|
205
|
-
// (primitives, strings, null) passes through.
|
|
206
164
|
function wrapReturn(value) {
|
|
207
165
|
if (native.isGObjectHandle(value)) return wrapInstance(value);
|
|
208
|
-
// A GLib.Variant is ALSO a boxed handle
|
|
209
|
-
//
|
|
166
|
+
// A GLib.Variant is ALSO a boxed handle by tag, so it must be checked first to get
|
|
167
|
+
// the Variant ergonomics rather than a plain method-routing proxy.
|
|
210
168
|
if (native.isVariantHandle(value)) return wrapVariant(value);
|
|
211
|
-
// A GParamSpec is a distinct GObject fundamental
|
|
212
|
-
// surfaced with the GObject.ParamSpec ergonomics (name/value_type/get_name()/…).
|
|
169
|
+
// A GParamSpec is a distinct GObject fundamental, tagged separately from boxed.
|
|
213
170
|
if (native.isParamSpecHandle(value)) return wrapParamSpec(value);
|
|
214
|
-
// A non-GObject GObject-fundamental (e.g. a GskRenderNode from Gtk.Snapshot.to_node):
|
|
215
|
-
// an opaque, round-trippable pass-through handle (see wrapFundamental).
|
|
216
171
|
if (native.isFundamentalHandle(value)) return wrapFundamental(value);
|
|
217
172
|
if (native.isBoxedHandle(value)) return wrapBoxed(value);
|
|
218
|
-
// A multi-value OUT tuple (return
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
//
|
|
222
|
-
// not left as a raw External. A Node Buffer (byte array) is NOT Array.isArray, so
|
|
223
|
-
// it passes through untouched; primitives/strings map to themselves.
|
|
173
|
+
// A multi-value OUT tuple (return + OUT params), or a container OUT, is a plain
|
|
174
|
+
// Array whose ELEMENTS may themselves be handles (GLib.Regex.match → [matched,
|
|
175
|
+
// GLib.MatchInfo]), so recurse rather than leave a raw External. A Node Buffer is
|
|
176
|
+
// not Array.isArray, so byte arrays pass through untouched.
|
|
224
177
|
if (Array.isArray(value)) return value.map(wrapReturn);
|
|
225
178
|
return value;
|
|
226
179
|
}
|
|
227
180
|
|
|
228
|
-
//
|
|
229
|
-
//
|
|
230
|
-
// arg
|
|
231
|
-
//
|
|
232
|
-
// the EMITTER (the object the signal fired on) as the first arg, so a positional
|
|
233
|
-
// handler `(obj, …params) => …` binds correctly; the emitter is itself a GObject
|
|
234
|
-
// arg, so wrapReturn turns it into the cached, toggle-ref-canonical instance. For
|
|
235
|
-
// `notify::x` the args are (object, pspec).
|
|
181
|
+
// GJS parity: the engine prepends the EMITTER as the first arg, so a positional handler
|
|
182
|
+
// `(obj, …params) => …` binds correctly (for `notify::x` the args are (object, pspec)).
|
|
183
|
+
// The emitter is itself a GObject arg, so wrapReturn turns it into the cached,
|
|
184
|
+
// toggle-ref-canonical instance.
|
|
236
185
|
function wrapSignalCallback(cb) {
|
|
237
186
|
return (...args) => cb(...args.map(wrapReturn));
|
|
238
187
|
}
|
|
239
188
|
|
|
240
|
-
// Resolve a Gtk.Template `<signal handler="…">` handler NAME to a dispatcher
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
// `_createClosure` (refs/gjs/modules/core/overrides/Gtk.js).
|
|
189
|
+
// Resolve a Gtk.Template `<signal handler="…">` handler NAME to a dispatcher calling the
|
|
190
|
+
// instance's bound JS method, for the native template-callback scope. Mirrors GJS's
|
|
191
|
+
// `_createClosure` in its Gtk override.
|
|
244
192
|
//
|
|
245
193
|
// LAZY by necessity: the engine resolves template signals during the C-side
|
|
246
|
-
// constructType (init_template → create_closure), which runs BEFORE this layer
|
|
247
|
-
//
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
// handler is therefore the widget — the SAME cached proxy the user constructed
|
|
251
|
-
// (wrapInstance is cached by the canonical handle). Native signal args are wrapped
|
|
252
|
-
// (wrapReturn) into chainable instances, exactly like wrapSignalCallback. GJS
|
|
253
|
-
// parity: the engine prepends the emitter at param 0, so a template handler
|
|
254
|
-
// `on_click(button)` receives the emitter first, then the signal's own params. A
|
|
255
|
-
// handler name with no matching user-prototype method throws a clear error when
|
|
256
|
-
// the signal fires.
|
|
194
|
+
// constructType (init_template → create_closure), which runs BEFORE this layer attaches
|
|
195
|
+
// the user-class prototype to the instance proxy. Deferring the lookup to FIRE time
|
|
196
|
+
// means `this` is the full userProto-carrying wrapper — the SAME cached proxy the user
|
|
197
|
+
// constructed. Emitter at param 0, as in wrapSignalCallback.
|
|
257
198
|
function resolveTemplateCallback(handle, handlerName) {
|
|
258
199
|
return (...args) => {
|
|
259
200
|
const proxy = wrapInstance(handle);
|
|
@@ -269,16 +210,14 @@ function resolveTemplateCallback(handle, handlerName) {
|
|
|
269
210
|
}
|
|
270
211
|
native.setTemplateCallbackResolver(resolveTemplateCallback);
|
|
271
212
|
|
|
272
|
-
// Run a registered class's JS constructor for a GObject the ENGINE instantiated
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
//
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// one wrapper GtkBuilder-fetched children later resolve to). GJS-faithful
|
|
281
|
-
// (gjs_object_constructor's native-construction branch runs the JS constructor).
|
|
213
|
+
// Run a registered class's JS constructor for a GObject the ENGINE instantiated from C
|
|
214
|
+
// — typically a GtkBuilder composite-template InternalChild, which has no JS-`new` to
|
|
215
|
+
// drive the ctor, so the overridden `constructor` vfunc calls this with the canonical
|
|
216
|
+
// handle + GType name. Setting `adoptHandle` before Reflect.construct makes the
|
|
217
|
+
// makeClass base ctor ADOPT that handle instead of creating a second GObject, so the
|
|
218
|
+
// ctor body's `this._x = …` land as expandos on the one wrapper GtkBuilder-fetched
|
|
219
|
+
// children later resolve to. GJS does the same in gjs_object_constructor's
|
|
220
|
+
// native-construction branch.
|
|
282
221
|
function runCtorForCObject(handle, gtypeName) {
|
|
283
222
|
const cls = classesByGType.get(gtypeName);
|
|
284
223
|
if (cls === undefined) return; // not a node-gi-registered class (shouldn't happen)
|
|
@@ -294,28 +233,22 @@ function runCtorForCObject(handle, gtypeName) {
|
|
|
294
233
|
}
|
|
295
234
|
native.setConstructCallback(runCtorForCObject);
|
|
296
235
|
|
|
297
|
-
//
|
|
298
|
-
//
|
|
299
|
-
//
|
|
300
|
-
//
|
|
301
|
-
//
|
|
302
|
-
//
|
|
303
|
-
// OBJECT External branch reads the pointer. Method dispatch on fundamentals is a
|
|
304
|
-
// documented follow-up; today they are pass-through intermediates (build → hand to
|
|
305
|
-
// a consumer → drop), which is what the GSK screenshot/render path needs.
|
|
236
|
+
// A non-GObject GObject-fundamental (GskRenderNode, GdkEvent) as an OPAQUE,
|
|
237
|
+
// round-trippable handle: the native tagged External already carries the pointer and
|
|
238
|
+
// drops the held ref on GC via the type's own unref func, so L1 need only expose it
|
|
239
|
+
// under HANDLE for unwrapArg to feed back into a fundamental-typed IN arg. No method
|
|
240
|
+
// dispatch — these are pass-through intermediates (build → hand to a consumer → drop),
|
|
241
|
+
// which is what the GSK screenshot/render path needs.
|
|
306
242
|
function wrapFundamental(handle) {
|
|
307
243
|
return { [HANDLE]: handle, [Symbol.toStringTag]: 'GIFundamental' };
|
|
308
244
|
}
|
|
309
245
|
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
313
|
-
//
|
|
314
|
-
//
|
|
315
|
-
//
|
|
316
|
-
// callable dispatcher), 2 = field (read via getBoxedField / write via
|
|
317
|
-
// setBoxedField), 0 = neither (fall back to a method dispatcher, so an unknown
|
|
318
|
-
// name still throws a clear "no method" at call time — the pre-fields behaviour).
|
|
246
|
+
// A boxed/struct/union handle: methods callable GJS-style (`mainLoop.run()`, snake_case
|
|
247
|
+
// or camelCase) and FIELDS readable/writable as plain properties (`union.long_ = 5`).
|
|
248
|
+
// Resolution rule, verified against gjs 1.88's find_unique_js_field_name: a name that is
|
|
249
|
+
// BOTH resolves to the METHOD, and gjs renames the colliding field to `_name`. Hence
|
|
250
|
+
// boxedMemberKind checks methods first — 1 = method, 2 = field, 0 = neither,
|
|
251
|
+
// 3 = undecidable.
|
|
319
252
|
function wrapBoxed(handle) {
|
|
320
253
|
const target = { [HANDLE]: handle };
|
|
321
254
|
const methodDispatch =
|
|
@@ -326,25 +259,22 @@ function wrapBoxed(handle) {
|
|
|
326
259
|
get(t, prop) {
|
|
327
260
|
if (prop === HANDLE) return handle;
|
|
328
261
|
if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
|
|
329
|
-
// A field
|
|
330
|
-
// target (gjs allows writing a boxed field
|
|
331
|
-
//
|
|
262
|
+
// A field written through the set trap below lands as an expando on the
|
|
263
|
+
// target (gjs likewise allows writing a GI-unwritable boxed field to a JS
|
|
264
|
+
// expando); surface it before consulting introspection.
|
|
332
265
|
if (Object.hasOwn(t, prop)) return t[prop];
|
|
333
|
-
// GBytes
|
|
334
|
-
//
|
|
335
|
-
//
|
|
336
|
-
// via the introspected get_data(). Gated on the boxed type so it never
|
|
337
|
-
// shadows a same-named method on some other struct.
|
|
266
|
+
// GBytes has no introspected `to_array`; gjs adds `toArray` to
|
|
267
|
+
// GLib.Bytes.prototype, so mirror it via the introspected get_data(). Gated
|
|
268
|
+
// on the boxed type so it never shadows a same-named method on another struct.
|
|
338
269
|
if (prop === 'toArray' && native.boxedTypeName(handle) === 'GBytes') {
|
|
339
270
|
return () => wrapReturn(native.callBoxedMethod(handle, 'get_data', []));
|
|
340
271
|
}
|
|
341
272
|
const snake = camelToSnake(prop);
|
|
342
|
-
//
|
|
343
|
-
// `typeof boxed.noSuchName
|
|
344
|
-
//
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
// fallback so a genuine method still resolves + throws a clear error at call.
|
|
273
|
+
// kind 0 (type info resolved, not a member) must read `undefined` to match
|
|
274
|
+
// gjs: a fabricated dispatcher would make `typeof boxed.noSuchName` report
|
|
275
|
+
// 'function' and break duck-typing checks. Kind 3 (unregistered struct, no
|
|
276
|
+
// static info) keeps the dispatcher fallback, so a genuine method still
|
|
277
|
+
// resolves and an unknown one throws a clear error at call time.
|
|
348
278
|
const kind = native.boxedMemberKind(handle, snake);
|
|
349
279
|
if (kind === 2) return wrapReturn(native.getBoxedField(handle, snake));
|
|
350
280
|
if (kind === 0) return undefined;
|
|
@@ -369,15 +299,10 @@ function wrapBoxed(handle) {
|
|
|
369
299
|
});
|
|
370
300
|
}
|
|
371
301
|
|
|
372
|
-
//
|
|
373
|
-
// surface
|
|
374
|
-
//
|
|
375
|
-
//
|
|
376
|
-
// a GParamSpec-typed value now surface as (fixing the old `{name, valueType}`
|
|
377
|
-
// plain-object shape). Carries [HANDLE] so it can round-trip back into the engine
|
|
378
|
-
// and so `instanceof GObject.ParamSpec` recognises it (Symbol.hasInstance below).
|
|
379
|
-
// value_type/owner_type are native GType handles (like gjs's GType objects); the
|
|
380
|
-
// GTypes' `.name` reads their type name via the introspected surface.
|
|
302
|
+
// The GJS-shaped GObject.ParamSpec surface a `notify` handler's second arg and any
|
|
303
|
+
// GParamSpec-typed value surface as. Carries [HANDLE] so it round-trips back into the
|
|
304
|
+
// engine and `instanceof GObject.ParamSpec` recognises it. value_type/owner_type are
|
|
305
|
+
// native GType handles, like gjs's GType objects.
|
|
381
306
|
function wrapParamSpec(handle) {
|
|
382
307
|
const prop = (which) => native.paramSpecProp(handle, which);
|
|
383
308
|
const pspec = {
|
|
@@ -411,23 +336,14 @@ function wrapParamSpec(handle) {
|
|
|
411
336
|
return pspec;
|
|
412
337
|
}
|
|
413
338
|
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
//
|
|
418
|
-
//
|
|
419
|
-
|
|
420
|
-
//
|
|
421
|
-
//
|
|
422
|
-
// unpack() → variantUnpack(h, false, false): children stay Variants
|
|
423
|
-
// deepUnpack() → variantUnpack(h, true, false): one level; nested `v`
|
|
424
|
-
// values (e.g. an a{sv} value) STAY Variants
|
|
425
|
-
// recursiveUnpack() → variantUnpack(h, true, true): fully plain JS, no
|
|
426
|
-
// Variants (discards `v` type info)
|
|
427
|
-
|
|
428
|
-
// Re-wrap the result of variantUnpack: any GLib.Variant handle that the native
|
|
429
|
-
// unpacker left in place (an `a{sv}` value under deepUnpack, every child under
|
|
430
|
-
// unpack(), …) becomes a GLib.Variant wrapper; arrays/plain dicts are walked.
|
|
339
|
+
// GLib.Variant ergonomics, mirroring GJS's GLib override. The three unpack depths the
|
|
340
|
+
// native unpacker implements:
|
|
341
|
+
// unpack() children stay Variants
|
|
342
|
+
// deepUnpack() one level; nested `v` values (an a{sv} value) STAY Variants
|
|
343
|
+
// recursiveUnpack() fully plain JS, discarding the `v` type info
|
|
344
|
+
|
|
345
|
+
// Re-wrap what variantUnpack left in place: any GLib.Variant handle still in the result
|
|
346
|
+
// becomes a wrapper; arrays and plain dicts are walked.
|
|
431
347
|
function wrapVariantResult(value) {
|
|
432
348
|
if (value === null || typeof value !== 'object') return value;
|
|
433
349
|
if (native.isVariantHandle(value)) return wrapVariant(value);
|
|
@@ -438,9 +354,8 @@ function wrapVariantResult(value) {
|
|
|
438
354
|
return out;
|
|
439
355
|
}
|
|
440
356
|
|
|
441
|
-
//
|
|
442
|
-
//
|
|
443
|
-
// (action.activate(variant), new_stateful state, change_state value, …).
|
|
357
|
+
// Carries [HANDLE] so the wrapper round-trips back into the engine as a GVariant IN
|
|
358
|
+
// argument (action.activate(variant), new_stateful state, change_state value, …).
|
|
444
359
|
function wrapVariant(handle) {
|
|
445
360
|
const target = { [HANDLE]: handle };
|
|
446
361
|
const api = {
|
|
@@ -466,11 +381,8 @@ function wrapVariant(handle) {
|
|
|
466
381
|
});
|
|
467
382
|
}
|
|
468
383
|
|
|
469
|
-
// Deep-unwrap a pack value
|
|
470
|
-
//
|
|
471
|
-
// the native `v` case reads a raw boxed handle, not an L1 Proxy. Primitives,
|
|
472
|
-
// byte arrays, plain arrays and dict objects are walked; HANDLE-carrying
|
|
473
|
-
// wrappers collapse to their handle.
|
|
384
|
+
// Deep-unwrap a pack value: the native `v` case reads a raw boxed handle, not an L1
|
|
385
|
+
// Proxy, so a nested GLib.Variant wrapper must collapse to its handle before packing.
|
|
474
386
|
function unwrapVariantValue(value) {
|
|
475
387
|
if (value === null || typeof value !== 'object') return value;
|
|
476
388
|
if (value[HANDLE] !== undefined) return value[HANDLE];
|
|
@@ -485,8 +397,6 @@ function packVariant(signature, value) {
|
|
|
485
397
|
return wrapVariant(native.variantNew(signature, unwrapVariantValue(value)));
|
|
486
398
|
}
|
|
487
399
|
|
|
488
|
-
// `GLib.Variant` as a class-like object: `new GLib.Variant(sig, value)` and the
|
|
489
|
-
// deprecated `GLib.Variant.new(sig, value)` both pack via the native engine.
|
|
490
400
|
function makeVariantClass() {
|
|
491
401
|
const ctor = function Variant(signature, value) {
|
|
492
402
|
return packVariant(signature, value);
|
|
@@ -494,10 +404,10 @@ function makeVariantClass() {
|
|
|
494
404
|
Object.defineProperty(ctor, 'name', { value: 'Variant', configurable: true });
|
|
495
405
|
ctor.$gtypeName = 'GLib.Variant';
|
|
496
406
|
ctor.new = (signature, value) => packVariant(signature, value);
|
|
497
|
-
//
|
|
498
|
-
//
|
|
499
|
-
//
|
|
500
|
-
//
|
|
407
|
+
// A wrapped Variant is a Proxy over a bare `{[HANDLE]}` target with no class
|
|
408
|
+
// prototype, so the default `value instanceof GLib.Variant` is always false —
|
|
409
|
+
// recognise any wrapper whose [HANDLE] is a native GVariant boxed handle instead.
|
|
410
|
+
// Real GAction/GSettings/GLib.log_structured code branches on it.
|
|
501
411
|
Object.defineProperty(ctor, Symbol.hasInstance, {
|
|
502
412
|
value(instance) {
|
|
503
413
|
if (instance === null || typeof instance !== 'object') return false;
|
|
@@ -521,21 +431,14 @@ function makeVariantClass() {
|
|
|
521
431
|
|
|
522
432
|
const variantClass = makeVariantClass();
|
|
523
433
|
|
|
524
|
-
//
|
|
525
|
-
//
|
|
526
|
-
//
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
//
|
|
531
|
-
//
|
|
532
|
-
// native.newGValue (a zeroed G_TYPE_VALUE boxed handle) and the method surface then
|
|
533
|
-
// routes through wrapBoxed's boxed-method dispatch (verified: init/set_int/get_int
|
|
534
|
-
// resolve as GValue methods).
|
|
535
|
-
|
|
536
|
-
// Fundamental GType name → GValue setter, for the `new GObject.Value(gtype, value)`
|
|
537
|
-
// convenience. Non-primitive types (enum/flags/boxed/object/param/variant) are
|
|
538
|
-
// matched by g_type_is_a below, exactly like gjs's default switch branch.
|
|
434
|
+
// GObject.Value ergonomics, mirroring GJS's GObject override: `new GObject.Value()` and
|
|
435
|
+
// the convenience `new GObject.Value(gtype, value)`. There is no g_value_new(), so
|
|
436
|
+
// construction goes through native.newGValue (a zeroed G_TYPE_VALUE boxed handle) and
|
|
437
|
+
// the .init/.set_*/.get_*/.copy/.reset/.unset surface routes through wrapBoxed's
|
|
438
|
+
// boxed-method dispatch.
|
|
439
|
+
|
|
440
|
+
// Fundamental GType name → GValue setter. Non-primitive types (enum/flags/boxed/object/
|
|
441
|
+
// param/variant) are matched by g_type_is_a below, like gjs's default switch branch.
|
|
539
442
|
const GVALUE_PRIMITIVE_SETTERS = {
|
|
540
443
|
gboolean: 'set_boolean',
|
|
541
444
|
gchar: 'set_schar',
|
|
@@ -554,9 +457,8 @@ const GVALUE_PRIMITIVE_SETTERS = {
|
|
|
554
457
|
GParam: 'set_param',
|
|
555
458
|
};
|
|
556
459
|
|
|
557
|
-
//
|
|
558
|
-
//
|
|
559
|
-
// (introspected GObject.type_name / type_is_a — both verified on node-gi).
|
|
460
|
+
// Mirrors gjs's constructor switch: an exact fundamental → its setter, else g_type_is_a
|
|
461
|
+
// → flags/enum/boxed/…
|
|
560
462
|
function gvalueSetterFor(gt) {
|
|
561
463
|
const G = requireGi('GObject', '2.0');
|
|
562
464
|
const prim = GVALUE_PRIMITIVE_SETTERS[G.type_name(gt)];
|
|
@@ -570,17 +472,13 @@ function gvalueSetterFor(gt) {
|
|
|
570
472
|
throw new TypeError(`Invalid type argument '${G.type_name(gt)}' to the GObject.Value constructor`);
|
|
571
473
|
}
|
|
572
474
|
|
|
573
|
-
// Build the empty/convenience GValue: a zeroed GValue boxed handle wrapped with the
|
|
574
|
-
// GValue struct methods; the 2-arg form inits it to `gtype` (a class ctor's $gtype
|
|
575
|
-
// or a GType handle) and sets `value` via the matching setter.
|
|
576
475
|
function buildValue(args) {
|
|
577
476
|
const v = wrapBoxed(native.newGValue());
|
|
578
477
|
if (args.length >= 2) {
|
|
579
478
|
const gt = resolveGTypeArg(args[0]);
|
|
580
479
|
// Resolve the setter BEFORE init so an unsupported type throws a clean JS
|
|
581
|
-
// TypeError
|
|
582
|
-
// GLib-CRITICAL that
|
|
583
|
-
// result as gjs's constructor, minus the noisy failed init.
|
|
480
|
+
// TypeError instead of driving g_value_init with a value-table-less type — a
|
|
481
|
+
// GLib-CRITICAL that aborts under G_DEBUG=fatal-criticals.
|
|
584
482
|
const setter = gvalueSetterFor(gt);
|
|
585
483
|
v.init(gt);
|
|
586
484
|
v[setter](args[1]);
|
|
@@ -588,10 +486,8 @@ function buildValue(args) {
|
|
|
588
486
|
return v;
|
|
589
487
|
}
|
|
590
488
|
|
|
591
|
-
// `GObject.Value`
|
|
592
|
-
//
|
|
593
|
-
// (type_compatible / type_transformable + camelCase) route through the engine, and
|
|
594
|
-
// `value instanceof GObject.Value` recognises any wrapper over a GValue boxed handle.
|
|
489
|
+
// `instanceof GObject.Value` recognises any wrapper over a GValue boxed handle (same
|
|
490
|
+
// prototype-less-Proxy reason as GLib.Variant above).
|
|
595
491
|
function makeValueClass() {
|
|
596
492
|
const ctor = function Value(...args) {
|
|
597
493
|
return buildValue(args);
|