@gjsify/node-gi 0.64.1 → 0.64.2
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/cairo.d.ts +38 -0
- package/cairo.js +95 -17
- package/gettext.js +29 -7
- package/gi.js +74 -21
- package/host.nativescript.js +6 -0
- package/host.node.js +6 -0
- package/index.js +192 -76
- package/package.json +1 -1
- package/prebuilds/darwin-arm64/node_gi.node +0 -0
- package/prebuilds/darwin-x64/node_gi.node +0 -0
- package/prebuilds/linux-arm64/node_gi.node +0 -0
- package/prebuilds/linux-x64/node_gi.node +0 -0
- package/prebuilds/win32-arm64/node_gi.node +0 -0
- package/prebuilds/win32-x64/node_gi.node +0 -0
- package/src/cairo.cc +96 -0
- package/src/class.cc +15 -0
- package/src/common.h +21 -3
- package/src/marshal.cc +39 -17
package/cairo.d.ts
CHANGED
|
@@ -36,6 +36,16 @@ export interface FontExtents {
|
|
|
36
36
|
maxYAdvance: number;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/** `cairo_matrix_t` as the plain object GJS passes around (no matrix wrapper). */
|
|
40
|
+
export interface Matrix {
|
|
41
|
+
xx: number;
|
|
42
|
+
yx: number;
|
|
43
|
+
xy: number;
|
|
44
|
+
yy: number;
|
|
45
|
+
x0: number;
|
|
46
|
+
y0: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
39
49
|
/** cairo drawing context (`cairo_t`). */
|
|
40
50
|
export class Context {
|
|
41
51
|
constructor(surface: Surface);
|
|
@@ -53,10 +63,17 @@ export class Context {
|
|
|
53
63
|
textPath(utf8: string): void;
|
|
54
64
|
textExtents(utf8: string): TextExtents;
|
|
55
65
|
fontExtents(): FontExtents;
|
|
66
|
+
/**
|
|
67
|
+
* The current font matrix. GJS has no cairo matrix wrapper
|
|
68
|
+
* (`getMatrix`/`setMatrix` stay unbound), so it travels as a plain object.
|
|
69
|
+
*/
|
|
70
|
+
getFontMatrix(): Matrix;
|
|
71
|
+
setFontMatrix(matrix: Matrix): void;
|
|
56
72
|
rectangle(x: number, y: number, width: number, height: number): void;
|
|
57
73
|
arc(xc: number, yc: number, radius: number, angle1: number, angle2: number): void;
|
|
58
74
|
arcNegative(xc: number, yc: number, radius: number, angle1: number, angle2: number): void;
|
|
59
75
|
curveTo(x1: number, y1: number, x2: number, y2: number, x3: number, y3: number): void;
|
|
76
|
+
relCurveTo(dx1: number, dy1: number, dx2: number, dy2: number, dx3: number, dy3: number): void;
|
|
60
77
|
fill(): void;
|
|
61
78
|
fillPreserve(): void;
|
|
62
79
|
stroke(): void;
|
|
@@ -65,6 +82,19 @@ export class Context {
|
|
|
65
82
|
paintWithAlpha(alpha: number): void;
|
|
66
83
|
clip(): void;
|
|
67
84
|
clipPreserve(): void;
|
|
85
|
+
resetClip(): void;
|
|
86
|
+
/** Redirect drawing to an intermediate group surface. */
|
|
87
|
+
pushGroup(): void;
|
|
88
|
+
/** Like {@link pushGroup} but with an explicit {@link Content} for the group surface. */
|
|
89
|
+
pushGroupWithContent(content: number): void;
|
|
90
|
+
/** End the current group and return it as a pattern (concrete subclass). */
|
|
91
|
+
popGroup(): Pattern;
|
|
92
|
+
/** End the current group and install it as this context's source. */
|
|
93
|
+
popGroupToSource(): void;
|
|
94
|
+
/** Paint the current source through `pattern`'s alpha. */
|
|
95
|
+
mask(pattern: Pattern): void;
|
|
96
|
+
/** Paint the current source through `surface`'s alpha, offset to `(x, y)`. */
|
|
97
|
+
maskSurface(surface: Surface, x: number, y: number): void;
|
|
68
98
|
translate(tx: number, ty: number): void;
|
|
69
99
|
scale(sx: number, sy: number): void;
|
|
70
100
|
rotate(angle: number): void;
|
|
@@ -74,6 +104,10 @@ export class Context {
|
|
|
74
104
|
setSourceSurface(surface: Surface, x: number, y: number): void;
|
|
75
105
|
/** The context's current source pattern (own reference; concrete subclass). */
|
|
76
106
|
getSource(): Pattern;
|
|
107
|
+
/** The surface this context draws to (own reference; concrete subclass). */
|
|
108
|
+
getTarget(): Surface;
|
|
109
|
+
/** The target of the current group, or {@link getTarget} outside a group. */
|
|
110
|
+
getGroupTarget(): Surface;
|
|
77
111
|
identityMatrix(): void;
|
|
78
112
|
newSubPath(): void;
|
|
79
113
|
/** User → device coordinates as `[x, y]` (GJS return shape). */
|
|
@@ -111,6 +145,10 @@ export class Context {
|
|
|
111
145
|
setOperator(op: number): void;
|
|
112
146
|
setMiterLimit(limit: number): void;
|
|
113
147
|
setTolerance(tolerance: number): void;
|
|
148
|
+
/** Emit the current page and clear it (paginated surfaces; a no-op elsewhere). */
|
|
149
|
+
copyPage(): void;
|
|
150
|
+
/** Like {@link copyPage} but also clears the current page's contents. */
|
|
151
|
+
showPage(): void;
|
|
114
152
|
status(): number;
|
|
115
153
|
$dispose(): void;
|
|
116
154
|
}
|
package/cairo.js
CHANGED
|
@@ -23,16 +23,25 @@
|
|
|
23
23
|
// (the `Object.assign({}, imports._cairo, cairoNative)` merge shape).
|
|
24
24
|
// Copyright (c) 2010 litl, LLC / GJS contributors. MIT OR LGPL-2.0-or-later.
|
|
25
25
|
|
|
26
|
-
import native from './index.js';
|
|
26
|
+
import native, { ensureNative, onNativeLoaded } from './index.js';
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
// Bound when the addon loads: at import on desktop, on first use on a lazy host (NativeScript).
|
|
29
|
+
// Every call that can run before any cairo object exists goes through `engine()`.
|
|
30
|
+
let c;
|
|
31
|
+
|
|
32
|
+
function missingBinding() {
|
|
33
|
+
return new Error(
|
|
31
34
|
'@gjsify/node-gi/cairo: the native cairo binding is missing from the addon. ' +
|
|
32
35
|
'Rebuild @gjsify/node-gi (node-gyp rebuild) — the addon must link libcairo.',
|
|
33
36
|
);
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
function engine() {
|
|
40
|
+
ensureNative();
|
|
41
|
+
if (c === undefined) throw missingBinding();
|
|
42
|
+
return c;
|
|
43
|
+
}
|
|
44
|
+
|
|
36
45
|
// ---- enums (ported verbatim from refs/gjs/modules/core/_cairo.js) -----------
|
|
37
46
|
|
|
38
47
|
export const Antialias = { DEFAULT: 0, NONE: 1, GRAY: 2, SUBPIXEL: 3 };
|
|
@@ -141,7 +150,7 @@ function handleOf(x, what) {
|
|
|
141
150
|
/** cairo drawing context. `new cairo.Context(surface)` wraps `cairo_create`. */
|
|
142
151
|
export class Context {
|
|
143
152
|
constructor(surface) {
|
|
144
|
-
setHandle(this,
|
|
153
|
+
setHandle(this, engine().contextCreate(handleOf(surface, 'cairo.Surface')));
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
save() {
|
|
@@ -181,6 +190,9 @@ export class Context {
|
|
|
181
190
|
curveTo(x1, y1, x2, y2, x3, y3) {
|
|
182
191
|
c.curveTo(this[H], x1, y1, x2, y2, x3, y3);
|
|
183
192
|
}
|
|
193
|
+
relCurveTo(dx1, dy1, dx2, dy2, dx3, dy3) {
|
|
194
|
+
c.relCurveTo(this[H], dx1, dy1, dx2, dy2, dx3, dy3);
|
|
195
|
+
}
|
|
184
196
|
|
|
185
197
|
fill() {
|
|
186
198
|
c.fill(this[H]);
|
|
@@ -206,6 +218,33 @@ export class Context {
|
|
|
206
218
|
clipPreserve() {
|
|
207
219
|
c.clipPreserve(this[H]);
|
|
208
220
|
}
|
|
221
|
+
resetClip() {
|
|
222
|
+
c.resetClip(this[H]);
|
|
223
|
+
}
|
|
224
|
+
/** Redirect drawing to an intermediate group surface. */
|
|
225
|
+
pushGroup() {
|
|
226
|
+
c.pushGroup(this[H]);
|
|
227
|
+
}
|
|
228
|
+
/** Like {@link pushGroup} but with an explicit {@link Content} for the group surface. */
|
|
229
|
+
pushGroupWithContent(content) {
|
|
230
|
+
c.pushGroupWithContent(this[H], content);
|
|
231
|
+
}
|
|
232
|
+
/** End the current group and return it as a pattern (concrete subclass). */
|
|
233
|
+
popGroup() {
|
|
234
|
+
return wrapPatternHandle(c.popGroup(this[H]));
|
|
235
|
+
}
|
|
236
|
+
/** End the current group and install it as this context's source. */
|
|
237
|
+
popGroupToSource() {
|
|
238
|
+
c.popGroupToSource(this[H]);
|
|
239
|
+
}
|
|
240
|
+
/** Paint the current source through `pattern`'s alpha. */
|
|
241
|
+
mask(pattern) {
|
|
242
|
+
c.mask(this[H], handleOf(pattern, 'cairo.Pattern'));
|
|
243
|
+
}
|
|
244
|
+
/** Paint the current source through `surface`'s alpha, offset to `(x, y)`. */
|
|
245
|
+
maskSurface(surface, x, y) {
|
|
246
|
+
c.maskSurface(this[H], handleOf(surface, 'cairo.Surface'), x, y);
|
|
247
|
+
}
|
|
209
248
|
|
|
210
249
|
translate(tx, ty) {
|
|
211
250
|
c.translate(this[H], tx, ty);
|
|
@@ -289,6 +328,14 @@ export class Context {
|
|
|
289
328
|
getSource() {
|
|
290
329
|
return wrapPatternHandle(c.getSource(this[H]));
|
|
291
330
|
}
|
|
331
|
+
/** The surface this context draws to (a ref of its own — safe to keep). */
|
|
332
|
+
getTarget() {
|
|
333
|
+
return wrapSurfaceHandle(c.getTarget(this[H]));
|
|
334
|
+
}
|
|
335
|
+
/** The target of the current group, or {@link getTarget} outside a group. */
|
|
336
|
+
getGroupTarget() {
|
|
337
|
+
return wrapSurfaceHandle(c.getGroupTarget(this[H]));
|
|
338
|
+
}
|
|
292
339
|
|
|
293
340
|
setLineWidth(width) {
|
|
294
341
|
c.setLineWidth(this[H], width);
|
|
@@ -355,6 +402,18 @@ export class Context {
|
|
|
355
402
|
fontExtents() {
|
|
356
403
|
return c.fontExtents(this[H]);
|
|
357
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* The current font matrix as `{ xx, yx, xy, yy, x0, y0}`. GJS has no cairo
|
|
407
|
+
* matrix wrapper (`getMatrix`/`setMatrix` stay unbound), so it travels as
|
|
408
|
+
* a plain object of the six doubles.
|
|
409
|
+
*/
|
|
410
|
+
getFontMatrix() {
|
|
411
|
+
return c.getFontMatrix(this[H]);
|
|
412
|
+
}
|
|
413
|
+
/** Set the font matrix from `{ xx, yx, xy, yy, x0, y0}`. */
|
|
414
|
+
setFontMatrix(matrix) {
|
|
415
|
+
c.setFontMatrix(this[H], matrix);
|
|
416
|
+
}
|
|
358
417
|
/** Stroke bounding box as `[x1, y1, x2, y2]`. */
|
|
359
418
|
strokeExtents() {
|
|
360
419
|
return c.strokeExtents(this[H]);
|
|
@@ -385,6 +444,15 @@ export class Context {
|
|
|
385
444
|
c.setTolerance(this[H], tolerance);
|
|
386
445
|
}
|
|
387
446
|
|
|
447
|
+
/** Emit the current page and clear it (paginated surfaces; a no-op elsewhere). */
|
|
448
|
+
copyPage() {
|
|
449
|
+
c.copyPage(this[H]);
|
|
450
|
+
}
|
|
451
|
+
/** Like {@link copyPage} but also clears the current page's contents. */
|
|
452
|
+
showPage() {
|
|
453
|
+
c.showPage(this[H]);
|
|
454
|
+
}
|
|
455
|
+
|
|
388
456
|
status() {
|
|
389
457
|
return c.status(this[H]);
|
|
390
458
|
}
|
|
@@ -422,14 +490,14 @@ export class Surface {
|
|
|
422
490
|
export class ImageSurface extends Surface {
|
|
423
491
|
constructor(format, width, height) {
|
|
424
492
|
super();
|
|
425
|
-
setHandle(this,
|
|
493
|
+
setHandle(this, engine().imageSurfaceCreate(format, width, height));
|
|
426
494
|
}
|
|
427
495
|
|
|
428
496
|
static create(format, width, height) {
|
|
429
497
|
return new ImageSurface(format, width, height);
|
|
430
498
|
}
|
|
431
499
|
static createFromPNG(filename) {
|
|
432
|
-
return wrapRaw(ImageSurface,
|
|
500
|
+
return wrapRaw(ImageSurface, engine().imageSurfaceCreateFromPNG(filename));
|
|
433
501
|
}
|
|
434
502
|
|
|
435
503
|
getWidth() {
|
|
@@ -471,10 +539,10 @@ export class Pattern {
|
|
|
471
539
|
/** A solid-color pattern (`cairo_pattern_create_rgb[a]`). */
|
|
472
540
|
export class SolidPattern extends Pattern {
|
|
473
541
|
static createRGB(red, green, blue) {
|
|
474
|
-
return wrapRaw(SolidPattern,
|
|
542
|
+
return wrapRaw(SolidPattern, engine().solidPatternCreateRGB(red, green, blue));
|
|
475
543
|
}
|
|
476
544
|
static createRGBA(red, green, blue, alpha) {
|
|
477
|
-
return wrapRaw(SolidPattern,
|
|
545
|
+
return wrapRaw(SolidPattern, engine().solidPatternCreateRGBA(red, green, blue, alpha));
|
|
478
546
|
}
|
|
479
547
|
}
|
|
480
548
|
|
|
@@ -492,7 +560,7 @@ export class Gradient extends Pattern {
|
|
|
492
560
|
export class LinearGradient extends Gradient {
|
|
493
561
|
constructor(x0, y0, x1, y1) {
|
|
494
562
|
super();
|
|
495
|
-
setHandle(this,
|
|
563
|
+
setHandle(this, engine().linearGradientCreate(x0, y0, x1, y1));
|
|
496
564
|
}
|
|
497
565
|
}
|
|
498
566
|
|
|
@@ -500,7 +568,7 @@ export class LinearGradient extends Gradient {
|
|
|
500
568
|
export class RadialGradient extends Gradient {
|
|
501
569
|
constructor(cx0, cy0, radius0, cx1, cy1, radius1) {
|
|
502
570
|
super();
|
|
503
|
-
setHandle(this,
|
|
571
|
+
setHandle(this, engine().radialGradientCreate(cx0, cy0, radius0, cx1, cy1, radius1));
|
|
504
572
|
}
|
|
505
573
|
}
|
|
506
574
|
|
|
@@ -508,7 +576,7 @@ export class RadialGradient extends Gradient {
|
|
|
508
576
|
export class SurfacePattern extends Pattern {
|
|
509
577
|
constructor(surface) {
|
|
510
578
|
super();
|
|
511
|
-
setHandle(this,
|
|
579
|
+
setHandle(this, engine().surfacePatternCreate(handleOf(surface, 'cairo.Surface')));
|
|
512
580
|
}
|
|
513
581
|
|
|
514
582
|
setExtend(extend) {
|
|
@@ -532,6 +600,12 @@ export class SurfacePattern extends Pattern {
|
|
|
532
600
|
*/
|
|
533
601
|
export class Path {}
|
|
534
602
|
|
|
603
|
+
// Fan a surface handle out to the concrete subclass by cairo runtime type —
|
|
604
|
+
// mirrors CairoSurface::from_c_ptr (only the image surface has a subclass here).
|
|
605
|
+
function wrapSurfaceHandle(handle) {
|
|
606
|
+
return wrapRaw(c.surfaceGetType(handle) === SurfaceType.IMAGE ? ImageSurface : Surface, handle);
|
|
607
|
+
}
|
|
608
|
+
|
|
535
609
|
// Fan a pattern handle out to the concrete subclass by cairo runtime type —
|
|
536
610
|
// mirrors gjs_cairo_pattern_from_pattern (MESH/RASTER_SOURCE fall back to the
|
|
537
611
|
// base Pattern; gjs throws for those, but they are unreachable from this module).
|
|
@@ -554,11 +628,15 @@ function wrapPatternHandle(handle) {
|
|
|
554
628
|
// a returned/callback cairo pointer into the right JS instance. Surface + Pattern
|
|
555
629
|
// fan out to the concrete subclass by cairo runtime type (mirrors
|
|
556
630
|
// CairoSurface::from_c_ptr / gjs_cairo_pattern_from_pattern).
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
631
|
+
onNativeLoaded(() => {
|
|
632
|
+
if (native.__cairo === undefined) throw missingBinding();
|
|
633
|
+
c = native.__cairo;
|
|
634
|
+
c.setup({
|
|
635
|
+
context: (handle) => wrapRaw(Context, handle),
|
|
636
|
+
surface: (handle) => wrapSurfaceHandle(handle),
|
|
637
|
+
pattern: (handle) => wrapPatternHandle(handle),
|
|
638
|
+
path: (handle) => wrapRaw(Path, handle),
|
|
639
|
+
});
|
|
562
640
|
});
|
|
563
641
|
|
|
564
642
|
// The default export mirrors GJS's ESM `cairo` object (enums + constructors), the
|
package/gettext.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
// default export. The gjsify `--app node` build aliases the bare `gettext`
|
|
24
24
|
// specifier to this module (kept external — `ALIASES_GJS_FOR_NODE`).
|
|
25
25
|
import * as native from './index.js';
|
|
26
|
+
import { ensureNative } from './index.js';
|
|
26
27
|
import { requireGi } from './gi.js';
|
|
27
28
|
|
|
28
29
|
// GLib is resolved on first lookup, not at import: loading a namespace walks the
|
|
@@ -48,7 +49,10 @@ function GLib() {
|
|
|
48
49
|
// lookups still work (they go through GLib, which every addon version has), and
|
|
49
50
|
// the binders are the no-ops they used to be. It is announced once, because a
|
|
50
51
|
// silently untranslated UI is the defect this file exists to close.
|
|
51
|
-
|
|
52
|
+
function haveLocaleBinders() {
|
|
53
|
+
ensureNative();
|
|
54
|
+
return typeof native.localeCategories === 'function';
|
|
55
|
+
}
|
|
52
56
|
|
|
53
57
|
let announcedStaleAddon = false;
|
|
54
58
|
function noLocaleBinders(fn) {
|
|
@@ -78,6 +82,18 @@ const FALLBACK_CATEGORIES = {
|
|
|
78
82
|
win32: { ALL: 0, COLLATE: 1, CTYPE: 2, MONETARY: 3, NUMERIC: 4, TIME: 5, MESSAGES: 1729 },
|
|
79
83
|
};
|
|
80
84
|
|
|
85
|
+
// Resolved on first read, not at import, so a lazy host (NativeScript) that only imports this
|
|
86
|
+
// module has not loaded the addon yet; a failed load throws here, there is no fallback table.
|
|
87
|
+
let categories = null;
|
|
88
|
+
function localeCategories() {
|
|
89
|
+
if (categories === null) {
|
|
90
|
+
categories = haveLocaleBinders()
|
|
91
|
+
? native.localeCategories()
|
|
92
|
+
: (FALLBACK_CATEGORIES[globalThis.process?.platform] ?? FALLBACK_CATEGORIES.linux);
|
|
93
|
+
}
|
|
94
|
+
return categories;
|
|
95
|
+
}
|
|
96
|
+
|
|
81
97
|
/** Look up `msgid` in the default domain (the one `textdomain()` last named). */
|
|
82
98
|
export function gettext(msgid) {
|
|
83
99
|
return GLib().dgettext(null, msgid);
|
|
@@ -145,13 +161,13 @@ export function domain(domainName) {
|
|
|
145
161
|
* @returns {string|null} the prior locale name, or null if it could not be set
|
|
146
162
|
*/
|
|
147
163
|
export function setlocale(category, locale) {
|
|
148
|
-
if (!haveLocaleBinders) return noLocaleBinders('setlocale');
|
|
164
|
+
if (!haveLocaleBinders()) return noLocaleBinders('setlocale');
|
|
149
165
|
return native.setThreadLocale(category, locale ?? null);
|
|
150
166
|
}
|
|
151
167
|
|
|
152
168
|
/** Set the default text domain, or report it when `domainName` is null. */
|
|
153
169
|
export function textdomain(domainName) {
|
|
154
|
-
if (!haveLocaleBinders) return noLocaleBinders('textdomain');
|
|
170
|
+
if (!haveLocaleBinders()) return noLocaleBinders('textdomain');
|
|
155
171
|
return native.textdomain(domainName ?? null);
|
|
156
172
|
}
|
|
157
173
|
|
|
@@ -162,7 +178,7 @@ export function textdomain(domainName) {
|
|
|
162
178
|
* `gjs_bindtextdomain` does — see the note in private.cc.
|
|
163
179
|
*/
|
|
164
180
|
export function bindtextdomain(domainName, dirName) {
|
|
165
|
-
if (!haveLocaleBinders) return noLocaleBinders('bindtextdomain');
|
|
181
|
+
if (!haveLocaleBinders()) return noLocaleBinders('bindtextdomain');
|
|
166
182
|
return native.bindtextdomain(domainName, dirName ?? null);
|
|
167
183
|
}
|
|
168
184
|
|
|
@@ -188,9 +204,15 @@ export function bindtextdomainCodeset(_domainName, _codeset) {
|
|
|
188
204
|
* claiming to address LC_MESSAGES — on two of the three platforms the translation
|
|
189
205
|
* blocker was reported from.
|
|
190
206
|
*/
|
|
191
|
-
export const LocaleCategory =
|
|
192
|
-
|
|
193
|
-
|
|
207
|
+
export const LocaleCategory = Object.defineProperties(
|
|
208
|
+
{},
|
|
209
|
+
Object.fromEntries(
|
|
210
|
+
Object.keys(FALLBACK_CATEGORIES.linux).map((name) => [
|
|
211
|
+
name,
|
|
212
|
+
{ enumerable: true, get: () => localeCategories()[name] },
|
|
213
|
+
]),
|
|
214
|
+
),
|
|
215
|
+
);
|
|
194
216
|
|
|
195
217
|
/**
|
|
196
218
|
* The GJS `Gettext` module as a default export — the object shape
|
package/gi.js
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
// Reference: gjs/modules/esm/gi.js for the require shape; node-gtk (romgrk, MIT) for the
|
|
8
8
|
// binding lineage. Hand-authored JS — the package ships no build step.
|
|
9
9
|
import * as native from './index.js';
|
|
10
|
+
import { ensureNative, onNativeLoaded } from './index.js';
|
|
10
11
|
// Timers, `process` and the windowing env record come from the host seam (host.node.js
|
|
11
12
|
// explains why they are the runtime's own, not the globals; host.nativescript.js is the
|
|
12
13
|
// NativeScript Android variant).
|
|
@@ -360,17 +361,19 @@ function makeErrorDomainClass(namespace, typeName, members, quark) {
|
|
|
360
361
|
// Called by the engine on a failed GI invoke, with ownership of the GError* already
|
|
361
362
|
// transferred to the boxed handle. Named, so the position properties report the
|
|
362
363
|
// application frame rather than this one.
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
364
|
+
onNativeLoaded(() =>
|
|
365
|
+
native.setErrorBuilder(function buildThrownGError(handle, quark, code, message) {
|
|
366
|
+
// Same cross-version pairing: an addon older than this JS calls the builder with
|
|
367
|
+
// the GError's FIELDS (domainName, quark, code, message) rather than a handle, and
|
|
368
|
+
// there is no second `errorClass` argument to give it. Rebuilding the GError from
|
|
369
|
+
// the fields keeps the ONE shape on that pairing too — it costs only the original
|
|
370
|
+
// GError's address, which nothing can read.
|
|
371
|
+
if (typeof handle === 'string') {
|
|
372
|
+
return wrapGError(newErrorLiteral(quark, code, message), true, buildThrownGError);
|
|
373
|
+
}
|
|
374
|
+
return wrapGError(handle, true, buildThrownGError);
|
|
375
|
+
}),
|
|
376
|
+
);
|
|
374
377
|
|
|
375
378
|
// Stamps a makeClass prototype with its { namespace, typeName } so Gio._promisify can
|
|
376
379
|
// record WHICH introspected class a registration belongs to — matched against the
|
|
@@ -443,7 +446,7 @@ function resolveTemplateCallback(handle, handlerName) {
|
|
|
443
446
|
return desc.value.apply(proxy, args.map(wrapReturn));
|
|
444
447
|
};
|
|
445
448
|
}
|
|
446
|
-
native.setTemplateCallbackResolver(resolveTemplateCallback);
|
|
449
|
+
onNativeLoaded(() => native.setTemplateCallbackResolver(resolveTemplateCallback));
|
|
447
450
|
|
|
448
451
|
// Run a registered class's JS constructor for a GObject the ENGINE instantiated from C
|
|
449
452
|
// — typically a GtkBuilder composite-template InternalChild, which has no JS-`new` to
|
|
@@ -466,7 +469,7 @@ function runCtorForCObject(handle, gtypeName) {
|
|
|
466
469
|
adoptHandle = prev;
|
|
467
470
|
}
|
|
468
471
|
}
|
|
469
|
-
native.setConstructCallback(runCtorForCObject);
|
|
472
|
+
onNativeLoaded(() => native.setConstructCallback(runCtorForCObject));
|
|
470
473
|
|
|
471
474
|
// A non-GObject GObject-fundamental (GskRenderNode, GdkEvent) as an OPAQUE,
|
|
472
475
|
// round-trippable handle: the native tagged External already carries the pointer and
|
|
@@ -927,13 +930,50 @@ function decorateGioNamespace(baseNs) {
|
|
|
927
930
|
});
|
|
928
931
|
}
|
|
929
932
|
|
|
933
|
+
// The internals of the PROXY links on an introspected prototype chain: the Proxy
|
|
934
|
+
// itself -> its target plus the materializer its getOwnPropertyDescriptor trap runs
|
|
935
|
+
// (`null` for a Proxy that traps no descriptor read). Registered by
|
|
936
|
+
// makeClassPrototype and vfuncChainProto; read only by findProtoDescriptor.
|
|
937
|
+
//
|
|
938
|
+
// Why: a descriptor read THROUGH a Proxy costs ~110 ns of V8 invariant machinery on
|
|
939
|
+
// top of the handler, and an introspected chain is mostly Proxies. Measured on the
|
|
940
|
+
// bench's depth-3 subject, a full MISS walk took 328 ns — 154 ns on the
|
|
941
|
+
// makeClassPrototype link, 65 ns on vfuncChainProto, 12 ns on the one plain link.
|
|
942
|
+
// With the target in hand the same answer costs 27 ns, because the trap body is
|
|
943
|
+
// exactly `materialize(prop); return Reflect.getOwnPropertyDescriptor(target, prop)`
|
|
944
|
+
// and running it directly skips only the machinery, never a step of the computation.
|
|
945
|
+
//
|
|
946
|
+
// This is NOT a cache of the ANSWER: nothing is remembered between calls. The target
|
|
947
|
+
// is the object every mutation of the prototype lands on — none of the handlers trap
|
|
948
|
+
// `set`, `defineProperty`, `deleteProperty` or `setPrototypeOf`, so
|
|
949
|
+
// `Cls.prototype.m = f`, `Object.defineProperty(…)`, `delete` and a spliced chain
|
|
950
|
+
// write through to it — and the descriptor still comes from a fresh read of it. That
|
|
951
|
+
// has to hold, because GJS pins nothing: on gjs 1.88.1 every one of those mutations
|
|
952
|
+
// is observable on an already-wrapped instance (measured), so an answer cache would
|
|
953
|
+
// need an invalidation signal JS does not offer. test/proto-descriptor-liveness.test.mjs
|
|
954
|
+
// is the proof obligation.
|
|
955
|
+
const protoInternals = new WeakMap();
|
|
956
|
+
|
|
930
957
|
// Walk a JS prototype chain (excluding Object.prototype) for an own property
|
|
931
958
|
// descriptor of `prop` — used to surface a registerClass subclass's own methods
|
|
932
|
-
// /getters/setters on the GObject wrapper.
|
|
959
|
+
// /getters/setters on the GObject wrapper. One full walk runs per property GET, per
|
|
960
|
+
// SET and per `emit`, so the Proxy bypass above is on the hottest path there is.
|
|
933
961
|
function findProtoDescriptor(proto, prop) {
|
|
934
|
-
for (let p = proto; p !== null && p !== Object.prototype;
|
|
935
|
-
const
|
|
962
|
+
for (let p = proto; p !== null && p !== Object.prototype;) {
|
|
963
|
+
const internals = protoInternals.get(p);
|
|
964
|
+
if (internals === undefined) {
|
|
965
|
+
const d = Object.getOwnPropertyDescriptor(p, prop);
|
|
966
|
+
if (d !== undefined) return d;
|
|
967
|
+
p = Object.getPrototypeOf(p);
|
|
968
|
+
continue;
|
|
969
|
+
}
|
|
970
|
+
const { target, materialize } = internals;
|
|
971
|
+
if (materialize !== null) materialize(prop);
|
|
972
|
+
const d = Object.getOwnPropertyDescriptor(target, prop);
|
|
936
973
|
if (d !== undefined) return d;
|
|
974
|
+
// The target's prototype, not the Proxy's: neither handler traps
|
|
975
|
+
// getPrototypeOf, so these are the same link — one fewer trap to pay.
|
|
976
|
+
p = Object.getPrototypeOf(target);
|
|
937
977
|
}
|
|
938
978
|
return undefined;
|
|
939
979
|
}
|
|
@@ -1131,9 +1171,9 @@ function runJsPropertySetter(handle, propertyName) {
|
|
|
1131
1171
|
// function` until the next release republishes the binary. Every native call added
|
|
1132
1172
|
// here degrades instead; without the callback, a post-construction set simply does
|
|
1133
1173
|
// not reach the JS setter, which is what 0.50.0 did.
|
|
1134
|
-
|
|
1135
|
-
native.setPropertySetCallback(runJsPropertySetter);
|
|
1136
|
-
}
|
|
1174
|
+
onNativeLoaded(() => {
|
|
1175
|
+
if (typeof native.setPropertySetCallback === 'function') native.setPropertySetCallback(runJsPropertySetter);
|
|
1176
|
+
});
|
|
1137
1177
|
|
|
1138
1178
|
// Which properties the construct-time replay must run.
|
|
1139
1179
|
//
|
|
@@ -1342,6 +1382,7 @@ export function startMainContextPump() {
|
|
|
1342
1382
|
// GLib's default main context — a JS-timer pump would only iterate the
|
|
1343
1383
|
// context recursively from within its own dispatch.
|
|
1344
1384
|
if (hostPumpsItself()) return () => {};
|
|
1385
|
+
ensureNative();
|
|
1345
1386
|
pumpRefCount++;
|
|
1346
1387
|
if (pumpTimer === null) {
|
|
1347
1388
|
if (pendingView === null) pendingView = native.makePumpPendingCount();
|
|
@@ -1812,7 +1853,8 @@ function wrapInstance(handle, userProto) {
|
|
|
1812
1853
|
// the introspected parent's `vfunc_x` is a thunk into the actual C vtable entry.
|
|
1813
1854
|
// Non-`vfunc_` lookups fall through to Object.prototype (so the base prototype
|
|
1814
1855
|
// keeps its normal object behaviour).
|
|
1815
|
-
const
|
|
1856
|
+
const vfuncChainTarget = Object.create(Object.prototype);
|
|
1857
|
+
const vfuncChainProto = new Proxy(vfuncChainTarget, {
|
|
1816
1858
|
get(target, prop, receiver) {
|
|
1817
1859
|
if (typeof prop === 'string' && prop.startsWith('vfunc_')) {
|
|
1818
1860
|
const vfuncName = prop.slice('vfunc_'.length);
|
|
@@ -1827,6 +1869,10 @@ const vfuncChainProto = new Proxy(Object.create(Object.prototype), {
|
|
|
1827
1869
|
return Reflect.get(target, prop, receiver);
|
|
1828
1870
|
},
|
|
1829
1871
|
});
|
|
1872
|
+
// No descriptor trap here: a `vfunc_*` access is synthesized by `get` alone and was
|
|
1873
|
+
// never reported as a descriptor, so the walk's answer for this link always came from
|
|
1874
|
+
// the target. `null` materializer = "read the target, that is the whole trap".
|
|
1875
|
+
protoInternals.set(vfuncChainProto, { target: vfuncChainTarget, materialize: null });
|
|
1830
1876
|
|
|
1831
1877
|
// Surface a lazy `$gtype` getter on an introspected class/struct ctor: the real
|
|
1832
1878
|
// GObject GType (a node-gi GType handle), resolved on first read via the engine
|
|
@@ -1994,7 +2040,7 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
1994
2040
|
}
|
|
1995
2041
|
return arity >= 0 && define(prop, makeGiMethod(where, prop, arity));
|
|
1996
2042
|
};
|
|
1997
|
-
|
|
2043
|
+
const proto = new Proxy(target, {
|
|
1998
2044
|
get(t, prop, receiver) {
|
|
1999
2045
|
materialize(prop);
|
|
2000
2046
|
return Reflect.get(t, prop, receiver);
|
|
@@ -2007,6 +2053,10 @@ function makeClassPrototype(namespace, typeName) {
|
|
|
2007
2053
|
return materialize(prop) || Reflect.has(t, prop);
|
|
2008
2054
|
},
|
|
2009
2055
|
});
|
|
2056
|
+
// Let findProtoDescriptor run the descriptor trap's body directly on the target —
|
|
2057
|
+
// same two steps, without V8's per-trap invariant checks (see protoInternals).
|
|
2058
|
+
protoInternals.set(proto, { target, materialize });
|
|
2059
|
+
return proto;
|
|
2010
2060
|
}
|
|
2011
2061
|
|
|
2012
2062
|
function makeClass(namespace, typeName) {
|
|
@@ -2526,6 +2576,8 @@ function paramSpecGTyped(type) {
|
|
|
2526
2576
|
const ParamSpec = Object.freeze({
|
|
2527
2577
|
string: paramSpecPlain('string'),
|
|
2528
2578
|
boolean: paramSpecPlain('boolean'),
|
|
2579
|
+
char: paramSpecRanged('char'),
|
|
2580
|
+
uchar: paramSpecRanged('uchar'),
|
|
2529
2581
|
int: paramSpecRanged('int'),
|
|
2530
2582
|
uint: paramSpecRanged('uint'),
|
|
2531
2583
|
int64: paramSpecRanged('int64'),
|
|
@@ -3190,6 +3242,7 @@ function mirrorWindowingEnvIntoCrt() {
|
|
|
3190
3242
|
* @returns {Record<string, unknown>}
|
|
3191
3243
|
*/
|
|
3192
3244
|
export function requireGi(namespace, version) {
|
|
3245
|
+
ensureNative();
|
|
3193
3246
|
native.requireNamespace(namespace, version);
|
|
3194
3247
|
// The one moment the win32 CRT-environment mirror can run: the addon is loaded, so there
|
|
3195
3248
|
// is a GLib to call, and nothing has built a font map or loaded a fontconfig
|
package/host.nativescript.js
CHANGED
|
@@ -44,6 +44,12 @@
|
|
|
44
44
|
/** @type {'nativescript'} */
|
|
45
45
|
export const RUNTIME = 'nativescript';
|
|
46
46
|
|
|
47
|
+
/**
|
|
48
|
+
* Whether the addon may be loaded on first use instead of at import — see index.js.
|
|
49
|
+
* @type {boolean}
|
|
50
|
+
*/
|
|
51
|
+
export const LAZY_NATIVE = true;
|
|
52
|
+
|
|
47
53
|
// Timers from the global scope: NativeScript has no node:timers, and there is no
|
|
48
54
|
// GLib-backed replacement to avoid here. setImmediate is not a V8 builtin.
|
|
49
55
|
export const setImmediate =
|
package/host.node.js
CHANGED
|
@@ -55,6 +55,12 @@ export const RUNTIME =
|
|
|
55
55
|
? 'deno'
|
|
56
56
|
: 'node';
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Whether the addon may be loaded on first use instead of at import — see index.js.
|
|
60
|
+
* @type {boolean}
|
|
61
|
+
*/
|
|
62
|
+
export const LAZY_NATIVE = false;
|
|
63
|
+
|
|
58
64
|
// GJS host mode: `GjsifyNapi.init()` installs the loader as
|
|
59
65
|
// `globalThis.__gjsifyNapiLoadAddon` and re-installs it if another consumer (the
|
|
60
66
|
// @gjsify/napi L1) already captured + deleted it. Needs the GjsifyNapi typelib
|
package/index.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
// a native package's JS entry is a loader, not a tsc artifact, and the repo
|
|
6
6
|
// ignores `lib/`. The addon is Node-API (ABI-stable), so the same binary loads on
|
|
7
7
|
// Node, Bun and Deno; runtime-specific behaviour is gated off RUNTIME below.
|
|
8
|
-
import { RUNTIME, loadNativeHost } from '#host';
|
|
8
|
+
import { RUNTIME, loadNativeHost, LAZY_NATIVE } from '#host';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Which JS runtime we are on. Detection — and why each runtime gets the main-loop
|
|
@@ -17,121 +17,237 @@ export { RUNTIME };
|
|
|
17
17
|
/** Whether we are on Node.js (the only runtime with the libuv main-loop bridge). */
|
|
18
18
|
export const isNodeRuntime = RUNTIME === 'node';
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* The native engine. Desktop runtimes load it HERE, while this module evaluates, exactly as
|
|
22
|
+
* before. A host that sets `LAZY_NATIVE` (NativeScript: dlopen of `libnode_gi.so` plus its
|
|
23
|
+
* closure costs real cold-start time) defers the load to the first `ensureNative()`, so an app
|
|
24
|
+
* that imports this package but never asks for GObject pays nothing. A failed load throws from
|
|
25
|
+
* `ensureNative()` every time it is asked — a selected real engine still fails loudly, there
|
|
26
|
+
* is no fallback here (ADR 0105 § 4).
|
|
27
|
+
* @type {Record<string, any>}
|
|
28
|
+
*/
|
|
29
|
+
let native;
|
|
30
|
+
let loaded = false;
|
|
31
|
+
/** @type {Array<() => void>} */
|
|
32
|
+
const loadHooks = [];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load the addon if that has not happened yet, and return it. The L1 layers call this before
|
|
36
|
+
* they touch any binding below.
|
|
37
|
+
* @returns {Record<string, any>}
|
|
38
|
+
*/
|
|
39
|
+
export function ensureNative() {
|
|
40
|
+
if (loaded) return native;
|
|
41
|
+
const addon = loadNativeHost();
|
|
42
|
+
native = addon;
|
|
43
|
+
requireNamespace = addon.requireNamespace;
|
|
44
|
+
listInfoNames = addon.listInfoNames;
|
|
45
|
+
findInfo = addon.findInfo;
|
|
46
|
+
getConstantValue = addon.getConstantValue;
|
|
47
|
+
getEnumValues = addon.getEnumValues;
|
|
48
|
+
getErrorDomain = addon.getErrorDomain;
|
|
49
|
+
errorDomainInfo = addon.errorDomainInfo;
|
|
50
|
+
setErrorBuilder = addon.setErrorBuilder;
|
|
51
|
+
prependSearchPath = addon.prependSearchPath;
|
|
52
|
+
prependLibraryPath = addon.prependLibraryPath;
|
|
53
|
+
callFunction = addon.callFunction;
|
|
54
|
+
callMethod = addon.callMethod;
|
|
55
|
+
hasMethod = addon.hasMethod;
|
|
56
|
+
hasClassMethod = addon.hasClassMethod;
|
|
57
|
+
classMethodArity = addon.classMethodArity;
|
|
58
|
+
callStaticMethod = addon.callStaticMethod;
|
|
59
|
+
constructStruct = addon.constructStruct;
|
|
60
|
+
newObject = addon.newObject;
|
|
61
|
+
registerClass = addon.registerClass;
|
|
62
|
+
registerClassFromGType = addon.registerClassFromGType;
|
|
63
|
+
constructType = addon.constructType;
|
|
64
|
+
callParentVfunc = addon.callParentVfunc;
|
|
65
|
+
hasClassVfunc = addon.hasClassVfunc;
|
|
66
|
+
callClassVfunc = addon.callClassVfunc;
|
|
67
|
+
getTemplateChild = addon.getTemplateChild;
|
|
68
|
+
getProperty = addon.getProperty;
|
|
69
|
+
setProperty = addon.setProperty;
|
|
70
|
+
hasProperty = addon.hasProperty;
|
|
71
|
+
getTypeName = addon.getTypeName;
|
|
72
|
+
classInfoForTypeName = addon.classInfoForTypeName;
|
|
73
|
+
getGType = addon.getGType;
|
|
74
|
+
isInstanceOf = addon.isInstanceOf;
|
|
75
|
+
isGObjectHandle = addon.isGObjectHandle;
|
|
76
|
+
newGValue = addon.newGValue;
|
|
77
|
+
callBoxedMethod = addon.callBoxedMethod;
|
|
78
|
+
isBoxedHandle = addon.isBoxedHandle;
|
|
79
|
+
boxedMemberKind = addon.boxedMemberKind;
|
|
80
|
+
getBoxedField = addon.getBoxedField;
|
|
81
|
+
setBoxedField = addon.setBoxedField;
|
|
82
|
+
boxedTypeName = addon.boxedTypeName;
|
|
83
|
+
__boxedAddress = addon.__boxedAddress;
|
|
84
|
+
isParamSpecHandle = addon.isParamSpecHandle;
|
|
85
|
+
isFundamentalHandle = addon.isFundamentalHandle;
|
|
86
|
+
paramSpecProp = addon.paramSpecProp;
|
|
87
|
+
variantNew = addon.variantNew;
|
|
88
|
+
variantUnpack = addon.variantUnpack;
|
|
89
|
+
variantGetTypeString = addon.variantGetTypeString;
|
|
90
|
+
isVariantHandle = addon.isVariantHandle;
|
|
91
|
+
startMainLoop = addon.startMainLoop;
|
|
92
|
+
iterateMainContext = addon.iterateMainContext;
|
|
93
|
+
mainContextHasPending = addon.mainContextHasPending;
|
|
94
|
+
makePumpPendingCount = addon.makePumpPendingCount;
|
|
95
|
+
pumpKick = addon.pumpKick;
|
|
96
|
+
setMicrotaskDrain = addon.setMicrotaskDrain;
|
|
97
|
+
connectSignal = addon.connectSignal;
|
|
98
|
+
emitSignal = addon.emitSignal;
|
|
99
|
+
disconnectSignal = addon.disconnectSignal;
|
|
100
|
+
setTemplateCallbackResolver = addon.setTemplateCallbackResolver;
|
|
101
|
+
setConstructCallback = addon.setConstructCallback;
|
|
102
|
+
setPropertySetCallback = addon.setPropertySetCallback;
|
|
103
|
+
storedPropertyNames = addon.storedPropertyNames;
|
|
104
|
+
logSetWriterFunc = addon.logSetWriterFunc;
|
|
105
|
+
logSetWriterDefault = addon.logSetWriterDefault;
|
|
106
|
+
bindPropertyFull = addon.bindPropertyFull;
|
|
107
|
+
bindingGroupBindFull = addon.bindingGroupBindFull;
|
|
108
|
+
setThreadLocale = addon.setThreadLocale;
|
|
109
|
+
textdomain = addon.textdomain;
|
|
110
|
+
bindtextdomain = addon.bindtextdomain;
|
|
111
|
+
localeCategories = addon.localeCategories;
|
|
112
|
+
probeHostOpenGL = addon.probeHostOpenGL;
|
|
113
|
+
currentGLStrings = addon.currentGLStrings;
|
|
114
|
+
loaded = true;
|
|
115
|
+
for (const hook of loadHooks.splice(0)) hook();
|
|
116
|
+
return native;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Whether the addon is loaded now — what the lazy host makes observable. */
|
|
120
|
+
export function isNativeLoaded() {
|
|
121
|
+
return loaded;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Run `hook` once the addon is loaded: now if it is, else straight after the first load. The
|
|
126
|
+
* L1 module-scope registrations (error builder, template resolver, …) go through this, so they
|
|
127
|
+
* run at import on desktop and at first use on a lazy host.
|
|
128
|
+
* @param {() => void} hook
|
|
129
|
+
*/
|
|
130
|
+
export function onNativeLoaded(hook) {
|
|
131
|
+
if (loaded) hook();
|
|
132
|
+
else loadHooks.push(hook);
|
|
133
|
+
}
|
|
21
134
|
|
|
22
135
|
// Straight re-exports of the native engine surface. Each contract — arguments,
|
|
23
136
|
// ownership, the OUT/INOUT return-tuple convention, the keep-alive rules of the
|
|
24
137
|
// main-loop group — is documented once in index.d.ts, the published type surface;
|
|
25
138
|
// only what index.d.ts does not carry is noted here.
|
|
26
|
-
export
|
|
27
|
-
export
|
|
28
|
-
export
|
|
29
|
-
export
|
|
30
|
-
export
|
|
31
|
-
export
|
|
32
|
-
export
|
|
33
|
-
export
|
|
34
|
-
export
|
|
35
|
-
export
|
|
36
|
-
export
|
|
37
|
-
export
|
|
38
|
-
export
|
|
39
|
-
export
|
|
40
|
-
export
|
|
41
|
-
export
|
|
42
|
-
export
|
|
43
|
-
export
|
|
44
|
-
export
|
|
139
|
+
export let requireNamespace;
|
|
140
|
+
export let listInfoNames;
|
|
141
|
+
export let findInfo;
|
|
142
|
+
export let getConstantValue;
|
|
143
|
+
export let getEnumValues;
|
|
144
|
+
export let getErrorDomain;
|
|
145
|
+
export let errorDomainInfo;
|
|
146
|
+
export let setErrorBuilder;
|
|
147
|
+
export let prependSearchPath;
|
|
148
|
+
export let prependLibraryPath;
|
|
149
|
+
export let callFunction;
|
|
150
|
+
export let callMethod;
|
|
151
|
+
export let hasMethod;
|
|
152
|
+
export let hasClassMethod;
|
|
153
|
+
export let classMethodArity;
|
|
154
|
+
export let callStaticMethod;
|
|
155
|
+
export let constructStruct;
|
|
156
|
+
export let newObject;
|
|
157
|
+
export let registerClass;
|
|
45
158
|
|
|
46
159
|
// Subclass an ALREADY-REGISTERED parent by its GType handle instead of a
|
|
47
160
|
// `namespace.typeName`: a registered (dynamic) parent has no introspection entry, so it
|
|
48
161
|
// cannot be resolved by name. Custom properties, signals and vfunc slots of registered
|
|
49
162
|
// ancestors compose through ordinary GObject inheritance. The L1 `GObject.registerClass`
|
|
50
163
|
// picks this variant when the nearest base is itself a registered class.
|
|
51
|
-
export
|
|
52
|
-
|
|
53
|
-
export
|
|
54
|
-
export
|
|
55
|
-
export
|
|
56
|
-
export
|
|
57
|
-
export
|
|
58
|
-
export
|
|
59
|
-
export
|
|
60
|
-
export
|
|
61
|
-
export
|
|
62
|
-
export
|
|
63
|
-
export
|
|
64
|
-
export
|
|
65
|
-
export
|
|
66
|
-
export
|
|
67
|
-
export
|
|
68
|
-
export
|
|
69
|
-
export
|
|
70
|
-
export
|
|
71
|
-
export
|
|
72
|
-
export
|
|
164
|
+
export let registerClassFromGType;
|
|
165
|
+
|
|
166
|
+
export let constructType;
|
|
167
|
+
export let callParentVfunc;
|
|
168
|
+
export let hasClassVfunc;
|
|
169
|
+
export let callClassVfunc;
|
|
170
|
+
export let getTemplateChild;
|
|
171
|
+
export let getProperty;
|
|
172
|
+
export let setProperty;
|
|
173
|
+
export let hasProperty;
|
|
174
|
+
export let getTypeName;
|
|
175
|
+
export let classInfoForTypeName;
|
|
176
|
+
export let getGType;
|
|
177
|
+
export let isInstanceOf;
|
|
178
|
+
export let isGObjectHandle;
|
|
179
|
+
export let newGValue;
|
|
180
|
+
export let callBoxedMethod;
|
|
181
|
+
export let isBoxedHandle;
|
|
182
|
+
export let boxedMemberKind;
|
|
183
|
+
export let getBoxedField;
|
|
184
|
+
export let setBoxedField;
|
|
185
|
+
export let boxedTypeName;
|
|
73
186
|
/** TEST-ONLY: the address a boxed handle wraps, so an ownership rule about pointer
|
|
74
187
|
* IDENTITY can be asserted instead of waited on (see marshal.cc BoxedAddress). */
|
|
75
|
-
export
|
|
76
|
-
export
|
|
188
|
+
export let __boxedAddress;
|
|
189
|
+
export let isParamSpecHandle;
|
|
77
190
|
|
|
78
191
|
// True for a non-GObject GObject-fundamental handle (a GskRenderNode from
|
|
79
192
|
// Gtk.Snapshot.to_node, a GdkEvent): introspected as object info, but ref-counted
|
|
80
193
|
// through its own ref/unref funcs, NOT g_object_ref. L1 surfaces it as an opaque,
|
|
81
194
|
// round-trippable pass-through handle.
|
|
82
|
-
export
|
|
83
|
-
|
|
84
|
-
export
|
|
85
|
-
export
|
|
86
|
-
export
|
|
87
|
-
export
|
|
88
|
-
export
|
|
89
|
-
export
|
|
90
|
-
export
|
|
91
|
-
export
|
|
92
|
-
export
|
|
93
|
-
export
|
|
94
|
-
export
|
|
95
|
-
export
|
|
96
|
-
export
|
|
97
|
-
export
|
|
98
|
-
export
|
|
195
|
+
export let isFundamentalHandle;
|
|
196
|
+
|
|
197
|
+
export let paramSpecProp;
|
|
198
|
+
export let variantNew;
|
|
199
|
+
export let variantUnpack;
|
|
200
|
+
export let variantGetTypeString;
|
|
201
|
+
export let isVariantHandle;
|
|
202
|
+
export let startMainLoop;
|
|
203
|
+
export let iterateMainContext;
|
|
204
|
+
export let mainContextHasPending;
|
|
205
|
+
export let makePumpPendingCount;
|
|
206
|
+
export let pumpKick;
|
|
207
|
+
export let setMicrotaskDrain;
|
|
208
|
+
export let connectSignal;
|
|
209
|
+
export let emitSignal;
|
|
210
|
+
export let disconnectSignal;
|
|
211
|
+
export let setTemplateCallbackResolver;
|
|
99
212
|
|
|
100
213
|
// Registers the L1 callback the engine's overridden `constructor` vfunc invokes to run a
|
|
101
214
|
// registered class's JS constructor for a GObject that C instantiated (a GtkBuilder
|
|
102
215
|
// composite-template InternalChild): given (instanceHandle, gtypeName) it
|
|
103
216
|
// Reflect.constructs the class in adopt mode — see gi.js runCtorForCObject.
|
|
104
|
-
export
|
|
217
|
+
export let setConstructCallback;
|
|
105
218
|
|
|
106
219
|
// Registers the L1 callback the engine's set_property vfunc invokes after storing a
|
|
107
220
|
// custom property: given (instanceHandle, propertyName) it runs the class's own JS
|
|
108
221
|
// setter — see gi.js runJsPropertySetter. The engine calls it only once the instance
|
|
109
222
|
// HAS a wrapper, so a construct-time set never reaches it; those are replayed from
|
|
110
223
|
// the base ctor over storedPropertyNames.
|
|
111
|
-
export
|
|
224
|
+
export let setPropertySetCallback;
|
|
112
225
|
|
|
113
226
|
// The custom properties actually SET on an instance (the per-instance store's keys) —
|
|
114
227
|
// what that construct-time replay has to push through the class's JS setters.
|
|
115
|
-
export
|
|
228
|
+
export let storedPropertyNames;
|
|
116
229
|
|
|
117
|
-
export
|
|
118
|
-
export
|
|
119
|
-
export
|
|
120
|
-
export
|
|
230
|
+
export let logSetWriterFunc;
|
|
231
|
+
export let logSetWriterDefault;
|
|
232
|
+
export let bindPropertyFull;
|
|
233
|
+
export let bindingGroupBindFull;
|
|
121
234
|
|
|
122
235
|
// The libintl half of GjsPrivate — the binders GLib's GIR does not publish, plus
|
|
123
236
|
// the LC_* constants read from the host's own <locale.h>. Consumed by
|
|
124
237
|
// `./gettext.js`; loading THIS module is also what puts the process in the
|
|
125
238
|
// environment's locale (the addon's Init does it, as gjs's entry point does).
|
|
126
|
-
export
|
|
127
|
-
export
|
|
128
|
-
export
|
|
129
|
-
export
|
|
239
|
+
export let setThreadLocale;
|
|
240
|
+
export let textdomain;
|
|
241
|
+
export let bindtextdomain;
|
|
242
|
+
export let localeCategories;
|
|
130
243
|
|
|
131
244
|
// win32 only (undefined elsewhere): which OpenGL the process got — see gtk-runtime.js
|
|
132
245
|
// activateBundledOpenGL. currentGLStrings asks the implementation behind the context
|
|
133
246
|
// current on this thread, so a caller can tell Mesa from a vendor driver.
|
|
134
|
-
export
|
|
135
|
-
export
|
|
247
|
+
export let probeHostOpenGL;
|
|
248
|
+
export let currentGLStrings;
|
|
249
|
+
|
|
250
|
+
export { native as default };
|
|
136
251
|
|
|
137
|
-
export
|
|
252
|
+
// Last, so every `export let` above is initialised before the eager load assigns it.
|
|
253
|
+
if (!LAZY_NATIVE) ensureNative();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjsify/node-gi",
|
|
3
|
-
"version": "0.64.
|
|
3
|
+
"version": "0.64.2",
|
|
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/src/cairo.cc
CHANGED
|
@@ -621,6 +621,11 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
621
621
|
c.Set("paint", Napi::Function::New(env, CTX_0(cairo_paint)));
|
|
622
622
|
c.Set("clip", Napi::Function::New(env, CTX_0(cairo_clip)));
|
|
623
623
|
c.Set("clipPreserve", Napi::Function::New(env, CTX_0(cairo_clip_preserve)));
|
|
624
|
+
c.Set("resetClip", Napi::Function::New(env, CTX_0(cairo_reset_clip)));
|
|
625
|
+
c.Set("pushGroup", Napi::Function::New(env, CTX_0(cairo_push_group)));
|
|
626
|
+
c.Set("popGroupToSource", Napi::Function::New(env, CTX_0(cairo_pop_group_to_source)));
|
|
627
|
+
c.Set("copyPage", Napi::Function::New(env, CTX_0(cairo_copy_page)));
|
|
628
|
+
c.Set("showPage", Napi::Function::New(env, CTX_0(cairo_show_page)));
|
|
624
629
|
|
|
625
630
|
c.Set("setLineWidth", Napi::Function::New(env, CTX_1D(cairo_set_line_width)));
|
|
626
631
|
c.Set("paintWithAlpha", Napi::Function::New(env, CTX_1D(cairo_paint_with_alpha)));
|
|
@@ -641,7 +646,10 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
641
646
|
c.Set("arc", Napi::Function::New(env, CTX_5D(cairo_arc)));
|
|
642
647
|
c.Set("arcNegative", Napi::Function::New(env, CTX_5D(cairo_arc_negative)));
|
|
643
648
|
c.Set("curveTo", Napi::Function::New(env, CTX_6D(cairo_curve_to)));
|
|
649
|
+
c.Set("relCurveTo", Napi::Function::New(env, CTX_6D(cairo_rel_curve_to)));
|
|
644
650
|
|
|
651
|
+
c.Set("pushGroupWithContent",
|
|
652
|
+
Napi::Function::New(env, CTX_ENUM(cairo_push_group_with_content, cairo_content_t)));
|
|
645
653
|
c.Set("setOperator", Napi::Function::New(env, CTX_ENUM(cairo_set_operator, cairo_operator_t)));
|
|
646
654
|
c.Set("setLineCap", Napi::Function::New(env, CTX_ENUM(cairo_set_line_cap, cairo_line_cap_t)));
|
|
647
655
|
c.Set("setLineJoin", Napi::Function::New(env, CTX_ENUM(cairo_set_line_join, cairo_line_join_t)));
|
|
@@ -798,6 +806,45 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
798
806
|
o.Set("maxYAdvance", Napi::Number::New(env, e.max_y_advance));
|
|
799
807
|
return o;
|
|
800
808
|
}));
|
|
809
|
+
// The font matrix is a plain `{xx, yx, xy, yy, x0, y0}` object in GJS — cairo's
|
|
810
|
+
// matrix type has no wrapper there (getMatrix/setMatrix stay unbound for that
|
|
811
|
+
// reason), so the font matrix travels as the six doubles.
|
|
812
|
+
c.Set("getFontMatrix", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
813
|
+
Napi::Env env = info.Env();
|
|
814
|
+
cairo_t* cr = CtxArg(env, info);
|
|
815
|
+
if (cr == nullptr) return env.Undefined();
|
|
816
|
+
cairo_matrix_t m;
|
|
817
|
+
cairo_get_font_matrix(cr, &m);
|
|
818
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined();
|
|
819
|
+
Napi::Object o = Napi::Object::New(env);
|
|
820
|
+
o.Set("xx", Napi::Number::New(env, m.xx));
|
|
821
|
+
o.Set("yx", Napi::Number::New(env, m.yx));
|
|
822
|
+
o.Set("xy", Napi::Number::New(env, m.xy));
|
|
823
|
+
o.Set("yy", Napi::Number::New(env, m.yy));
|
|
824
|
+
o.Set("x0", Napi::Number::New(env, m.x0));
|
|
825
|
+
o.Set("y0", Napi::Number::New(env, m.y0));
|
|
826
|
+
return o;
|
|
827
|
+
}));
|
|
828
|
+
c.Set("setFontMatrix", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
829
|
+
Napi::Env env = info.Env();
|
|
830
|
+
cairo_t* cr = CtxArg(env, info);
|
|
831
|
+
if (cr == nullptr) return env.Undefined();
|
|
832
|
+
if (!info[1].IsObject()) {
|
|
833
|
+
Napi::TypeError::New(env, "matrix must be an object").ThrowAsJavaScriptException();
|
|
834
|
+
return env.Undefined();
|
|
835
|
+
}
|
|
836
|
+
Napi::Object o = info[1].As<Napi::Object>();
|
|
837
|
+
cairo_matrix_t m;
|
|
838
|
+
m.xx = o.Get("xx").ToNumber().DoubleValue();
|
|
839
|
+
m.yx = o.Get("yx").ToNumber().DoubleValue();
|
|
840
|
+
m.xy = o.Get("xy").ToNumber().DoubleValue();
|
|
841
|
+
m.yy = o.Get("yy").ToNumber().DoubleValue();
|
|
842
|
+
m.x0 = o.Get("x0").ToNumber().DoubleValue();
|
|
843
|
+
m.y0 = o.Get("y0").ToNumber().DoubleValue();
|
|
844
|
+
cairo_set_font_matrix(cr, &m);
|
|
845
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
846
|
+
return env.Undefined();
|
|
847
|
+
}));
|
|
801
848
|
c.Set("status", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
802
849
|
Napi::Env env = info.Env();
|
|
803
850
|
cairo_t* cr = CtxArg(env, info);
|
|
@@ -824,6 +871,26 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
824
871
|
CheckStatus(env, cairo_status(cr), "context");
|
|
825
872
|
return env.Undefined();
|
|
826
873
|
}));
|
|
874
|
+
c.Set("mask", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
875
|
+
Napi::Env env = info.Env();
|
|
876
|
+
cairo_t* cr = CtxArg(env, info);
|
|
877
|
+
if (cr == nullptr) return env.Undefined();
|
|
878
|
+
cairo_pattern_t* p = PatternFromExt(env, info[1]);
|
|
879
|
+
if (p == nullptr) return env.Undefined();
|
|
880
|
+
cairo_mask(cr, p);
|
|
881
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
882
|
+
return env.Undefined();
|
|
883
|
+
}));
|
|
884
|
+
c.Set("maskSurface", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
885
|
+
Napi::Env env = info.Env();
|
|
886
|
+
cairo_t* cr = CtxArg(env, info);
|
|
887
|
+
if (cr == nullptr) return env.Undefined();
|
|
888
|
+
cairo_surface_t* s = SurfaceFromExt(env, info[1]);
|
|
889
|
+
if (s == nullptr) return env.Undefined();
|
|
890
|
+
cairo_mask_surface(cr, s, Num(info, 2), Num(info, 3));
|
|
891
|
+
CheckStatus(env, cairo_status(cr), "context");
|
|
892
|
+
return env.Undefined();
|
|
893
|
+
}));
|
|
827
894
|
|
|
828
895
|
c.Set("identityMatrix", Napi::Function::New(env, CTX_0(cairo_identity_matrix)));
|
|
829
896
|
c.Set("newSubPath", Napi::Function::New(env, CTX_0(cairo_new_sub_path)));
|
|
@@ -970,6 +1037,35 @@ void InitCairo(Napi::Env env, Napi::Object exports) {
|
|
|
970
1037
|
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
971
1038
|
}));
|
|
972
1039
|
|
|
1040
|
+
// popGroup: cairo_pop_group returns a NEW pattern (transfer-full) — the handle
|
|
1041
|
+
// adopts that ref directly, no extra reference (gjs wraps and then drops its own).
|
|
1042
|
+
c.Set("popGroup", Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value {
|
|
1043
|
+
Napi::Env env = info.Env();
|
|
1044
|
+
cairo_t* cr = CtxArg(env, info);
|
|
1045
|
+
if (cr == nullptr) return env.Undefined();
|
|
1046
|
+
cairo_pattern_t* p = cairo_pop_group(cr);
|
|
1047
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) {
|
|
1048
|
+
cairo_pattern_destroy(p);
|
|
1049
|
+
return env.Undefined();
|
|
1050
|
+
}
|
|
1051
|
+
return MakeCairoHandle(env, p, CairoKind::Pattern);
|
|
1052
|
+
}));
|
|
1053
|
+
|
|
1054
|
+
// getTarget/getGroupTarget: the surface belongs to the context — ref it so the
|
|
1055
|
+
// JS wrapper owns one (mirrors CairoSurface::from_c_ptr's kept reference).
|
|
1056
|
+
#define CTX_SURFACE_GETTER(NAME, CFN) \
|
|
1057
|
+
c.Set(NAME, Napi::Function::New(env, +[](const Napi::CallbackInfo& info) -> Napi::Value { \
|
|
1058
|
+
Napi::Env env = info.Env(); \
|
|
1059
|
+
cairo_t* cr = CtxArg(env, info); \
|
|
1060
|
+
if (cr == nullptr) return env.Undefined(); \
|
|
1061
|
+
cairo_surface_t* s = CFN(cr); \
|
|
1062
|
+
if (!CheckStatus(env, cairo_status(cr), "context")) return env.Undefined(); \
|
|
1063
|
+
cairo_surface_reference(s); \
|
|
1064
|
+
return MakeCairoHandle(env, s, CairoKind::Surface); \
|
|
1065
|
+
}));
|
|
1066
|
+
CTX_SURFACE_GETTER("getTarget", cairo_get_target)
|
|
1067
|
+
CTX_SURFACE_GETTER("getGroupTarget", cairo_get_group_target)
|
|
1068
|
+
|
|
973
1069
|
// ---- $dispose (Context / Surface / Pattern / Path) ----
|
|
974
1070
|
// Eagerly destroy the underlying cairo object + mark the handle so the finalizer
|
|
975
1071
|
// won't double-free and any further method call throws (a live-handle check).
|
package/src/class.cc
CHANGED
|
@@ -44,6 +44,8 @@ static GType UnwrapGType(Napi::Env env, Napi::Value v) {
|
|
|
44
44
|
static GType TypeNameToGType(const std::string& t) {
|
|
45
45
|
if (t == "string" || t == "utf8") return G_TYPE_STRING;
|
|
46
46
|
if (t == "boolean" || t == "bool") return G_TYPE_BOOLEAN;
|
|
47
|
+
if (t == "char") return G_TYPE_CHAR;
|
|
48
|
+
if (t == "uchar") return G_TYPE_UCHAR;
|
|
47
49
|
if (t == "int") return G_TYPE_INT;
|
|
48
50
|
if (t == "uint") return G_TYPE_UINT;
|
|
49
51
|
if (t == "int64") return G_TYPE_INT64;
|
|
@@ -95,6 +97,19 @@ static GParamSpec* BuildParamSpec(Napi::Env env, Napi::Object spec, std::string*
|
|
|
95
97
|
: (def.IsNumber() ? NodeGiToBool(def) : FALSE);
|
|
96
98
|
return g_param_spec_boolean(nm, nm, nm, d, flags);
|
|
97
99
|
}
|
|
100
|
+
if (type == "char") {
|
|
101
|
+
return g_param_spec_char(nm, nm, nm, hasMin ? static_cast<gint8>(mn) : G_MININT8,
|
|
102
|
+
hasMax ? static_cast<gint8>(mx) : G_MAXINT8,
|
|
103
|
+
def.IsNumber() ? static_cast<gint8>(def.As<Napi::Number>().Int32Value()) : 0,
|
|
104
|
+
flags);
|
|
105
|
+
}
|
|
106
|
+
if (type == "uchar") {
|
|
107
|
+
return g_param_spec_uchar(nm, nm, nm, hasMin ? static_cast<guint8>(mn) : 0,
|
|
108
|
+
hasMax ? static_cast<guint8>(mx) : G_MAXUINT8,
|
|
109
|
+
def.IsNumber() ? static_cast<guint8>(def.As<Napi::Number>().Uint32Value())
|
|
110
|
+
: 0,
|
|
111
|
+
flags);
|
|
112
|
+
}
|
|
98
113
|
if (type == "int") {
|
|
99
114
|
return g_param_spec_int(nm, nm, nm, hasMin ? static_cast<gint>(mn) : G_MININT,
|
|
100
115
|
hasMax ? static_cast<gint>(mx) : G_MAXINT,
|
package/src/common.h
CHANGED
|
@@ -363,7 +363,7 @@ inline NodeGiIntResult NodeGiJsToInteger(Napi::Value v, GITypeTag tag, GIArgumen
|
|
|
363
363
|
}
|
|
364
364
|
}
|
|
365
365
|
|
|
366
|
-
enum class NodeGiIntContext { kArgument, kField, kReturnValue };
|
|
366
|
+
enum class NodeGiIntContext { kArgument, kField, kReturnValue, kListElement, kHashElement };
|
|
367
367
|
inline const char* NodeGiIntTagName(GITypeTag tag) {
|
|
368
368
|
switch (tag) {
|
|
369
369
|
case GI_TYPE_TAG_INT8: return "int8";
|
|
@@ -385,13 +385,31 @@ inline void NodeGiThrowIntOutOfRange(Napi::Env env, GITypeTag tag, NodeGiIntCont
|
|
|
385
385
|
msg = std::string("Argument ") + (name != nullptr ? name : "") + ": value is out of range for " + t;
|
|
386
386
|
} else {
|
|
387
387
|
msg = "value " + shown + " is out of range for " +
|
|
388
|
-
(ctx == NodeGiIntContext::kField
|
|
389
|
-
|
|
388
|
+
(ctx == NodeGiIntContext::kField
|
|
389
|
+
? std::string("Field '") + (name ? name : "") + "'"
|
|
390
|
+
: ctx == NodeGiIntContext::kListElement ? std::string("List element")
|
|
391
|
+
: ctx == NodeGiIntContext::kHashElement ? std::string("Hash element")
|
|
392
|
+
: std::string("Return value")) +
|
|
390
393
|
" (type " + t + ")";
|
|
391
394
|
}
|
|
392
395
|
Napi::Error::New(env, msg).ThrowAsJavaScriptException();
|
|
393
396
|
}
|
|
394
397
|
|
|
398
|
+
// The text gjs_debug_value (refs/gjs/gi/../jsapi-util-string.cpp) prints for the offending
|
|
399
|
+
// value in a list/hash element error: a Number goes through C++ ostream (%g, 6 digits), a
|
|
400
|
+
// BigInt is shown as its signed 64-bit value with a "(modulo 2^64)" suffix.
|
|
401
|
+
inline std::string NodeGiDebugValue(Napi::Value v) {
|
|
402
|
+
if (v.IsBigInt()) {
|
|
403
|
+
bool lossless = false;
|
|
404
|
+
return std::to_string(v.As<Napi::BigInt>().Int64Value(&lossless)) + "n (modulo 2^64)";
|
|
405
|
+
}
|
|
406
|
+
Napi::Number n = v.ToNumber();
|
|
407
|
+
if (n.IsEmpty()) return std::string();
|
|
408
|
+
char buf[G_ASCII_DTOSTR_BUF_SIZE];
|
|
409
|
+
g_ascii_formatd(buf, sizeof(buf), "%g", n.DoubleValue()); // locale-independent, like ostream
|
|
410
|
+
return buf;
|
|
411
|
+
}
|
|
412
|
+
|
|
395
413
|
// IN array elements and GValue int/uint, GJS-exact: these WRAP instead of refusing.
|
|
396
414
|
// An array element goes through js_value_to_c_strict (refs/gjs/gi/arg.cpp:543), which
|
|
397
415
|
// is ToInt32/ToUint32 truncated to the tag for 8/16/32 bits and ToInt64/ToUint64 —
|
package/src/marshal.cc
CHANGED
|
@@ -1060,6 +1060,8 @@ static bool IsSupportedElementType(GITypeInfo* elem, std::string* why, bool byVa
|
|
|
1060
1060
|
case GI_TYPE_TAG_UTF8:
|
|
1061
1061
|
case GI_TYPE_TAG_FILENAME:
|
|
1062
1062
|
return true;
|
|
1063
|
+
// A gunichar is a 4-byte cell; only a C array writes one (no list/hash node).
|
|
1064
|
+
case GI_TYPE_TAG_UNICHAR: return cArrayCell;
|
|
1063
1065
|
case GI_TYPE_TAG_INTERFACE: {
|
|
1064
1066
|
GIBaseInfo* iface = gi_type_info_get_interface(elem);
|
|
1065
1067
|
bool ok = iface != nullptr && (GI_IS_OBJECT_INFO(iface) || GI_IS_INTERFACE_INFO(iface));
|
|
@@ -1178,7 +1180,8 @@ size_t CElementSize(GITypeInfo* elem) {
|
|
|
1178
1180
|
case GI_TYPE_TAG_INT16:
|
|
1179
1181
|
case GI_TYPE_TAG_UINT16: return 2;
|
|
1180
1182
|
case GI_TYPE_TAG_INT32:
|
|
1181
|
-
case GI_TYPE_TAG_UINT32:
|
|
1183
|
+
case GI_TYPE_TAG_UINT32:
|
|
1184
|
+
case GI_TYPE_TAG_UNICHAR: return 4;
|
|
1182
1185
|
case GI_TYPE_TAG_INT64:
|
|
1183
1186
|
case GI_TYPE_TAG_UINT64: return 8;
|
|
1184
1187
|
case GI_TYPE_TAG_FLOAT: return sizeof(gfloat);
|
|
@@ -1601,16 +1604,16 @@ Napi::Value ReadOutOrReturn(Napi::Env env, GICallableInfo* callable, GITypeInfo*
|
|
|
1601
1604
|
// the element, so it gets a ref (objects) or an independent copy (boxed) instead.
|
|
1602
1605
|
// Throws + returns false on an unsupported element.
|
|
1603
1606
|
static bool ElementToGIArgument(Napi::Env env, GITypeInfo* elem, Napi::Value v, GIArgument* a,
|
|
1604
|
-
GITransfer elemTransfer,
|
|
1607
|
+
GITransfer elemTransfer, NodeGiIntContext intContext = NodeGiIntContext::kListElement,
|
|
1608
|
+
bool wrapIntegers = false) {
|
|
1605
1609
|
memset(a, 0, sizeof(*a));
|
|
1606
1610
|
switch (gi_type_info_get_tag(elem)) {
|
|
1607
1611
|
// Scalar coercions via the terminate-safe helpers (common.h): a swallowed
|
|
1608
1612
|
// napi failure mid worker.terminate() must degrade to a zero, not cascade
|
|
1609
1613
|
// into Error::New(nullptr)'s fatal sites.
|
|
1610
1614
|
case GI_TYPE_TAG_BOOLEAN: a->v_boolean = NodeGiToBool(v); return true;
|
|
1611
|
-
// Integers WRAP
|
|
1612
|
-
//
|
|
1613
|
-
// coercion: gjs's rule for those is not probed yet.
|
|
1615
|
+
// Integers WRAP in an array (arg.cpp gjs_array_to_auto_array, NodeGiJsToIntegerWrap in
|
|
1616
|
+
// common.h) and are REFUSED out of range in a GList/GHashTable node.
|
|
1614
1617
|
case GI_TYPE_TAG_INT8:
|
|
1615
1618
|
case GI_TYPE_TAG_UINT8:
|
|
1616
1619
|
case GI_TYPE_TAG_INT16:
|
|
@@ -1621,16 +1624,20 @@ static bool ElementToGIArgument(Napi::Env env, GITypeInfo* elem, Napi::Value v,
|
|
|
1621
1624
|
case GI_TYPE_TAG_UINT64:
|
|
1622
1625
|
if (wrapIntegers)
|
|
1623
1626
|
return NodeGiJsToIntegerWrap(v, gi_type_info_get_tag(elem), a) == NodeGiIntResult::kOk;
|
|
1624
|
-
|
|
1625
|
-
|
|
1626
|
-
|
|
1627
|
-
case
|
|
1628
|
-
case
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1627
|
+
// A list/hash node is checked, not wrapped (arg.cpp gjs_value_to_basic_gi_argument
|
|
1628
|
+
// -> gjs_arg_set_from_js_value): out of range is a plain Error.
|
|
1629
|
+
switch (NodeGiJsToInteger(v, gi_type_info_get_tag(elem), a, nullptr)) {
|
|
1630
|
+
case NodeGiIntResult::kPending: return false;
|
|
1631
|
+
case NodeGiIntResult::kOutOfRange:
|
|
1632
|
+
NodeGiThrowIntOutOfRange(env, gi_type_info_get_tag(elem), intContext, nullptr,
|
|
1633
|
+
NodeGiDebugValue(v));
|
|
1634
|
+
return false;
|
|
1635
|
+
case NodeGiIntResult::kOk: return true;
|
|
1633
1636
|
}
|
|
1637
|
+
return true;
|
|
1638
|
+
// gjs_array_to_auto_array<char32_t> wraps like uint32 (arg.cpp js_value_to_c_strict).
|
|
1639
|
+
case GI_TYPE_TAG_UNICHAR:
|
|
1640
|
+
return NodeGiJsToIntegerWrap(v, GI_TYPE_TAG_UINT32, a) == NodeGiIntResult::kOk;
|
|
1634
1641
|
case GI_TYPE_TAG_FLOAT: a->v_float = static_cast<gfloat>(NodeGiToDouble(v)); return true;
|
|
1635
1642
|
case GI_TYPE_TAG_DOUBLE: a->v_double = NodeGiToDouble(v); return true;
|
|
1636
1643
|
case GI_TYPE_TAG_GTYPE: {
|
|
@@ -2267,6 +2274,20 @@ static bool JsToCArray(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfe
|
|
|
2267
2274
|
return true;
|
|
2268
2275
|
}
|
|
2269
2276
|
|
|
2277
|
+
// A JS string as a gunichar C array → its code points (arg.cpp:584, gjs_string_to_ucs4).
|
|
2278
|
+
if (at == GI_ARRAY_TYPE_C && etag == GI_TYPE_TAG_UNICHAR && v.IsString()) {
|
|
2279
|
+
std::string s = v.As<Napi::String>().Utf8Value();
|
|
2280
|
+
glong n = 0;
|
|
2281
|
+
gunichar* ucs = g_utf8_to_ucs4_fast(s.c_str(), static_cast<glong>(s.size()), &n);
|
|
2282
|
+
void* buf = g_malloc0(elemSize * (n + (zt ? 1 : 0)));
|
|
2283
|
+
if (n > 0) memcpy(buf, ucs, elemSize * n);
|
|
2284
|
+
g_free(ucs);
|
|
2285
|
+
*outPtr = buf;
|
|
2286
|
+
*outCount = static_cast<long>(n);
|
|
2287
|
+
gi_base_info_unref(elem);
|
|
2288
|
+
return true;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2270
2291
|
if (!v.IsArray()) {
|
|
2271
2292
|
gi_base_info_unref(elem);
|
|
2272
2293
|
Napi::TypeError::New(env, "expected an array for the array argument")
|
|
@@ -2301,7 +2322,7 @@ static bool JsToCArray(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransfe
|
|
|
2301
2322
|
}
|
|
2302
2323
|
GIArgument a;
|
|
2303
2324
|
ok = ElementToGIArgument(env, elem, arr.Get(static_cast<uint32_t>(i)), &a, elemTransfer,
|
|
2304
|
-
/*wrapIntegers=*/true);
|
|
2325
|
+
NodeGiIntContext::kListElement, /*wrapIntegers=*/true);
|
|
2305
2326
|
if (!ok) break;
|
|
2306
2327
|
// Copy the element's storage bytes (LE: the low elemSize bytes of the union
|
|
2307
2328
|
// alias the active field — v_string/v_pointer for pointers, the scalar
|
|
@@ -2448,7 +2469,8 @@ static bool JsToGHashIn(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransf
|
|
|
2448
2469
|
} else {
|
|
2449
2470
|
GIArgument ka;
|
|
2450
2471
|
Napi::Value keyNum = Napi::Number::New(env, static_cast<double>(g_ascii_strtoll(ks.c_str(), nullptr, 10)));
|
|
2451
|
-
ok = ElementToGIArgument(env, kt, keyNum, &ka, GI_TRANSFER_NOTHING
|
|
2472
|
+
ok = ElementToGIArgument(env, kt, keyNum, &ka, GI_TRANSFER_NOTHING,
|
|
2473
|
+
NodeGiIntContext::kHashElement, /*wrapIntegers=*/true);
|
|
2452
2474
|
if (!ok) break;
|
|
2453
2475
|
kp = gi_type_info_hash_pointer_from_argument(kt, &ka);
|
|
2454
2476
|
}
|
|
@@ -2459,7 +2481,7 @@ static bool JsToGHashIn(Napi::Env env, Napi::Value v, GITypeInfo* type, GITransf
|
|
|
2459
2481
|
Napi::Value val = obj.Get(key);
|
|
2460
2482
|
gpointer vp = nullptr;
|
|
2461
2483
|
GIArgument va;
|
|
2462
|
-
ok = ElementToGIArgument(env, vt, val, &va, elemTransfer);
|
|
2484
|
+
ok = ElementToGIArgument(env, vt, val, &va, elemTransfer, NodeGiIntContext::kHashElement);
|
|
2463
2485
|
if (!ok) {
|
|
2464
2486
|
g_free(kp);
|
|
2465
2487
|
break;
|