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
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
<a>
|
|
3
3
|
<img
|
|
4
4
|
alt="NODE-GTK"
|
|
5
|
-
width="
|
|
5
|
+
width="200"
|
|
6
6
|
src="https://raw.githubusercontent.com/romgrk/node-gtk/master/img/node-gtk-logo.svg?sanitize=true"
|
|
7
7
|
/>
|
|
8
8
|
</a>
|
|
@@ -10,191 +10,91 @@
|
|
|
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
|
-
<img src="https://img.shields.io/npm/v/node-gtk" alt="Package Version" />
|
|
16
15
|
</p>
|
|
17
16
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
Supported Node.js versions: **20**, **22**, **24** (other versions may work but are untested)<br>
|
|
23
|
-
Pre-built binaries available for: **Linux**, **macOS**
|
|
24
|
-
|
|
25
|
-
### Table of contents
|
|
26
|
-
|
|
27
|
-
- [Usage](#usage)
|
|
28
|
-
- [Documentation](#documentation)
|
|
29
|
-
- [TypeScript](#typescript)
|
|
30
|
-
- [Installing and building](#installing-and-building)
|
|
31
|
-
- [Contributing](#contributing)
|
|
32
|
-
|
|
33
|
-
## Usage
|
|
34
|
-
|
|
35
|
-
Below is a [minimal example](./examples/hello-world.js) of how to use node-gtk:
|
|
36
|
-
|
|
37
|
-
```javascript
|
|
38
|
-
const gi = require('node-gtk');
|
|
39
|
-
const GLib = gi.require('GLib', '2.0');
|
|
40
|
-
const Gtk = gi.require('Gtk', '4.0');
|
|
41
|
-
const Adw = gi.require('Adw', '1');
|
|
42
|
-
|
|
43
|
-
const loop = GLib.MainLoop.new(null, false);
|
|
44
|
-
const app = new Adw.Application('com.github.romgrk.node-gtk.hello', 0);
|
|
45
|
-
|
|
46
|
-
app.on('activate', () => {
|
|
47
|
-
const content = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
|
|
48
|
-
content.append(new Adw.HeaderBar());
|
|
49
|
-
content.append(new Gtk.Label({ label: 'Hello Adwaita!', vexpand: true }));
|
|
17
|
+
<p align="center">
|
|
18
|
+
<a href="#usage">Usage</a> · <a href="#installing">Installing</a> · <a href="./doc/index.md">Documentation</a> · <a href="#contributing">Contributing</a>
|
|
19
|
+
</p>
|
|
50
20
|
|
|
51
|
-
|
|
52
|
-
window.setTitle('node-gtk');
|
|
53
|
-
window.setDefaultSize(300, 120);
|
|
54
|
-
window.setContent(content);
|
|
55
|
-
window.on('close-request', () => (loop.quit(), app.quit(), false));
|
|
56
|
-
window.present();
|
|
21
|
+
<br />
|
|
57
22
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
process.exit(app.run([]));
|
|
63
|
-
```
|
|
23
|
+
`node-gtk` lets you build native GTK apps on **linux**, **macOS** and **windows**
|
|
24
|
+
with full **ESM**, **TypeScript** and **CSS hot-reload** support. Prebuilt binaries
|
|
25
|
+
are available for Node.js versions **20**, **22** and **24**.
|
|
64
26
|
|
|
65
27
|
<p align="center">
|
|
66
|
-
<img src="./img/
|
|
28
|
+
<img src="./img/browser.png" style="max-width: 500px; height: auto;" alt="A web browser build with node-gtk" />
|
|
67
29
|
</p>
|
|
68
30
|
|
|
69
|
-
|
|
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
|
-
You can also easily create custom applications:
|
|
112
|
-
|
|
113
|
-
[A web browser (using WebKit2GTK)](./examples/browser.js)
|
|
31
|
+
## Usage
|
|
114
32
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
</p>
|
|
33
|
+
The **create** tool generates a complete, ready-to-run GTK/Adwaita project, so you
|
|
34
|
+
can start building immediately after [installing GTK4](#installing):
|
|
118
35
|
|
|
119
|
-
|
|
36
|
+
```sh
|
|
37
|
+
npx node-gtk create <your-app>
|
|
38
|
+
```
|
|
120
39
|
|
|
121
40
|
<p align="center">
|
|
122
|
-
<img src="./img/
|
|
41
|
+
<img src="./img/create-app-example.png" style="width: 500px; height: auto;"/>
|
|
123
42
|
</p>
|
|
124
43
|
|
|
125
|
-
|
|
44
|
+
Also see our [hello world](./examples/hello-world.mjs), [web browser](./examples/browser.mjs)
|
|
45
|
+
or [system monitor](./examples/system-monitor.mjs) examples.
|
|
126
46
|
|
|
127
|
-
|
|
47
|
+
## Installing
|
|
128
48
|
|
|
129
|
-
|
|
49
|
+
There are two steps:
|
|
130
50
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
## TypeScript
|
|
51
|
+
1. Install `node-gtk` itself (*done by the create tool*)
|
|
52
|
+
2. Install the native libraries you use (see examples per platform below)
|
|
134
53
|
|
|
135
|
-
|
|
136
|
-
straight from the GObject-Introspection typelibs installed on your machine — so
|
|
137
|
-
the types always match your actual library versions and node-gtk's own runtime
|
|
138
|
-
shape (camelCase methods, signal callbacks, nullability, etc.).
|
|
54
|
+
#### Linux
|
|
139
55
|
|
|
140
56
|
```sh
|
|
141
|
-
#
|
|
142
|
-
|
|
143
|
-
```
|
|
57
|
+
# archlinux
|
|
58
|
+
pacman -S gtk4 libadwaita
|
|
144
59
|
|
|
145
|
-
|
|
146
|
-
|
|
60
|
+
# fedora
|
|
61
|
+
dnf install gtk4 libadwaita
|
|
147
62
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
"compilerOptions": {
|
|
151
|
-
"moduleResolution": "node16",
|
|
152
|
-
"paths": { "node-gtk": ["./node_modules/.node-gtk-types/node-gtk.d.ts"] }
|
|
153
|
-
}
|
|
154
|
-
}
|
|
63
|
+
# ubuntu
|
|
64
|
+
# Already installed :)
|
|
155
65
|
```
|
|
156
66
|
|
|
157
|
-
|
|
158
|
-
arguments:
|
|
67
|
+
#### macOS
|
|
159
68
|
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const Gtk = gi.require('Gtk', '4.0') // typed as the Gtk-4.0 namespace
|
|
164
|
-
const win = new Gtk.ApplicationWindow({ title: 'Hello', defaultWidth: 400 })
|
|
165
|
-
win.on('close-request', () => false) // signal name + callback are typed
|
|
69
|
+
```sh
|
|
70
|
+
brew install gtk4 libadwaita adwaita-icon-theme
|
|
166
71
|
```
|
|
167
72
|
|
|
168
|
-
|
|
169
|
-
camelCase methods with real return types, GI nullability, typed signal
|
|
170
|
-
overloads, enums, `bigint` for 64-bit integers, out-parameters surfaced as the
|
|
171
|
-
return value, and cross-namespace types. GNOME's API documentation is included
|
|
172
|
-
as JSDoc (with `@param`/`@returns`), so editors show it on hover — this reads the
|
|
173
|
-
`.gir` files installed by the libraries' `-dev`/`-devel` packages; pass
|
|
174
|
-
`--no-docs` for leaner output if they aren't installed or you don't want them.
|
|
73
|
+
#### Windows
|
|
175
74
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
```json
|
|
180
|
-
{ "scripts": { "postinstall": "node-gtk generate-types Gtk-4.0 Adw-1" } }
|
|
75
|
+
```sh
|
|
76
|
+
# Already installed :)
|
|
181
77
|
```
|
|
182
78
|
|
|
183
|
-
|
|
79
|
+
> [!NOTE]
|
|
80
|
+
> Windows doesn't have the dependencies we need in a package manager, therefore
|
|
81
|
+
> `node-gtk` ships prebuilt versions of GTK 4 / Adwaita, so `npm install node-gtk`
|
|
82
|
+
> is all you need **if** your dependency is in our
|
|
83
|
+
> [list of prebuilt libraries](./windows/runtime-libraries.txt).
|
|
184
84
|
|
|
185
|
-
|
|
85
|
+
### build from source
|
|
186
86
|
|
|
187
|
-
|
|
87
|
+
Building from source, or [contributing](./doc/contributing.md)? See [Building from source](./doc/building.md).
|
|
188
88
|
|
|
189
|
-
##
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
[Read our documentation here](./doc/index.md)
|
|
190
92
|
|
|
191
|
-
|
|
192
|
-
run `npm run configure`. You can then build the project with `npm run build`. To generate the `compile_commands.json`
|
|
193
|
-
for LSP to work nicely, you can use [bear](https://github.com/rizsotto/Bear) as `bear -- npm run build`.
|
|
93
|
+
## Other notes
|
|
194
94
|
|
|
195
|
-
- https://
|
|
196
|
-
|
|
197
|
-
|
|
95
|
+
`node-gtk` is a [gobject-introspection](https://gi.readthedocs.io/en/latest) library
|
|
96
|
+
for nodejs. It makes it possible to use any introspected C library, such as GTK,
|
|
97
|
+
usable. It is similar in essence to [GJS](https://wiki.gnome.org/action/show/Projects/Gjs)
|
|
98
|
+
or [PyGObject](https://pygobject.readthedocs.io).
|
|
198
99
|
|
|
199
|
-
|
|
200
|
-
discussions preferably.
|
|
100
|
+
[MIT License](./LICENSE)
|
package/bin/node-gtk.js
CHANGED
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Subcommands:
|
|
6
6
|
* generate-types Generate TypeScript declarations from the installed typelibs.
|
|
7
|
+
* create Create a new GTK/Adwaita application.
|
|
8
|
+
* list List the GObject-Introspection libraries available locally.
|
|
7
9
|
*/
|
|
8
10
|
|
|
9
11
|
const cmd = process.argv[2]
|
|
@@ -12,6 +14,13 @@ switch (cmd) {
|
|
|
12
14
|
case 'generate-types':
|
|
13
15
|
require('../tools/generate-types.js').run(process.argv.slice(3))
|
|
14
16
|
break
|
|
17
|
+
case 'create':
|
|
18
|
+
require('../tools/create-app.js').run(process.argv.slice(3))
|
|
19
|
+
break
|
|
20
|
+
case 'list':
|
|
21
|
+
case 'list-libraries':
|
|
22
|
+
require('../tools/list-libraries.js').run(process.argv.slice(3))
|
|
23
|
+
break
|
|
15
24
|
case undefined:
|
|
16
25
|
case '-h':
|
|
17
26
|
case '--help':
|
|
@@ -20,9 +29,11 @@ switch (cmd) {
|
|
|
20
29
|
Usage: node-gtk <command> [options]
|
|
21
30
|
|
|
22
31
|
Commands:
|
|
32
|
+
create <directory> Create a new GTK/Adwaita app
|
|
23
33
|
generate-types <Namespace-Version> [...] Generate TypeScript types (.d.ts)
|
|
34
|
+
list [filter] List available libraries & versions
|
|
24
35
|
|
|
25
|
-
Run \`node-gtk
|
|
36
|
+
Run \`node-gtk <command> --help\` for details.`)
|
|
26
37
|
process.exit(cmd ? 0 : 1)
|
|
27
38
|
break
|
|
28
39
|
default:
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* hooks.mjs — Node.js ESM module-customization hooks for the `gi:` scheme.
|
|
3
|
+
*
|
|
4
|
+
* Enables `import Gtk from 'gi:Gtk-4.0'`, where the default export is the
|
|
5
|
+
* namespace object returned by node-gtk's `gi.require('Gtk', '4.0')`. Members are
|
|
6
|
+
* read off that object: `const { Box, Label } = Gtk`.
|
|
7
|
+
*
|
|
8
|
+
* Install them with `node --import node-gtk/register app.mjs` (see register.mjs).
|
|
9
|
+
*
|
|
10
|
+
* The hooks run on a separate loader thread, so they do NO native work: `load`
|
|
11
|
+
* only emits a tiny synthetic ES module whose body calls `gi.require` on the
|
|
12
|
+
* main thread when the module is evaluated. Requires Node >= 20.6 (module.register).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const PREFIX = 'gi:'
|
|
16
|
+
|
|
17
|
+
/* Absolute file:// URL to lib/index.js, embedded into the generated source. The
|
|
18
|
+
* synthetic module's parent URL is the schemeless `gi:` URL, which has no
|
|
19
|
+
* filesystem base, so a bare `import 'node-gtk'` could not be resolved from it —
|
|
20
|
+
* the absolute URL sidesteps resolution entirely. */
|
|
21
|
+
const INDEX_URL = new URL('../index.js', import.meta.url).href
|
|
22
|
+
|
|
23
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
24
|
+
if (specifier.startsWith(PREFIX))
|
|
25
|
+
return { url: specifier, shortCircuit: true }
|
|
26
|
+
return nextResolve(specifier, context)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function load(url, context, nextLoad) {
|
|
30
|
+
if (!url.startsWith(PREFIX))
|
|
31
|
+
return nextLoad(url, context)
|
|
32
|
+
|
|
33
|
+
// `gi:Gtk-4.0` -> ('Gtk', '4.0'); `gi:Adw-1` -> ('Adw', '1'); `gi:cairo` -> ('cairo', null).
|
|
34
|
+
// Split on the first '-' only: GI namespace names never contain '-', versions may.
|
|
35
|
+
const spec = url.slice(PREFIX.length)
|
|
36
|
+
const dash = spec.indexOf('-')
|
|
37
|
+
const name = dash === -1 ? spec : spec.slice(0, dash)
|
|
38
|
+
const version = dash === -1 ? null : spec.slice(dash + 1)
|
|
39
|
+
|
|
40
|
+
const call = version === null
|
|
41
|
+
? `gi.require(${JSON.stringify(name)})`
|
|
42
|
+
: `gi.require(${JSON.stringify(name)}, ${JSON.stringify(version)})`
|
|
43
|
+
|
|
44
|
+
const source =
|
|
45
|
+
`import gi from ${JSON.stringify(INDEX_URL)};\n` +
|
|
46
|
+
`export default ${call};\n`
|
|
47
|
+
|
|
48
|
+
return { format: 'module', shortCircuit: true, source }
|
|
49
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* register.mjs — install the `gi:` import hooks.
|
|
3
|
+
*
|
|
4
|
+
* Usage: node --import node-gtk/register app.mjs
|
|
5
|
+
*
|
|
6
|
+
* Then, in app.mjs:
|
|
7
|
+
* import Gtk from 'gi:Gtk-4.0'
|
|
8
|
+
* const { Box, Label } = Gtk
|
|
9
|
+
*
|
|
10
|
+
* Note: hooks only affect imports evaluated *after* registration. To use a static
|
|
11
|
+
* `import ... from 'gi:...'` in your entry module, register via the `--import`
|
|
12
|
+
* flag above (not a programmatic `import 'node-gtk/register'` in that same file).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { register } from 'node:module'
|
|
16
|
+
|
|
17
|
+
register(new URL('./hooks.mjs', import.meta.url))
|
package/lib/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const moduleCache = internal.GetModuleCache()
|
|
|
8
8
|
// Must be loaded first, to setup the GI functions
|
|
9
9
|
const bootstrap = require('./bootstrap.js')
|
|
10
10
|
const module_ = require('./module.js')
|
|
11
|
-
|
|
11
|
+
require('./loop.js') // installs the automatic main-loop integration
|
|
12
12
|
const registerClass = require('./register-class.js')
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -42,7 +42,6 @@ function getGType(value) {
|
|
|
42
42
|
module.exports = {
|
|
43
43
|
// Public API
|
|
44
44
|
...module_,
|
|
45
|
-
startLoop: loop.start,
|
|
46
45
|
registerClass: registerClass,
|
|
47
46
|
getGType: getGType,
|
|
48
47
|
System: internal.System,
|
package/lib/index.mjs
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* index.mjs
|
|
3
|
+
*
|
|
4
|
+
* ESM facade over the CommonJS entry (index.js), so that named imports work:
|
|
5
|
+
*
|
|
6
|
+
* import gi, { require, registerClass } from 'node-gtk'
|
|
7
|
+
*
|
|
8
|
+
* Node's CommonJS-to-ESM named-export detection (cjs-module-lexer) cannot see
|
|
9
|
+
* through index.js's computed `module.exports`, so we re-export explicitly here.
|
|
10
|
+
* Both this file and `require('node-gtk')` share the same underlying index.js
|
|
11
|
+
* instance (Node caches it by path), so there is no duplicated state.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import gi from './index.js'
|
|
15
|
+
|
|
16
|
+
export default gi
|
|
17
|
+
|
|
18
|
+
export const require = gi.require
|
|
19
|
+
export const isLoaded = gi.isLoaded
|
|
20
|
+
export const prependSearchPath = gi.prependSearchPath
|
|
21
|
+
export const prependLibraryPath = gi.prependLibraryPath
|
|
22
|
+
export const listAvailableModules = gi.listAvailableModules
|
|
23
|
+
export const registerClass = gi.registerClass
|
|
24
|
+
export const getGType = gi.getGType
|
|
25
|
+
export const System = gi.System
|
package/lib/inspect.js
CHANGED
package/lib/loop.js
CHANGED
|
@@ -58,6 +58,11 @@ function start() {
|
|
|
58
58
|
* @returns {*} the native return value, or undefined when deferred
|
|
59
59
|
*/
|
|
60
60
|
function runLoopEntry(run) {
|
|
61
|
+
// The loop integration must be active before we block in a native main loop,
|
|
62
|
+
// so start it automatically on the first run. Calling startLoop() explicitly
|
|
63
|
+
// still works and is no longer required (start() is idempotent).
|
|
64
|
+
start()
|
|
65
|
+
|
|
61
66
|
if (internal.IsRunningMicrotasks()) {
|
|
62
67
|
setImmediate(run)
|
|
63
68
|
return undefined
|
package/lib/module.js
CHANGED
|
@@ -129,6 +129,12 @@ function listAvailableModules() {
|
|
|
129
129
|
// Helpers
|
|
130
130
|
|
|
131
131
|
function parseModuleFilename(filename) {
|
|
132
|
-
|
|
133
|
-
|
|
132
|
+
// Typelibs are named `Name-Version.typelib`; the version is the trailing
|
|
133
|
+
// `-X.Y`, so split on the LAST dash — namespaces themselves may contain dashes
|
|
134
|
+
// (e.g. `GUPnP-DLNA-2.0` -> name `GUPnP-DLNA`, version `2.0`).
|
|
135
|
+
const base = filename.replace(/\.typelib$/, '')
|
|
136
|
+
const dash = base.lastIndexOf('-')
|
|
137
|
+
if (dash === -1)
|
|
138
|
+
return { name: base, version: '' }
|
|
139
|
+
return { name: base.slice(0, dash), version: base.slice(dash + 1) }
|
|
134
140
|
}
|
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/lib/register-class.js
CHANGED
|
@@ -11,17 +11,50 @@ const GObject = module_.require('GObject')
|
|
|
11
11
|
|
|
12
12
|
module.exports = registerClass
|
|
13
13
|
|
|
14
|
+
// Make registerClass() optional: the first `new Subclass()` of an unregistered
|
|
15
|
+
// JS subclass lazily registers it through this hook (see GObjectConstructor in
|
|
16
|
+
// src/gobject.cc). registerClass() stays available for callers that need the
|
|
17
|
+
// GType before constructing (e.g. getGType, GtkBuilder templates).
|
|
18
|
+
internal.SetLazyClassRegister(registerClass)
|
|
19
|
+
|
|
20
|
+
// A registered class owns its `__gtype__` (native classes via the prototype
|
|
21
|
+
// template, JS classes via registerClass below); an unregistered subclass only
|
|
22
|
+
// inherits one. Used to make registration idempotent and to find ancestors that
|
|
23
|
+
// still need registering.
|
|
24
|
+
function isRegistered(klass) {
|
|
25
|
+
return Object.prototype.hasOwnProperty.call(klass.prototype, '__gtype__')
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
/**
|
|
15
|
-
* Create a new GObject type
|
|
29
|
+
* Create a new GObject type.
|
|
30
|
+
*
|
|
31
|
+
* To override a virtual function, define a method named `virtual_` + the
|
|
32
|
+
* camelCase vfunc name (e.g. `virtual_sizeAllocate` overrides
|
|
33
|
+
* `size_allocate`, `virtual_getRequestMode` overrides `get_request_mode`).
|
|
34
|
+
* Only `virtual_*`-prefixed methods are wired into the vtable; plain methods are
|
|
35
|
+
* never treated as overrides. Chain up to the parent implementation with
|
|
36
|
+
* `super.virtual_sizeAllocate(...)`. See doc/index.md "Inheritance".
|
|
37
|
+
*
|
|
16
38
|
* @param {Class} klass - The class to register
|
|
17
39
|
* @param {string} [klass.GTypeName] - The name of the GType (klass.name by default)
|
|
40
|
+
* @returns {Class} the same class (so it can be assigned or used as a decorator)
|
|
18
41
|
*/
|
|
19
42
|
function registerClass(klass) {
|
|
43
|
+
// Idempotent: a class that already owns a GType is registered. This also makes
|
|
44
|
+
// the lazy-on-first-construct path a no-op for explicitly-registered classes.
|
|
45
|
+
if (isRegistered(klass))
|
|
46
|
+
return klass
|
|
47
|
+
|
|
20
48
|
const parent = Object.getPrototypeOf(klass.prototype).constructor
|
|
21
49
|
|
|
22
50
|
if (!(klass.prototype instanceof GObject.Object))
|
|
23
51
|
throw new Error(`Invalid base class (${parent.name})`)
|
|
24
52
|
|
|
53
|
+
// Register any unregistered ancestor first, so a subclass can be constructed
|
|
54
|
+
// (or registered) without its superclass having been registered by hand.
|
|
55
|
+
if (!isRegistered(parent))
|
|
56
|
+
registerClass(parent)
|
|
57
|
+
|
|
25
58
|
const name = createGTypeName(klass)
|
|
26
59
|
const gtype = GObject.typeFromName(name)
|
|
27
60
|
const parentName = getGTypeName(parent)
|
|
@@ -41,26 +74,51 @@ function registerClass(klass) {
|
|
|
41
74
|
|
|
42
75
|
// Setup virtual functions
|
|
43
76
|
setupVirtualFunctions(klass, klassGtype, parentGtype)
|
|
77
|
+
|
|
78
|
+
return klass
|
|
44
79
|
}
|
|
45
80
|
|
|
46
81
|
// Helpers
|
|
47
82
|
|
|
83
|
+
/* Methods whose name starts with `virtual_` are treated as virtual-function
|
|
84
|
+
* overrides — and *only* those. This makes overriding explicit and opt-in: a
|
|
85
|
+
* plain method named `dispose`, `getProperty`, `sizeAllocate`, … can no longer
|
|
86
|
+
* silently hijack the matching GObject vfunc (issue #457). The prefix also keeps
|
|
87
|
+
* the override name distinct from the public invoker method of the same vfunc
|
|
88
|
+
* (e.g. `widget.sizeAllocate(...)` the method vs. the `virtual_sizeAllocate`
|
|
89
|
+
* override), so the two no longer collide. */
|
|
90
|
+
const VIRTUAL_PREFIX = /^virtual_/
|
|
91
|
+
|
|
92
|
+
/* `virtual_getRequestMode` -> `get_request_mode` (drop the prefix, snake_case the
|
|
93
|
+
* rest). snakeCase('virtual_getRequestMode') === 'virtual_get_request_mode'. */
|
|
94
|
+
function vfuncNativeName(key) {
|
|
95
|
+
return snakeCase(key).replace(/^virtual_/, '')
|
|
96
|
+
}
|
|
97
|
+
|
|
48
98
|
function setupVirtualFunctions(klass, klassGtype, parentGtype) {
|
|
49
99
|
const parentInfo = findInfoByGtype(parentGtype)
|
|
50
100
|
if (!parentInfo)
|
|
51
101
|
throw new Error(`Could not find GIR data in inheritance chain`)
|
|
52
102
|
|
|
103
|
+
const parentPrototype = Object.getPrototypeOf(klass.prototype)
|
|
104
|
+
|
|
53
105
|
Object.getOwnPropertyNames(klass.prototype).forEach(key => {
|
|
54
106
|
if (key === 'constructor')
|
|
55
107
|
return
|
|
108
|
+
if (!VIRTUAL_PREFIX.test(key))
|
|
109
|
+
return
|
|
56
110
|
if (typeof klass.prototype[key] !== 'function')
|
|
57
111
|
return
|
|
58
112
|
|
|
59
|
-
const nativeName =
|
|
113
|
+
const nativeName = vfuncNativeName(key)
|
|
60
114
|
const vfuncInfo = findVFunc(klassGtype, parentInfo, nativeName)
|
|
61
115
|
|
|
62
116
|
if (!vfuncInfo)
|
|
63
|
-
|
|
117
|
+
throw new Error(
|
|
118
|
+
`${klass.name}.${key}: no virtual function '${nativeName}' on ` +
|
|
119
|
+
`'${GObject.typeName(parentGtype)}' or its interfaces. A 'virtual_*' ` +
|
|
120
|
+
`method must name an existing vfunc (e.g. 'virtual_sizeAllocate' for ` +
|
|
121
|
+
`'size_allocate'); rename it if it is a plain method.`)
|
|
64
122
|
|
|
65
123
|
internal.RegisterVFunc(
|
|
66
124
|
vfuncInfo,
|
|
@@ -68,6 +126,31 @@ function setupVirtualFunctions(klass, klassGtype, parentGtype) {
|
|
|
68
126
|
nativeName,
|
|
69
127
|
klass.prototype[key]
|
|
70
128
|
)
|
|
129
|
+
|
|
130
|
+
installParentVFunc(parentPrototype, parentGtype, key, vfuncInfo)
|
|
131
|
+
})
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/* Make `super.<vfunc>(...)` reachable from an override. The override replaces
|
|
135
|
+
* the parent's implementation in the class vtable, so a JS subclass otherwise
|
|
136
|
+
* has no way to call the implementation it overrode. We install, on the parent
|
|
137
|
+
* GI class's prototype, a method that invokes the *parent's* native vfunc impl
|
|
138
|
+
* (resolved through `parentGtype`'s vtable, not the overriding subclass's).
|
|
139
|
+
*
|
|
140
|
+
* Only the native boundary needs bridging: if the parent prototype already owns
|
|
141
|
+
* `key` — i.e. the parent is itself a registered JS class that overrode this
|
|
142
|
+
* vfunc — then `super.<vfunc>()` resolves to that JS method on its own. */
|
|
143
|
+
function installParentVFunc(parentPrototype, parentGtype, key, vfuncInfo) {
|
|
144
|
+
if (Object.prototype.hasOwnProperty.call(parentPrototype, key))
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
Object.defineProperty(parentPrototype, key, {
|
|
148
|
+
value: function (...args) {
|
|
149
|
+
return internal.CallVFunc(vfuncInfo, parentGtype, this, args)
|
|
150
|
+
},
|
|
151
|
+
writable: true,
|
|
152
|
+
configurable: true,
|
|
153
|
+
enumerable: false,
|
|
71
154
|
})
|
|
72
155
|
}
|
|
73
156
|
|