node-gtk 2.1.0 → 3.0.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 +52 -152
- package/bin/node-gtk.js +12 -1
- package/lib/esm/hooks.mjs +49 -0
- package/lib/esm/register.mjs +17 -0
- package/lib/index.js +1 -2
- package/lib/index.mjs +25 -0
- package/lib/inspect.js +1 -1
- package/lib/loop.js +5 -0
- package/lib/module.js +8 -2
- package/lib/native.js +51 -0
- package/lib/register-class.js +86 -3
- package/lib/styles.d.ts +81 -0
- package/lib/styles.js +428 -0
- package/package.json +15 -1
- package/scripts/windows-bundle-runtime.sh +163 -0
- package/scripts/windows-smoke-test.js +104 -0
- package/src/closure.cc +19 -6
- package/src/closure.h +8 -4
- package/src/function.cc +47 -0
- package/src/gi.cc +10 -0
- package/src/gobject.cc +170 -3
- package/src/gobject.h +3 -0
- package/tools/README.md +52 -2
- package/tools/create-app.js +246 -0
- package/tools/generate-types.js +80 -3
- package/tools/list-libraries.js +125 -0
- package/tools/templates/app/README.md.tmpl +97 -0
- package/tools/templates/app/gitignore.tmpl +10 -0
- package/tools/templates/app/package.json.tmpl +26 -0
- package/tools/templates/app/src/main.ts.tmpl +110 -0
- package/tools/templates/app/src/welcome.ts.tmpl +41 -0
- package/tools/templates/app/style.css.tmpl +19 -0
- package/tools/templates/app/tsconfig.json.tmpl +19 -0
- /package/{COPYING → LICENSE} +0 -0
|
@@ -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 ===')
|
package/src/closure.cc
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
#include "closure.h"
|
|
5
5
|
#include "error.h"
|
|
6
|
+
#include "gobject.h"
|
|
6
7
|
#include "macros.h"
|
|
7
8
|
#include "loop.h"
|
|
8
9
|
#include "type.h"
|
|
@@ -54,9 +55,9 @@ static void LoadGIArgumentFromPointer (GITypeInfo *type_info, GIArgument *arg) {
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
GClosure *Closure::New (
|
|
58
|
+
GClosure *Closure::New (guint handlerIndex, GICallableInfo* info, guint signalId) {
|
|
58
59
|
Closure *closure = (Closure *) g_closure_new_simple (sizeof (*closure), GUINT_TO_POINTER(signalId));
|
|
59
|
-
closure->
|
|
60
|
+
closure->handlerIndex = handlerIndex;
|
|
60
61
|
if (info) {
|
|
61
62
|
closure->info = g_base_info_ref(info);
|
|
62
63
|
} else {
|
|
@@ -69,11 +70,18 @@ GClosure *Closure::New (Local<Function> function, GICallableInfo* info, guint si
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
void Closure::Execute(GICallableInfo *info, guint signal_id,
|
|
72
|
-
|
|
73
|
+
GObject *instance, guint handlerIndex,
|
|
73
74
|
GValue *g_return_value, guint n_param_values,
|
|
74
75
|
const GValue *param_values) {
|
|
75
76
|
Nan::HandleScope scope;
|
|
76
|
-
|
|
77
|
+
|
|
78
|
+
/* The handler lives in a JS array on the instance's wrapper (#375). If the
|
|
79
|
+
* wrapper has been collected (the object was dropped from JS) there is
|
|
80
|
+
* nothing to call. */
|
|
81
|
+
Local<Value> handlerValue = GetSignalHandler(instance, handlerIndex);
|
|
82
|
+
if (handlerValue.IsEmpty() || !handlerValue->IsFunction())
|
|
83
|
+
return;
|
|
84
|
+
auto func = handlerValue.As<Function>();
|
|
77
85
|
|
|
78
86
|
GSignalQuery signal_query = { 0, };
|
|
79
87
|
|
|
@@ -242,16 +250,21 @@ void Closure::Marshal(GClosure *base,
|
|
|
242
250
|
|
|
243
251
|
auto closure = (Closure *) base;
|
|
244
252
|
auto signal_id = GPOINTER_TO_UINT(marshal_data);
|
|
253
|
+
auto handlerIndex = closure->handlerIndex;
|
|
254
|
+
|
|
255
|
+
/* param_values[0] is always the instance the signal was emitted on, which
|
|
256
|
+
* is the object whose wrapper holds this handler (#375). */
|
|
257
|
+
GObject *instance = (GObject *) g_value_get_object(¶m_values[0]);
|
|
245
258
|
|
|
246
259
|
AsyncCallEnvironment* env = reinterpret_cast<AsyncCallEnvironment *>(AsyncCallEnvironment::asyncHandle.data);
|
|
247
260
|
|
|
248
261
|
if (env->IsSameThread()) {
|
|
249
262
|
/* Case 1: same thread */
|
|
250
|
-
Closure::Execute(closure->info, signal_id,
|
|
263
|
+
Closure::Execute(closure->info, signal_id, instance, handlerIndex, g_return_value, n_param_values, param_values);
|
|
251
264
|
} else {
|
|
252
265
|
/* Case 2: different thread */
|
|
253
266
|
env->Call([&]() {
|
|
254
|
-
Closure::Execute(closure->info, signal_id,
|
|
267
|
+
Closure::Execute(closure->info, signal_id, instance, handlerIndex, g_return_value, n_param_values, param_values);
|
|
255
268
|
});
|
|
256
269
|
}
|
|
257
270
|
}
|
package/src/closure.h
CHANGED
|
@@ -16,20 +16,24 @@ namespace GNodeJS {
|
|
|
16
16
|
|
|
17
17
|
struct Closure {
|
|
18
18
|
GClosure base;
|
|
19
|
-
|
|
19
|
+
/* The handler function is NOT held here. It lives in a JS array on the
|
|
20
|
+
* wrapper object (reachable only through the wrapper), and we keep just its
|
|
21
|
+
* index. This breaks the wrapper <-> handler reference loop that a strong
|
|
22
|
+
* Nan::Persistent used to create and leak (#375); see
|
|
23
|
+
* doc/signal-handler-gc.md. */
|
|
24
|
+
guint handlerIndex;
|
|
20
25
|
GICallableInfo* info;
|
|
21
26
|
|
|
22
27
|
~Closure() {
|
|
23
|
-
persistent.Reset();
|
|
24
28
|
if (info) g_base_info_unref (info);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
static GClosure *New(
|
|
31
|
+
static GClosure *New(guint handlerIndex,
|
|
28
32
|
GICallableInfo* info,
|
|
29
33
|
guint signalId);
|
|
30
34
|
|
|
31
35
|
static void Execute(GICallableInfo *info, guint signal_id,
|
|
32
|
-
|
|
36
|
+
GObject *instance, guint handlerIndex,
|
|
33
37
|
GValue *g_return_value, guint n_param_values,
|
|
34
38
|
const GValue *param_values);
|
|
35
39
|
|
package/src/function.cc
CHANGED
|
@@ -134,6 +134,43 @@ bool IsDestroyNotify (GIBaseInfo *info) {
|
|
|
134
134
|
&& strcmp(g_base_info_get_namespace(info), "GLib") == 0;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
/*
|
|
138
|
+
* A transfer-full GObject return value hands node-gtk an *owning* reference. The
|
|
139
|
+
* JS wrapper only needs node-gtk's toggle reference, so that extra reference has
|
|
140
|
+
* to be released — otherwise the refcount never falls back to 1, ToggleNotify
|
|
141
|
+
* never flips the V8 handle to weak, and the GObject (plus its wrapper) leaks for
|
|
142
|
+
* the lifetime of the process. This is the leak reported in #446: objects from
|
|
143
|
+
* GI function returns (`Type.new()`, transfer-full getters, …) were never GC'd,
|
|
144
|
+
* while `new Type()` — which drops its construction ref in GObjectConstructor —
|
|
145
|
+
* was. It is the return-value counterpart of OUT GObject args, which
|
|
146
|
+
* FreeGIArgument already balances; the return value is otherwise never freed on
|
|
147
|
+
* the success path. (The IN counterpart is RefObjectForTransferFullIn, #439.)
|
|
148
|
+
*
|
|
149
|
+
* Must be sampled *before* the value is wrapped: associating the wrapper sinks a
|
|
150
|
+
* floating reference (consuming it), after which a floating incoming ref (nothing
|
|
151
|
+
* extra to drop) is indistinguishable from a real owned one. G_IS_OBJECT excludes
|
|
152
|
+
* GParamSpec (wrapped via ParamSpec::FromGParamSpec) and boxed/fundamental
|
|
153
|
+
* interface types.
|
|
154
|
+
*/
|
|
155
|
+
static bool OwnsExtraGObjectReturnRef(GITypeInfo *return_type, GITransfer transfer, gpointer ptr) {
|
|
156
|
+
if (transfer != GI_TRANSFER_EVERYTHING || ptr == NULL)
|
|
157
|
+
return false;
|
|
158
|
+
|
|
159
|
+
if (g_type_info_get_tag(return_type) != GI_TYPE_TAG_INTERFACE)
|
|
160
|
+
return false;
|
|
161
|
+
|
|
162
|
+
GIBaseInfo *iface = g_type_info_get_interface(return_type);
|
|
163
|
+
GIInfoType itype = g_base_info_get_type(iface);
|
|
164
|
+
|
|
165
|
+
bool result =
|
|
166
|
+
(itype == GI_INFO_TYPE_OBJECT || itype == GI_INFO_TYPE_INTERFACE)
|
|
167
|
+
&& G_IS_OBJECT(ptr)
|
|
168
|
+
&& !g_object_is_floating(ptr);
|
|
169
|
+
|
|
170
|
+
g_base_info_unref(iface);
|
|
171
|
+
return result;
|
|
172
|
+
}
|
|
173
|
+
|
|
137
174
|
|
|
138
175
|
/**
|
|
139
176
|
* The constructor just stores the GIBaseInfo ref. The rest of the
|
|
@@ -549,6 +586,13 @@ Local<Value> FunctionCall (
|
|
|
549
586
|
|
|
550
587
|
} else if (!use_return_value) {
|
|
551
588
|
|
|
589
|
+
// A transfer-full GObject return hands us an extra owning reference on
|
|
590
|
+
// top of the wrapper's toggle ref; drop it once wrapped so the object
|
|
591
|
+
// isn't pinned alive forever (#446). Sampled now, before wrapping sinks
|
|
592
|
+
// any floating reference.
|
|
593
|
+
bool release_return_ref =
|
|
594
|
+
OwnsExtraGObjectReturnRef(&return_type, return_transfer, return_value_stack.v_pointer);
|
|
595
|
+
|
|
552
596
|
// Value transferred to jsReturnValue
|
|
553
597
|
jsReturnValue = func->JsReturnValue (
|
|
554
598
|
info.This(),
|
|
@@ -556,6 +600,9 @@ Local<Value> FunctionCall (
|
|
|
556
600
|
&return_value_stack,
|
|
557
601
|
callable_arg_values,
|
|
558
602
|
return_transfer);
|
|
603
|
+
|
|
604
|
+
if (release_return_ref)
|
|
605
|
+
g_object_unref (return_value_stack.v_pointer);
|
|
559
606
|
} else {
|
|
560
607
|
|
|
561
608
|
// Value returned in return_value
|
package/src/gi.cc
CHANGED
|
@@ -405,6 +405,10 @@ NAN_METHOD(GetModuleCache) {
|
|
|
405
405
|
info.GetReturnValue().Set(Nan::New<Object>(GNodeJS::moduleCache));
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
+
NAN_METHOD(SetLazyClassRegister) {
|
|
409
|
+
GNodeJS::ObjectClass::SetLazyClassRegister(info);
|
|
410
|
+
}
|
|
411
|
+
|
|
408
412
|
NAN_METHOD(RegisterClass) {
|
|
409
413
|
GNodeJS::ObjectClass::RegisterClass(info);
|
|
410
414
|
}
|
|
@@ -413,6 +417,10 @@ NAN_METHOD(RegisterVFunc) {
|
|
|
413
417
|
GNodeJS::ObjectClass::RegisterVFunc(info);
|
|
414
418
|
}
|
|
415
419
|
|
|
420
|
+
NAN_METHOD(CallVFunc) {
|
|
421
|
+
GNodeJS::ObjectClass::CallVFunc(info);
|
|
422
|
+
}
|
|
423
|
+
|
|
416
424
|
void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
417
425
|
GNodeJS::AsyncCallEnvironment::Initialize();
|
|
418
426
|
|
|
@@ -431,8 +439,10 @@ void InitModule(Local<Object> exports, Local<Value> module, void *priv) {
|
|
|
431
439
|
Nan::Export(exports, "StartLoop", StartLoop);
|
|
432
440
|
Nan::Export(exports, "IsRunningMicrotasks", IsRunningMicrotasks);
|
|
433
441
|
Nan::Export(exports, "GetLoopStack", GetLoopStack);
|
|
442
|
+
Nan::Export(exports, "SetLazyClassRegister", SetLazyClassRegister);
|
|
434
443
|
Nan::Export(exports, "RegisterClass", RegisterClass);
|
|
435
444
|
Nan::Export(exports, "RegisterVFunc", RegisterVFunc);
|
|
445
|
+
Nan::Export(exports, "CallVFunc", CallVFunc);
|
|
436
446
|
|
|
437
447
|
Nan::Set(exports, UTF8("System"), GNodeJS::System::GetModule());
|
|
438
448
|
Nan::Set(exports, UTF8("Cairo"), GNodeJS::Cairo::GetModule());
|