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 CHANGED
@@ -2,7 +2,7 @@
2
2
  <a>
3
3
  <img
4
4
  alt="NODE-GTK"
5
- width="250"
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
- <img src="./img/hello-world.png" style="width: 290px; height: auto;"/>
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
- You can also easily create custom applications:
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
- [A system monitor](./examples/system-monitor.js)
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/system-monitor.png" style="width: 400px; height: auto;"/>
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
- ## ES modules
91
-
92
- The Usage example above is CommonJS. node-gtk also works under ESM, but the
93
- blocking main-loop calls (`GLib.MainLoop.run`, `Gio`/`Gtk.Application.run`,
94
- `Gtk.main`) **return immediately** instead of blocking and **don't return a
95
- value** — so make the run call the last statement and exit from your handler:
96
-
97
- ```javascript
98
- app.on('activate', () => {
99
- // ...build the window...
100
- window.on('close-request', () => (loop.quit(), app.quit(), false));
101
- window.present();
102
-
103
- gi.startLoop();
104
- loop.run(); // returns immediately under ESM; do cleanup/exit in the handler
105
- });
106
-
107
- app.run([]); // not `process.exit(app.run([]))` — the return value is unavailable
108
- ```
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
- node-gtk can generate TypeScript declarations for the libraries you use,
120
- straight from the GObject-Introspection typelibs installed on your machine — so
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
- # generates ./node_modules/.node-gtk-types (a hidden, git-ignored cache)
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
- You get typed constructor properties (including inherited and interface ones),
153
- camelCase methods with real return types, GI nullability, typed signal
154
- overloads, enums, `bigint` for 64-bit integers, out-parameters surfaced as the
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
- Run `npx node-gtk generate-types --help` for options.
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
- 1. Install `node-gtk` itself
172
- 2. Install the native libraries you use (see examples per platform below)
49
+ There are two steps:
173
50
 
174
- ```sh
175
- npm install node-gtk
176
-
177
- # This installs a prebuilt binary when one is available for your platform and
178
- # Node.js version, otherwise it falls back to building from source.
179
- ```
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
- apt install libgtk-4-1
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
- Windows doesn't have the dependencies we need in a package manager, therefore
200
- `node-gtk` ships prebuilt versions of GTK 4 / Adwaita runtime (DLLs, typelibs,
201
- icons), so `npm install node-gtk` is all you need **if** your dependency is in
202
- our [list of prebuilt libraries](./windows/runtime-libraries.txt).
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
- ## Contributing
89
+ ## Documentation
90
+
91
+ [Read our documentation here](./doc/index.md)
92
+
93
+ ## Other notes
209
94
 
210
- If you'd like to help, we'd be more than happy to have support. To setup your development environment, you can
211
- run `npm run configure`. You can then build the project with `npm run build`. To generate the `compile_commands.json`
212
- for LSP to work nicely, you can use [bear](https://github.com/rizsotto/Bear) as `bear -- npm run build`.
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
- - https://developer.gnome.org/gi/stable/index.html
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 generate-types --help\` for details.`)
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
- const loop = require('./loop.js')
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
@@ -3,7 +3,7 @@ const chalk = require('chalk')
3
3
 
4
4
  const internal = require('./native.js')
5
5
  const gi = require('../lib/index');
6
- gi.startLoop();
6
+ require('./loop.js').start();
7
7
 
8
8
  const infos = []
9
9
 
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
- const [name, version] = filename.replace('.typelib', '').split('-')
133
- return { name, version }
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
  }
@@ -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 = snakeCase(key)
113
+ const nativeName = vfuncNativeName(key)
60
114
  const vfuncInfo = findVFunc(klassGtype, parentInfo, nativeName)
61
115
 
62
116
  if (!vfuncInfo)
63
- return
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
 
@@ -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