node-gtk 1.0.0 → 2.1.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 +123 -190
- package/bin/node-gtk.js +31 -0
- package/lib/bootstrap.js +22 -4
- package/lib/index.js +25 -0
- package/lib/loop.js +34 -0
- package/lib/overrides/GLib-2.0.js +12 -9
- package/lib/overrides/Gio-2.0.js +26 -0
- package/lib/overrides/Gtk-3.0.js +17 -12
- package/lib/overrides/Gtk-4.0.js +4 -2
- package/lib/register-class.js +1 -1
- package/package.json +9 -2
- package/scripts/build-test-fixtures.js +237 -0
- package/scripts/ci.sh +5 -3
- package/src/boxed.cc +33 -3
- package/src/boxed.h +13 -0
- package/src/callback.cc +12 -0
- package/src/callback.h +1 -0
- package/src/closure.cc +110 -2
- package/src/function.cc +68 -7
- package/src/function.h +1 -0
- package/src/gi.cc +72 -0
- package/src/gobject.cc +148 -27
- package/src/loop.cc +39 -3
- package/src/loop.h +2 -0
- package/src/type.cc +3 -2
- package/src/value.cc +369 -31
- package/src/value.h +22 -0
- package/tools/README.md +91 -0
- package/tools/generate-types.js +1045 -0
- package/lib/binding/node-v102-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v108-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v115-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v127-linux-x64/node_gtk.node +0 -0
- package/lib/binding/node-v93-linux-x64/node_gtk.node +0 -0
package/lib/register-class.js
CHANGED
|
@@ -119,7 +119,7 @@ function findVFuncOnParents(info, name) {
|
|
|
119
119
|
function findVFuncOnInterfaces(gtype, name) {
|
|
120
120
|
const interfaces = GObject.typeInterfaces(gtype);
|
|
121
121
|
|
|
122
|
-
for (i = 0; i < interfaces.length; i++) {
|
|
122
|
+
for (let i = 0; i < interfaces.length; i++) {
|
|
123
123
|
const interfaceInfo = findInfoByGtype(interfaces[i])
|
|
124
124
|
|
|
125
125
|
/* The interface doesn't have to exist, it could be private
|
package/package.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-gtk",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "GNOME Gtk+ bindings for NodeJS",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"node-gtk": "bin/node-gtk.js"
|
|
8
|
+
},
|
|
6
9
|
"scripts": {
|
|
7
10
|
"install": "npx node-pre-gyp install --fallback-to-build",
|
|
11
|
+
"build:test-fixtures": "node scripts/build-test-fixtures.js",
|
|
12
|
+
"pretest": "node scripts/build-test-fixtures.js",
|
|
8
13
|
"test": "mocha tests/__run__.js",
|
|
9
14
|
"build": "npx node-pre-gyp build",
|
|
10
15
|
"build:full": "npx node-pre-gyp rebuild",
|
|
@@ -36,7 +41,7 @@
|
|
|
36
41
|
"lodash.camelcase": "4.3.0",
|
|
37
42
|
"lodash.isequal": "4.5.0",
|
|
38
43
|
"lodash.snakecase": "^4.1.1",
|
|
39
|
-
"nan": "^2.
|
|
44
|
+
"nan": "^2.27.0",
|
|
40
45
|
"node-gyp": "^11.2.0",
|
|
41
46
|
"remove-trailing-spaces": "^1.0.7",
|
|
42
47
|
"unindent": "^2.0.0"
|
|
@@ -51,9 +56,11 @@
|
|
|
51
56
|
"node-pre-gyp-github": "^1.4.5"
|
|
52
57
|
},
|
|
53
58
|
"files": [
|
|
59
|
+
"/bin",
|
|
54
60
|
"/lib",
|
|
55
61
|
"/src",
|
|
56
62
|
"/scripts",
|
|
63
|
+
"/tools",
|
|
57
64
|
"binding.gyp"
|
|
58
65
|
],
|
|
59
66
|
"binary": {
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* build-test-fixtures.js
|
|
3
|
+
*
|
|
4
|
+
* Makes the gobject-introspection test libraries (Utility, GIMarshallingTests,
|
|
5
|
+
* Regress) available for node-gtk's test suite. These libraries systematically
|
|
6
|
+
* exercise marshalling of every GObject type in every direction
|
|
7
|
+
* (in/out/inout/return), so they let us test node-gtk's type conversions
|
|
8
|
+
* exhaustively instead of ad-hoc.
|
|
9
|
+
*
|
|
10
|
+
* Strategy: always build from a single pinned upstream source revision, on
|
|
11
|
+
* every platform. Distro-shipped copies (the gobject-introspection package's
|
|
12
|
+
* bundled tests, or prebuilt gjs `installed-tests` typelibs) vary wildly by
|
|
13
|
+
* version — functions and types present on one machine are absent on another —
|
|
14
|
+
* which makes the test suite non-portable. Pinning one revision of the
|
|
15
|
+
* canonical `gobject-introspection-tests` repo and compiling it ourselves gives
|
|
16
|
+
* every machine (dev, Linux CI, macOS CI) the exact same API surface.
|
|
17
|
+
*
|
|
18
|
+
* The pinned sources are downloaded once (as a tarball) and cached under
|
|
19
|
+
* tests/gi-fixtures/.src/; the compiled output goes to tests/gi-fixtures/
|
|
20
|
+
* ({NAME}-1.0.typelib + lib{name}.so). Both are git-ignored.
|
|
21
|
+
*
|
|
22
|
+
* Run directly (`node scripts/build-test-fixtures.js`) or via `npm test`
|
|
23
|
+
* (it runs as a pretest step). Idempotent: existing fixtures are reused unless
|
|
24
|
+
* --force is passed. To bump the upstream revision, change SOURCE_REF.
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
const fs = require('fs')
|
|
28
|
+
const path = require('path')
|
|
29
|
+
const { execFileSync, execSync } = require('child_process')
|
|
30
|
+
|
|
31
|
+
const FIXTURES_DIR = path.join(__dirname, '..', 'tests', 'gi-fixtures')
|
|
32
|
+
const SRC_CACHE_DIR = path.join(FIXTURES_DIR, '.src')
|
|
33
|
+
const FORCE = process.argv.includes('--force')
|
|
34
|
+
const VERBOSE = process.argv.includes('--verbose') || process.env.VERBOSE
|
|
35
|
+
|
|
36
|
+
// Canonical upstream test sources, pinned to a specific revision so every
|
|
37
|
+
// machine builds the identical API. Bump SOURCE_REF to update.
|
|
38
|
+
const SOURCE_REPO = 'https://gitlab.gnome.org/GNOME/gobject-introspection-tests'
|
|
39
|
+
const SOURCE_REF = '5987255086f59ca271a3a0aa53fbbb15b189be65'
|
|
40
|
+
|
|
41
|
+
// Each fixture mirrors the upstream meson.build recipe: the GI namespace, its
|
|
42
|
+
// shared-library basename, the source/header files that make it up, the
|
|
43
|
+
// pkg-config packages it links, and the GI namespaces its typelib includes.
|
|
44
|
+
// Order matters: Regress's typelib includes Utility, so Utility is built first.
|
|
45
|
+
const FIXTURES = [
|
|
46
|
+
{
|
|
47
|
+
namespace: 'Utility',
|
|
48
|
+
library: 'utility',
|
|
49
|
+
identifierPrefix: 'Utility',
|
|
50
|
+
symbolPrefix: 'utility_',
|
|
51
|
+
sources: ['utility.c'],
|
|
52
|
+
headers: ['utility.h'],
|
|
53
|
+
packages: ['gobject-2.0'],
|
|
54
|
+
includes: ['GObject-2.0'],
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
namespace: 'GIMarshallingTests',
|
|
58
|
+
library: 'gimarshallingtests',
|
|
59
|
+
identifierPrefix: 'GIMarshallingTests',
|
|
60
|
+
symbolPrefix: 'gi_marshalling_tests_',
|
|
61
|
+
sources: ['gimarshallingtests.c', 'gimarshallingtestsextra.c'],
|
|
62
|
+
headers: ['gimarshallingtests.h', 'gimarshallingtestsextra.h'],
|
|
63
|
+
packages: ['gobject-2.0', 'gio-2.0'],
|
|
64
|
+
includes: ['Gio-2.0'],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
namespace: 'Regress',
|
|
68
|
+
library: 'regress',
|
|
69
|
+
identifierPrefix: 'Regress',
|
|
70
|
+
symbolPrefix: 'regress_',
|
|
71
|
+
sources: ['annotation.c', 'drawable.c', 'foo.c', 'regress.c', 'regressextra.c'],
|
|
72
|
+
headers: ['annotation.h', 'drawable.h', 'foo.h', 'regress.h', 'regressextra.h'],
|
|
73
|
+
packages: ['gobject-2.0', 'gio-2.0', 'cairo', 'cairo-gobject'],
|
|
74
|
+
includes: ['Gio-2.0', 'cairo-1.0', 'Utility-1.0'],
|
|
75
|
+
},
|
|
76
|
+
]
|
|
77
|
+
|
|
78
|
+
function log(...args) {
|
|
79
|
+
console.log('[fixtures]', ...args)
|
|
80
|
+
}
|
|
81
|
+
function vlog(...args) {
|
|
82
|
+
if (VERBOSE) console.log('[fixtures]', ...args)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function pkgConfig(args) {
|
|
86
|
+
return execSync(`pkg-config ${args}`, { encoding: 'utf8' }).trim()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function which(bin) {
|
|
90
|
+
try {
|
|
91
|
+
return execFileSync('sh', ['-c', `command -v ${bin}`], { encoding: 'utf8' }).trim()
|
|
92
|
+
} catch (e) {
|
|
93
|
+
return null
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fixtureIsPresent(fixture) {
|
|
98
|
+
const typelib = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.typelib`)
|
|
99
|
+
const lib = path.join(FIXTURES_DIR, `lib${fixture.library}.so`)
|
|
100
|
+
return fs.existsSync(typelib) && fs.existsSync(lib)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Download + extract the pinned upstream sources once; return the dir holding
|
|
104
|
+
// the .c/.h files. Cached under SRC_CACHE_DIR/<ref>, keyed by revision.
|
|
105
|
+
function ensureSources() {
|
|
106
|
+
const destDir = path.join(SRC_CACHE_DIR, SOURCE_REF)
|
|
107
|
+
// The repo's files live at the tarball root; a marker file confirms a
|
|
108
|
+
// complete previous extraction.
|
|
109
|
+
if (fs.existsSync(path.join(destDir, 'gimarshallingtests.c'))) {
|
|
110
|
+
vlog(`sources present at ${destDir}`)
|
|
111
|
+
return destDir
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
fs.mkdirSync(SRC_CACHE_DIR, { recursive: true })
|
|
115
|
+
const tarball = path.join(SRC_CACHE_DIR, `${SOURCE_REF}.tar.gz`)
|
|
116
|
+
const url = `${SOURCE_REPO}/-/archive/${SOURCE_REF}/src-${SOURCE_REF}.tar.gz`
|
|
117
|
+
|
|
118
|
+
log(`downloading test sources @ ${SOURCE_REF.slice(0, 12)}`)
|
|
119
|
+
execFileSync('curl', ['-fsSL', url, '-o', tarball], { stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
120
|
+
|
|
121
|
+
// The tarball extracts to a single top-level dir; strip it into destDir.
|
|
122
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
123
|
+
execFileSync('tar', ['xzf', tarball, '-C', destDir, '--strip-components=1'],
|
|
124
|
+
{ stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
125
|
+
fs.unlinkSync(tarball)
|
|
126
|
+
return destDir
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function buildFixture(fixture, srcDir, tools) {
|
|
130
|
+
const sources = fixture.sources.map(s => path.join(srcDir, s))
|
|
131
|
+
const headers = fixture.headers.map(h => path.join(srcDir, h))
|
|
132
|
+
if (![...sources, ...headers].every(fs.existsSync)) {
|
|
133
|
+
throw new Error(`sources for ${fixture.namespace} missing in ${srcDir}`)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const cflags = pkgConfig(`--cflags ${fixture.packages.join(' ')}`)
|
|
137
|
+
const libs = pkgConfig(`--libs ${fixture.packages.join(' ')}`)
|
|
138
|
+
const libPath = path.join(FIXTURES_DIR, `lib${fixture.library}.so`)
|
|
139
|
+
const girPath = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.gir`)
|
|
140
|
+
const typelibPath = path.join(FIXTURES_DIR, `${fixture.namespace}-1.0.typelib`)
|
|
141
|
+
|
|
142
|
+
// 1. shared library
|
|
143
|
+
const cc = process.env.CC || 'cc'
|
|
144
|
+
const compileCmd =
|
|
145
|
+
`${cc} -shared -fPIC -I"${srcDir}" ${cflags} ` +
|
|
146
|
+
`${sources.map(s => `"${s}"`).join(' ')} ${libs} -o "${libPath}"`
|
|
147
|
+
vlog(compileCmd)
|
|
148
|
+
execSync(compileCmd, { stdio: VERBOSE ? 'inherit' : 'pipe' })
|
|
149
|
+
|
|
150
|
+
// 2. introspection data (.gir)
|
|
151
|
+
const scanArgs = [
|
|
152
|
+
...sources, ...headers,
|
|
153
|
+
'--warn-all',
|
|
154
|
+
'--namespace', fixture.namespace,
|
|
155
|
+
'--nsversion', '1.0',
|
|
156
|
+
'--identifier-prefix', fixture.identifierPrefix,
|
|
157
|
+
'--symbol-prefix', fixture.symbolPrefix,
|
|
158
|
+
'--library', fixture.library,
|
|
159
|
+
'--library-path', FIXTURES_DIR,
|
|
160
|
+
// So that --include of an already-built local fixture (e.g. Utility-1.0,
|
|
161
|
+
// which Regress depends on) resolves its .gir from our output dir.
|
|
162
|
+
'--add-include-path', FIXTURES_DIR,
|
|
163
|
+
...fixture.includes.flatMap(i => ['--include', i]),
|
|
164
|
+
...fixture.packages.flatMap(p => ['--pkg', p]),
|
|
165
|
+
'--cflags-begin', ...cflags.split(/\s+/).filter(Boolean), `-I${srcDir}`, '--cflags-end',
|
|
166
|
+
'--output', girPath,
|
|
167
|
+
]
|
|
168
|
+
vlog(tools.scanner, scanArgs.join(' '))
|
|
169
|
+
execFileSync(tools.scanner, scanArgs, {
|
|
170
|
+
stdio: VERBOSE ? 'inherit' : 'pipe',
|
|
171
|
+
env: { ...process.env, LD_LIBRARY_PATH: `${FIXTURES_DIR}:${process.env.LD_LIBRARY_PATH || ''}` },
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
// 3. compiled typelib (--includedir resolves locally-built included girs)
|
|
175
|
+
execFileSync(tools.compiler, [girPath, '--includedir', FIXTURES_DIR, '--output', typelibPath], {
|
|
176
|
+
stdio: VERBOSE ? 'inherit' : 'pipe',
|
|
177
|
+
})
|
|
178
|
+
|
|
179
|
+
log(`built ${fixture.namespace}`)
|
|
180
|
+
return true
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function main() {
|
|
184
|
+
fs.mkdirSync(FIXTURES_DIR, { recursive: true })
|
|
185
|
+
|
|
186
|
+
const allPresent = FIXTURES.every(fixtureIsPresent)
|
|
187
|
+
if (!FORCE && allPresent) {
|
|
188
|
+
vlog('all fixtures already present, skipping (use --force to rebuild)')
|
|
189
|
+
log(`available fixtures: ${FIXTURES.map(f => f.namespace).join(', ')}`)
|
|
190
|
+
return
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const tools = {
|
|
194
|
+
scanner: which('g-ir-scanner'),
|
|
195
|
+
compiler: which('g-ir-compiler'),
|
|
196
|
+
}
|
|
197
|
+
if (!tools.scanner || !tools.compiler || !which('curl') || !which('tar')) {
|
|
198
|
+
log('WARNING: g-ir-scanner/g-ir-compiler/curl/tar not all available; ' +
|
|
199
|
+
'cannot build fixtures. Dependent tests will skip.')
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
let srcDir
|
|
204
|
+
try {
|
|
205
|
+
srcDir = ensureSources()
|
|
206
|
+
} catch (e) {
|
|
207
|
+
log(`WARNING: could not fetch test sources: ${e.message.split('\n')[0]}`)
|
|
208
|
+
log('dependent tests will skip')
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const results = []
|
|
213
|
+
for (const fixture of FIXTURES) {
|
|
214
|
+
if (!FORCE && fixtureIsPresent(fixture)) {
|
|
215
|
+
vlog(`${fixture.namespace} already present, skipping`)
|
|
216
|
+
results.push({ fixture, ok: true })
|
|
217
|
+
continue
|
|
218
|
+
}
|
|
219
|
+
let ok = false
|
|
220
|
+
try {
|
|
221
|
+
ok = buildFixture(fixture, srcDir, tools)
|
|
222
|
+
} catch (e) {
|
|
223
|
+
log(`failed to build ${fixture.namespace}: ${e.message.split('\n')[0]}`)
|
|
224
|
+
}
|
|
225
|
+
if (!ok)
|
|
226
|
+
log(`WARNING: could not provide ${fixture.namespace}; dependent tests will skip`)
|
|
227
|
+
results.push({ fixture, ok })
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const provided = results.filter(r => r.ok).map(r => r.fixture.namespace)
|
|
231
|
+
log(`available fixtures: ${provided.length ? provided.join(', ') : '(none)'}`)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (require.main === module)
|
|
235
|
+
main()
|
|
236
|
+
|
|
237
|
+
module.exports = { FIXTURES_DIR, FIXTURES }
|
package/scripts/ci.sh
CHANGED
|
@@ -42,14 +42,16 @@ function npm_test() {
|
|
|
42
42
|
|
|
43
43
|
if [[ $(uname -s) == 'Darwin' ]]; then
|
|
44
44
|
export GST_PLUGIN_SYSTEM_PATH=$(brew --prefix gstreamer)/lib/gstreamer-1.0;
|
|
45
|
+
# This branch calls mocha directly (not `npm test`), so the pretest
|
|
46
|
+
# fixture build does not run automatically; do it here. Best-effort:
|
|
47
|
+
# marshalling tests skip if fixtures cannot be produced on macOS.
|
|
48
|
+
npm run build:test-fixtures || true;
|
|
45
49
|
npx mocha \
|
|
46
50
|
--skip=callback \
|
|
47
|
-
--skip=union__fields \
|
|
48
51
|
tests/__run__.js
|
|
49
52
|
else
|
|
50
53
|
xvfb-run -a npm test -- \
|
|
51
|
-
--skip=callback
|
|
52
|
-
--skip=union__fields;
|
|
54
|
+
--skip=callback;
|
|
53
55
|
fi;
|
|
54
56
|
}
|
|
55
57
|
|
package/src/boxed.cc
CHANGED
|
@@ -43,6 +43,17 @@ size_t Boxed::GetSize (GIBaseInfo *boxed_info) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
gpointer AllocateBoxed (GType gtype, size_t size) {
|
|
47
|
+
// Registered boxed types are freed via g_boxed_free, which by GLib
|
|
48
|
+
// convention uses g_slice_free; match that with g_slice so freeing doesn't
|
|
49
|
+
// corrupt the slice allocator (#290, #213). Non-registered structs are
|
|
50
|
+
// freed with g_free, so allocate them with g_malloc0.
|
|
51
|
+
if (G_TYPE_IS_BOXED(gtype))
|
|
52
|
+
return g_slice_alloc0(size);
|
|
53
|
+
else
|
|
54
|
+
return g_malloc0(size);
|
|
55
|
+
}
|
|
56
|
+
|
|
46
57
|
static bool IsNoArgsConstructor (GIFunctionInfo *info) {
|
|
47
58
|
auto flags = g_function_info_get_flags (info);
|
|
48
59
|
return ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0
|
|
@@ -162,7 +173,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
162
173
|
return;
|
|
163
174
|
}
|
|
164
175
|
|
|
165
|
-
|
|
176
|
+
int n_constructor_args = -1;
|
|
166
177
|
|
|
167
178
|
void *boxed = NULL;
|
|
168
179
|
unsigned long size = 0;
|
|
@@ -201,6 +212,8 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
201
212
|
|
|
202
213
|
if (constructorInfo != NULL) {
|
|
203
214
|
|
|
215
|
+
n_constructor_args = g_callable_info_get_n_args(constructorInfo);
|
|
216
|
+
|
|
204
217
|
FunctionInfo func(constructorInfo);
|
|
205
218
|
GIArgument return_value;
|
|
206
219
|
GError *error = NULL;
|
|
@@ -223,7 +236,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
223
236
|
boxed = return_value.v_pointer;
|
|
224
237
|
|
|
225
238
|
} else if ((size = Boxed::GetSize(gi_info)) != 0) {
|
|
226
|
-
boxed =
|
|
239
|
+
boxed = AllocateBoxed(gtype, size);
|
|
227
240
|
|
|
228
241
|
} else {
|
|
229
242
|
Nan::ThrowError("Boxed allocation failed: no constructor found");
|
|
@@ -250,7 +263,7 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
|
|
|
250
263
|
|
|
251
264
|
SET_OBJECT_GTYPE (self, gtype);
|
|
252
265
|
|
|
253
|
-
if (
|
|
266
|
+
if (n_constructor_args <= 0)
|
|
254
267
|
InitBoxedFromObject(self, info[0]);
|
|
255
268
|
}
|
|
256
269
|
|
|
@@ -445,4 +458,21 @@ void* PointerFromWrapper(Local<Value> value) {
|
|
|
445
458
|
return boxed;
|
|
446
459
|
}
|
|
447
460
|
|
|
461
|
+
void DisownBoxed(Local<Value> value) {
|
|
462
|
+
if (!value->IsObject())
|
|
463
|
+
return;
|
|
464
|
+
|
|
465
|
+
Local<Object> object = TO_OBJECT (value);
|
|
466
|
+
// Boxed wrappers store the data pointer in field 0 and the Boxed* in field 1.
|
|
467
|
+
if (object->InternalFieldCount() < 2)
|
|
468
|
+
return;
|
|
469
|
+
|
|
470
|
+
Boxed *box = static_cast<Boxed *>(object->GetAlignedPointerFromInternalField(1));
|
|
471
|
+
if (box != NULL) {
|
|
472
|
+
box->owns_memory = false;
|
|
473
|
+
box->data = NULL;
|
|
474
|
+
}
|
|
475
|
+
object->SetAlignedPointerInInternalField(0, NULL);
|
|
476
|
+
}
|
|
477
|
+
|
|
448
478
|
};
|
package/src/boxed.h
CHANGED
|
@@ -29,9 +29,22 @@ public:
|
|
|
29
29
|
static size_t GetSize (GIBaseInfo *boxed_info) ;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
+
// Allocate zero-filled backing memory for a boxed/struct instance. Registered
|
|
33
|
+
// boxed types are freed with g_boxed_free (which, by GLib convention, uses
|
|
34
|
+
// g_slice_free), so they must be allocated with g_slice — allocating them with
|
|
35
|
+
// g_malloc0 and freeing with g_slice_free corrupts the slice allocator on
|
|
36
|
+
// GLib builds where GSlice is a real slab allocator (#290, #213).
|
|
37
|
+
gpointer AllocateBoxed (GType gtype, size_t size);
|
|
38
|
+
|
|
32
39
|
Local<Function> MakeBoxedClass (GIBaseInfo *info);
|
|
33
40
|
Local<FunctionTemplate> GetBoxedTemplate (GIBaseInfo *info, GType gtype);
|
|
34
41
|
Local<Value> WrapperFromBoxed (GIBaseInfo *info, void *data, ResourceOwnership ownership = kNone);
|
|
35
42
|
void * PointerFromWrapper (Local<Value>);
|
|
36
43
|
|
|
44
|
+
// Relinquish ownership of a boxed wrapper's memory: clears owns_memory so the
|
|
45
|
+
// GC finalizer won't free it, and nulls the data pointer so later use fails
|
|
46
|
+
// cleanly instead of touching freed memory. Used after an introspected method
|
|
47
|
+
// that frees the instance itself (e.g. *_free / *_unref) — see #429.
|
|
48
|
+
void DisownBoxed (Local<Value>);
|
|
49
|
+
|
|
37
50
|
};
|
package/src/callback.cc
CHANGED
|
@@ -34,8 +34,18 @@ Callback::Callback(Local<Function> fn, GICallableInfo* callback_info, GIScopeTyp
|
|
|
34
34
|
info = g_base_info_ref (callback_info);
|
|
35
35
|
#ifdef GI_AVAILABLE_IN_1_72
|
|
36
36
|
closure = g_callable_info_create_closure(info, &cif, Callback::Call, this);
|
|
37
|
+
/* On libffi 3.4+ the executable trampoline is a separate mapping from the
|
|
38
|
+
* writable ffi_closure, so the closure pointer is not itself callable and
|
|
39
|
+
* invoking it segfaults (#390, seen on Ubuntu 26 / libffi 3.5). Use the
|
|
40
|
+
* closure's native (executable) address instead. Fall back to the closure
|
|
41
|
+
* pointer if introspection can't supply one — passing NULL as the callback
|
|
42
|
+
* would break startup, since the bindings register callbacks at bootstrap. */
|
|
43
|
+
native_address = g_callable_info_get_closure_native_address(info, closure);
|
|
44
|
+
if (native_address == NULL)
|
|
45
|
+
native_address = (gpointer) closure;
|
|
37
46
|
#else
|
|
38
47
|
closure = g_callable_info_prepare_closure(info, &cif, Callback::Call, this);
|
|
48
|
+
native_address = (gpointer) closure;
|
|
39
49
|
#endif
|
|
40
50
|
scope_type = scope_type_;
|
|
41
51
|
}
|
|
@@ -171,6 +181,7 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
171
181
|
if (jsReturnArray->Length() != n_js_return_values) {
|
|
172
182
|
Throw::Error("Virtual function must return %u arguments but returned %u",
|
|
173
183
|
n_js_return_values, jsReturnArray->Length());
|
|
184
|
+
goto out;
|
|
174
185
|
}
|
|
175
186
|
|
|
176
187
|
if (hasVoidReturn)
|
|
@@ -194,6 +205,7 @@ void Callback::Execute (GIArgument *result, GIArgument **args, Callback *callbac
|
|
|
194
205
|
|
|
195
206
|
if (!success) {
|
|
196
207
|
Throw::InvalidReturnValue (&return_type_info, jsReturnValue);
|
|
208
|
+
goto out;
|
|
197
209
|
}
|
|
198
210
|
}
|
|
199
211
|
}
|
package/src/callback.h
CHANGED
|
@@ -16,6 +16,7 @@ namespace GNodeJS {
|
|
|
16
16
|
struct Callback {
|
|
17
17
|
ffi_cif cif;
|
|
18
18
|
ffi_closure *closure;
|
|
19
|
+
gpointer native_address; /* callable trampoline; may differ from closure on libffi 3.4+ */
|
|
19
20
|
Nan::Persistent<Function> persistent;
|
|
20
21
|
GICallableInfo *info;
|
|
21
22
|
GIScopeType scope_type;
|
package/src/closure.cc
CHANGED
|
@@ -19,6 +19,41 @@ using Nan::Persistent;
|
|
|
19
19
|
|
|
20
20
|
namespace GNodeJS {
|
|
21
21
|
|
|
22
|
+
/*
|
|
23
|
+
* An (out)/(inout) signal parameter arrives as a GValue holding a *pointer* to
|
|
24
|
+
* the value (e.g. a gint* for an `(inout) (type int)` parameter), not the value
|
|
25
|
+
* itself. Dereference it in place so the argument holds the pointed-to value.
|
|
26
|
+
*/
|
|
27
|
+
static void LoadGIArgumentFromPointer (GITypeInfo *type_info, GIArgument *arg) {
|
|
28
|
+
gpointer ptr = arg->v_pointer;
|
|
29
|
+
|
|
30
|
+
if (ptr == NULL) {
|
|
31
|
+
memset(arg, 0, sizeof(*arg));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
switch (g_type_info_get_tag(type_info)) {
|
|
36
|
+
case GI_TYPE_TAG_BOOLEAN: arg->v_boolean = *(gboolean*) ptr; break;
|
|
37
|
+
case GI_TYPE_TAG_INT8: arg->v_int8 = *(gint8*) ptr; break;
|
|
38
|
+
case GI_TYPE_TAG_UINT8: arg->v_uint8 = *(guint8*) ptr; break;
|
|
39
|
+
case GI_TYPE_TAG_INT16: arg->v_int16 = *(gint16*) ptr; break;
|
|
40
|
+
case GI_TYPE_TAG_UINT16: arg->v_uint16 = *(guint16*) ptr; break;
|
|
41
|
+
case GI_TYPE_TAG_INT32: arg->v_int32 = *(gint32*) ptr; break;
|
|
42
|
+
case GI_TYPE_TAG_UNICHAR:
|
|
43
|
+
case GI_TYPE_TAG_UINT32: arg->v_uint32 = *(guint32*) ptr; break;
|
|
44
|
+
case GI_TYPE_TAG_INT64: arg->v_int64 = *(gint64*) ptr; break;
|
|
45
|
+
case GI_TYPE_TAG_UINT64: arg->v_uint64 = *(guint64*) ptr; break;
|
|
46
|
+
case GI_TYPE_TAG_FLOAT: arg->v_float = *(gfloat*) ptr; break;
|
|
47
|
+
case GI_TYPE_TAG_DOUBLE: arg->v_double = *(gdouble*) ptr; break;
|
|
48
|
+
case GI_TYPE_TAG_GTYPE: arg->v_size = *(gsize*) ptr; break;
|
|
49
|
+
default:
|
|
50
|
+
// Pointer-like types (utf8, interface, array, list, ...): the value
|
|
51
|
+
// is itself a pointer stored at *ptr.
|
|
52
|
+
arg->v_pointer = *(gpointer*) ptr;
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
22
57
|
GClosure *Closure::New (Local<Function> function, GICallableInfo* info, guint signalId) {
|
|
23
58
|
Closure *closure = (Closure *) g_closure_new_simple (sizeof (*closure), GUINT_TO_POINTER(signalId));
|
|
24
59
|
closure->persistent.Reset(function);
|
|
@@ -70,8 +105,27 @@ void Closure::Execute(GICallableInfo *info, guint signal_id,
|
|
|
70
105
|
ownership = kNone;
|
|
71
106
|
}
|
|
72
107
|
}
|
|
73
|
-
|
|
108
|
+
|
|
109
|
+
GIDirection direction = g_arg_info_get_direction(&arg_info);
|
|
110
|
+
if (direction == GI_DIRECTION_INOUT) {
|
|
111
|
+
// The GValue holds a pointer to the in/out value; dereference it
|
|
112
|
+
// so the handler receives the actual value (#405).
|
|
113
|
+
LoadGIArgumentFromPointer(&type_info, &argument);
|
|
74
114
|
ownership = kNone;
|
|
115
|
+
} else if (direction == GI_DIRECTION_OUT) {
|
|
116
|
+
if (g_arg_info_is_caller_allocates(&arg_info)) {
|
|
117
|
+
// Caller-allocated out (e.g. a GdkRectangle in
|
|
118
|
+
// GtkOverlay::get-child-position): the GValue already holds a
|
|
119
|
+
// pointer to caller (GLib/GTK) memory. Pass a live wrapper so the
|
|
120
|
+
// handler fills it in place — it is NOT written back from the
|
|
121
|
+
// return value (see the output loop below). #444.
|
|
122
|
+
ownership = kNone;
|
|
123
|
+
} else {
|
|
124
|
+
// Callee-allocated out: no meaningful incoming value; the handler
|
|
125
|
+
// supplies it through the return value.
|
|
126
|
+
memset(&argument, 0, sizeof(argument));
|
|
127
|
+
ownership = kNone;
|
|
128
|
+
}
|
|
75
129
|
}
|
|
76
130
|
|
|
77
131
|
js_args[i - 1] = GIArgumentToV8(&type_info, &argument, -1, ownership);
|
|
@@ -104,7 +158,61 @@ void Closure::Execute(GICallableInfo *info, guint signal_id,
|
|
|
104
158
|
if (!try_catch.HasCaught()
|
|
105
159
|
&& result.ToLocal(&return_value)) {
|
|
106
160
|
|
|
107
|
-
if (
|
|
161
|
+
if (info) {
|
|
162
|
+
// Distribute the handler's return value across the signal's return
|
|
163
|
+
// value and its (out)/(inout) parameters, writing each back (#405).
|
|
164
|
+
// When there is more than one output the handler returns an array,
|
|
165
|
+
// mirroring how out-arguments are returned from a function call.
|
|
166
|
+
GIArgInfo out_arg_info;
|
|
167
|
+
GITypeInfo out_type_info;
|
|
168
|
+
|
|
169
|
+
// Caller-allocated out structs are filled in place via the wrapper
|
|
170
|
+
// passed to the handler, so they are not part of the return value.
|
|
171
|
+
int n_outputs = (g_return_value != NULL) ? 1 : 0;
|
|
172
|
+
for (guint i = 1; i < n_param_values; i++) {
|
|
173
|
+
g_callable_info_load_arg(info, i - 1, &out_arg_info);
|
|
174
|
+
GIDirection d = g_arg_info_get_direction(&out_arg_info);
|
|
175
|
+
if (d == GI_DIRECTION_INOUT ||
|
|
176
|
+
(d == GI_DIRECTION_OUT && !g_arg_info_is_caller_allocates(&out_arg_info)))
|
|
177
|
+
n_outputs++;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
bool from_array = n_outputs > 1 && return_value->IsArray();
|
|
181
|
+
int output_index = 0;
|
|
182
|
+
|
|
183
|
+
#define NEXT_OUTPUT() ( \
|
|
184
|
+
n_outputs <= 1 \
|
|
185
|
+
? return_value \
|
|
186
|
+
: (from_array \
|
|
187
|
+
? Nan::Get(return_value.As<v8::Array>(), output_index++).ToLocalChecked() \
|
|
188
|
+
: (output_index++, Nan::Undefined().As<Value>())))
|
|
189
|
+
|
|
190
|
+
if (g_return_value) {
|
|
191
|
+
if (!V8ToGValue (g_return_value, NEXT_OUTPUT(), kCopy))
|
|
192
|
+
goto throw_exception;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
for (guint i = 1; i < n_param_values; i++) {
|
|
196
|
+
g_callable_info_load_arg(info, i - 1, &out_arg_info);
|
|
197
|
+
GIDirection d = g_arg_info_get_direction(&out_arg_info);
|
|
198
|
+
if (d != GI_DIRECTION_OUT && d != GI_DIRECTION_INOUT)
|
|
199
|
+
continue;
|
|
200
|
+
// Caller-allocated out structs were filled in place by the handler
|
|
201
|
+
// via their wrapper; nothing to write back from the return value.
|
|
202
|
+
if (d == GI_DIRECTION_OUT && g_arg_info_is_caller_allocates(&out_arg_info))
|
|
203
|
+
continue;
|
|
204
|
+
|
|
205
|
+
g_arg_info_load_type(&out_arg_info, &out_type_info);
|
|
206
|
+
|
|
207
|
+
GIArgument out_arg;
|
|
208
|
+
memcpy(&out_arg, ¶m_values[i].data[0], sizeof(GIArgument));
|
|
209
|
+
if (out_arg.v_pointer != NULL)
|
|
210
|
+
V8ToOutGIArgument(&out_type_info, &out_arg, NEXT_OUTPUT(), true);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
#undef NEXT_OUTPUT
|
|
214
|
+
}
|
|
215
|
+
else if (g_return_value) {
|
|
108
216
|
if (!V8ToGValue (g_return_value, return_value, kCopy))
|
|
109
217
|
goto throw_exception;
|
|
110
218
|
}
|