node-gtk 2.1.0 → 2.2.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 CHANGED
@@ -10,24 +10,31 @@
10
10
 
11
11
  <h1 align="center">node-gtk</h1>
12
12
  <p align="center">
13
- <b>GNOME Gtk+ bindings for NodeJS</b>
13
+ <b>GTK bindings for NodeJS</b>
14
14
  <br/>
15
15
  <img src="https://img.shields.io/npm/v/node-gtk" alt="Package Version" />
16
16
  </p>
17
17
 
18
- Node-Gtk is a [gobject-introspection](https://gi.readthedocs.io/en/latest) library for nodejs. It makes it possible to
19
- use any introspected library, such as Gtk+, usable. It is similar in essence to [GJS](https://wiki.gnome.org/action/show/Projects/Gjs) or [PyGObject](https://pygobject.readthedocs.io). Please note this project is currently in a _beta_ state and is being developed. Any contributors willing to help
20
- will be welcomed.
18
+ `node-gtk` is a [gobject-introspection](https://gi.readthedocs.io/en/latest) library
19
+ for nodejs. It makes it possible to use any introspected C library, such as GTK,
20
+ usable. It is similar in essence to [GJS](https://wiki.gnome.org/action/show/Projects/Gjs)
21
+ or [PyGObject](https://pygobject.readthedocs.io). Please note this project is
22
+ currently in a _alpha_ state.
21
23
 
22
24
  Supported Node.js versions: **20**, **22**, **24** (other versions may work but are untested)<br>
23
- Pre-built binaries available for: **Linux**, **macOS**
25
+ Supported platforms:
26
+ - **Linux** — prebuilt binaries available
27
+ - **macOS** — prebuilt binaries available
28
+ - **Windows** — prebuilt binaries available (but read [Windows](#windows))
29
+
24
30
 
25
31
  ### Table of contents
26
32
 
27
33
  - [Usage](#usage)
34
+ - [ES modules](#es-modules)
28
35
  - [Documentation](#documentation)
29
36
  - [TypeScript](#typescript)
30
- - [Installing and building](#installing-and-building)
37
+ - [Installing](#installing)
31
38
  - [Contributing](#contributing)
32
39
 
33
40
  ## Usage
@@ -66,48 +73,6 @@ process.exit(app.run([]));
66
73
  <img src="./img/hello-world.png" style="width: 290px; height: auto;"/>
67
74
  </p>
68
75
 
69
- #### ES modules
70
-
71
- The example above is CommonJS. node-gtk also works under ES modules, but with one
72
- behavioral difference around the blocking main-loop calls (`GLib.MainLoop.run`,
73
- `Gio`/`Gtk.Application.run`, `Gtk.main`).
74
-
75
- Under ESM, a module's top-level body runs as a V8 *microtask*. If a blocking
76
- loop call ran synchronously from there, it would nest inside V8's microtask
77
- drain and starve every `Promise`/`async` continuation (so `await fetch(...)`,
78
- `fs/promises`, etc. would never settle) for the entire lifetime of the loop —
79
- see [#442](https://github.com/romgrk/node-gtk/issues/442). To avoid this,
80
- node-gtk defers the blocking call to the next event-loop tick when it detects it
81
- is being invoked from within a microtask. Two consequences for ESM code:
82
-
83
- - The run call **returns immediately** instead of blocking until quit, so any
84
- code placed *after* it executes *before* the loop. Make the run call the last
85
- statement, and do cleanup/exit from your quit/`close-request` handler.
86
- - Its **return value is not available** (e.g. the application's exit status), so
87
- don't wrap it like `process.exit(app.run([]))` — call `app.run([])` on its own
88
- and exit from the handler instead.
89
-
90
- Under CommonJS (and inside signal callbacks) nothing changes: the run calls block
91
- synchronously and return their value exactly as before.
92
-
93
- > **Design note (may be reconsidered).** Two approaches were considered for #442:
94
- >
95
- > - **Transparent auto-defer (chosen).** node-gtk detects the microtask context
96
- > and defers the blocking call automatically, so existing ESM code keeps
97
- > working with no changes. The cost is the leaky behavior above: under ESM the
98
- > run call no longer blocks, which is surprising and silently breaks idioms
99
- > like `process.exit(app.run([]))`.
100
- > - **Explicit async API (alternative).** Keep the blocking calls strictly
101
- > synchronous and add awaitable variants (e.g. `await loop.runAsync()`),
102
- > leaving ESM users to opt in. This has clean, predictable semantics and no
103
- > hidden return-immediately behavior, but it does *not* transparently fix
104
- > existing ESM code — users must change their code, and plain `loop.run()`
105
- > would still starve microtasks under ESM (or would need to throw/warn).
106
- >
107
- > The transparent approach was chosen to fix existing code out of the box, but
108
- > given the leaky semantics this trade-off may be revisited — possibly moving to
109
- > (or also offering) the explicit async API.
110
-
111
76
  You can also easily create custom applications:
112
77
 
113
78
  [A web browser (using WebKit2GTK)](./examples/browser.js)
@@ -122,9 +87,28 @@ You can also easily create custom applications:
122
87
  <img src="./img/system-monitor.png" style="width: 400px; height: auto;"/>
123
88
  </p>
124
89
 
125
- #### Other projects
90
+ ## ES modules
91
+
92
+ The Usage example above is CommonJS. node-gtk also works under ESM, but the
93
+ blocking main-loop calls (`GLib.MainLoop.run`, `Gio`/`Gtk.Application.run`,
94
+ `Gtk.main`) **return immediately** instead of blocking and **don't return a
95
+ value** — so make the run call the last statement and exit from your handler:
96
+
97
+ ```javascript
98
+ app.on('activate', () => {
99
+ // ...build the window...
100
+ window.on('close-request', () => (loop.quit(), app.quit(), false));
101
+ window.present();
102
+
103
+ gi.startLoop();
104
+ loop.run(); // returns immediately under ESM; do cleanup/exit in the handler
105
+ });
106
+
107
+ app.run([]); // not `process.exit(app.run([]))` — the return value is unavailable
108
+ ```
126
109
 
127
- The [react-gtk](https://github.com/codejamninja/react-gtk) project may also allow you to use GTK via React (unmaintained).
110
+ CommonJS (and signal callbacks) are unaffected. For the why and the design
111
+ trade-off, see [#449](https://github.com/romgrk/node-gtk/issues/449).
128
112
 
129
113
  ## Documentation
130
114
 
@@ -182,9 +166,44 @@ script so it regenerates on install:
182
166
 
183
167
  Run `npx node-gtk generate-types --help` for options.
184
168
 
185
- ## Installing and building
169
+ ## Installing
170
+
171
+ 1. Install `node-gtk` itself
172
+ 2. Install the native libraries you use (see examples per platform below)
186
173
 
187
- See [Installing & building](./doc/installation.md) for prebuilt-binary notes, per-platform build instructions (Linux, macOS, Windows), and how to run the tests and examples.
174
+ ```sh
175
+ npm install node-gtk
176
+
177
+ # This installs a prebuilt binary when one is available for your platform and
178
+ # Node.js version, otherwise it falls back to building from source.
179
+ ```
180
+
181
+ #### Linux
182
+
183
+ ```sh
184
+ # archlinux
185
+ pacman -S gtk4
186
+
187
+ # ubuntu
188
+ apt install libgtk-4-1
189
+ ```
190
+
191
+ #### macOS
192
+
193
+ ```sh
194
+ brew install gtk4
195
+ ```
196
+
197
+ #### Windows
198
+
199
+ Windows doesn't have the dependencies we need in a package manager, therefore
200
+ `node-gtk` ships prebuilt versions of GTK 4 / Adwaita runtime (DLLs, typelibs,
201
+ icons), so `npm install node-gtk` is all you need **if** your dependency is in
202
+ our [list of prebuilt libraries](./windows/runtime-libraries.txt).
203
+
204
+ ### build from source
205
+
206
+ Building from source, or contributing? See [Building from source](./doc/building.md).
188
207
 
189
208
  ## Contributing
190
209
 
@@ -195,6 +214,3 @@ for LSP to work nicely, you can use [bear](https://github.com/rizsotto/Bear) as
195
214
  - https://developer.gnome.org/gi/stable/index.html
196
215
  - https://v8docs.nodesource.com/
197
216
  - https://github.com/nodejs/nan#api
198
-
199
- There is a [Discord channel](https://discord.gg/r2VqPUV) but it receives little monitoring, use github issues or
200
- discussions preferably.
package/lib/native.js CHANGED
@@ -4,10 +4,61 @@
4
4
 
5
5
  const binary = require('@mapbox/node-pre-gyp')
6
6
  const path = require('path')
7
+ const fs = require('fs')
7
8
 
8
9
  const packagePath = path.resolve(path.join(__dirname,'../package.json'))
9
10
  const bindingPath = binary.find(packagePath)
10
11
 
12
+ // On Windows, the prebuilt binary ships with its whole GTK runtime bundled in
13
+ // the same directory as the .node (DLLs, GObject-Introspection typelibs, and
14
+ // runtime data). Wire the process environment to that bundle BEFORE the addon
15
+ // is loaded, so a plain `npm install node-gtk` works with no MSYS2, no compiler
16
+ // and no manual PATH setup. Done in this file specifically because it must run
17
+ // before `require(bindingPath)` (the addon's DLL imports are resolved at load
18
+ // time) and before bootstrap.js requires the GIRepository typelib.
19
+ if (process.platform === 'win32')
20
+ setupBundledRuntime(path.dirname(bindingPath))
21
+
22
+ function setupBundledRuntime(bundleDir) {
23
+ // A self-contained prebuilt always bundles its typelibs; if that marker is
24
+ // absent the addon was built from source against a system GTK, so leave the
25
+ // environment alone.
26
+ const typelibDir = path.join(bundleDir, 'girepository-1.0')
27
+ if (!fs.existsSync(typelibDir))
28
+ return
29
+
30
+ const prepend = (name, value) => {
31
+ const cur = process.env[name]
32
+ process.env[name] = cur ? value + path.delimiter + cur : value
33
+ }
34
+ const exists = p => { try { return fs.existsSync(p) } catch (e) { return false } }
35
+
36
+ // 1) DLLs — both the addon's own imports and the namespace shared libraries
37
+ // GObject-Introspection loads at runtime via g_module_open().
38
+ prepend('PATH', bundleDir)
39
+
40
+ // 2) GI typelibs.
41
+ prepend('GI_TYPELIB_PATH', typelibDir)
42
+
43
+ // 3) Runtime data: icon themes, GtkSourceView languages/styles, schemas.
44
+ const share = path.join(bundleDir, 'share')
45
+ if (exists(share))
46
+ prepend('XDG_DATA_DIRS', share)
47
+ const schemas = path.join(share, 'glib-2.0', 'schemas')
48
+ if (exists(schemas) && !process.env.GSETTINGS_SCHEMA_DIR)
49
+ process.env.GSETTINGS_SCHEMA_DIR = schemas
50
+
51
+ // 4) gdk-pixbuf image loaders (made path-portable at bundle time). The
52
+ // loaders dir goes on PATH so g_module_open() of a bare loader name from
53
+ // the cache resolves.
54
+ const loadersDir = path.join(bundleDir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders')
55
+ if (exists(loadersDir))
56
+ prepend('PATH', loadersDir)
57
+ const loaderCache = path.join(bundleDir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders.cache')
58
+ if (exists(loaderCache) && !process.env.GDK_PIXBUF_MODULE_FILE)
59
+ process.env.GDK_PIXBUF_MODULE_FILE = loaderCache
60
+ }
61
+
11
62
  const binding = require(bindingPath)
12
63
 
13
64
  module.exports = binding
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-gtk",
3
- "version": "2.1.0",
3
+ "version": "2.2.0",
4
4
  "description": "GNOME Gtk+ bindings for NodeJS",
5
5
  "main": "lib/index.js",
6
6
  "bin": {
@@ -0,0 +1,163 @@
1
+ #!/bin/bash
2
+ #
3
+ # windows-bundle-runtime.sh
4
+ #
5
+ # Make a freshly-built Windows prebuilt self-contained so it can be used WITHOUT
6
+ # MSYS2/MinGW or any compiler on the target machine. We copy the addon's entire
7
+ # transitive MinGW DLL closure next to the .node, plus the GObject-Introspection
8
+ # typelibs and runtime data it needs at runtime.
9
+ #
10
+ # The set of bundled libraries is driven by the curated seed list
11
+ # windows/runtime-libraries.txt (also a user-facing reference). The exact
12
+ # resolved DLL set is written to <binding-dir>/bundled-dlls.txt.
13
+ #
14
+ # Run inside the MINGW64 shell, after building.
15
+ #
16
+ # ./scripts/windows-bundle-runtime.sh lib/binding/node-v127-win32-x64
17
+ #
18
+ set -euo pipefail
19
+
20
+ SCRIPT_DIR=$(cd "$(dirname "$0")" && pwd)
21
+
22
+ BINDING_DIR="${1:?usage: windows-bundle-runtime.sh <binding-dir> [runtime-libraries.txt]}"
23
+ # The curated seed list lives in a text file so it doubles as user-facing
24
+ # reference (windows/runtime-libraries.txt). Override with $2 if needed.
25
+ LIBS_FILE="${2:-$SCRIPT_DIR/../windows/runtime-libraries.txt}"
26
+ # MinGW bin dir the seed names resolve against.
27
+ MB="${MINGW_BIN:-/mingw64/bin}"
28
+
29
+ NODE_FILE="$BINDING_DIR/node_gtk.node"
30
+
31
+ if [ ! -f "$NODE_FILE" ]; then
32
+ echo "error: $NODE_FILE not found"
33
+ ls -la "$BINDING_DIR" || true
34
+ exit 1
35
+ fi
36
+ if [ ! -f "$LIBS_FILE" ]; then
37
+ echo "error: seed list $LIBS_FILE not found"
38
+ exit 1
39
+ fi
40
+
41
+ # GObject-Introspection loads each namespace's shared library at runtime via
42
+ # g_module_open() when you call gi.require('Gtk', ...). Those libraries
43
+ # (libgio, libgtk, libgdk, libpango, libgdk_pixbuf, ...) are NOT linked by
44
+ # node_gtk.node, so ntldd on the addon alone misses them. We therefore seed the
45
+ # closure from the addon AND from the GTK runtime libraries listed in LIBS_FILE.
46
+ echo "## Seed libraries from $LIBS_FILE"
47
+ ENTRY_LIBS=("$NODE_FILE")
48
+ while IFS= read -r line; do
49
+ # strip comments and surrounding whitespace; skip blanks
50
+ name="${line%%#*}"
51
+ name="$(echo "$name" | tr -d '[:space:]')"
52
+ [ -z "$name" ] && continue
53
+ if [ -f "$MB/$name" ]; then
54
+ ENTRY_LIBS+=("$MB/$name")
55
+ echo " - $name"
56
+ else
57
+ echo " (skip missing seed $name)"
58
+ fi
59
+ done < "$LIBS_FILE"
60
+
61
+ echo "## Computing recursive DLL closure for the addon + GTK runtime"
62
+ # ntldd -R prints every transitive dependency with its resolved Windows path:
63
+ # libgtk-3-0.dll => C:\msys64\mingw64\bin\libgtk-3-0.dll (0x...)
64
+ # Collect the union of every entry's closure, keep only MinGW-provided DLLs
65
+ # (skip C:\Windows\System32 OS DLLs), and copy them next to the .node.
66
+ : > /tmp/dll-closure.txt
67
+ for lib in "${ENTRY_LIBS[@]}"; do
68
+ [ -f "$lib" ] || { echo " (skip missing entry $lib)"; continue; }
69
+ # the entry library itself (when it is one of the GTK runtime DLLs)
70
+ case "$lib" in *mingw64*) echo "$lib" >> /tmp/dll-closure.txt ;; esac
71
+ ntldd -R "$lib" \
72
+ | sed -n 's/.* => \(.*\) (0x.*/\1/p' \
73
+ | while IFS= read -r winpath; do
74
+ [ -z "$winpath" ] && continue
75
+ u=$(cygpath -u "$winpath" 2>/dev/null || echo "$winpath")
76
+ case "$u" in *mingw64*) echo "$u" >> /tmp/dll-closure.txt ;; esac
77
+ done
78
+ done
79
+
80
+ copied=0
81
+ sort -u /tmp/dll-closure.txt | while IFS= read -r u; do
82
+ if [ -f "$u" ]; then
83
+ cp -f "$u" "$BINDING_DIR/"
84
+ echo " + $(basename "$u")"
85
+ copied=$((copied + 1))
86
+ fi
87
+ done
88
+ DLL_COUNT=$(ls "$BINDING_DIR"/*.dll 2>/dev/null | wc -l | tr -d ' ')
89
+ echo "## DLLs bundled into $BINDING_DIR ($DLL_COUNT total)"
90
+
91
+ # Write the exact set that shipped, as a reference manifest (ships in the
92
+ # bundle and the artifact). Generated from the curated seed list above.
93
+ MANIFEST="$BINDING_DIR/bundled-dlls.txt"
94
+ {
95
+ echo "# bundled-dlls.txt — Windows DLLs shipped with this node-gtk prebuilt."
96
+ echo "# GENERATED by scripts/windows-bundle-runtime.sh; do not edit by hand."
97
+ echo "# Edit the curated seed list windows/runtime-libraries.txt instead."
98
+ echo "# $DLL_COUNT DLLs, expanded from the seed list via 'ntldd -R'."
99
+ echo "#"
100
+ ( cd "$BINDING_DIR" && ls -1 *.dll 2>/dev/null | sort )
101
+ } > "$MANIFEST"
102
+ echo "## Wrote manifest $MANIFEST"
103
+
104
+ echo "## Bundling GObject-Introspection typelibs"
105
+ TYPELIB_SRC=$(pkg-config --variable=typelibdir gobject-introspection-1.0 2>/dev/null || true)
106
+ if [ -z "$TYPELIB_SRC" ] || [ ! -d "$TYPELIB_SRC" ]; then
107
+ TYPELIB_SRC=/mingw64/lib/girepository-1.0
108
+ fi
109
+ TYPELIB_DST="$BINDING_DIR/girepository-1.0"
110
+ mkdir -p "$TYPELIB_DST"
111
+ # Copy the full typelib set; it is small and guarantees every transitive
112
+ # namespace dependency (Gdk, Pango, cairo, GdkPixbuf, Graphene, ...) is present.
113
+ cp -f "$TYPELIB_SRC"/*.typelib "$TYPELIB_DST/"
114
+ echo "## Typelibs bundled from $TYPELIB_SRC -> $TYPELIB_DST"
115
+
116
+ # ---------------------------------------------------------------------------
117
+ # Runtime DATA a real GTK4/Adwaita app needs at run time (beyond DLLs/typelibs).
118
+ # These are copied so we can MEASURE the realistic bundle size and ship a
119
+ # self-contained app. cp is best-effort: a missing source is not fatal.
120
+ # ---------------------------------------------------------------------------
121
+ copy_tree() { # src dst
122
+ if [ -d "$1" ]; then mkdir -p "$2"; cp -rf "$1"/. "$2"/ 2>/dev/null || true; fi
123
+ }
124
+
125
+ echo "## Bundling runtime data (loaders, schemas, icons, gtksourceview)"
126
+ # gdk-pixbuf image loaders (PNG/SVG/... — needed for icons/images)
127
+ copy_tree /mingw64/lib/gdk-pixbuf-2.0 "$BINDING_DIR/lib/gdk-pixbuf-2.0"
128
+ # compiled GSettings schemas (GTK4/Adw/GtkSourceView read settings)
129
+ copy_tree /mingw64/share/glib-2.0/schemas "$BINDING_DIR/share/glib-2.0/schemas"
130
+ # icon themes (Adwaita + hicolor fallback) — typically the biggest chunk
131
+ copy_tree /mingw64/share/icons/Adwaita "$BINDING_DIR/share/icons/Adwaita"
132
+ copy_tree /mingw64/share/icons/hicolor "$BINDING_DIR/share/icons/hicolor"
133
+ # GtkSourceView language definitions + style schemes
134
+ copy_tree /mingw64/share/gtksourceview-5 "$BINDING_DIR/share/gtksourceview-5"
135
+
136
+ # Make the gdk-pixbuf loaders cache path-portable: the build machine bakes in
137
+ # absolute paths to each loader DLL, which don't exist on the user's machine.
138
+ # Rewrite each loader path to its bare file name; lib/native.js puts the loaders
139
+ # dir on PATH so g_module_open() resolves the bare name at run time.
140
+ LOADER_CACHE="$BINDING_DIR/lib/gdk-pixbuf-2.0/2.10.0/loaders.cache"
141
+ if [ -f "$LOADER_CACHE" ]; then
142
+ sed -i -E 's#^"[^"]*[\\/]([^"\\/]+\.dll)"#"\1"#' "$LOADER_CACHE"
143
+ echo "## Rewrote $LOADER_CACHE to portable (bare) loader names"
144
+ fi
145
+
146
+ echo
147
+ echo "## ===== SIZE BREAKDOWN ====="
148
+ size() { # label dir-or-glob
149
+ local label="$1"; shift
150
+ local total
151
+ total=$(du -ch "$@" 2>/dev/null | tail -1 | cut -f1)
152
+ printf ' %-26s %s\n' "$label" "${total:-0}"
153
+ }
154
+ size "DLLs (load-critical)" "$BINDING_DIR"/*.dll
155
+ size "typelibs (load-critical)" "$TYPELIB_DST"
156
+ size " + .node" "$NODE_FILE"
157
+ size "gdk-pixbuf loaders" "$BINDING_DIR/lib/gdk-pixbuf-2.0"
158
+ size "glib schemas" "$BINDING_DIR/share/glib-2.0/schemas"
159
+ size "icons (Adwaita+hicolor)" "$BINDING_DIR/share/icons"
160
+ size "gtksourceview data" "$BINDING_DIR/share/gtksourceview-5"
161
+ echo " --------------------------------------"
162
+ size "TOTAL (uncompressed)" "$BINDING_DIR"
163
+ echo "## =========================="
@@ -0,0 +1,104 @@
1
+ /*
2
+ * windows-smoke-test.js
3
+ *
4
+ * Verifies that a Windows prebuilt + bundled GTK runtime can be loaded and used
5
+ * on a clean machine that has NO MSYS2/MinGW and NO compiler — i.e. exactly what
6
+ * a user gets from `npm install node-gtk`.
7
+ *
8
+ * CRITICAL: this test sets NO environment variables. Everything (DLL search
9
+ * path, GI typelib path, icon/schema/loader data) is wired up automatically by
10
+ * lib/native.js when it loads the bundled prebuilt. The workflow runs this with
11
+ * the runner's own MinGW stripped from PATH, so a pass proves the bundle is
12
+ * fully self-sufficient via the auto-wiring alone.
13
+ */
14
+
15
+ const path = require('path')
16
+ const fs = require('fs')
17
+
18
+ const abi = process.versions.modules
19
+ const bindingDir = path.join(__dirname, '..', 'lib', 'binding', `node-v${abi}-win32-x64`)
20
+ const typelibDir = path.join(bindingDir, 'girepository-1.0')
21
+
22
+ console.log('node:', process.version, '| abi:', abi)
23
+ console.log('binding dir:', bindingDir, '| exists:', fs.existsSync(bindingDir))
24
+ console.log('.node:', fs.existsSync(path.join(bindingDir, 'node_gtk.node')))
25
+ console.log('typelibs:', fs.existsSync(typelibDir))
26
+ if (fs.existsSync(bindingDir)) {
27
+ const dlls = fs.readdirSync(bindingDir).filter(f => f.endsWith('.dll'))
28
+ console.log(`bundled DLLs: ${dlls.length}`)
29
+ }
30
+
31
+ // Require the local package. lib/native.js resolves the prebuilt via
32
+ // node-pre-gyp's binary.find and auto-wires the bundled runtime — NO manual
33
+ // PATH / GI_TYPELIB_PATH / XDG_DATA_DIRS setup here on purpose.
34
+ const gi = require(path.join(__dirname, '..'))
35
+ console.log('OK: require(node-gtk) — prebuilt loaded and runtime auto-wired by native.js')
36
+
37
+ // The full quilx namespace set. Vte (3.91) has no Windows port, so it is
38
+ // expected to be unavailable; everything else must load.
39
+ const REQUIRED = [
40
+ ['GLib', '2.0'], ['GObject', '2.0'], ['Gio', '2.0'],
41
+ ['Pango', '1.0'], ['PangoCairo', '1.0'],
42
+ ['Gdk', '4.0'], ['GdkPixbuf', '2.0'], ['Graphene', '1.0'],
43
+ ['Gtk', '4.0'], ['Adw', '1'], ['GtkSource', '5'],
44
+ ]
45
+ const OPTIONAL = [['Vte', '3.91']]
46
+
47
+ const loaded = {}
48
+ function load(ns, version, optional) {
49
+ try {
50
+ const mod = gi.require(ns, version)
51
+ console.log(`OK: gi.require('${ns}', '${version}')`)
52
+ loaded[ns] = mod
53
+ return mod
54
+ } catch (e) {
55
+ console.log(`${optional ? 'note' : 'FAIL'}: gi.require('${ns}', '${version}') — ${e.message}`)
56
+ if (!optional) throw e
57
+ return null
58
+ }
59
+ }
60
+
61
+ for (const [ns, v] of REQUIRED) load(ns, v, false)
62
+ for (const [ns, v] of OPTIONAL) load(ns, v, true)
63
+
64
+ // Sanity: the typelibs really resolved their symbols.
65
+ if (typeof loaded.GLib.getMonotonicTime !== 'function')
66
+ throw new Error('GLib.getMonotonicTime missing — typelib not really loaded')
67
+ if (typeof loaded.Gtk.Window !== 'function')
68
+ throw new Error('Gtk.Window missing — Gtk4 typelib not really loaded')
69
+ if (typeof loaded.Adw.ApplicationWindow !== 'function')
70
+ throw new Error('Adw.ApplicationWindow missing — libadwaita not really loaded')
71
+
72
+ // Exercise a real GTK4 + Adwaita object graph (needs a display; the runner has one).
73
+ let appOk = false
74
+ try {
75
+ loaded.Gtk.init()
76
+ const win = new loaded.Adw.ApplicationWindow()
77
+ const buffer = new loaded.GtkSource.Buffer()
78
+ const view = new loaded.GtkSource.View()
79
+ view.setBuffer(buffer)
80
+ win.setContent(view)
81
+ console.log('OK: created Adw.ApplicationWindow + GtkSource.View')
82
+ appOk = true
83
+ } catch (e) {
84
+ console.log('note: live widget creation threw (likely no display):', e.message)
85
+ }
86
+ console.log('live GTK4/Adwaita widgets:', appOk ? 'ok' : 'skipped (no display)')
87
+
88
+ // Exercise the bundled gdk-pixbuf image loaders + (portable) loaders.cache by
89
+ // decoding a real PNG. This proves the loader subsystem works from the bundle.
90
+ const PNG_1x1 = Buffer.from(
91
+ 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR4nGP4z8AAAAMBAQDJ/pLvAAAAAElFTkSuQmCC',
92
+ 'base64')
93
+ const pngPath = path.join(__dirname, '..', 'smoke-test-pixel.png')
94
+ fs.writeFileSync(pngPath, PNG_1x1)
95
+ try {
96
+ const pixbuf = loaded.GdkPixbuf.Pixbuf.newFromFile(pngPath)
97
+ if (pixbuf.getWidth() !== 1 || pixbuf.getHeight() !== 1)
98
+ throw new Error(`unexpected pixbuf size ${pixbuf.getWidth()}x${pixbuf.getHeight()}`)
99
+ console.log('OK: GdkPixbuf decoded a PNG via the bundled loaders')
100
+ } finally {
101
+ try { fs.unlinkSync(pngPath) } catch (e) { /* ignore */ }
102
+ }
103
+
104
+ console.log('\n=== SMOKE TEST PASSED: GTK4/Adwaita prebuilt usable with NO compiler/MSYS2 ===')