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 +71 -55
- package/lib/binding/node-v127-linux-x64/node_gtk.node +0 -0
- package/lib/native.js +51 -0
- package/package.json +1 -1
- package/scripts/windows-bundle-runtime.sh +163 -0
- package/scripts/windows-smoke-test.js +104 -0
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>
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
Binary file
|
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
|
@@ -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 ===')
|