@gjsify/node-gi 0.51.1 → 0.53.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 +2 -1
- package/binding.gyp +1 -0
- package/gi.js +83 -1
- package/gtk-runtime.d.ts +31 -0
- package/gtk-runtime.js +288 -21
- package/index.d.ts +14 -0
- package/index.js +18 -3
- package/package.json +13 -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/src/addon.cc +1 -0
- package/src/calls.cc +10 -34
- package/src/class.cc +14 -37
- package/src/common.h +38 -0
- package/src/loop.cc +28 -4
- package/src/marshal.cc +371 -9
- package/src/opengl-win32.cc +234 -0
- package/src/toggle.cc +1 -1
- package/system-gi.js +1 -1
package/README.md
CHANGED
|
@@ -124,8 +124,9 @@ console.log(action.get_name()); // 'greet' — introspected method
|
|
|
124
124
|
action.enabled = false; // property set → set_property
|
|
125
125
|
|
|
126
126
|
const cancellable = new Gio.Cancellable();
|
|
127
|
-
cancellable.
|
|
127
|
+
cancellable.connect_after('cancelled', () => console.log('cancelled'));
|
|
128
128
|
cancellable.cancel(); // fires the signal
|
|
129
|
+
// (plain `connect` is Cancellable's OWN g_cancellable_connect(cb), as on gjs)
|
|
129
130
|
|
|
130
131
|
console.log(Gio.BusType.SESSION); // 2 — enums, flags and constants
|
|
131
132
|
const file = Gio.File.new_for_path('/usr/bin/gjs');
|
package/binding.gyp
CHANGED
package/gi.js
CHANGED
|
@@ -18,6 +18,9 @@ import { setImmediate, setInterval, clearInterval } from 'node:timers';
|
|
|
18
18
|
// loop-liveness hooks below would silently never fire.
|
|
19
19
|
import runtimeProcess from 'node:process';
|
|
20
20
|
import { createGioDBus } from './overrides/gio-dbus.js';
|
|
21
|
+
// The windowing loader's record of what it wrote into process.env. Read here and not there
|
|
22
|
+
// because replaying it needs a GLib (mirrorWindowingEnvIntoCrt below).
|
|
23
|
+
import { windowingEnvWrites } from './gtk-runtime.js';
|
|
21
24
|
|
|
22
25
|
// The raw native GObject handle on a wrapped instance, so it can be unwrapped again
|
|
23
26
|
// when passed back into the engine as a GI argument.
|
|
@@ -33,6 +36,19 @@ const USER_PROTO = Symbol('nodeGiUserProto');
|
|
|
33
36
|
// make every wrapper look thenable).
|
|
34
37
|
const RESERVED = new Set(['then', 'toString', 'valueOf', 'constructor', 'inspect']);
|
|
35
38
|
|
|
39
|
+
// Names the instance wrapper's `get` trap otherwise shortcuts straight to the
|
|
40
|
+
// GObject signal API (see wrapInstance below). A concrete class can introspect
|
|
41
|
+
// its OWN method under one of these — `Soup.Server.disconnect(): void` is
|
|
42
|
+
// `soup_server_disconnect`, zero-arg, nothing to do with a signal handler id —
|
|
43
|
+
// and on gjs that own prototype property already shadows the inherited
|
|
44
|
+
// override through ordinary JS lookup. This Proxy has no such chain, so
|
|
45
|
+
// `wrapInstance` checks for an own introspected member under the name FIRST
|
|
46
|
+
// and only falls through to the shortcut when the class declares none.
|
|
47
|
+
// Regression: `Http2SecureServer.close()` called `this._soupServer.disconnect()`
|
|
48
|
+
// (real libsoup API, zero args) and got `disconnectSignal(handle, handlerId:
|
|
49
|
+
// number)` — the shortcut's `disconnect(id)` — under node-gi on Node.
|
|
50
|
+
const SIGNAL_API_SHORTCUTS = new Set(['connect', 'connect_after', 'emit', 'disconnect']);
|
|
51
|
+
|
|
36
52
|
// GJS accepts both snake_case and camelCase for methods/properties: map a JS accessor
|
|
37
53
|
// to the GI method name (snake_case) and to a GObject property name (kebab-case); a
|
|
38
54
|
// name already in the target case passes through.
|
|
@@ -1301,6 +1317,19 @@ function wrapInstance(handle, userProto) {
|
|
|
1301
1317
|
get(t, prop) {
|
|
1302
1318
|
if (prop === HANDLE) return handle;
|
|
1303
1319
|
if (typeof prop !== 'string' || RESERVED.has(prop)) return t[prop];
|
|
1320
|
+
if (SIGNAL_API_SHORTCUTS.has(prop)) {
|
|
1321
|
+
const up = t[USER_PROTO];
|
|
1322
|
+
const own = up !== undefined ? findProtoDescriptor(up, prop) : undefined;
|
|
1323
|
+
if (own !== undefined) return typeof own.get === 'function' ? own.get.call(proxy) : own.value;
|
|
1324
|
+
// An INTERFACE method under the name never reaches a class prototype
|
|
1325
|
+
// when the concrete type is private: GSocks5Proxy wraps as its nearest
|
|
1326
|
+
// introspectable ancestor (GObject.Object), yet on gjs 1.88.1
|
|
1327
|
+
// `socks5.connect` IS g_proxy_connect (Gio.Proxy, 3 IN args) — measured.
|
|
1328
|
+
// The same native walk the GI-method fallback below uses sees it.
|
|
1329
|
+
if (instanceHasMethod(handle, prop)) {
|
|
1330
|
+
return (...args) => wrapReturn(native.callMethod(handle, prop, unwrapArgs(args)));
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1304
1333
|
switch (prop) {
|
|
1305
1334
|
case '$typeName':
|
|
1306
1335
|
// The instance's concrete RUNTIME GType name —
|
|
@@ -2295,8 +2324,15 @@ function collectVfuncs(klass) {
|
|
|
2295
2324
|
if (desc === undefined || typeof desc.value !== 'function') continue;
|
|
2296
2325
|
seen.add(key);
|
|
2297
2326
|
const userFn = desc.value;
|
|
2327
|
+
// The ARGUMENTS cross the same boundary `this` does and need the same
|
|
2328
|
+
// treatment: the engine hands a GObject arg over as a raw handle, so a
|
|
2329
|
+
// `Gtk.LayoutManager.vfunc_measure(widget, …)` override received an object
|
|
2330
|
+
// with no prototype and `widget.get_ancestor` was `undefined`. The return
|
|
2331
|
+
// goes back through `unwrapArg` because the engine reads a GObject return
|
|
2332
|
+
// as a handle, never as the wrapper user code naturally hands back.
|
|
2333
|
+
// Same contract as `wrapCallbackFn`, which callbacks have always had.
|
|
2298
2334
|
vfuncs[key.slice('vfunc_'.length)] = function (...args) {
|
|
2299
|
-
return userFn.apply(wrapInstance(this, klass.prototype), args);
|
|
2335
|
+
return unwrapArg(userFn.apply(wrapInstance(this, klass.prototype), args.map(wrapReturn)));
|
|
2300
2336
|
};
|
|
2301
2337
|
}
|
|
2302
2338
|
}
|
|
@@ -2682,6 +2718,45 @@ function namespaceObject(namespace) {
|
|
|
2682
2718
|
// startMainLoop is itself idempotent; this just avoids the extra call.
|
|
2683
2719
|
let loopAttached = false;
|
|
2684
2720
|
|
|
2721
|
+
// Whether the windowing loader's win32 env writes have been copied into the C runtime's
|
|
2722
|
+
// environment yet. Set BEFORE the copy runs, because the copy calls requireGi() again.
|
|
2723
|
+
let windowingEnvMirrored = false;
|
|
2724
|
+
|
|
2725
|
+
/**
|
|
2726
|
+
* Give the bundled GTK's `getenv()` readers the variables the loader wrote, on win32.
|
|
2727
|
+
*
|
|
2728
|
+
* THE VARIABLE WAS SET AND UNREACHABLE, which reads in a log exactly like a platform
|
|
2729
|
+
* declining a request — and was read that way once (#1668). A Windows process carries
|
|
2730
|
+
* two environments: the Win32 block, which `GetEnvironmentVariableW()`, `g_getenv()`, the DLL
|
|
2731
|
+
* loader and every child process read, and the C runtime's own copy, which is what `getenv()`
|
|
2732
|
+
* returns. Node's `process.env` setter is `uv_os_setenv()` → `SetEnvironmentVariableW()` and
|
|
2733
|
+
* nothing else, so it writes the first and not the second. `pango_cairo_font_map_new()` reads
|
|
2734
|
+
* `getenv("PANGOCAIRO_BACKEND")` and fontconfig reads `getenv("FONTCONFIG_FILE")`: both saw
|
|
2735
|
+
* an empty environment while `process.env` reported the values back correctly.
|
|
2736
|
+
*
|
|
2737
|
+
* `g_setenv()` is the repair because glib already solved this — it calls `_wputenv()` first
|
|
2738
|
+
* and `SetEnvironmentVariableW()` second, and says why in its own source. Called through the
|
|
2739
|
+
* bundle's OWN GLib, so the runtime copy written is the one pango and fontconfig link against.
|
|
2740
|
+
* `overwrite` is true and that is not a policy decision: every value replayed came from
|
|
2741
|
+
* `setIfUnset`, so a value the operator set is already the value being written back.
|
|
2742
|
+
*
|
|
2743
|
+
* TIMING is why this is not in the loader: both readers are lazy — pango at the first font
|
|
2744
|
+
* map, fontconfig at the first config load — and neither can fire before a namespace is
|
|
2745
|
+
* required, while GLib cannot be called before the addon is loaded. The first `requireGi()` is
|
|
2746
|
+
* the one moment after the second and before the first.
|
|
2747
|
+
*
|
|
2748
|
+
* Strict no-op off win32 (POSIX `setenv()` is the only environment there, and Node uses it)
|
|
2749
|
+
* and when the windowing wiring made no writes. Held by `test/font-script-coverage.test.mjs`,
|
|
2750
|
+
* which measures the loader-set value against the same value put in the LAUNCH environment.
|
|
2751
|
+
* @returns {void}
|
|
2752
|
+
*/
|
|
2753
|
+
function mirrorWindowingEnvIntoCrt() {
|
|
2754
|
+
const writes = windowingEnvWrites();
|
|
2755
|
+
if (writes.length === 0) return;
|
|
2756
|
+
const GLib = requireGi('GLib', '2.0');
|
|
2757
|
+
for (const [name, value] of writes) GLib.setenv(name, value, true);
|
|
2758
|
+
}
|
|
2759
|
+
|
|
2685
2760
|
/**
|
|
2686
2761
|
* Require a GObject-Introspection namespace and return a GJS-shaped namespace
|
|
2687
2762
|
* object. The Node twin of `import Ns from 'gi://Ns?version=X'` /
|
|
@@ -2692,6 +2767,13 @@ let loopAttached = false;
|
|
|
2692
2767
|
*/
|
|
2693
2768
|
export function requireGi(namespace, version) {
|
|
2694
2769
|
native.requireNamespace(namespace, version);
|
|
2770
|
+
// The one moment the win32 CRT-environment mirror can run: the addon is loaded, so there
|
|
2771
|
+
// is a GLib to call, and nothing has built a font map or loaded a fontconfig
|
|
2772
|
+
// configuration yet. Flag first — mirrorWindowingEnvIntoCrt() re-enters here for GLib.
|
|
2773
|
+
if (!windowingEnvMirrored) {
|
|
2774
|
+
windowingEnvMirrored = true;
|
|
2775
|
+
mirrorWindowingEnvIntoCrt();
|
|
2776
|
+
}
|
|
2695
2777
|
// Attach the libuv↔GLib integration once. On Node this arms BOTH directions:
|
|
2696
2778
|
// the uv-in-GLib bridge (a blocking GLib.MainLoop.run / Gio.Application.run
|
|
2697
2779
|
// keeps Node's event loop alive) AND the uv-driven auto-pump (pending GLib
|
package/gtk-runtime.d.ts
CHANGED
|
@@ -26,3 +26,34 @@ export function appGiLibraryDirs(opts?: { platform?: NodeJS.Platform | string; e
|
|
|
26
26
|
export function activateGiLibraryPath(native: { prependLibraryPath?: (p: string) => void }): string[];
|
|
27
27
|
/** TEST-ONLY: allow the activation to run again. */
|
|
28
28
|
export function resetGiLibraryPathForTests(): void;
|
|
29
|
+
|
|
30
|
+
/** What `native.probeHostOpenGL()` reports on win32 — each field "" when absent. */
|
|
31
|
+
export interface HostOpenGL {
|
|
32
|
+
wddmIcd: string;
|
|
33
|
+
registryIcd: string;
|
|
34
|
+
loadedFrom: string;
|
|
35
|
+
}
|
|
36
|
+
/** The OpenGL a win32 process uses: the optional GL package's Mesa opengl32.dll or the host's. */
|
|
37
|
+
export interface OpenGLDecision {
|
|
38
|
+
source: 'bundle' | 'system';
|
|
39
|
+
reason: string;
|
|
40
|
+
/** Set when the host has no ICD and the optional GL package is not installed. */
|
|
41
|
+
missing?: true;
|
|
42
|
+
}
|
|
43
|
+
/** `@gjsify/gl-runtime-<tag>` — the optional package carrying Mesa for driverless hosts. */
|
|
44
|
+
export function glRuntimePackageName(tag?: string): string;
|
|
45
|
+
/** The optional GL package's opengl32.dll (monorepo sibling or installed package), or null. */
|
|
46
|
+
export function resolveGlRuntime(): string | null;
|
|
47
|
+
/** Decide which OpenGL serves GTK on win32 (pure). `override` is `GJSIFY_OPENGL`. */
|
|
48
|
+
export function decideOpenGLSource(opts: {
|
|
49
|
+
bundled: string | null;
|
|
50
|
+
host: HostOpenGL;
|
|
51
|
+
override?: string;
|
|
52
|
+
}): OpenGLDecision;
|
|
53
|
+
/** win32: preload the optional GL package's opengl32.dll when the host has no OpenGL ICD (no-op elsewhere). */
|
|
54
|
+
export function activateBundledOpenGL(native: {
|
|
55
|
+
probeHostOpenGL?: () => HostOpenGL;
|
|
56
|
+
preloadOpenGL?: (path: string) => string;
|
|
57
|
+
}): (OpenGLDecision & { loadedFrom: string | null }) | null;
|
|
58
|
+
/** The decision `activateBundledOpenGL` made for this process, or null. */
|
|
59
|
+
export function openGLActivation(): (OpenGLDecision & { loadedFrom: string | null }) | null;
|
package/gtk-runtime.js
CHANGED
|
@@ -25,6 +25,31 @@ const here = dirname(fileURLToPath(import.meta.url)); // package root
|
|
|
25
25
|
// Sentinel: set on the re-exec so a bundle-activated child never re-execs again.
|
|
26
26
|
const REEXEC_SENTINEL = 'GJSIFY_GTK_REEXEC';
|
|
27
27
|
|
|
28
|
+
/** @type {Array<[string, string]>} every write maybeWireGtkWindowingEnv() made, in order. */
|
|
29
|
+
let windowingEnvWritesMade = [];
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* What `maybeWireGtkWindowingEnv()` wrote into `process.env`, as `[name, value]` pairs.
|
|
33
|
+
*
|
|
34
|
+
* A WINDOWS PROCESS HAS TWO ENVIRONMENTS and a JS `process.env` write reaches one of them.
|
|
35
|
+
* Node's setter is `uv_os_setenv()`, which is `SetEnvironmentVariableW()` and nothing else:
|
|
36
|
+
* that updates the Win32 environment block — what `GetEnvironmentVariableW()`, `g_getenv()`,
|
|
37
|
+
* the DLL loader and every child process read — and never the C runtime's own copy, which is
|
|
38
|
+
* what `getenv()` returns. Libraries in the bundle use BOTH: GLib, GIO, gdk-pixbuf and GTK go
|
|
39
|
+
* through `g_getenv()` and saw every variable all along, while `pango_cairo_font_map_new()`
|
|
40
|
+
* and fontconfig's config lookup call `getenv()` and saw none of them. That is measured
|
|
41
|
+
* rather than reasoned: see the PANGOCAIRO_BACKEND note in `maybeWireGtkWindowingEnv()`.
|
|
42
|
+
*
|
|
43
|
+
* The repair is `g_setenv()`, which glib implements as `_wputenv()` + `SetEnvironmentVariableW()`
|
|
44
|
+
* for exactly this reason — so it needs a loaded GLib, which does not exist when the loader
|
|
45
|
+
* runs. `mirrorWindowingEnvIntoCrt()` in `gi.js` replays this list on the first `requireGi()`.
|
|
46
|
+
* Empty off win32 (POSIX `setenv()` has one environment) and empty when the wiring no-opped.
|
|
47
|
+
* @returns {Array<[string, string]>}
|
|
48
|
+
*/
|
|
49
|
+
export function windowingEnvWrites() {
|
|
50
|
+
return process.platform === 'win32' ? windowingEnvWritesMade.slice() : [];
|
|
51
|
+
}
|
|
52
|
+
|
|
28
53
|
/**
|
|
29
54
|
* Resolve the GTK runtime bundle directory for this platform, or `null`. A valid
|
|
30
55
|
* bundle dir contains the native-code dir (`lib/` relocated dylibs on darwin,
|
|
@@ -64,6 +89,22 @@ export function resolveGtkRuntimeBundle() {
|
|
|
64
89
|
return null;
|
|
65
90
|
}
|
|
66
91
|
|
|
92
|
+
/**
|
|
93
|
+
* The bundle the GTK-in-use is served by, but only when it can open a WINDOW — the
|
|
94
|
+
* `share/glib-2.0/schemas/gschemas.compiled` marker the --windowing build produces.
|
|
95
|
+
* Null for no bundle, a system GTK, or the DEFAULT display-free bundle.
|
|
96
|
+
*
|
|
97
|
+
* ONE definition of that marker for both callers that need it: the windowing env
|
|
98
|
+
* wiring, which is a strict no-op without the data it locates, and the OpenGL advice
|
|
99
|
+
* below, which must not tell a headless process to add a GL package. Spelled twice the
|
|
100
|
+
* question "can this process show a window?" had two answers to drift apart.
|
|
101
|
+
* @returns {{ dir: string, libDir: string, typelibDir: string } | null}
|
|
102
|
+
*/
|
|
103
|
+
function windowingBundle() {
|
|
104
|
+
const bundle = gtkSource() === 'bundle' ? resolveGtkRuntimeBundle() : null;
|
|
105
|
+
return bundle && existsSync(join(bundle.dir, 'share', 'glib-2.0', 'schemas', 'gschemas.compiled')) ? bundle : null;
|
|
106
|
+
}
|
|
107
|
+
|
|
67
108
|
// ---- WHICH GTK WINS ---------------------------------------------------------
|
|
68
109
|
//
|
|
69
110
|
// Two GTKs can be reachable at once: a batteries-included bundle
|
|
@@ -283,7 +324,7 @@ export function maybeReexecForGtkRuntime() {
|
|
|
283
324
|
// gjs process, one hop earlier: `buildNativeEnv()` in `@gjsify/cli` puts
|
|
284
325
|
// `systemGiLibraryDirs()` on the child's `DYLD_FALLBACK_LIBRARY_PATH` (its
|
|
285
326
|
// `system-gi.ts` is the pinned mirror of this module's `system-gi.js`). A gjs
|
|
286
|
-
// launched by hand keeps the gap — tracked in `status/open-todos.md`.
|
|
327
|
+
// launched by hand keeps the gap — tracked in `status/open-todos/README.md`.
|
|
287
328
|
// Same probe index.js uses for RUNTIME — as with the Bun/Deno pair above, the
|
|
288
329
|
// check has to live here too because this runs before that const is defined.
|
|
289
330
|
if (typeof globalThis.imports !== 'undefined' && typeof globalThis.print === 'function') return;
|
|
@@ -362,7 +403,8 @@ export function maybePrependGtkRuntimeDllPath() {
|
|
|
362
403
|
* finds its runtime DATA on Windows: compiled GSettings schemas
|
|
363
404
|
* (`GSETTINGS_SCHEMA_DIR`), the gdk-pixbuf image loaders (`GDK_PIXBUF_MODULEDIR` +
|
|
364
405
|
* `GDK_PIXBUF_MODULE_FILE`), the icon themes (`XDG_DATA_DIRS` → `<bundle>/share`),
|
|
365
|
-
* and — when bundled — Fontconfig (`FONTCONFIG_PATH`/`FONTCONFIG_FILE`)
|
|
406
|
+
* and — when bundled — Fontconfig (`FONTCONFIG_PATH`/`FONTCONFIG_FILE`) plus the Pango
|
|
407
|
+
* backend that actually READS it (`PANGOCAIRO_BACKEND`). These live
|
|
366
408
|
* beside the DLLs the display-free path already wires (maybePrependGtkRuntimeDllPath).
|
|
367
409
|
*
|
|
368
410
|
* STRICT no-op off win32, without a bundle, or when the bundle carries NO windowing
|
|
@@ -370,7 +412,12 @@ export function maybePrependGtkRuntimeDllPath() {
|
|
|
370
412
|
* --windowing build produces, so the DEFAULT display-free bundle is byte-unchanged.
|
|
371
413
|
* Windows re-reads these vars at first use (schema/loader/icon-theme init runs AFTER
|
|
372
414
|
* the addon loads), so an in-process mutation here suffices — no re-exec, the DLL-
|
|
373
|
-
* search analog.
|
|
415
|
+
* search analog. TRUE OF EVERY `g_getenv()` READER, and only of those: on win32 an
|
|
416
|
+
* in-process write lands in the WIN32 environment block alone, which is where
|
|
417
|
+
* `g_getenv()` looks and where `getenv()` does not. What every write here is recorded
|
|
418
|
+
* in `windowingEnvWrites()` for is the repair of that second half — see the
|
|
419
|
+
* `PANGOCAIRO_BACKEND` note below and `mirrorWindowingEnvIntoCrt()` in `gi.js`.
|
|
420
|
+
* Each var is set only when currently unset (a host override wins).
|
|
374
421
|
* Idempotent; MUST run at module top-level with maybePrependGtkRuntimeDllPath, before
|
|
375
422
|
* the app initializes GTK. GLib uses `;` as the win32 search-path separator.
|
|
376
423
|
* @returns {void}
|
|
@@ -384,17 +431,23 @@ export function maybeWireGtkWindowingEnv() {
|
|
|
384
431
|
if (process.platform !== 'win32' && process.platform !== 'darwin') return;
|
|
385
432
|
// Same gate as the DLL/dylib path: the runtime DATA belongs to the bundle, so
|
|
386
433
|
// it is wired only when the bundle is the source the policy picked.
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
if (!bundle) return; // strict no-op when no bundle is present
|
|
434
|
+
const bundle = windowingBundle();
|
|
435
|
+
if (!bundle) return; // strict no-op without a windowing bundle
|
|
390
436
|
|
|
391
437
|
const schemaDir = join(bundle.dir, 'share', 'glib-2.0', 'schemas');
|
|
392
|
-
// gschemas.compiled = the windowing-data marker; absent → display-free bundle.
|
|
393
|
-
if (!existsSync(join(schemaDir, 'gschemas.compiled'))) return;
|
|
394
438
|
|
|
395
439
|
const pathSep = process.platform === 'win32' ? ';' : ':';
|
|
440
|
+
// Recorded, not just written. On win32 `process.env.X = v` reaches ONE of the two
|
|
441
|
+
// environments this process has (see `windowingEnvWrites` and the PANGOCAIRO_BACKEND
|
|
442
|
+
// note below), and the mirror that reaches the other one needs a GLib, which does not
|
|
443
|
+
// exist yet at module top level. Reset per call so the function stays idempotent.
|
|
444
|
+
windowingEnvWritesMade = [];
|
|
445
|
+
const write = (name, value) => {
|
|
446
|
+
process.env[name] = value;
|
|
447
|
+
windowingEnvWritesMade.push([name, value]);
|
|
448
|
+
};
|
|
396
449
|
const setIfUnset = (name, value) => {
|
|
397
|
-
if (!process.env[name])
|
|
450
|
+
if (!process.env[name]) write(name, value);
|
|
398
451
|
};
|
|
399
452
|
setIfUnset('GSETTINGS_SCHEMA_DIR', schemaDir);
|
|
400
453
|
|
|
@@ -427,7 +480,7 @@ export function maybeWireGtkWindowingEnv() {
|
|
|
427
480
|
if (existsSync(shareDir)) {
|
|
428
481
|
const cur = process.env.XDG_DATA_DIRS ?? '';
|
|
429
482
|
if (!cur.split(pathSep).filter(Boolean).includes(shareDir)) {
|
|
430
|
-
|
|
483
|
+
write('XDG_DATA_DIRS', cur ? `${shareDir}${pathSep}${cur}` : shareDir);
|
|
431
484
|
}
|
|
432
485
|
}
|
|
433
486
|
|
|
@@ -437,18 +490,92 @@ export function maybeWireGtkWindowingEnv() {
|
|
|
437
490
|
setIfUnset('FONTCONFIG_FILE', fontsConf);
|
|
438
491
|
}
|
|
439
492
|
|
|
440
|
-
//
|
|
441
|
-
//
|
|
493
|
+
// WHICH FONT MAP PANGO BUILDS AT ALL, which is upstream of the two lines above and is the
|
|
494
|
+
// assumption neither of them stated. `pangocairo-fontmap.c` picks the FIRST backend
|
|
495
|
+
// COMPILED IN, in the order coretext → win32 → fc, and `PANGOCAIRO_BACKEND` is the only
|
|
496
|
+
// thing that overrides it. Both bundles carry every backend their platform builds, so
|
|
497
|
+
// without this line darwin gets CoreText and win32 gets pangowin32/DirectWrite — and
|
|
498
|
+
// fontconfig, which everything else here configures, drives nothing.
|
|
499
|
+
//
|
|
500
|
+
// MEASURED 2026-09-12 on real hardware — Learn6502 0.8.0 on the published windowing
|
|
501
|
+
// bundle, macOS 15.7.9 and Windows 11, with the variable exported BY HAND into the
|
|
502
|
+
// process before launch (nothing in the loader set it then — this line is what changed
|
|
503
|
+
// that, and the distinction turns out to be the whole story on win32):
|
|
504
|
+
//
|
|
505
|
+
// Tamil தமிழ் default backend: TOFU PANGOCAIRO_BACKEND=fc: renders
|
|
506
|
+
// Japanese 日本語 default backend: renders PANGOCAIRO_BACKEND=fc: renders
|
|
507
|
+
//
|
|
508
|
+
// JAPANESE IS NOT A TEST — it came out right in the SAME window of the SAME run in which
|
|
509
|
+
// Tamil was empty boxes, because both platform maps carry a CJK fallback. What neither
|
|
510
|
+
// reaches from a GTK process are the Indic faces the OS itself installs (`Tamil Sangam
|
|
511
|
+
// MN.ttc`, `Nirmala.ttf` — present on both machines, and found immediately by fontconfig).
|
|
512
|
+
// So anything checking this must use a script the DEFAULT backend genuinely misses; Tamil
|
|
513
|
+
// is the one that was measured, and `test/font-script-coverage.test.mjs` carries its own
|
|
514
|
+
// negative control rather than trusting that it still is.
|
|
515
|
+
//
|
|
516
|
+
// It was NOT the translation (the UI switched language, only the glyphs were missing), not
|
|
517
|
+
// a broken fallback chain, not a missing face and not missing configuration: the win32
|
|
518
|
+
// bundle already ships `etc/fonts/fonts.conf` and the two lines above already point at it.
|
|
519
|
+
// The configuration was being read by nobody.
|
|
520
|
+
//
|
|
521
|
+
// ON WIN32 THIS WRITE ALONE DOES NOT REACH PANGO, and the first reading of that is kept
|
|
522
|
+
// here because it is the kind of wrong answer that gets re-derived. CI run 34873488108
|
|
523
|
+
// measured `PANGOCAIRO_BACKEND=fc` in `process.env` and a `PangoCairoWin32FontMap` anyway,
|
|
524
|
+
// and that was written up as "gvsbuild's pango has no fontconfig backend to select". It has
|
|
525
|
+
// one. `bin/pangocairo-1.0-0.dll` out of GTK4_Gvsbuild_2026.6.0_x64.zip registers
|
|
526
|
+
// `PangoCairoFcFontMap` as well as `PangoCairoWin32FontMap`, imports `pangoft2-1.0-0.dll`
|
|
527
|
+
// and `fontconfig-1.dll`, and carries ` win32 fontconfig` — the string pango prints for an
|
|
528
|
+
// unknown value — beside the three literals it compares against: `win32`, `fc`,
|
|
529
|
+
// `fontconfig`. Two things hid that: a `PangoCairo[A-Za-z]*FontMap` regex cannot match the
|
|
530
|
+
// digits in `Win32`, and `strings` defaults to a 4-character minimum, one more than `fc`
|
|
531
|
+
// has. An invalid value is also LOUD (`Unknown $PANGOCAIRO_BACKEND value` + a NULL map),
|
|
532
|
+
// and the run printed none — which was the evidence against the first reading all along.
|
|
533
|
+
//
|
|
534
|
+
// WHAT IS ACTUALLY WRONG IS THE REACH OF THE WRITE. `pango_cairo_font_map_new()` reads
|
|
535
|
+
// plain `getenv()`, not `g_getenv()`. On win32 Node's `process.env` setter is
|
|
536
|
+
// `uv_os_setenv()`, which is `SetEnvironmentVariableW()` and nothing else — the WIN32
|
|
537
|
+
// environment block, never the C runtime's own copy, and `getenv()` returns the copy. So
|
|
538
|
+
// `process.env.PANGOCAIRO_BACKEND` reads back `fc` (Node reads it through
|
|
539
|
+
// `GetEnvironmentVariableW`), every CHILD process inherits it, every `g_getenv()` reader on
|
|
540
|
+
// this list sees it — and pango in THIS process sees nothing, takes the `!backend` branch
|
|
541
|
+
// and builds the platform map. glib documents the split in `g_setenv()` itself and repairs
|
|
542
|
+
// it by calling `_wputenv()` first and `SetEnvironmentVariableW()` second; that is the
|
|
543
|
+
// repair here too, and it needs a GLib, which does not exist at module top level. So it
|
|
544
|
+
// happens on the first `requireGi()` — `mirrorWindowingEnvIntoCrt()` in `gi.js`, over
|
|
545
|
+
// exactly the writes recorded above. This write stays: it is what the mirror copies, what a
|
|
546
|
+
// child inherits, and what every GLib reader already uses.
|
|
547
|
+
//
|
|
548
|
+
// NOTHING ABOVE CONTRADICTS THE 2026-09-12 WINDOWS MEASUREMENT — both readings are kept
|
|
549
|
+
// because together they name which half is which. There the variable was in the process at
|
|
550
|
+
// LAUNCH, so the C runtime had it before any code of ours ran, and Tamil rendered. The
|
|
551
|
+
// gvsbuild pin was already 2026.6.0 on that date, so those are the same DLLs CI builds
|
|
552
|
+
// against: same backend, same value, different reach.
|
|
442
553
|
//
|
|
443
|
-
// `
|
|
444
|
-
//
|
|
445
|
-
//
|
|
446
|
-
//
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
|
|
451
|
-
|
|
554
|
+
// `setIfUnset`, because an operator who pins `coretext`/`win32` has to win: the price of
|
|
555
|
+
// this line is a different RASTERISER (FreeType instead of ClearType/CoreText), which is a
|
|
556
|
+
// preference, while a glyph that never arrives is a defect. No platform branch is needed —
|
|
557
|
+
// the function has already returned on anything but darwin/win32 — and it is scoped to the
|
|
558
|
+
// BUNDLE deliberately: fontconfig is a safe choice only where something hands it both a
|
|
559
|
+
// configuration and faces, which is exactly what a windowing bundle does and what an
|
|
560
|
+
// arbitrary host's Pango does not owe us.
|
|
561
|
+
setIfUnset('PANGOCAIRO_BACKEND', 'fc');
|
|
562
|
+
|
|
563
|
+
// THE FACES, which are a different question from the three settings above and on the
|
|
564
|
+
// platform font maps not answerable by an environment variable at all.
|
|
565
|
+
//
|
|
566
|
+
// `XDG_DATA_DIRS` reaches `<bundle>/share/fonts` wherever fontconfig drives Pango — its
|
|
567
|
+
// stock configuration carries `<dir prefix="xdg">fonts</dir>` — which is true only BECAUSE
|
|
568
|
+
// of the backend selection above. This comment used to read "so on darwin the set above is
|
|
569
|
+
// enough", and that sentence WAS the defect: nothing made fontconfig the darwin backend, so
|
|
570
|
+
// the directory was being named to a reader nobody had asked.
|
|
571
|
+
// It stays the route for any process that ends up on a PLATFORM map — an operator pinning
|
|
572
|
+
// `win32`/`coretext`, a host GTK, a pango built without the fontconfig backend. GTK4 on
|
|
573
|
+
// pangowin32 has a font map filled exclusively by `pango_font_map_dwrite_populate()` from
|
|
574
|
+
// the DirectWrite system collection, so a `FONTCONFIG_FILE` naming a directory of faces
|
|
575
|
+
// moves THAT map by ZERO families however it is set (measured on Windows 11 / GTK 4.22.4,
|
|
576
|
+
// both directions — ADR 0038 § W1-W5). The face has to be handed to the map through
|
|
577
|
+
// `add_font_file`, which is a RUNTIME call somebody has to make — so `initFonts()` stays
|
|
578
|
+
// wired on both platforms and is not conditional on which map won.
|
|
452
579
|
//
|
|
453
580
|
// So the loader's job here is to name the directory, not to register anything: it runs
|
|
454
581
|
// before the addon loads and has no Pango to talk to. `@gjsify/gtk-host`'s `initFonts()`
|
|
@@ -668,3 +795,143 @@ export function activateBundledGtkRuntime(native) {
|
|
|
668
795
|
activated = bundle;
|
|
669
796
|
return bundle;
|
|
670
797
|
}
|
|
798
|
+
|
|
799
|
+
// --- OpenGL on win32 (#1097) -------------------------------------------------
|
|
800
|
+
//
|
|
801
|
+
// GTK 4 needs desktop GL >= 3.2 core over WGL on win32 — the gvsbuild GTK is built without
|
|
802
|
+
// EGL, so ANGLE is unreachable, and without Vulkan. A host with no OpenGL ICD (a GPU-less VM,
|
|
803
|
+
// an RDP session, a CI runner) offers only Microsoft's GDI generic OpenGL 1.1, which GDK
|
|
804
|
+
// rejects: every Gtk.GLArea then reads "No GL implementation is available" and GSK falls
|
|
805
|
+
// back to cairo. The OPTIONAL package `@gjsify/gl-runtime-win32-x64` carries Mesa's
|
|
806
|
+
// opengl32.dll for exactly those hosts; it is ~22 MB, so it is a consumer's opt-in and never
|
|
807
|
+
// part of the bundle or a dependency of node-gi (ADR 0023). This decides per process whether
|
|
808
|
+
// it or the host's OpenGL serves GTK. Why it has to be a native preload rather than a PATH
|
|
809
|
+
// entry: src/opengl-win32.cc.
|
|
810
|
+
//
|
|
811
|
+
// `GJSIFY_OPENGL=bundle|system` overrides — `system` keeps a host's OpenGL even when the
|
|
812
|
+
// probe finds no ICD, `bundle` takes Mesa over a vendor driver.
|
|
813
|
+
|
|
814
|
+
/** The optional GL package's npm name for a platform-arch tag. */
|
|
815
|
+
export function glRuntimePackageName(tag = `${process.platform}-${process.arch}`) {
|
|
816
|
+
return `@gjsify/gl-runtime-${tag}`;
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
/**
|
|
820
|
+
* Where the optional GL package's opengl32.dll is, or null. Resolved BY NAME, like the GTK
|
|
821
|
+
* bundle: the monorepo sibling first (what CI builds in place), then the installed package.
|
|
822
|
+
* @returns {string | null}
|
|
823
|
+
*/
|
|
824
|
+
export function resolveGlRuntime() {
|
|
825
|
+
const tag = `${process.platform}-${process.arch}`;
|
|
826
|
+
const candidates = [join(here, '..', `gl-runtime-${tag}`, 'bin')];
|
|
827
|
+
try {
|
|
828
|
+
candidates.push(join(dirname(require.resolve(glRuntimePackageName(tag))), 'bin'));
|
|
829
|
+
} catch {
|
|
830
|
+
// Not installed — the common case; decideOpenGLSource says what that costs.
|
|
831
|
+
}
|
|
832
|
+
for (const dir of candidates) {
|
|
833
|
+
const dll = join(dir, 'opengl32.dll');
|
|
834
|
+
if (existsSync(dll)) return dll;
|
|
835
|
+
}
|
|
836
|
+
return null;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
/** @typedef {{ wddmIcd: string, registryIcd: string, loadedFrom: string }} HostOpenGL */
|
|
840
|
+
|
|
841
|
+
/**
|
|
842
|
+
* Which OpenGL this process should use. PURE, so every branch runs on any host.
|
|
843
|
+
*
|
|
844
|
+
* A host ICD wins by default: Mesa over a real GPU would still work (its d3d12 driver runs on
|
|
845
|
+
* the hardware) but replaces the vendor's driver for this process, which is a regression
|
|
846
|
+
* nobody asked for. An opengl32 that is ALREADY loaded is a fact, not a preference — no
|
|
847
|
+
* override can move it, so it is reported rather than fought. `missing` marks the one case a
|
|
848
|
+
* consumer can fix: no ICD, no package.
|
|
849
|
+
*
|
|
850
|
+
* @param {object} opts
|
|
851
|
+
* @param {string | null} opts.bundled absolute path of the GL package's opengl32.dll, or null
|
|
852
|
+
* @param {HostOpenGL} opts.host `native.probeHostOpenGL()`
|
|
853
|
+
* @param {string} [opts.override] raw `GJSIFY_OPENGL`
|
|
854
|
+
* @returns {{ source: 'bundle' | 'system', reason: string, missing?: true }}
|
|
855
|
+
*/
|
|
856
|
+
export function decideOpenGLSource({ bundled, host, override }) {
|
|
857
|
+
if (host.loadedFrom) return { source: 'system', reason: `opengl32 was already loaded from ${host.loadedFrom}` };
|
|
858
|
+
if (override === 'system') return { source: 'system', reason: 'GJSIFY_OPENGL=system' };
|
|
859
|
+
if (bundled && override === 'bundle') return { source: 'bundle', reason: 'GJSIFY_OPENGL=bundle' };
|
|
860
|
+
if (host.wddmIcd)
|
|
861
|
+
return { source: 'system', reason: `the display driver provides an OpenGL ICD (${host.wddmIcd})` };
|
|
862
|
+
if (host.registryIcd) return { source: 'system', reason: `an OpenGL ICD is registered (${host.registryIcd})` };
|
|
863
|
+
if (!bundled) {
|
|
864
|
+
return {
|
|
865
|
+
source: 'system',
|
|
866
|
+
missing: true,
|
|
867
|
+
reason:
|
|
868
|
+
'this host has no OpenGL driver (only the GDI generic OpenGL 1.1, which GTK rejects), so ' +
|
|
869
|
+
'Gtk.GLArea fails and GSK renders with cairo. Add ' +
|
|
870
|
+
`${glRuntimePackageName('win32-x64')} as a dependency to ship Mesa for such hosts ` +
|
|
871
|
+
'(VMs, RDP sessions, CI), or set GJSIFY_OPENGL=system to accept this',
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
return { source: 'bundle', reason: 'the host has no OpenGL ICD, only the GDI generic OpenGL 1.1' };
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
let openGL = null;
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Apply `decideOpenGLSource` to this process: preload the GL package's opengl32.dll when it
|
|
881
|
+
* wins. MUST run before anything loads gtk-4-1.dll, which imports OPENGL32 statically —
|
|
882
|
+
* index.js calls it right after the addon loads. Strict no-op off win32. Idempotent.
|
|
883
|
+
* @param {{ probeHostOpenGL?: () => HostOpenGL, preloadOpenGL?: (p: string) => string }} native
|
|
884
|
+
* @param {object} [deps] the host facts this reads, so a spec can drive the win32 advice path
|
|
885
|
+
* on a host that has neither win32 nor a bundle: `platform` (default `process.platform`),
|
|
886
|
+
* `bundled` (the GL package's opengl32.dll, default `resolveGlRuntime()`), `windowing`
|
|
887
|
+
* (default `hasWindowingRuntime()`). Every default is the real reading; no production caller
|
|
888
|
+
* passes this — it exists so the warning and the preload fallback are executed by a test at all.
|
|
889
|
+
* @returns {{ source: 'bundle' | 'system', reason: string, loadedFrom: string | null } | null}
|
|
890
|
+
*/
|
|
891
|
+
export function activateBundledOpenGL(native, deps) {
|
|
892
|
+
if (openGL !== null) return openGL || null;
|
|
893
|
+
openGL = false;
|
|
894
|
+
const platform = deps?.platform ?? process.platform;
|
|
895
|
+
if (platform !== 'win32' || typeof native?.probeHostOpenGL !== 'function') return null;
|
|
896
|
+
const candidate = deps?.bundled !== undefined ? deps.bundled : resolveGlRuntime();
|
|
897
|
+
const host = native.probeHostOpenGL();
|
|
898
|
+
const decision = decideOpenGLSource({ bundled: candidate, host, override: process.env.GJSIFY_OPENGL });
|
|
899
|
+
if (decision.source === 'system') {
|
|
900
|
+
// Said once, at the point the process could still be told what to add — the
|
|
901
|
+
// alternative is a GLArea painting "No GL implementation is available" with nothing
|
|
902
|
+
// naming the package. Only where a WINDOWING runtime is present: a headless process
|
|
903
|
+
// never asks for GL, and this would be noise there.
|
|
904
|
+
const canOpenWindows = deps?.windowing ?? hasWindowingRuntime();
|
|
905
|
+
if (decision.missing && canOpenWindows) {
|
|
906
|
+
process.emitWarning(decision.reason, { code: 'GJSIFY_OPENGL_MISSING' });
|
|
907
|
+
}
|
|
908
|
+
openGL = { ...decision, loadedFrom: host.loadedFrom || null };
|
|
909
|
+
return openGL;
|
|
910
|
+
}
|
|
911
|
+
try {
|
|
912
|
+
openGL = { ...decision, loadedFrom: native.preloadOpenGL(candidate) };
|
|
913
|
+
} catch (error) {
|
|
914
|
+
// preloadOpenGL throws with the loader's own reason (a missing libgallium_wgl.dll, a
|
|
915
|
+
// blocked file). The process still runs — GTK falls back to cairo — so this is a
|
|
916
|
+
// warning with the cause, where silence would read as "the host has no GL".
|
|
917
|
+
const reason = `preloading the bundled OpenGL failed: ${error.message}`;
|
|
918
|
+
process.emitWarning(reason, { code: 'GJSIFY_OPENGL_PRELOAD' });
|
|
919
|
+
openGL = { source: 'system', reason, loadedFrom: null };
|
|
920
|
+
}
|
|
921
|
+
return openGL;
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
/** TEST-ONLY: drop the memoized decision so a spec can run the activation again. */
|
|
925
|
+
export function resetOpenGLForTests() {
|
|
926
|
+
openGL = null;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/** Whether the GTK in use can open windows — the bundle's windowing marker. */
|
|
930
|
+
function hasWindowingRuntime() {
|
|
931
|
+
return !!windowingBundle();
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
/** What `activateBundledOpenGL` decided for this process, or null when it did not apply. */
|
|
935
|
+
export function openGLActivation() {
|
|
936
|
+
return openGL || null;
|
|
937
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -73,6 +73,17 @@ export function prependSearchPath(path: string): void;
|
|
|
73
73
|
/** Prepend a directory GI searches for the SHARED LIBRARY a typelib names. */
|
|
74
74
|
export function prependLibraryPath(path: string): void;
|
|
75
75
|
|
|
76
|
+
/** win32 only: the host's OpenGL ICDs (display driver / registry) and any opengl32 already loaded; each "" when absent. */
|
|
77
|
+
export const probeHostOpenGL: (() => { wddmIcd: string; registryIcd: string; loadedFrom: string }) | undefined;
|
|
78
|
+
/**
|
|
79
|
+
* win32 only: load an opengl32.dll by ABSOLUTE path and return the path the loaded module
|
|
80
|
+
* reports. THROWS when the loader refuses it (a missing sibling, a blocked file) — the reason
|
|
81
|
+
* is in the message, and the caller treats it as "this host has no usable GL", not a crash.
|
|
82
|
+
*/
|
|
83
|
+
export const preloadOpenGL: ((path: string) => string) | undefined;
|
|
84
|
+
/** win32 only: GL_VENDOR/GL_RENDERER/GL_VERSION of the context current on this thread, or null without one. */
|
|
85
|
+
export const currentGLStrings: (() => { vendor: string; renderer: string; version: string } | null) | undefined;
|
|
86
|
+
|
|
76
87
|
/**
|
|
77
88
|
* Invoke a namespace-level GObject-Introspection function (not an instance
|
|
78
89
|
* method) with primitive/string/object args. OUT and INOUT parameters are
|
|
@@ -591,6 +602,9 @@ declare const native: {
|
|
|
591
602
|
setErrorBuilder: typeof setErrorBuilder;
|
|
592
603
|
prependSearchPath: typeof prependSearchPath;
|
|
593
604
|
prependLibraryPath: typeof prependLibraryPath;
|
|
605
|
+
probeHostOpenGL: typeof probeHostOpenGL;
|
|
606
|
+
preloadOpenGL: typeof preloadOpenGL;
|
|
607
|
+
currentGLStrings: typeof currentGLStrings;
|
|
594
608
|
callFunction: typeof callFunction;
|
|
595
609
|
callMethod: typeof callMethod;
|
|
596
610
|
hasMethod: typeof hasMethod;
|
package/index.js
CHANGED
|
@@ -11,6 +11,7 @@ import { hostTarget, nativeCandidates, packageRoot } from './native-paths.js';
|
|
|
11
11
|
import { describeAddonLoadFailure } from './load-diagnostics.js';
|
|
12
12
|
import {
|
|
13
13
|
activateBundledGtkRuntime,
|
|
14
|
+
activateBundledOpenGL,
|
|
14
15
|
activateGiLibraryPath,
|
|
15
16
|
maybePrependGtkRuntimeDllPath,
|
|
16
17
|
maybeReexecForGtkRuntime,
|
|
@@ -30,9 +31,12 @@ maybeReexecForGtkRuntime();
|
|
|
30
31
|
// g_module_open of typelib backers — no re-exec needed.
|
|
31
32
|
maybePrependGtkRuntimeDllPath();
|
|
32
33
|
|
|
33
|
-
// Windows full-windowing GTK: point GSETTINGS_SCHEMA_DIR / GDK_PIXBUF_MODULE_FILE /
|
|
34
|
-
// XDG_DATA_DIRS / FONTCONFIG_* at the data a real GTK window needs
|
|
35
|
-
// the
|
|
34
|
+
// Windows/macOS full-windowing GTK: point GSETTINGS_SCHEMA_DIR / GDK_PIXBUF_MODULE_FILE /
|
|
35
|
+
// XDG_DATA_DIRS / FONTCONFIG_* at the data a real GTK window needs, and PANGOCAIRO_BACKEND
|
|
36
|
+
// at the one backend that reads any of the font half. Strict no-op for the display-free
|
|
37
|
+
// bundle, which therefore loads byte-unchanged. On win32 these writes reach the Win32
|
|
38
|
+
// environment block ONLY — gi.js replays them into the C runtime's copy on the first
|
|
39
|
+
// requireGi(), which is the environment pango's and fontconfig's getenv() read.
|
|
36
40
|
maybeWireGtkWindowingEnv();
|
|
37
41
|
|
|
38
42
|
const require = createRequire(import.meta.url);
|
|
@@ -125,6 +129,11 @@ try {
|
|
|
125
129
|
// Never fatal: a missing/partial bundle just leaves the host GTK in charge.
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
// win32: decide whether the bundle's Mesa opengl32.dll or the host's OpenGL serves GTK, and
|
|
133
|
+
// preload the bundled one when it wins. Here because gtk-4-1.dll imports OPENGL32 statically,
|
|
134
|
+
// so it must happen before any namespace loads GTK. Reports rather than throws.
|
|
135
|
+
activateBundledOpenGL(native);
|
|
136
|
+
|
|
128
137
|
// Every OTHER staged prebuild typelib (@gjsify/webkit-native and its kin): same
|
|
129
138
|
// env-free treatment, so a namespace whose typelib is installed in node_modules
|
|
130
139
|
// resolves under a plain `node app.mjs` and not only under `gjsify run`. Deliberately
|
|
@@ -302,4 +311,10 @@ export const textdomain = native.textdomain;
|
|
|
302
311
|
export const bindtextdomain = native.bindtextdomain;
|
|
303
312
|
export const localeCategories = native.localeCategories;
|
|
304
313
|
|
|
314
|
+
// win32 only (undefined elsewhere): which OpenGL the process got — see gtk-runtime.js
|
|
315
|
+
// activateBundledOpenGL. currentGLStrings asks the implementation behind the context
|
|
316
|
+
// current on this thread, so a caller can tell Mesa from a vendor driver.
|
|
317
|
+
export const probeHostOpenGL = native.probeHostOpenGL;
|
|
318
|
+
export const currentGLStrings = native.currentGLStrings;
|
|
319
|
+
|
|
305
320
|
export default native;
|