@gjsify/node-gi 0.49.0 → 0.51.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 +6 -1
- package/gettext.d.ts +18 -18
- package/gettext.js +141 -54
- package/gi.js +94 -29
- package/globals.d.ts +1 -1
- package/gtk-runtime.js +22 -0
- package/index.js +20 -0
- package/overrides/package.js +6 -6
- package/package.json +14 -13
- 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/mac-intl-gyp-flags.mjs +70 -0
- package/scripts/win-gi-gyp-flags.mjs +22 -4
- package/src/addon.cc +17 -0
- package/src/calls.cc +57 -0
- package/src/class.cc +135 -0
- package/src/common.h +35 -0
- package/src/private.cc +229 -0
- package/src/repo.cc +1 -0
- package/src/toggle.cc +17 -0
package/binding.gyp
CHANGED
|
@@ -35,7 +35,12 @@
|
|
|
35
35
|
"-std=c++17"
|
|
36
36
|
],
|
|
37
37
|
"libraries": [
|
|
38
|
-
"<!@(pkg-config --libs girepository-2.0 cairo)"
|
|
38
|
+
"<!@(pkg-config --libs girepository-2.0 cairo)",
|
|
39
|
+
# libintl for the gettext binders (private.cc). Empty off darwin: glibc
|
|
40
|
+
# absorbs bindtextdomain/textdomain into libc, macOS's libSystem has no
|
|
41
|
+
# gettext at all and a two-level-namespace Mach-O must name the library
|
|
42
|
+
# it takes them from. See scripts/mac-intl-gyp-flags.mjs.
|
|
43
|
+
"<!@(node scripts/mac-intl-gyp-flags.mjs)"
|
|
39
44
|
],
|
|
40
45
|
"xcode_settings": {
|
|
41
46
|
"OTHER_CFLAGS": [
|
package/gettext.d.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// @gjsify/node-gi/gettext — types for the GJS `Gettext` module on Node.
|
|
3
3
|
|
|
4
|
-
/** Look up `msgid` in the default domain
|
|
4
|
+
/** Look up `msgid` in the default domain. */
|
|
5
5
|
export function gettext(msgid: string): string;
|
|
6
|
-
/** Look up `msgid` in `domain
|
|
6
|
+
/** Look up `msgid` in `domain`. */
|
|
7
7
|
export function dgettext(domain: string | null, msgid: string): string;
|
|
8
|
-
/** Look up `msgid` in `domain`/`category
|
|
8
|
+
/** Look up `msgid` in `domain`/`category`. */
|
|
9
9
|
export function dcgettext(domain: string | null, msgid: string, category: number): string;
|
|
10
|
-
/** Plural lookup in the default domain
|
|
10
|
+
/** Plural lookup in the default domain. */
|
|
11
11
|
export function ngettext(msgid1: string, msgid2: string, n: number): string;
|
|
12
|
-
/** Plural lookup in `domain
|
|
12
|
+
/** Plural lookup in `domain`. */
|
|
13
13
|
export function dngettext(domain: string | null, msgid1: string, msgid2: string, n: number): string;
|
|
14
|
-
/** Context lookup in the default domain
|
|
14
|
+
/** Context lookup in the default domain. */
|
|
15
15
|
export function pgettext(context: string, msgid: string): string;
|
|
16
|
-
/** Context lookup in `domain
|
|
16
|
+
/** Context lookup in `domain`. */
|
|
17
17
|
export function dpgettext(domain: string | null, context: string, msgid: string): string;
|
|
18
18
|
/** Domain-bound gettext bindings. */
|
|
19
19
|
export function domain(domainName: string): {
|
|
@@ -21,15 +21,15 @@ export function domain(domainName: string): {
|
|
|
21
21
|
ngettext(msgid1: string, msgid2: string, n: number): string;
|
|
22
22
|
pgettext(context: string, msgid: string): string;
|
|
23
23
|
};
|
|
24
|
-
/** Set the locale for `category`
|
|
25
|
-
export function setlocale(category: number, locale: string | null): null;
|
|
26
|
-
/** Set the default text domain
|
|
27
|
-
export function textdomain(domainName: string | null): null;
|
|
28
|
-
/** Bind a text domain to
|
|
29
|
-
export function bindtextdomain(domainName: string, dirName: string | null): null;
|
|
30
|
-
/** Set the output codeset for a text domain
|
|
24
|
+
/** Set the locale for `category`, or report it when `locale` is null. */
|
|
25
|
+
export function setlocale(category: number, locale: string | null): string | null;
|
|
26
|
+
/** Set the default text domain, or report it when `domainName` is null. */
|
|
27
|
+
export function textdomain(domainName: string | null): string | null;
|
|
28
|
+
/** Bind a text domain to the directory its catalogs live in; pins UTF-8. */
|
|
29
|
+
export function bindtextdomain(domainName: string, dirName: string | null): string | null;
|
|
30
|
+
/** Set the output codeset for a text domain. No-op — `bindtextdomain` pins UTF-8. */
|
|
31
31
|
export function bindtextdomainCodeset(domainName: string, codeset: string | null): null;
|
|
32
|
-
/** The
|
|
32
|
+
/** The POSIX locale categories, read from the host C library's own <locale.h>. */
|
|
33
33
|
export const LocaleCategory: {
|
|
34
34
|
CTYPE: number;
|
|
35
35
|
NUMERIC: number;
|
|
@@ -54,9 +54,9 @@ export interface GettextModule {
|
|
|
54
54
|
ngettext(msgid1: string, msgid2: string, n: number): string;
|
|
55
55
|
pgettext(context: string, msgid: string): string;
|
|
56
56
|
};
|
|
57
|
-
setlocale(category: number, locale: string | null): null;
|
|
58
|
-
textdomain(domainName: string | null): null;
|
|
59
|
-
bindtextdomain(domainName: string, dirName: string | null): null;
|
|
57
|
+
setlocale(category: number, locale: string | null): string | null;
|
|
58
|
+
textdomain(domainName: string | null): string | null;
|
|
59
|
+
bindtextdomain(domainName: string, dirName: string | null): string | null;
|
|
60
60
|
bindtextdomainCodeset(domainName: string, codeset: string | null): null;
|
|
61
61
|
LocaleCategory: typeof LocaleCategory;
|
|
62
62
|
}
|
package/gettext.js
CHANGED
|
@@ -2,108 +2,195 @@
|
|
|
2
2
|
// @gjsify/node-gi/gettext — the GJS `Gettext` module on Node.
|
|
3
3
|
//
|
|
4
4
|
// GJS exposes a built-in `gettext` module (`import Gettext from 'gettext'` /
|
|
5
|
-
// `imports.gettext`) — a convenience layer over GLib's gettext family.
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
5
|
+
// `imports.gettext`) — a convenience layer over GLib's gettext family. This is the
|
|
6
|
+
// same layer with the same split of responsibilities as GJS's:
|
|
7
|
+
// • LOOKUPS go to GLib (`g_dgettext` / `g_dngettext` / `g_dpgettext2`), which is
|
|
8
|
+
// introspectable and therefore needs no native code here;
|
|
9
|
+
// • the BINDERS (`setlocale` / `textdomain` / `bindtextdomain`) are libintl, which
|
|
10
|
+
// has no GIR at all, so they come from the addon — the same reason GJS routes
|
|
11
|
+
// them through GjsPrivate instead of `imports.gi.GLib`.
|
|
12
|
+
// Reference: refs/gjs/modules/core/_gettext.js, function for function.
|
|
13
|
+
//
|
|
14
|
+
// THIS MODULE USED TO BE A PASSTHROUGH — every lookup returned its msgid, every
|
|
15
|
+
// binder a no-op returning null. That is the right fallback when no catalog can be
|
|
16
|
+
// bound, and it was written when node-gi had no way to bind one; what made it a
|
|
17
|
+
// defect is that it stayed after the rest of the bridge could. It was also only
|
|
18
|
+
// HALF of what was wrong — the process locale (private.cc) had to be fixed too, or
|
|
19
|
+
// a real `bindtextdomain` would still have translated nothing.
|
|
11
20
|
//
|
|
12
21
|
// This is the single source of truth for the Gettext surface: the legacy
|
|
13
22
|
// `imports.gettext` exposed by `@gjsify/node-gi/globals` re-uses this module's
|
|
14
23
|
// default export. The gjsify `--app node` build aliases the bare `gettext`
|
|
15
24
|
// specifier to this module (kept external — `ALIASES_GJS_FOR_NODE`).
|
|
25
|
+
import * as native from './index.js';
|
|
26
|
+
import { requireGi } from './gi.js';
|
|
27
|
+
|
|
28
|
+
// GLib is resolved on first lookup, not at import: loading a namespace walks the
|
|
29
|
+
// typelib, and a module imported for its LocaleCategory alone should not pay for
|
|
30
|
+
// it. The addon itself is already loaded — `./index.js` above is what puts the
|
|
31
|
+
// process in the environment's locale, and that has to happen whether or not
|
|
32
|
+
// anything ever looks a string up.
|
|
33
|
+
let glib = null;
|
|
34
|
+
function GLib() {
|
|
35
|
+
if (glib === null) glib = requireGi('GLib', '2.0');
|
|
36
|
+
return glib;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// THE ADDON AND THIS JS CAN BE PAIRED ACROSS VERSIONS, so the binders are probed
|
|
40
|
+
// rather than assumed — the same reason `native-prebuilds.js` probes
|
|
41
|
+
// `prependLibraryPath`. `gtk-os-suites.yml`'s shipped-closure legs stage the
|
|
42
|
+
// PUBLISHED addon against THIS tree's JS deliberately ("the tarball a consumer
|
|
43
|
+
// actually receives"), and a bare `native.localeCategories()` died there with
|
|
44
|
+
// `TypeError: native.localeCategories is not a function` — taking the whole module
|
|
45
|
+
// down, and `globals.js` with it, because it is evaluated at import.
|
|
16
46
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
47
|
+
// Degrading means behaving exactly as an addon without the binders always did:
|
|
48
|
+
// lookups still work (they go through GLib, which every addon version has), and
|
|
49
|
+
// the binders are the no-ops they used to be. It is announced once, because a
|
|
50
|
+
// silently untranslated UI is the defect this file exists to close.
|
|
51
|
+
const haveLocaleBinders = typeof native.localeCategories === 'function';
|
|
52
|
+
|
|
53
|
+
let announcedStaleAddon = false;
|
|
54
|
+
function noLocaleBinders(fn) {
|
|
55
|
+
if (!announcedStaleAddon) {
|
|
56
|
+
announcedStaleAddon = true;
|
|
57
|
+
process.emitWarning(
|
|
58
|
+
`@gjsify/node-gi: the loaded native addon predates the gettext binders, so ` +
|
|
59
|
+
`${fn}() cannot take effect and messages stay untranslated. Rebuild the addon ` +
|
|
60
|
+
`(npm run rebuild) or update @gjsify/node-gi.`,
|
|
61
|
+
'NodeGiStaleAddon',
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
19
66
|
|
|
20
|
-
|
|
67
|
+
// The POSIX categories per C library, used ONLY when the addon cannot report its
|
|
68
|
+
// own. The addon is the source of truth precisely because these are C-library
|
|
69
|
+
// constants rather than a standard; this table exists so a stale pairing gets the
|
|
70
|
+
// right numbers for its platform instead of glibc's everywhere, which is the bug
|
|
71
|
+
// the module used to ship.
|
|
72
|
+
const FALLBACK_CATEGORIES = {
|
|
73
|
+
// glibc
|
|
74
|
+
linux: { CTYPE: 0, NUMERIC: 1, TIME: 2, COLLATE: 3, MONETARY: 4, MESSAGES: 5, ALL: 6 },
|
|
75
|
+
// Darwin libc
|
|
76
|
+
darwin: { ALL: 0, COLLATE: 1, CTYPE: 2, MONETARY: 3, NUMERIC: 4, TIME: 5, MESSAGES: 6 },
|
|
77
|
+
// The MSVC CRT, which has no LC_MESSAGES — GNU gettext's <libintl.h> picks 1729.
|
|
78
|
+
win32: { ALL: 0, COLLATE: 1, CTYPE: 2, MONETARY: 3, NUMERIC: 4, TIME: 5, MESSAGES: 1729 },
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Look up `msgid` in the default domain (the one `textdomain()` last named). */
|
|
21
82
|
export function gettext(msgid) {
|
|
22
|
-
return msgid;
|
|
83
|
+
return GLib().dgettext(null, msgid);
|
|
23
84
|
}
|
|
24
85
|
|
|
25
|
-
/** Look up `msgid` in `domain`.
|
|
26
|
-
export function dgettext(
|
|
27
|
-
return msgid;
|
|
86
|
+
/** Look up `msgid` in `domain`. */
|
|
87
|
+
export function dgettext(domain, msgid) {
|
|
88
|
+
return GLib().dgettext(domain, msgid);
|
|
28
89
|
}
|
|
29
90
|
|
|
30
|
-
/** Look up `msgid` in `domain
|
|
31
|
-
export function dcgettext(
|
|
32
|
-
return msgid;
|
|
91
|
+
/** Look up `msgid` in `domain` for `category`. */
|
|
92
|
+
export function dcgettext(domain, msgid, category) {
|
|
93
|
+
return GLib().dcgettext(domain, msgid, category);
|
|
33
94
|
}
|
|
34
95
|
|
|
35
|
-
/** Plural lookup in the default domain.
|
|
96
|
+
/** Plural lookup in the default domain. */
|
|
36
97
|
export function ngettext(msgid1, msgid2, n) {
|
|
37
|
-
return
|
|
98
|
+
return GLib().dngettext(null, msgid1, msgid2, n);
|
|
38
99
|
}
|
|
39
100
|
|
|
40
|
-
/** Plural lookup in `domain`.
|
|
41
|
-
export function dngettext(
|
|
42
|
-
return
|
|
101
|
+
/** Plural lookup in `domain`. */
|
|
102
|
+
export function dngettext(domain, msgid1, msgid2, n) {
|
|
103
|
+
return GLib().dngettext(domain, msgid1, msgid2, n);
|
|
43
104
|
}
|
|
44
105
|
|
|
45
|
-
/** Context lookup in the default domain.
|
|
46
|
-
export function pgettext(
|
|
47
|
-
return msgid;
|
|
106
|
+
/** Context lookup in the default domain. */
|
|
107
|
+
export function pgettext(context, msgid) {
|
|
108
|
+
return GLib().dpgettext2(null, context, msgid);
|
|
48
109
|
}
|
|
49
110
|
|
|
50
|
-
/** Context lookup in `domain`.
|
|
51
|
-
export function dpgettext(
|
|
52
|
-
return msgid;
|
|
111
|
+
/** Context lookup in `domain`. */
|
|
112
|
+
export function dpgettext(domain, context, msgid) {
|
|
113
|
+
return GLib().dpgettext2(domain, context, msgid);
|
|
53
114
|
}
|
|
54
115
|
|
|
55
116
|
/**
|
|
56
117
|
* Create gettext bindings bound to a particular translation domain. Mirrors
|
|
57
118
|
* GJS's `Gettext.domain()` — returns an object with `gettext`/`ngettext`/
|
|
58
|
-
* `pgettext` bound to `domainName
|
|
119
|
+
* `pgettext` bound to `domainName`.
|
|
59
120
|
*/
|
|
60
|
-
export function domain(
|
|
121
|
+
export function domain(domainName) {
|
|
61
122
|
return {
|
|
62
123
|
gettext(msgid) {
|
|
63
|
-
return msgid;
|
|
124
|
+
return GLib().dgettext(domainName, msgid);
|
|
64
125
|
},
|
|
65
126
|
ngettext(msgid1, msgid2, n) {
|
|
66
|
-
return
|
|
127
|
+
return GLib().dngettext(domainName, msgid1, msgid2, n);
|
|
67
128
|
},
|
|
68
|
-
pgettext(
|
|
69
|
-
return msgid;
|
|
129
|
+
pgettext(context, msgid) {
|
|
130
|
+
return GLib().dpgettext2(domainName, context, msgid);
|
|
70
131
|
},
|
|
71
132
|
};
|
|
72
133
|
}
|
|
73
134
|
|
|
74
|
-
/**
|
|
75
|
-
|
|
76
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Set the locale for `category`, or report it when `locale` is null.
|
|
137
|
+
*
|
|
138
|
+
* Thread-local, as GJS's is (`GjsPrivate.set_thread_locale` → `uselocale`), and it
|
|
139
|
+
* inherits GJS's reporting quirk with it: a query answers with the name that was
|
|
140
|
+
* in effect BEFORE the last successful set on this thread, because the report
|
|
141
|
+
* comes from the global locale `uselocale` does not touch. Measured against
|
|
142
|
+
* gjs 1.88 — setting LC_MESSAGES to "C" stops translation immediately while the
|
|
143
|
+
* next query still answers the old name.
|
|
144
|
+
*
|
|
145
|
+
* @returns {string|null} the prior locale name, or null if it could not be set
|
|
146
|
+
*/
|
|
147
|
+
export function setlocale(category, locale) {
|
|
148
|
+
if (!haveLocaleBinders) return noLocaleBinders('setlocale');
|
|
149
|
+
return native.setThreadLocale(category, locale ?? null);
|
|
77
150
|
}
|
|
78
151
|
|
|
79
|
-
/** Set the default text domain
|
|
80
|
-
export function textdomain(
|
|
81
|
-
return
|
|
152
|
+
/** Set the default text domain, or report it when `domainName` is null. */
|
|
153
|
+
export function textdomain(domainName) {
|
|
154
|
+
if (!haveLocaleBinders) return noLocaleBinders('textdomain');
|
|
155
|
+
return native.textdomain(domainName ?? null);
|
|
82
156
|
}
|
|
83
157
|
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
158
|
+
/**
|
|
159
|
+
* Bind a text domain to the directory its catalogs live in.
|
|
160
|
+
*
|
|
161
|
+
* The codeset is pinned to UTF-8 alongside the bind, exactly as
|
|
162
|
+
* `gjs_bindtextdomain` does — see the note in private.cc.
|
|
163
|
+
*/
|
|
164
|
+
export function bindtextdomain(domainName, dirName) {
|
|
165
|
+
if (!haveLocaleBinders) return noLocaleBinders('bindtextdomain');
|
|
166
|
+
return native.bindtextdomain(domainName, dirName ?? null);
|
|
87
167
|
}
|
|
88
168
|
|
|
89
|
-
/**
|
|
169
|
+
/**
|
|
170
|
+
* Set the output codeset for a text domain.
|
|
171
|
+
*
|
|
172
|
+
* `bindtextdomain()` already pins UTF-8, which is what every string crossing this
|
|
173
|
+
* bridge must be, so this stays a no-op returning null. It is the one member of
|
|
174
|
+
* the surface GJS does not implement either — it is absent from
|
|
175
|
+
* refs/gjs/modules/core/_gettext.js — and is kept for source that calls it.
|
|
176
|
+
*/
|
|
90
177
|
export function bindtextdomainCodeset(_domainName, _codeset) {
|
|
91
178
|
return null;
|
|
92
179
|
}
|
|
93
180
|
|
|
94
181
|
/**
|
|
95
|
-
* The `LocaleCategory` enum — the
|
|
96
|
-
*
|
|
182
|
+
* The `LocaleCategory` enum — the POSIX locale categories GJS surfaces via
|
|
183
|
+
* `GjsPrivate.LocaleCategory`.
|
|
184
|
+
*
|
|
185
|
+
* Read from THIS platform's `<locale.h>` rather than written down: the numbers are
|
|
186
|
+
* C-library constants, not a standard (LC_MESSAGES is 5 on glibc and 6 on darwin),
|
|
187
|
+
* so the literal table this module used to carry addressed LC_TIME on macOS while
|
|
188
|
+
* claiming to address LC_MESSAGES — on two of the three platforms the translation
|
|
189
|
+
* blocker was reported from.
|
|
97
190
|
*/
|
|
98
|
-
export const LocaleCategory =
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
TIME: 2,
|
|
102
|
-
COLLATE: 3,
|
|
103
|
-
MONETARY: 4,
|
|
104
|
-
MESSAGES: 5,
|
|
105
|
-
ALL: 6,
|
|
106
|
-
};
|
|
191
|
+
export const LocaleCategory = haveLocaleBinders
|
|
192
|
+
? native.localeCategories()
|
|
193
|
+
: (FALLBACK_CATEGORIES[process.platform] ?? FALLBACK_CATEGORIES.linux);
|
|
107
194
|
|
|
108
195
|
/**
|
|
109
196
|
* The GJS `Gettext` module as a default export — the object shape
|
package/gi.js
CHANGED
|
@@ -832,31 +832,99 @@ function assignTemplateChildren(instance, handle, reg) {
|
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
//
|
|
835
|
+
// The prototype SETTER a GObject property name resolves to, across the three
|
|
836
|
+
// spellings GJS makes equivalent, or undefined when the class declares none.
|
|
836
837
|
//
|
|
837
|
-
//
|
|
838
|
-
//
|
|
839
|
-
//
|
|
840
|
-
//
|
|
841
|
-
//
|
|
842
|
-
//
|
|
843
|
-
//
|
|
838
|
+
// GJS's set_property vfunc writes only the UNDERSCORE spelling
|
|
839
|
+
// (refs/gjs/gi/gobject.cpp jsobj_set_gproperty → JS_SetProperty(underscore_name)),
|
|
840
|
+
// and reaches a camelCase accessor anyway because registerClass has already mirrored
|
|
841
|
+
// whichever spelling the class declared onto the dash, underscore AND camel names
|
|
842
|
+
// (`_checkAccessors`, refs/gjs/modules/core/_common.js). node-gi keeps the engine
|
|
843
|
+
// store as the backing store rather than installing GJS's generated accessors, so it
|
|
844
|
+
// resolves the same three spellings at the point of use instead — same reachability,
|
|
845
|
+
// no accessor synthesis. Learn6502's SourceView is exactly why this matters: its
|
|
846
|
+
// property is `line-numbers` and its accessor is `lineNumbers`.
|
|
847
|
+
function findPropertySetter(userProto, propertyName) {
|
|
848
|
+
const underscored = propertyName.replace(/-/g, '_');
|
|
849
|
+
const camel = propertyName.replace(/-([a-z])/g, (m) => m[1].toUpperCase());
|
|
850
|
+
for (const spelling of [propertyName, underscored, camel]) {
|
|
851
|
+
const desc = findProtoDescriptor(userProto, spelling);
|
|
852
|
+
if (desc !== undefined && typeof desc.set === 'function') return desc.set;
|
|
853
|
+
}
|
|
854
|
+
return undefined;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
// Run a class's own JS setter for a custom property the ENGINE has just stored —
|
|
858
|
+
// the L1 half of NodeGiSetProperty (src/class.cc), which is node-gi's
|
|
859
|
+
// gjs_object_set_gproperty. The engine calls this only once the instance HAS a
|
|
860
|
+
// wrapper, so a set that lands during construction never reaches here; those are
|
|
861
|
+
// replayed by flushPropertiesToJsSetters below.
|
|
862
|
+
function runJsPropertySetter(handle, propertyName) {
|
|
863
|
+
const instance = instanceCache.get(handle);
|
|
864
|
+
if (instance === undefined) return;
|
|
865
|
+
const up = instance[USER_PROTO];
|
|
866
|
+
if (up === undefined) return;
|
|
867
|
+
const set = findPropertySetter(up, propertyName);
|
|
868
|
+
if (set === undefined) return; // no JS accessor: the store is the single backing store
|
|
869
|
+
set.call(instance, wrapReturn(native.getProperty(handle, propertyName)));
|
|
870
|
+
}
|
|
871
|
+
// Guarded because the addon paired with this JS may PREDATE it, and that pairing is
|
|
872
|
+
// deliberate rather than hypothetical: gtk-os-suites.yml's shipped-closure legs run
|
|
873
|
+
// this checkout's JS against the PUBLISHED addon (`NODE_GI_NATIVE=prebuild`, logged
|
|
874
|
+
// as "node-gi JS: (this checkout) / addon: (published)"), so a bare call to a new
|
|
875
|
+
// native export takes those three legs down with `native.<new export> is not a
|
|
876
|
+
// function` until the next release republishes the binary. Every native call added
|
|
877
|
+
// here degrades instead; without the callback, a post-construction set simply does
|
|
878
|
+
// not reach the JS setter, which is what 0.50.0 did.
|
|
879
|
+
if (typeof native.setPropertySetCallback === 'function') {
|
|
880
|
+
native.setPropertySetCallback(runJsPropertySetter);
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
// Which properties the construct-time replay must run.
|
|
884
|
+
//
|
|
885
|
+
// `native.storedPropertyNames` is the honest answer — what was ACTUALLY set, in
|
|
886
|
+
// first-set order. An addon older than this JS does not export it (same cross-version
|
|
887
|
+
// pairing as the guard above), so the fallback is the CONSTRUCT-flag filter this used
|
|
888
|
+
// to be: narrower, since it misses a plain property passed to `new`, but never wrong
|
|
889
|
+
// — GObject does apply CONSTRUCT properties at construction. Keeping it is what makes
|
|
890
|
+
// the shipped-closure legs measure the published addon rather than this gap.
|
|
891
|
+
function propertiesToReplay(handle, reg) {
|
|
892
|
+
if (typeof native.storedPropertyNames === 'function') return native.storedPropertyNames(handle);
|
|
893
|
+
return reg.constructPropertyNames ?? [];
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Replay the properties set DURING construction through the class's own JS SETTERS.
|
|
897
|
+
//
|
|
898
|
+
// A class can declare a GObject property (`Properties: { displayWidth: ParamSpec }`)
|
|
899
|
+
// AND a matching JS accessor (`get/set displayWidth` over a `_displayWidth` backing
|
|
900
|
+
// field). On GJS the set vfunc delegates to the JS setter, so a value applied by
|
|
901
|
+
// g_object_new reaches `_displayWidth` at construction. node-gi builds the wrapper
|
|
902
|
+
// AFTER g_object_new, so during construction there is no USER_PROTO to route through
|
|
903
|
+
// — the value lands only in the engine's per-instance store, and the class's getter
|
|
844
904
|
// (reading `_displayWidth`) sees `undefined` (the Learn6502 Display "DrawingArea is
|
|
845
905
|
// required" wall). Once USER_PROTO is attached (here, right after construction), read
|
|
846
|
-
// each such
|
|
847
|
-
//
|
|
848
|
-
//
|
|
849
|
-
//
|
|
850
|
-
//
|
|
851
|
-
//
|
|
852
|
-
|
|
853
|
-
|
|
906
|
+
// each such value back out and run it through the JS setter, BEFORE the user ctor
|
|
907
|
+
// body runs — the same order GJS produces.
|
|
908
|
+
//
|
|
909
|
+
// WHICH properties: the ones the engine actually STORED, i.e. that something really
|
|
910
|
+
// set (`native.storedPropertyNames`). Not every declared property — flushing those
|
|
911
|
+
// runs a setter for a property nobody assigned, against state the ctor body has not
|
|
912
|
+
// created yet, which is the Learn6502 SourceView `selectable` → `_signalHandlers`
|
|
913
|
+
// forEach crash. "Was it set?" is the honest question and the store is its honest
|
|
914
|
+
// answer; the CONSTRUCT flag is only a proxy for it (GObject applies a CONSTRUCT
|
|
915
|
+
// property's default at construction, so it is stored too, and a plain READWRITE
|
|
916
|
+
// property passed to `new` is stored as well — which GJS also routes through the
|
|
917
|
+
// setter, and the old CONSTRUCT-only filter missed).
|
|
918
|
+
//
|
|
919
|
+
// Only properties whose name resolves to a prototype SETTER are touched, so a plain
|
|
920
|
+
// GObject property (no JS accessor) keeps the store as its single backing store.
|
|
921
|
+
function flushPropertiesToJsSetters(instance, handle, reg) {
|
|
854
922
|
const up = instance[USER_PROTO];
|
|
855
923
|
if (up === undefined) return;
|
|
856
|
-
for (const name of reg
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
859
|
-
|
|
924
|
+
for (const name of propertiesToReplay(handle, reg)) {
|
|
925
|
+
const set = findPropertySetter(up, name);
|
|
926
|
+
if (set === undefined) continue;
|
|
927
|
+
set.call(instance, wrapReturn(native.getProperty(handle, name)));
|
|
860
928
|
}
|
|
861
929
|
}
|
|
862
930
|
|
|
@@ -1605,7 +1673,7 @@ function makeClass(namespace, typeName) {
|
|
|
1605
1673
|
assignTemplateChildren(instance, handle, reg);
|
|
1606
1674
|
// Route construct-time property values through the class's JS setters now
|
|
1607
1675
|
// that USER_PROTO is attached — before the user ctor body runs (GJS order).
|
|
1608
|
-
|
|
1676
|
+
flushPropertiesToJsSetters(instance, handle, reg);
|
|
1609
1677
|
return instance;
|
|
1610
1678
|
}
|
|
1611
1679
|
// `nt` is a subclass of this introspected GObject class but was NEVER passed to
|
|
@@ -2340,13 +2408,11 @@ function registerClass(metaOrClass, maybeClass) {
|
|
|
2340
2408
|
// Record the registration so the introspected base ctor (makeClass) routes
|
|
2341
2409
|
// `new X(args)` (where new.target === klass) to constructType(typeHandle, …) +
|
|
2342
2410
|
// the canonical wrapper. Template children move here from the old Subclass.
|
|
2343
|
-
//
|
|
2344
|
-
//
|
|
2345
|
-
//
|
|
2346
|
-
//
|
|
2347
|
-
//
|
|
2348
|
-
// forEach crash). Matches GJS, which only runs setters for props GObject applies at
|
|
2349
|
-
// construct. Flags are the ABI-stable G_PARAM_CONSTRUCT (1<<2) | CONSTRUCT_ONLY (1<<3).
|
|
2411
|
+
// Which properties the construct-time flush replays is a question about this
|
|
2412
|
+
// INSTANCE ("what was actually set?"), and the engine's per-instance store answers
|
|
2413
|
+
// it directly. These CONSTRUCT / CONSTRUCT_ONLY names are only the FALLBACK for an
|
|
2414
|
+
// addon too old to answer it — see propertiesToReplay. Flags are the ABI-stable
|
|
2415
|
+
// G_PARAM_CONSTRUCT (1<<2) | CONSTRUCT_ONLY (1<<3).
|
|
2350
2416
|
const PARAM_CONSTRUCT_MASK = 0x4 | 0x8;
|
|
2351
2417
|
const constructPropertyNames = properties
|
|
2352
2418
|
.filter((p) => typeof p.flags === 'number' && (p.flags & PARAM_CONSTRUCT_MASK) !== 0)
|
|
@@ -2356,7 +2422,6 @@ function registerClass(metaOrClass, maybeClass) {
|
|
|
2356
2422
|
typeHandle,
|
|
2357
2423
|
children: children.length > 0 ? children : undefined,
|
|
2358
2424
|
internalChildren: internalChildren.length > 0 ? internalChildren : undefined,
|
|
2359
|
-
// CONSTRUCT-property names, for the construct-property flush (below).
|
|
2360
2425
|
constructPropertyNames: constructPropertyNames.length > 0 ? constructPropertyNames : undefined,
|
|
2361
2426
|
});
|
|
2362
2427
|
// Reverse index for runCtorForCObject: the engine identifies a C-created instance
|
package/globals.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export interface GjsImports {
|
|
|
13
13
|
};
|
|
14
14
|
/** `imports.system` — the `@gjsify/node-gi/system` module (process identity + lifecycle). */
|
|
15
15
|
system: SystemModule;
|
|
16
|
-
/** `imports.gettext` — the `@gjsify/node-gi/gettext` module (
|
|
16
|
+
/** `imports.gettext` — the `@gjsify/node-gi/gettext` module (real gettext via GLib + libintl). */
|
|
17
17
|
gettext: GettextModule;
|
|
18
18
|
/** `imports.byteArray` — the legacy GJS byte-array module (GJS semantics: zero-terminated, fatal decode). */
|
|
19
19
|
byteArray: {
|
package/gtk-runtime.js
CHANGED
|
@@ -437,6 +437,28 @@ export function maybeWireGtkWindowingEnv() {
|
|
|
437
437
|
setIfUnset('FONTCONFIG_FILE', fontsConf);
|
|
438
438
|
}
|
|
439
439
|
|
|
440
|
+
// THE FACES, which are a different question from the two lines above and on one of the
|
|
441
|
+
// two platforms not answerable by an environment variable at all.
|
|
442
|
+
//
|
|
443
|
+
// `XDG_DATA_DIRS` already reaches `<bundle>/share/fonts` wherever fontconfig drives
|
|
444
|
+
// Pango — its stock configuration carries `<dir prefix="xdg">fonts</dir>` — so on darwin
|
|
445
|
+
// the set above is enough and this variable is a second, cheaper route to the same files.
|
|
446
|
+
// On WIN32 it is the only route there is: GTK4 uses pangowin32, whose font map is filled
|
|
447
|
+
// exclusively by `pango_font_map_dwrite_populate()` from the DirectWrite system
|
|
448
|
+
// collection, and a `FONTCONFIG_FILE` naming a directory of faces moves that map by ZERO
|
|
449
|
+
// families even when it is the only configuration present (measured on Windows 11 /
|
|
450
|
+
// GTK 4.22.4, both directions — ADR 0038 § W1-W5). The face has to be handed to the map
|
|
451
|
+
// through `add_font_file`, which is a RUNTIME call somebody has to make.
|
|
452
|
+
//
|
|
453
|
+
// So the loader's job here is to name the directory, not to register anything: it runs
|
|
454
|
+
// before the addon loads and has no Pango to talk to. `@gjsify/gtk-host`'s `initFonts()`
|
|
455
|
+
// is the reader — the same handover `gjsify ship` makes for an application's OWN faces
|
|
456
|
+
// through `GJSIFY_FONT_DIR`, one layer down, because these faces belong to the RUNTIME
|
|
457
|
+
// and every application on it needs them. Two variables and not one: an app that ships a
|
|
458
|
+
// brand face must not have to choose between its face and the platform's.
|
|
459
|
+
const runtimeFontDir = join(shareDir, 'fonts');
|
|
460
|
+
if (existsSync(runtimeFontDir)) setIfUnset('GJSIFY_GTK_RUNTIME_FONT_DIR', runtimeFontDir);
|
|
461
|
+
|
|
440
462
|
// GStreamer, which finds its plugins the way GTK finds its schemas: by env,
|
|
441
463
|
// read at init. Without this the bundle can ship every Gst typelib and still
|
|
442
464
|
// play nothing — `Gst.init()` succeeds against an EMPTY registry, so the
|
package/index.js
CHANGED
|
@@ -277,9 +277,29 @@ export const setTemplateCallbackResolver = native.setTemplateCallbackResolver;
|
|
|
277
277
|
// Reflect.constructs the class in adopt mode — see gi.js runCtorForCObject.
|
|
278
278
|
export const setConstructCallback = native.setConstructCallback;
|
|
279
279
|
|
|
280
|
+
// Registers the L1 callback the engine's set_property vfunc invokes after storing a
|
|
281
|
+
// custom property: given (instanceHandle, propertyName) it runs the class's own JS
|
|
282
|
+
// setter — see gi.js runJsPropertySetter. The engine calls it only once the instance
|
|
283
|
+
// HAS a wrapper, so a construct-time set never reaches it; those are replayed from
|
|
284
|
+
// the base ctor over storedPropertyNames.
|
|
285
|
+
export const setPropertySetCallback = native.setPropertySetCallback;
|
|
286
|
+
|
|
287
|
+
// The custom properties actually SET on an instance (the per-instance store's keys) —
|
|
288
|
+
// what that construct-time replay has to push through the class's JS setters.
|
|
289
|
+
export const storedPropertyNames = native.storedPropertyNames;
|
|
290
|
+
|
|
280
291
|
export const logSetWriterFunc = native.logSetWriterFunc;
|
|
281
292
|
export const logSetWriterDefault = native.logSetWriterDefault;
|
|
282
293
|
export const bindPropertyFull = native.bindPropertyFull;
|
|
283
294
|
export const bindingGroupBindFull = native.bindingGroupBindFull;
|
|
284
295
|
|
|
296
|
+
// The libintl half of GjsPrivate — the binders GLib's GIR does not publish, plus
|
|
297
|
+
// the LC_* constants read from the host's own <locale.h>. Consumed by
|
|
298
|
+
// `./gettext.js`; loading THIS module is also what puts the process in the
|
|
299
|
+
// environment's locale (the addon's Init does it, as gjs's entry point does).
|
|
300
|
+
export const setThreadLocale = native.setThreadLocale;
|
|
301
|
+
export const textdomain = native.textdomain;
|
|
302
|
+
export const bindtextdomain = native.bindtextdomain;
|
|
303
|
+
export const localeCategories = native.localeCategories;
|
|
304
|
+
|
|
285
305
|
export default native;
|
package/overrides/package.js
CHANGED
|
@@ -71,13 +71,13 @@ export function createPackage({ requireGi, gettext, imports }) {
|
|
|
71
71
|
},
|
|
72
72
|
|
|
73
73
|
initGettext() {
|
|
74
|
+
// No try/catch: these were no-ops returning null when node-gi had no
|
|
75
|
+
// gettext binding, and the catch that tolerated that would now swallow
|
|
76
|
+
// the one failure worth seeing — a localedir that does not hold the
|
|
77
|
+
// app's catalogs, whose only other symptom is an untranslated UI.
|
|
74
78
|
if (pkg.name) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
gettext.textdomain?.(pkg.name);
|
|
78
|
-
} catch {
|
|
79
|
-
/* passthrough gettext — no catalog, ignore */
|
|
80
|
-
}
|
|
79
|
+
gettext.bindtextdomain(pkg.name, pkg.localedir ?? null);
|
|
80
|
+
gettext.textdomain(pkg.name);
|
|
81
81
|
}
|
|
82
82
|
// The globals GJS apps + compiled Blueprint use pervasively.
|
|
83
83
|
globalThis._ = (msgid) => gettext.gettext(msgid);
|