@gjsify/node-gi 0.13.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +818 -5
- package/binding.gyp +55 -19
- package/cairo.d.ts +187 -0
- package/cairo.js +570 -0
- package/gettext.d.ts +65 -0
- package/gettext.js +128 -0
- package/gi.d.ts +178 -0
- package/gi.js +2018 -23
- package/globals.d.ts +7 -33
- package/globals.js +40 -51
- package/gtk-runtime.d.ts +17 -0
- package/gtk-runtime.js +245 -0
- package/index.d.ts +306 -12
- package/index.js +447 -14
- package/overrides/_signals.js +167 -0
- package/overrides/gio-dbus.js +749 -0
- package/overrides/mainloop.js +60 -0
- package/package.json +45 -4
- package/src/addon.cc +98 -2074
- package/src/cairo.cc +926 -0
- package/src/calls.cc +1425 -0
- package/src/class.cc +1170 -0
- package/src/common.h +624 -0
- package/src/loop.cc +766 -0
- package/src/marshal.cc +1738 -0
- package/src/object.cc +814 -0
- package/src/private.cc +351 -0
- package/src/repo.cc +297 -0
- package/src/signals.cc +535 -0
- package/src/template.cc +116 -0
- package/src/toggle.cc +718 -0
- package/src/variant.cc +587 -0
- package/system.d.ts +49 -0
- package/system.js +116 -0
package/globals.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// SPDX-License-Identifier: MIT
|
|
2
2
|
// @gjsify/node-gi/globals — types for the GJS ambient-globals shim.
|
|
3
3
|
|
|
4
|
+
import type { SystemModule } from './system.js';
|
|
5
|
+
import type { GettextModule } from './gettext.js';
|
|
6
|
+
|
|
4
7
|
/** The legacy GJS `imports` object (a minimal Node-backed subset). */
|
|
5
8
|
export interface GjsImports {
|
|
6
9
|
/** `imports.gi.<Ns>` resolves a namespace; `imports.gi.versions.<Ns>` pins a version. */
|
|
@@ -8,39 +11,10 @@ export interface GjsImports {
|
|
|
8
11
|
versions: Record<string, string | undefined>;
|
|
9
12
|
[namespace: string]: unknown;
|
|
10
13
|
};
|
|
11
|
-
/** `imports.system` — process identity + lifecycle
|
|
12
|
-
system:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
readonly programInvocationName: string;
|
|
16
|
-
readonly programPath: string | null;
|
|
17
|
-
version: number;
|
|
18
|
-
addressOf(): string;
|
|
19
|
-
refcount(): number;
|
|
20
|
-
breakpoint(): void;
|
|
21
|
-
dumpHeap(): void;
|
|
22
|
-
dumpMemoryInfo(): void;
|
|
23
|
-
};
|
|
24
|
-
/** `imports.gettext` — a no-translation passthrough. */
|
|
25
|
-
gettext: {
|
|
26
|
-
gettext(s: string): string;
|
|
27
|
-
dgettext(domain: string, s: string): string;
|
|
28
|
-
dcgettext(domain: string, s: string): string;
|
|
29
|
-
ngettext(s: string, p: string, n: number): string;
|
|
30
|
-
dngettext(domain: string, s: string, p: string, n: number): string;
|
|
31
|
-
pgettext(ctx: string, s: string): string;
|
|
32
|
-
dpgettext(domain: string, ctx: string, s: string): string;
|
|
33
|
-
domain(domain: string): {
|
|
34
|
-
gettext(s: string): string;
|
|
35
|
-
ngettext(s: string, p: string, n: number): string;
|
|
36
|
-
pgettext(ctx: string, s: string): string;
|
|
37
|
-
};
|
|
38
|
-
setlocale(): null;
|
|
39
|
-
bindtextdomain(): null;
|
|
40
|
-
textdomain(): null;
|
|
41
|
-
bindtextdomainCodeset(): null;
|
|
42
|
-
LocaleCategory: Record<string, number>;
|
|
43
|
-
};
|
|
14
|
+
/** `imports.system` — the `@gjsify/node-gi/system` module (process identity + lifecycle). */
|
|
15
|
+
system: SystemModule;
|
|
16
|
+
/** `imports.gettext` — the `@gjsify/node-gi/gettext` module (no-translation passthrough). */
|
|
17
|
+
gettext: GettextModule;
|
|
44
18
|
versions: Record<string, unknown>;
|
|
45
19
|
}
|
|
46
20
|
|
package/globals.js
CHANGED
|
@@ -12,6 +12,17 @@
|
|
|
12
12
|
// Reference: GJS's global definitions (gjs/modules/{print,system,gettext}). The
|
|
13
13
|
// legacy `imports.*` namespace mirrors gjs/modules/esm/gi.js's require shape.
|
|
14
14
|
import { requireGi } from './gi.js';
|
|
15
|
+
// `imports.system` / `imports.gettext` / `imports.cairo` reuse the standalone
|
|
16
|
+
// module implementations — ONE source of truth, also reachable directly as
|
|
17
|
+
// `@gjsify/node-gi/{system,gettext,cairo}` (and the bare `system` / `gettext` /
|
|
18
|
+
// `cairo` specifiers on the `--app node` build).
|
|
19
|
+
import system from './system.js';
|
|
20
|
+
import gettext from './gettext.js';
|
|
21
|
+
import cairo from './cairo.js';
|
|
22
|
+
// Legacy `imports.signals` (the pure-JS Signals mixin) + `imports.mainloop` (the
|
|
23
|
+
// GLib.MainLoop convenience layer) — the GJS script modules many older sources use.
|
|
24
|
+
import * as signalsModule from './overrides/_signals.js';
|
|
25
|
+
import { createMainloop } from './overrides/mainloop.js';
|
|
15
26
|
|
|
16
27
|
// GJS stringifies each argument with String() and joins with a space (no
|
|
17
28
|
// util.inspect object formatting) — match that for fidelity.
|
|
@@ -80,58 +91,36 @@ function makeImports() {
|
|
|
80
91
|
},
|
|
81
92
|
);
|
|
82
93
|
|
|
83
|
-
//
|
|
84
|
-
//
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
refcount() {
|
|
103
|
-
return 0;
|
|
104
|
-
},
|
|
105
|
-
breakpoint() {},
|
|
106
|
-
dumpHeap() {},
|
|
107
|
-
dumpMemoryInfo() {},
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
// Minimal `imports.gettext` — a no-translation passthrough (the strings are
|
|
111
|
-
// returned untranslated, which is the correct fallback when no catalog is
|
|
112
|
-
// bound). Mirrors the surface GJS apps call at module load.
|
|
113
|
-
const identity = (s) => s;
|
|
114
|
-
const gettext = {
|
|
115
|
-
gettext: identity,
|
|
116
|
-
dgettext: (_domain, s) => s,
|
|
117
|
-
dcgettext: (_domain, s) => s,
|
|
118
|
-
ngettext: (s, p, n) => (n === 1 ? s : p),
|
|
119
|
-
dngettext: (_domain, s, p, n) => (n === 1 ? s : p),
|
|
120
|
-
pgettext: (_ctx, s) => s,
|
|
121
|
-
dpgettext: (_domain, _ctx, s) => s,
|
|
122
|
-
domain: (_domain) => ({
|
|
123
|
-
gettext: identity,
|
|
124
|
-
ngettext: (s, p, n) => (n === 1 ? s : p),
|
|
125
|
-
pgettext: (_ctx, s) => s,
|
|
126
|
-
}),
|
|
127
|
-
setlocale: () => null,
|
|
128
|
-
bindtextdomain: () => null,
|
|
129
|
-
textdomain: () => null,
|
|
130
|
-
bindtextdomainCodeset: () => null,
|
|
131
|
-
LocaleCategory: { ALL: 6, COLLATE: 3, CTYPE: 0, MESSAGES: 5, MONETARY: 4, NUMERIC: 1, TIME: 2 },
|
|
94
|
+
// `imports.system` + `imports.gettext` + `imports.cairo` are the standalone
|
|
95
|
+
// module objects (`./system.js` / `./gettext.js` / `./cairo.js`) — the single
|
|
96
|
+
// source of truth for those surfaces, shared with the bare `system` / `gettext` /
|
|
97
|
+
// `cairo` specifiers on Node.
|
|
98
|
+
//
|
|
99
|
+
// `imports.signals` is the pure-JS Signals mixin (`addSignalMethods` + the
|
|
100
|
+
// `_connect`/`_disconnect`/`_emit`/… primitives), the SAME module the DBus proxy
|
|
101
|
+
// surface uses. `imports.mainloop` (a lazy getter — it needs GLib, and a source
|
|
102
|
+
// that never touches the main loop should not pull GLib in) is the thin
|
|
103
|
+
// GLib.MainLoop convenience layer.
|
|
104
|
+
const { addSignalMethods, _connect, _connectAfter, _disconnect, _emit, _signalHandlerIsConnected, _disconnectAll } =
|
|
105
|
+
signalsModule;
|
|
106
|
+
const imports = {
|
|
107
|
+
gi,
|
|
108
|
+
system,
|
|
109
|
+
gettext,
|
|
110
|
+
cairo,
|
|
111
|
+
signals: { addSignalMethods, _connect, _connectAfter, _disconnect, _emit, _signalHandlerIsConnected, _disconnectAll },
|
|
112
|
+
versions: Object.create(null),
|
|
132
113
|
};
|
|
133
|
-
|
|
134
|
-
|
|
114
|
+
let mainloop;
|
|
115
|
+
Object.defineProperty(imports, 'mainloop', {
|
|
116
|
+
get() {
|
|
117
|
+
if (mainloop === undefined) mainloop = createMainloop(requireGi('GLib', '2.0'));
|
|
118
|
+
return mainloop;
|
|
119
|
+
},
|
|
120
|
+
enumerable: true,
|
|
121
|
+
configurable: true,
|
|
122
|
+
});
|
|
123
|
+
return imports;
|
|
135
124
|
}
|
|
136
125
|
|
|
137
126
|
// Side effect on import: seed the globals.
|
package/gtk-runtime.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/** A resolved GTK runtime bundle: native-code dir (dylibs on darwin, DLLs on win32) + typelib dir. */
|
|
3
|
+
export interface GtkRuntimeBundle {
|
|
4
|
+
dir: string;
|
|
5
|
+
libDir: string;
|
|
6
|
+
typelibDir: string;
|
|
7
|
+
}
|
|
8
|
+
/** Resolve the GTK runtime bundle directory for this platform, or null. */
|
|
9
|
+
export function resolveGtkRuntimeBundle(): GtkRuntimeBundle | null;
|
|
10
|
+
/** macOS: re-exec once with DYLD_FALLBACK_LIBRARY_PATH set to the bundle (no-op off darwin). */
|
|
11
|
+
export function maybeReexecForGtkRuntime(): void;
|
|
12
|
+
/** Windows: prepend the bundle's gtk/bin DLL dir to process.env.PATH (no-op off win32). */
|
|
13
|
+
export function maybePrependGtkRuntimeDllPath(): void;
|
|
14
|
+
/** Windows: wire the env for a full-windowing bundle's runtime data — GSettings schemas, gdk-pixbuf loaders, icon themes, fontconfig (no-op off win32 / for a display-free bundle). */
|
|
15
|
+
export function maybeWireGtkWindowingEnv(): void;
|
|
16
|
+
/** Activate the bundled GTK runtime for the native engine, if one is present. */
|
|
17
|
+
export function activateBundledGtkRuntime(native: { prependSearchPath: (p: string) => void }): GtkRuntimeBundle | null;
|
package/gtk-runtime.js
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/node-gi/gtk-runtime — locate + activate a batteries-included, relocated
|
|
3
|
+
// GTK/GObject-Introspection runtime bundle so gi:// namespaces load with NO system
|
|
4
|
+
// or Homebrew GTK (Phase 2 of cross-platform node-gi). Today only darwin-arm64
|
|
5
|
+
// ships such a bundle (@gjsify/gtk-runtime-darwin-arm64); this is a no-op on every
|
|
6
|
+
// other platform, and harmless when no bundle is present (the addon then uses the
|
|
7
|
+
// host's GTK exactly as before).
|
|
8
|
+
import { createRequire } from 'node:module';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join } from 'node:path';
|
|
11
|
+
import { existsSync } from 'node:fs';
|
|
12
|
+
|
|
13
|
+
// NB: node:child_process is intentionally NOT imported at module top level. It is
|
|
14
|
+
// pulled in lazily (via `require` below) ONLY on the darwin re-exec path — importing
|
|
15
|
+
// it eagerly put it in every runtime's module graph, and under Deno that tripped a
|
|
16
|
+
// teardown regression on an unrelated conformance file (all its tests passed but the
|
|
17
|
+
// process exited non-zero). This keeps non-darwin loads free of that side effect.
|
|
18
|
+
const require = createRequire(import.meta.url);
|
|
19
|
+
const here = dirname(fileURLToPath(import.meta.url)); // package root
|
|
20
|
+
|
|
21
|
+
// Sentinel: set on the re-exec so a bundle-activated child never re-execs again.
|
|
22
|
+
const REEXEC_SENTINEL = 'GJSIFY_GTK_REEXEC';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the GTK runtime bundle directory for this platform, or `null`. A valid
|
|
26
|
+
* bundle dir contains the native-code dir (`lib/` relocated dylibs on darwin,
|
|
27
|
+
* `bin/` DLLs on win32) + `girepository-1.0/` (typelibs).
|
|
28
|
+
* Search order (first hit wins):
|
|
29
|
+
* 1. GJSIFY_GTK_RUNTIME env (an explicit bundle dir),
|
|
30
|
+
* 2. node-gi's own prebuilds/<platform>-<arch>/gtk/ (CI/dev staging path),
|
|
31
|
+
* 3. the sibling monorepo dir ../gtk-runtime-<platform>-<arch>/gtk,
|
|
32
|
+
* 4. the published optional dep @gjsify/gtk-runtime-<platform>-<arch>.
|
|
33
|
+
* @returns {{ dir: string, libDir: string, typelibDir: string } | null}
|
|
34
|
+
*/
|
|
35
|
+
export function resolveGtkRuntimeBundle() {
|
|
36
|
+
const tag = `${process.platform}-${process.arch}`;
|
|
37
|
+
const candidates = [];
|
|
38
|
+
|
|
39
|
+
if (process.env.GJSIFY_GTK_RUNTIME) candidates.push(process.env.GJSIFY_GTK_RUNTIME);
|
|
40
|
+
candidates.push(join(here, 'prebuilds', tag, 'gtk'));
|
|
41
|
+
candidates.push(join(here, '..', `gtk-runtime-${tag}`, 'gtk'));
|
|
42
|
+
try {
|
|
43
|
+
// The package's index.js sits at its root; the bundle is ./gtk beside it.
|
|
44
|
+
const pkgIndex = require.resolve(`@gjsify/gtk-runtime-${tag}`);
|
|
45
|
+
candidates.push(join(dirname(pkgIndex), 'gtk'));
|
|
46
|
+
} catch {
|
|
47
|
+
// Not installed — fine.
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// The loadable native code lives in `lib` (dylibs) on macOS and `bin` (DLLs) on
|
|
51
|
+
// Windows. `libDir` is the dir node-gi adds to the OS library search path
|
|
52
|
+
// (DYLD_FALLBACK on darwin, PATH on win32) — its NAME differs, its ROLE does not.
|
|
53
|
+
const nativeSubdir = process.platform === 'win32' ? 'bin' : 'lib';
|
|
54
|
+
for (const dir of candidates) {
|
|
55
|
+
if (!dir) continue;
|
|
56
|
+
const libDir = join(dir, nativeSubdir);
|
|
57
|
+
const typelibDir = join(dir, 'girepository-1.0');
|
|
58
|
+
if (existsSync(libDir) && existsSync(typelibDir)) return { dir, libDir, typelibDir };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Make the bundled GTK runtime genuinely env-free by RE-EXECING this process once
|
|
65
|
+
* with `DYLD_FALLBACK_LIBRARY_PATH` (+ `GI_TYPELIB_PATH`) pointed at the bundle.
|
|
66
|
+
*
|
|
67
|
+
* Why a re-exec and not just `process.env` mutation: on macOS, dyld captures
|
|
68
|
+
* `DYLD_FALLBACK_LIBRARY_PATH` at PROCESS LAUNCH, so setting it from JS never
|
|
69
|
+
* affects the running dyld. It is load-bearing beyond the addon's own link
|
|
70
|
+
* closure: GObject-Introspection resolves a type's `get_type()` (e.g. for
|
|
71
|
+
* `registerClass` subclassing) — and the Pango/Gdk/Graphene backers — via
|
|
72
|
+
* `g_module_open(<leaf soname>)`, and dyld will NOT satisfy a bare-leaf dlopen
|
|
73
|
+
* from an `@loader_path`-loaded image. Re-launching with the fallback set (exactly
|
|
74
|
+
* what the Homebrew-based CI leg does) resolves every such leaf lookup from the
|
|
75
|
+
* bundle. Guarded by a sentinel env var so it fires AT MOST ONCE; a no-op unless
|
|
76
|
+
* darwin + a bundle is present + the fallback does not already cover it.
|
|
77
|
+
*
|
|
78
|
+
* MUST be called at module top-level BEFORE the native addon (and thus its GTK
|
|
79
|
+
* dependency closure) is dlopen'd — the re-exec then loads everything correctly.
|
|
80
|
+
* On re-exec this calls `process.exit()` and never returns to the caller.
|
|
81
|
+
* @returns {void}
|
|
82
|
+
*/
|
|
83
|
+
export function maybeReexecForGtkRuntime() {
|
|
84
|
+
if (process.platform !== 'darwin') return; // strict no-op on every non-darwin runtime
|
|
85
|
+
// Node only: the argv/execArgv reconstruction below is Node-shaped, and Node is
|
|
86
|
+
// the runtime the conformance proves. Bun/Deno set globals of their own — skip the
|
|
87
|
+
// re-exec there (they still get env-free TYPELIBS via activateBundledGtkRuntime;
|
|
88
|
+
// their DYLD path for non-addon-linked backers is a documented follow-up), so the
|
|
89
|
+
// darwin path can never spawn a malformed re-exec under a non-Node runtime.
|
|
90
|
+
if (typeof globalThis.Bun !== 'undefined' || typeof globalThis.Deno !== 'undefined') return;
|
|
91
|
+
if (process.env[REEXEC_SENTINEL]) return; // already re-exec'd — the child path
|
|
92
|
+
const bundle = resolveGtkRuntimeBundle();
|
|
93
|
+
if (!bundle) return; // strict no-op when no bundle is present
|
|
94
|
+
|
|
95
|
+
const curDyld = process.env.DYLD_FALLBACK_LIBRARY_PATH ?? '';
|
|
96
|
+
if (curDyld.split(':').filter(Boolean).includes(bundle.libDir)) return; // already covered
|
|
97
|
+
|
|
98
|
+
// Lazily load node:child_process ONLY here (darwin + bundle present + not yet
|
|
99
|
+
// covered) — see the top-of-file note; keeps it out of non-darwin module graphs.
|
|
100
|
+
const { spawnSync } = require('node:child_process');
|
|
101
|
+
const curTypelib = process.env.GI_TYPELIB_PATH ?? '';
|
|
102
|
+
const env = {
|
|
103
|
+
...process.env,
|
|
104
|
+
[REEXEC_SENTINEL]: '1',
|
|
105
|
+
DYLD_FALLBACK_LIBRARY_PATH: curDyld ? `${bundle.libDir}:${curDyld}` : `${bundle.libDir}:/usr/lib`,
|
|
106
|
+
GI_TYPELIB_PATH: curTypelib ? `${bundle.typelibDir}:${curTypelib}` : bundle.typelibDir,
|
|
107
|
+
};
|
|
108
|
+
// Reproduce this exact invocation (node flags + argv) with the augmented env.
|
|
109
|
+
const res = spawnSync(process.execPath, [...process.execArgv, ...process.argv.slice(1)], {
|
|
110
|
+
stdio: 'inherit',
|
|
111
|
+
env,
|
|
112
|
+
});
|
|
113
|
+
if (res.error) throw res.error; // spawn failed outright — surface it, don't silently continue mis-linked
|
|
114
|
+
process.exit(res.status === null ? 1 : res.status);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Prepend the bundled GTK runtime's DLL dir (`gtk/bin`) to `process.env.PATH` so a
|
|
119
|
+
* later `LoadLibrary` resolves against the bundle with NO gvsbuild/system GTK — both
|
|
120
|
+
* the native addon's OWN static imports (glib/gobject/gio/girepository/cairo/ffi/…)
|
|
121
|
+
* AND every runtime `g_module_open(<soname>)` a typelib does for its backers
|
|
122
|
+
* (Pango/Gdk/Graphene). This is the Windows analog of the darwin re-exec, but
|
|
123
|
+
* SIMPLER: Windows re-reads the DLL search path at EACH `LoadLibrary` (unlike dyld,
|
|
124
|
+
* which captures `DYLD_FALLBACK_LIBRARY_PATH` only at process launch), so an
|
|
125
|
+
* in-process `process.env.PATH` mutation is sufficient — no re-exec, and no native
|
|
126
|
+
* `AddDllDirectory` (which would be chicken-and-egg: the addon must already be loaded
|
|
127
|
+
* to expose it). MUST be called at module top-level BEFORE the addon is `require()`'d
|
|
128
|
+
* (index.js does exactly that, before loadNative()). Strict no-op off win32 / without
|
|
129
|
+
* a bundle / when the dir is already on PATH. Idempotent.
|
|
130
|
+
* @returns {void}
|
|
131
|
+
*/
|
|
132
|
+
export function maybePrependGtkRuntimeDllPath() {
|
|
133
|
+
if (process.platform !== 'win32') return; // strict no-op on every non-win32 runtime
|
|
134
|
+
const bundle = resolveGtkRuntimeBundle();
|
|
135
|
+
if (!bundle) return; // strict no-op when no bundle is present
|
|
136
|
+
const binDir = bundle.libDir; // on win32 the native-code dir is gtk/bin (the DLLs)
|
|
137
|
+
const cur = process.env.PATH ?? '';
|
|
138
|
+
if (cur.split(';').filter(Boolean).includes(binDir)) return; // already on PATH
|
|
139
|
+
process.env.PATH = cur ? `${binDir};${cur}` : binDir;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Wire the env a bundled FULL-WINDOWING GTK runtime needs so a REAL GTK WINDOW
|
|
144
|
+
* finds its runtime DATA on Windows: compiled GSettings schemas
|
|
145
|
+
* (`GSETTINGS_SCHEMA_DIR`), the gdk-pixbuf image loaders (`GDK_PIXBUF_MODULEDIR` +
|
|
146
|
+
* `GDK_PIXBUF_MODULE_FILE`), the icon themes (`XDG_DATA_DIRS` → `<bundle>/share`),
|
|
147
|
+
* and — when bundled — Fontconfig (`FONTCONFIG_PATH`/`FONTCONFIG_FILE`). These live
|
|
148
|
+
* beside the DLLs the display-free path already wires (maybePrependGtkRuntimeDllPath).
|
|
149
|
+
*
|
|
150
|
+
* STRICT no-op off win32, without a bundle, or when the bundle carries NO windowing
|
|
151
|
+
* data — keyed on the `share/glib-2.0/schemas/gschemas.compiled` marker the
|
|
152
|
+
* --windowing build produces, so the DEFAULT display-free bundle is byte-unchanged.
|
|
153
|
+
* Windows re-reads these vars at first use (schema/loader/icon-theme init runs AFTER
|
|
154
|
+
* the addon loads), so an in-process mutation here suffices — no re-exec, the DLL-
|
|
155
|
+
* search analog. Each var is set only when currently unset (a host override wins).
|
|
156
|
+
* Idempotent; MUST run at module top-level with maybePrependGtkRuntimeDllPath, before
|
|
157
|
+
* the app initializes GTK. GLib uses `;` as the win32 search-path separator.
|
|
158
|
+
* @returns {void}
|
|
159
|
+
*/
|
|
160
|
+
export function maybeWireGtkWindowingEnv() {
|
|
161
|
+
if (process.platform !== 'win32') return; // strict no-op on every non-win32 runtime
|
|
162
|
+
const bundle = resolveGtkRuntimeBundle();
|
|
163
|
+
if (!bundle) return; // strict no-op when no bundle is present
|
|
164
|
+
|
|
165
|
+
const schemaDir = join(bundle.dir, 'share', 'glib-2.0', 'schemas');
|
|
166
|
+
// gschemas.compiled = the windowing-data marker; absent → display-free bundle.
|
|
167
|
+
if (!existsSync(join(schemaDir, 'gschemas.compiled'))) return;
|
|
168
|
+
|
|
169
|
+
const setIfUnset = (name, value) => {
|
|
170
|
+
if (!process.env[name]) process.env[name] = value;
|
|
171
|
+
};
|
|
172
|
+
setIfUnset('GSETTINGS_SCHEMA_DIR', schemaDir);
|
|
173
|
+
|
|
174
|
+
const loaderCache = join(bundle.dir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders.cache');
|
|
175
|
+
if (existsSync(loaderCache)) {
|
|
176
|
+
setIfUnset('GDK_PIXBUF_MODULEDIR', join(bundle.dir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders'));
|
|
177
|
+
setIfUnset('GDK_PIXBUF_MODULE_FILE', loaderCache);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const shareDir = join(bundle.dir, 'share');
|
|
181
|
+
if (existsSync(join(shareDir, 'icons'))) {
|
|
182
|
+
const cur = process.env.XDG_DATA_DIRS ?? '';
|
|
183
|
+
if (!cur.split(';').filter(Boolean).includes(shareDir)) {
|
|
184
|
+
process.env.XDG_DATA_DIRS = cur ? `${shareDir};${cur}` : shareDir;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const fontsConf = join(bundle.dir, 'etc', 'fonts', 'fonts.conf');
|
|
189
|
+
if (existsSync(fontsConf)) {
|
|
190
|
+
setIfUnset('FONTCONFIG_PATH', join(bundle.dir, 'etc', 'fonts'));
|
|
191
|
+
setIfUnset('FONTCONFIG_FILE', fontsConf);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let activated = null; // memoize: the activation result (idempotent)
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Activate the bundled GTK runtime for the native engine, if one is present.
|
|
199
|
+
* • typelibs — prepend the bundle's girepository-1.0 dir to the GIRepository
|
|
200
|
+
* search path (env-free, via the native prependSearchPath); this alone lets
|
|
201
|
+
* node-gi FIND the bundled typelibs without GI_TYPELIB_PATH.
|
|
202
|
+
* • native code — darwin: prepend the bundle's lib dir to
|
|
203
|
+
* process.env.DYLD_FALLBACK_LIBRARY_PATH. NB dyld captures that variable at
|
|
204
|
+
* LAUNCH, so setting it here only helps child processes / a re-exec; a bundle
|
|
205
|
+
* whose dylibs a namespace loads by leaf name still needs the value present in
|
|
206
|
+
* the launching environment (see the package README's env-free caveat).
|
|
207
|
+
* Namespaces whose dylib is already in-process via the addon's link closure
|
|
208
|
+
* (GLib/GObject/Gio/cairo) resolve regardless. win32: prepend the bundle's bin
|
|
209
|
+
* dir to process.env.PATH — already done by maybePrependGtkRuntimeDllPath before
|
|
210
|
+
* the addon loaded; re-asserted here for child processes + the runtime
|
|
211
|
+
* g_module_open of typelib backers. Windows re-reads PATH per LoadLibrary, so no
|
|
212
|
+
* launch-time capture applies (see the package README).
|
|
213
|
+
* Scoped to darwin + win32 (the platforms shipping a batteries-included bundle).
|
|
214
|
+
* @param {{ prependSearchPath: (p: string) => void }} native the loaded addon
|
|
215
|
+
* @returns {{ dir: string, libDir: string, typelibDir: string } | null}
|
|
216
|
+
*/
|
|
217
|
+
export function activateBundledGtkRuntime(native) {
|
|
218
|
+
if (activated !== null) return activated || null;
|
|
219
|
+
activated = false;
|
|
220
|
+
if (process.platform !== 'darwin' && process.platform !== 'win32') return null;
|
|
221
|
+
|
|
222
|
+
const bundle = resolveGtkRuntimeBundle();
|
|
223
|
+
if (!bundle) return null;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
native.prependSearchPath(bundle.typelibDir);
|
|
227
|
+
} catch {
|
|
228
|
+
// A stubbed/old addon without prependSearchPath — non-fatal.
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
if (process.platform === 'win32') {
|
|
232
|
+
const existing = process.env.PATH ?? '';
|
|
233
|
+
if (!existing.split(';').filter(Boolean).includes(bundle.libDir)) {
|
|
234
|
+
process.env.PATH = existing ? `${bundle.libDir};${existing}` : bundle.libDir;
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
const existing = process.env.DYLD_FALLBACK_LIBRARY_PATH;
|
|
238
|
+
if (!existing || !existing.split(':').includes(bundle.libDir)) {
|
|
239
|
+
process.env.DYLD_FALLBACK_LIBRARY_PATH = existing ? `${bundle.libDir}:${existing}` : `${bundle.libDir}:/usr/lib`;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
activated = bundle;
|
|
244
|
+
return bundle;
|
|
245
|
+
}
|