node-gtk 2.2.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 +45 -161
- 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/register-class.js +86 -3
- package/lib/styles.d.ts +81 -0
- package/lib/styles.js +428 -0
- package/package.json +15 -1
- 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/lib/binding/node-v127-linux-x64/node_gtk.node +0 -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>
|
|
@@ -12,205 +12,89 @@
|
|
|
12
12
|
<p align="center">
|
|
13
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
|
-
`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.
|
|
23
|
-
|
|
24
|
-
Supported Node.js versions: **20**, **22**, **24** (other versions may work but are untested)<br>
|
|
25
|
-
Supported platforms:
|
|
26
|
-
- **Linux** — prebuilt binaries available
|
|
27
|
-
- **macOS** — prebuilt binaries available
|
|
28
|
-
- **Windows** — prebuilt binaries available (but read [Windows](#windows))
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
### Table of contents
|
|
32
|
-
|
|
33
|
-
- [Usage](#usage)
|
|
34
|
-
- [ES modules](#es-modules)
|
|
35
|
-
- [Documentation](#documentation)
|
|
36
|
-
- [TypeScript](#typescript)
|
|
37
|
-
- [Installing](#installing)
|
|
38
|
-
- [Contributing](#contributing)
|
|
39
|
-
|
|
40
|
-
## Usage
|
|
41
|
-
|
|
42
|
-
Below is a [minimal example](./examples/hello-world.js) of how to use node-gtk:
|
|
43
|
-
|
|
44
|
-
```javascript
|
|
45
|
-
const gi = require('node-gtk');
|
|
46
|
-
const GLib = gi.require('GLib', '2.0');
|
|
47
|
-
const Gtk = gi.require('Gtk', '4.0');
|
|
48
|
-
const Adw = gi.require('Adw', '1');
|
|
49
|
-
|
|
50
|
-
const loop = GLib.MainLoop.new(null, false);
|
|
51
|
-
const app = new Adw.Application('com.github.romgrk.node-gtk.hello', 0);
|
|
52
|
-
|
|
53
|
-
app.on('activate', () => {
|
|
54
|
-
const content = new Gtk.Box({ orientation: Gtk.Orientation.VERTICAL });
|
|
55
|
-
content.append(new Adw.HeaderBar());
|
|
56
|
-
content.append(new Gtk.Label({ label: 'Hello Adwaita!', vexpand: true }));
|
|
57
|
-
|
|
58
|
-
const window = new Adw.ApplicationWindow(app);
|
|
59
|
-
window.setTitle('node-gtk');
|
|
60
|
-
window.setDefaultSize(300, 120);
|
|
61
|
-
window.setContent(content);
|
|
62
|
-
window.on('close-request', () => (loop.quit(), app.quit(), false));
|
|
63
|
-
window.present();
|
|
64
|
-
|
|
65
|
-
gi.startLoop();
|
|
66
|
-
loop.run();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
process.exit(app.run([]));
|
|
70
|
-
```
|
|
71
|
-
|
|
72
17
|
<p align="center">
|
|
73
|
-
<
|
|
18
|
+
<a href="#usage">Usage</a> · <a href="#installing">Installing</a> · <a href="./doc/index.md">Documentation</a> · <a href="#contributing">Contributing</a>
|
|
74
19
|
</p>
|
|
75
20
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
[A web browser (using WebKit2GTK)](./examples/browser.js)
|
|
79
|
-
|
|
80
|
-
<p align="center">
|
|
81
|
-
<img src="./img/browser.png" style="max-width: 500px; height: auto;"/>
|
|
82
|
-
</p>
|
|
21
|
+
<br />
|
|
83
22
|
|
|
84
|
-
|
|
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**.
|
|
85
26
|
|
|
86
27
|
<p align="center">
|
|
87
|
-
<img src="./img/
|
|
28
|
+
<img src="./img/browser.png" style="max-width: 500px; height: auto;" alt="A web browser build with node-gtk" />
|
|
88
29
|
</p>
|
|
89
30
|
|
|
90
|
-
##
|
|
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
|
-
```
|
|
109
|
-
|
|
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).
|
|
112
|
-
|
|
113
|
-
## Documentation
|
|
114
|
-
|
|
115
|
-
[Read our documentation here](./doc/index.md)
|
|
116
|
-
|
|
117
|
-
## TypeScript
|
|
31
|
+
## Usage
|
|
118
32
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
the types always match your actual library versions and node-gtk's own runtime
|
|
122
|
-
shape (camelCase methods, signal callbacks, nullability, etc.).
|
|
33
|
+
The **create** tool generates a complete, ready-to-run GTK/Adwaita project, so you
|
|
34
|
+
can start building immediately after [installing GTK4](#installing):
|
|
123
35
|
|
|
124
36
|
```sh
|
|
125
|
-
|
|
126
|
-
npx node-gtk generate-types Gtk-4.0 Adw-1
|
|
127
|
-
```
|
|
128
|
-
|
|
129
|
-
The command emits one declaration file per namespace (plus the full dependency
|
|
130
|
-
closure) and a `node-gtk.d.ts` shim. Point your `tsconfig.json` at it:
|
|
131
|
-
|
|
132
|
-
```jsonc
|
|
133
|
-
{
|
|
134
|
-
"compilerOptions": {
|
|
135
|
-
"moduleResolution": "node16",
|
|
136
|
-
"paths": { "node-gtk": ["./node_modules/.node-gtk-types/node-gtk.d.ts"] }
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
Then `gi.require` is fully typed — the namespace is inferred from the string
|
|
142
|
-
arguments:
|
|
143
|
-
|
|
144
|
-
```ts
|
|
145
|
-
import * as gi from 'node-gtk'
|
|
146
|
-
|
|
147
|
-
const Gtk = gi.require('Gtk', '4.0') // typed as the Gtk-4.0 namespace
|
|
148
|
-
const win = new Gtk.ApplicationWindow({ title: 'Hello', defaultWidth: 400 })
|
|
149
|
-
win.on('close-request', () => false) // signal name + callback are typed
|
|
37
|
+
npx node-gtk create <your-app>
|
|
150
38
|
```
|
|
151
39
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
return value, and cross-namespace types. GNOME's API documentation is included
|
|
156
|
-
as JSDoc (with `@param`/`@returns`), so editors show it on hover — this reads the
|
|
157
|
-
`.gir` files installed by the libraries' `-dev`/`-devel` packages; pass
|
|
158
|
-
`--no-docs` for leaner output if they aren't installed or you don't want them.
|
|
159
|
-
|
|
160
|
-
Because the output is a generated cache under `node_modules`, add a `postinstall`
|
|
161
|
-
script so it regenerates on install:
|
|
162
|
-
|
|
163
|
-
```json
|
|
164
|
-
{ "scripts": { "postinstall": "node-gtk generate-types Gtk-4.0 Adw-1" } }
|
|
165
|
-
```
|
|
40
|
+
<p align="center">
|
|
41
|
+
<img src="./img/create-app-example.png" style="width: 500px; height: auto;"/>
|
|
42
|
+
</p>
|
|
166
43
|
|
|
167
|
-
|
|
44
|
+
Also see our [hello world](./examples/hello-world.mjs), [web browser](./examples/browser.mjs)
|
|
45
|
+
or [system monitor](./examples/system-monitor.mjs) examples.
|
|
168
46
|
|
|
169
47
|
## Installing
|
|
170
48
|
|
|
171
|
-
|
|
172
|
-
2. Install the native libraries you use (see examples per platform below)
|
|
49
|
+
There are two steps:
|
|
173
50
|
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
```
|
|
51
|
+
1. Install `node-gtk` itself (*done by the create tool*)
|
|
52
|
+
2. Install the native libraries you use (see examples per platform below)
|
|
180
53
|
|
|
181
54
|
#### Linux
|
|
182
55
|
|
|
183
56
|
```sh
|
|
184
57
|
# archlinux
|
|
185
|
-
pacman -S gtk4
|
|
58
|
+
pacman -S gtk4 libadwaita
|
|
59
|
+
|
|
60
|
+
# fedora
|
|
61
|
+
dnf install gtk4 libadwaita
|
|
186
62
|
|
|
187
63
|
# ubuntu
|
|
188
|
-
|
|
64
|
+
# Already installed :)
|
|
189
65
|
```
|
|
190
66
|
|
|
191
67
|
#### macOS
|
|
192
68
|
|
|
193
69
|
```sh
|
|
194
|
-
brew install gtk4
|
|
70
|
+
brew install gtk4 libadwaita adwaita-icon-theme
|
|
195
71
|
```
|
|
196
72
|
|
|
197
73
|
#### Windows
|
|
198
74
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
75
|
+
```sh
|
|
76
|
+
# Already installed :)
|
|
77
|
+
```
|
|
78
|
+
|
|
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).
|
|
203
84
|
|
|
204
85
|
### build from source
|
|
205
86
|
|
|
206
|
-
Building from source, or contributing? See [Building from source](./doc/building.md).
|
|
87
|
+
Building from source, or [contributing](./doc/contributing.md)? See [Building from source](./doc/building.md).
|
|
207
88
|
|
|
208
|
-
##
|
|
89
|
+
## Documentation
|
|
90
|
+
|
|
91
|
+
[Read our documentation here](./doc/index.md)
|
|
92
|
+
|
|
93
|
+
## Other notes
|
|
209
94
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
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).
|
|
213
99
|
|
|
214
|
-
|
|
215
|
-
- https://v8docs.nodesource.com/
|
|
216
|
-
- https://github.com/nodejs/nan#api
|
|
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/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
|
|
package/lib/styles.d.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Type declarations for node-gtk/styles (see lib/styles.js).
|
|
3
|
+
*
|
|
4
|
+
* The public surface deals only in strings, URLs, and plain handles, so these
|
|
5
|
+
* types are self-contained — they don't reference the GTK typings.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Options shared by the style sheets. */
|
|
9
|
+
export interface StyleOptions {
|
|
10
|
+
/** Provider priority; defaults to `Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION`. */
|
|
11
|
+
priority?: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/** Options for {@link StyleManager.add}. */
|
|
15
|
+
export interface StyleAddOptions extends StyleOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Watch the calling module for hot-reload (defaults to on in development).
|
|
18
|
+
* Pass `false` for a programmatic sheet whose source can't be re-imported
|
|
19
|
+
* safely (built inside a method, or owned by a stateful module); the handle
|
|
20
|
+
* still supports `update`/`refresh`/`remove`.
|
|
21
|
+
*/
|
|
22
|
+
watch?: boolean
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Options for {@link StyleManager.addFile}. */
|
|
26
|
+
export interface StyleFileOptions extends StyleOptions {
|
|
27
|
+
/** Watch the file for hot-reload (defaults to on in development). */
|
|
28
|
+
watch?: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** A handle to an installed (or queued) stylesheet. */
|
|
32
|
+
export interface StyleSheet {
|
|
33
|
+
/**
|
|
34
|
+
* Replace the CSS (or, for a file sheet, re-read the path) in place. For an
|
|
35
|
+
* inline sheet a string replaces any render function — it becomes fixed CSS.
|
|
36
|
+
*/
|
|
37
|
+
update(next: string): void
|
|
38
|
+
/**
|
|
39
|
+
* Re-apply the sheet from its current source: re-run the render function
|
|
40
|
+
* (inline) or re-read the path (file). Call it when the state a render reads
|
|
41
|
+
* changes. A no-op for a queued sheet (not yet installed).
|
|
42
|
+
*/
|
|
43
|
+
refresh(): void
|
|
44
|
+
/** Remove the sheet from the display. */
|
|
45
|
+
remove(): void
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Applies CSS to a GTK app and, in development, hot-reloads it. The module
|
|
50
|
+
* exports a shared {@link styles} instance; constructing your own is rarely
|
|
51
|
+
* needed.
|
|
52
|
+
*/
|
|
53
|
+
export declare class StyleManager {
|
|
54
|
+
/**
|
|
55
|
+
* Queue (or, once the display exists, install) inline CSS. The source file it
|
|
56
|
+
* is called from is watched for hot-reload (unless `watch` is false).
|
|
57
|
+
*
|
|
58
|
+
* `css` may be a `() => string` *render* function instead of a string, for a
|
|
59
|
+
* dynamic stylesheet built from live state (theme, fonts, …). The render runs
|
|
60
|
+
* now and again on every hot-reload of its module, and on demand via the
|
|
61
|
+
* handle's {@link StyleSheet.refresh}. Keep such a module side-effect-free at
|
|
62
|
+
* its top level (a re-import re-runs it).
|
|
63
|
+
*/
|
|
64
|
+
add(css: string | (() => string), options?: StyleAddOptions): StyleSheet
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Queue (or install) a `.css` file. Unless `watch` is false, the file is
|
|
68
|
+
* watched and re-read into its provider on every edit. Idempotent per path.
|
|
69
|
+
* @param path A path, a `file://` URL string, or a `URL`.
|
|
70
|
+
*/
|
|
71
|
+
addFile(path: string | URL, options?: StyleFileOptions): StyleSheet
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Install everything queued before the display existed, and start the file
|
|
75
|
+
* watcher. Call once from your `activate` handler. Safe to call repeatedly.
|
|
76
|
+
*/
|
|
77
|
+
install(): void
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The application's shared StyleManager. */
|
|
81
|
+
export declare const styles: StyleManager
|