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 CHANGED
@@ -16,7 +16,7 @@
16
16
  </p>
17
17
 
18
18
  Node-Gtk is a [gobject-introspection](https://gi.readthedocs.io/en/latest) library for nodejs. It makes it possible to
19
- use any introspected library, such as Gtk+, usable. It is similar in essence to [GJS](https://wiki.gnome.org/action/show/Projects/Gjs) or [PyGObject](https://pygobject.readthedocs.io). Please note this project is currently in a _beta_ state and is being developed. Any contributors willing to help
19
+ use any introspected library, such as Gtk+, usable. It is similar in essence to [GJS](https://wiki.gnome.org/action/show/Projects/Gjs) or [PyGObject](https://pygobject.readthedocs.io). Please note this project is currently in a _beta_ state and is being developed. Any contributors willing to help
20
20
  will be welcomed.
21
21
 
22
22
  Supported Node.js versions: **20**, **22**, **24** (other versions may work but are untested)<br>
@@ -26,232 +26,165 @@ Pre-built binaries available for: **Linux**, **macOS**
26
26
 
27
27
  - [Usage](#usage)
28
28
  - [Documentation](#documentation)
29
+ - [TypeScript](#typescript)
29
30
  - [Installing and building](#installing-and-building)
30
- - [Target Platforms](#target-platforms)
31
- - [Requirements](#requirements)
32
- - [How to build on Ubuntu](#how-to-build-on-ubuntu)
33
- - [How to build on Fedora](#how-to-build-on-fedora)
34
- - [How to build on ArchLinux](#how-to-build-on-archlinux)
35
- - [How to build on macOS](#how-to-build-on-macos)
36
- - [How to build on Windows](#how-to-build-on-windows)
37
- - [Testing the project](#testing-the-project)
38
- - [Browser demo](#browser-demo)
39
31
  - [Contributing](#contributing)
40
32
 
41
33
  ## Usage
42
34
 
43
- Below is a minimal example of how to use the code, but take a look at
44
- our [template](https://github.com/romgrk/node-gtk-template) or at
45
- [react-gtk](https://github.com/codejamninja/react-gtk) to bootstrap your
46
- project.
35
+ Below is a [minimal example](./examples/hello-world.js) of how to use node-gtk:
47
36
 
48
37
  ```javascript
49
- const gi = require('node-gtk')
50
- const Gtk = gi.require('Gtk', '3.0')
51
-
52
- gi.startLoop()
53
- Gtk.init()
54
-
55
- const win = new Gtk.Window()
56
- win.on('destroy', () => Gtk.mainQuit())
57
- win.on('delete-event', () => false)
58
-
59
- win.setDefaultSize(200, 80)
60
- win.add(new Gtk.Label({ label: 'Hello Gtk+' }))
61
-
62
- win.showAll()
63
- Gtk.main()
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 }));
50
+
51
+ const window = new Adw.ApplicationWindow(app);
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();
57
+
58
+ gi.startLoop();
59
+ loop.run();
60
+ });
61
+
62
+ process.exit(app.run([]));
64
63
  ```
65
64
 
66
65
  <p align="center">
67
- <img src="./img/hello-node-gtk.png" alt="Hello Gtk" style="width: 220px; height: auto;"/>
66
+ <img src="./img/hello-world.png" style="width: 290px; height: auto;"/>
68
67
  </p>
69
68
 
70
- See our [examples](./examples) folder for more examples, and in particular the
71
- [browser demo source](https://github.com/romgrk/node-gtk/blob/master/examples/browser.js) for
72
- a more complex application.
69
+ #### ES modules
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)
73
114
 
74
115
  <p align="center">
75
- <img src="./img/browser.png" alt="Hello Gtk" style="max-width: 500px; height: auto;"/>
116
+ <img src="./img/browser.png" style="max-width: 500px; height: auto;"/>
76
117
  </p>
77
118
 
119
+ [A system monitor](./examples/system-monitor.js)
78
120
 
79
- ## Documentation
80
-
81
- [Read our documentation here](./doc/index.md)
82
-
83
- ## Installing and building
84
-
85
- Note that prebuilt binaries are available for common systems, in those cases building is not necessary.
86
-
87
- ##### Target Platforms
88
-
89
- - **Linux**: prebuilt binaries available
90
- - **macOS**: prebuilt binaries available
91
- - **Windows**: no prebuilt binaries
92
-
93
- ### Requirements
94
-
95
- - `git`
96
- - `nodejs@10` or higher
97
- - `python3` (for `node-gyp`)
98
- - C compiler (`gcc@8` or higher, or `clang`)
99
-
100
- ### How to build on Ubuntu
101
-
102
- Install basic dependencies.
103
-
104
- ```sh
105
- sudo apt-get install \
106
- build-essential git \
107
- gobject-introspection \
108
- libgirepository1.0-dev \
109
- libcairo2 \
110
- libcairo2-dev
111
- ```
112
-
113
- At this point `npm install node-gtk` should already install, fallback and build `node-gtk` without problems.
114
-
115
- ### How to build on Fedora
116
-
117
- Install basic dependencies:
118
-
119
- ```sh
120
- sudo dnf install \
121
- @development-tools \
122
- nodejs \
123
- nodejs-devel \
124
- gobject-introspection \
125
- gobject-introspection-devel \
126
- gtk3 \
127
- gtk3-devel \
128
- cairo \
129
- cairo-devel
130
- ```
131
-
132
- After installing of packages, run `npm install node-gtk`.
133
-
134
- ### How to build on ArchLinux
121
+ <p align="center">
122
+ <img src="./img/system-monitor.png" style="width: 400px; height: auto;"/>
123
+ </p>
135
124
 
136
- The following should be the bare minimum to be able to build the project.
125
+ #### Other projects
137
126
 
138
- ```sh
139
- pacman -S --needed \
140
- base-devel git \
141
- nodejs npm \
142
- gtk3 gobject-introspection \
143
- cairo
144
- ```
127
+ The [react-gtk](https://github.com/codejamninja/react-gtk) project may also allow you to use GTK via React (unmaintained).
145
128
 
146
- Feel free to install all `base-devel` utilities.
129
+ ## Documentation
147
130
 
148
- After installing those packages, `npm install node-gtk` would do.
131
+ [Read our documentation here](./doc/index.md)
149
132
 
150
- ### How to build on macOS
133
+ ## TypeScript
151
134
 
152
- Assuming you have [brew](http://brew.sh) installed, the following has been successfully tested on El Captain.
135
+ node-gtk can generate TypeScript declarations for the libraries you use,
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.).
153
139
 
154
140
  ```sh
155
- brew install git node gobject-introspection gtk+3 cairo
141
+ # generates ./node_modules/.node-gtk-types (a hidden, git-ignored cache)
142
+ npx node-gtk generate-types Gtk-4.0 Adw-1
156
143
  ```
157
144
 
158
- At this point `npm install node-gtk` should already install, fallback and build `node-gtk` without problems.
159
-
160
- ### How to build on Windows
145
+ The command emits one declaration file per namespace (plus the full dependency
146
+ closure) and a `node-gtk.d.ts` shim. Point your `tsconfig.json` at it:
161
147
 
162
- Mandatory dependency is Visual C++ Build Environment: Visual Studio Build Tools (using "Visual C++ build tools" workload) or Visual Studio Community (using the "Desktop development with C++" workload).
163
-
164
- The easiest/tested way to build this repository is within a _MinGW shell_ provided by the [MSYS2 installer](https://msys2.github.io/).
165
-
166
- Once VS and its C++ compiler is available and MSYS2 installed, launch the MinGW shell.
167
-
168
- ```sh
169
- # update the system
170
- # in case of errors, wait for the update to complete
171
- # then close and open again MingW shell
172
- pacman -Syyu --noconfirm
173
-
174
- # install git, gtk3 and extra dependencie
175
- pacman -S --needed --noconfirm git mingw-w64-$(uname -m)-{gtk3,gobject-introspection,pkg-config,cairo}
176
-
177
- # where to put the repository clone?
178
- # pick your flder or use ~/oss (Open Source Software)
179
- mkdir -p ~/oss/
180
- cd ~/oss
181
-
182
- # clone node-gtk there
183
- git clone https://github.com/romgrk/node-gtk
184
- cd node-gtk
185
-
186
- # don't include /mingw64/include directly since it conflicts with
187
- # Windows SDK headers. we copy needed headers to __extra__ directory:
188
- ./windows/mingw_include_extra.sh
189
-
190
- # if MSYS2 is NOT installed in C:/msys64 run:
191
- export MINGW_WINDOWS_PATH=$(./windows/mingw_windows_path.sh)
192
-
193
- # first run might take a while
194
- GYP_MSVS_VERSION=2017 npm install
148
+ ```jsonc
149
+ {
150
+ "compilerOptions": {
151
+ "moduleResolution": "node16",
152
+ "paths": { "node-gtk": ["./node_modules/.node-gtk-types/node-gtk.d.ts"] }
153
+ }
154
+ }
195
155
  ```
196
156
 
197
- The `GYP_MSVS_VERSION` could be 2017 or above.
198
- Please verify [which version you should use](https://github.com/nodejs/node-gyp#installation)
199
-
200
- The below blog post series will help you get started:
157
+ Then `gi.require` is fully typed the namespace is inferred from the string
158
+ arguments:
201
159
 
202
- 1. [Node.js GTK Hello World on Windows](https://ten0s.github.io/blog/2022/07/22/nodejs-gtk-hello-world-on-windows)
203
- 2. [Find DLLs and Typelibs dependencies for Node.js GTK Application on Windows](https://ten0s.github.io/blog/2022/07/25/find-dlls-and-typelibs-dependencies-for-nodejs-gtk-application-on-windows)
204
- 3. [Package Node.js GTK Application on Windows](https://ten0s.github.io/blog/2022/07/27/package-nodejs-gtk-application-on-windows)
160
+ ```ts
161
+ import * as gi from 'node-gtk'
205
162
 
206
- #### Possible issue on MinGW shell
207
-
208
- In case you are launching the general executable without knowing the correct platform,
209
- the binary path might not be available.
210
-
211
- In such case `python` won't be available either, and you can check via `which python` command.
212
-
213
- If not found, you need to export the platform related binary path:
214
-
215
- ```sh
216
- # example for the 32bit version
217
- export PATH="/mingw32/bin:$PATH"
218
- npm run install
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
219
166
  ```
220
167
 
221
- This should do the trick. You can also check if there is any python at all via `pacman -Qs python`.
222
-
223
- ### Testing the project
168
+ You get typed constructor properties (including inherited and interface ones),
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.
224
175
 
225
- If you'd like to test everything builds and work properly, after installing and building you can run any of the
226
- examples:
176
+ Because the output is a generated cache under `node_modules`, add a `postinstall`
177
+ script so it regenerates on install:
227
178
 
228
- ```sh
229
- node ./examples/hello-gtk.js
179
+ ```json
180
+ { "scripts": { "postinstall": "node-gtk generate-types Gtk-4.0 Adw-1" } }
230
181
  ```
231
182
 
232
- If you'll see a little window saying hello that's it: it works!
233
-
234
- Please note in macOS the window doesn't automatically open above other windows.
235
- Try <kbd>Cmd</kbd> + <kbd>Tab</kbd> if you don't see it.
183
+ Run `npx node-gtk generate-types --help` for options.
236
184
 
237
- #### Browser demo
238
-
239
- If you'd like to test `./examples/browser.js` you'll need [WebKit2 GTK+](http://webkitgtk.org/) libary.
240
-
241
- - in **Ubuntu**, you can `apt-get install libwebkit2gtk-3.0` (`4.0` works too) and try it out.
242
- - in **Fedora**, you should run `sudo dnf install webkit2gtk3`
243
- - in **ArchLinux**, you can `pacman -S --needed webkitgtk` and try it out.
244
- - in **macOS**, there is no way to run it right now because `webkitgtk` was removed from homebrew
245
-
246
- Once installed, you can `./examples/browser.js google.com` or any other page, and you might try the _dark theme_ out too:
247
-
248
- ```sh
249
- # macOS needs to have the Adwaita theme installed
250
- # brew install adwaita-icon-theme
185
+ ## Installing and building
251
186
 
252
- # Usage: ./examples/browser.js <url> [theme]
253
- ./examples/browser.js google.com dark
254
- ```
187
+ See [Installing & building](./doc/installation.md) for prebuilt-binary notes, per-platform build instructions (Linux, macOS, Windows), and how to run the tests and examples.
255
188
 
256
189
  ## Contributing
257
190
 
@@ -259,9 +192,9 @@ If you'd like to help, we'd be more than happy to have support. To setup your de
259
192
  run `npm run configure`. You can then build the project with `npm run build`. To generate the `compile_commands.json`
260
193
  for LSP to work nicely, you can use [bear](https://github.com/rizsotto/Bear) as `bear -- npm run build`.
261
194
 
262
- - https://developer.gnome.org/gi/stable/index.html
263
- - https://v8docs.nodesource.com/
264
- - https://github.com/nodejs/nan#api
195
+ - https://developer.gnome.org/gi/stable/index.html
196
+ - https://v8docs.nodesource.com/
197
+ - https://github.com/nodejs/nan#api
265
198
 
266
199
  There is a [Discord channel](https://discord.gg/r2VqPUV) but it receives little monitoring, use github issues or
267
200
  discussions preferably.
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ /*
3
+ * node-gtk CLI
4
+ *
5
+ * Subcommands:
6
+ * generate-types Generate TypeScript declarations from the installed typelibs.
7
+ */
8
+
9
+ const cmd = process.argv[2]
10
+
11
+ switch (cmd) {
12
+ case 'generate-types':
13
+ require('../tools/generate-types.js').run(process.argv.slice(3))
14
+ break
15
+ case undefined:
16
+ case '-h':
17
+ case '--help':
18
+ console.log(`node-gtk — GNOME GObject-Introspection bindings for Node.js
19
+
20
+ Usage: node-gtk <command> [options]
21
+
22
+ Commands:
23
+ generate-types <Namespace-Version> [...] Generate TypeScript types (.d.ts)
24
+
25
+ Run \`node-gtk generate-types --help\` for details.`)
26
+ process.exit(cmd ? 0 : 1)
27
+ break
28
+ default:
29
+ console.error(`node-gtk: unknown command '${cmd}'. Try 'node-gtk --help'.`)
30
+ process.exit(1)
31
+ }
package/lib/bootstrap.js CHANGED
@@ -82,11 +82,26 @@ function extendBaseClass(BaseClass) {
82
82
  }
83
83
 
84
84
  BaseClass.prototype.once = function once(event, callback, after) {
85
+ defineListeners(this)
86
+
87
+ if (!this._listeners.has(event))
88
+ this._listeners.set(event, new WeakMap())
89
+
90
+ const fnMap = this._listeners.get(event)
91
+
85
92
  const newCallback = (...args) => {
86
- callback(...args)
87
- this.off(event, newCallback)
93
+ this.off(event, callback)
94
+ return callback(...args)
95
+ }
96
+
97
+ const handlerID = this.connect(event, newCallback, after)
98
+ if (handlerID === 0) {
99
+ throw new Error(`Could not connect to signal ${event}`)
88
100
  }
89
- this.on(event, newCallback, after)
101
+ // Key the listener map by the user's original callback so that it can be
102
+ // cancelled with off(event, callback), even though the connected handler
103
+ // is the wrapper.
104
+ fnMap.set(callback, handlerID)
90
105
  return this
91
106
  }
92
107
 
@@ -220,7 +235,10 @@ function makeEnum(info) {
220
235
  for (let i = 0; i < nValues; i++) {
221
236
  const valueInfo = GI.enum_info_get_value(info, i);
222
237
  const valueName = getName(valueInfo).toUpperCase()
223
- const value = GI.value_info_get_value(valueInfo)
238
+ // g_value_info_get_value returns a gint64 (-> BigInt since #323), but enum
239
+ // and flags members are small integers and are compared/bitwise-combined
240
+ // as Numbers throughout, so coerce here.
241
+ const value = Number(GI.value_info_get_value(valueInfo))
224
242
  Object.defineProperty(object, valueName, {
225
243
  configurable: true,
226
244
  enumerable: true,
package/lib/index.js CHANGED
@@ -11,6 +11,30 @@ const module_ = require('./module.js')
11
11
  const loop = require('./loop.js')
12
12
  const registerClass = require('./register-class.js')
13
13
 
14
+ /**
15
+ * Returns the GType (as a BigInt) of a GObject/boxed class, an instance of one,
16
+ * or a GType passed through as-is. See #286.
17
+ *
18
+ * @param {Function|object|bigint} value a class, an instance, or a GType
19
+ * @returns {bigint} the associated GType
20
+ */
21
+ function getGType(value) {
22
+ if (typeof value === 'bigint')
23
+ return value
24
+
25
+ if (value != null) {
26
+ // A class: the GType lives on its prototype.
27
+ if (typeof value === 'function' && value.prototype != null && value.prototype.__gtype__ !== undefined)
28
+ return value.prototype.__gtype__
29
+
30
+ // An instance (or prototype).
31
+ if (value.__gtype__ !== undefined)
32
+ return value.__gtype__
33
+ }
34
+
35
+ throw new TypeError('getGType: expected a GObject/boxed class, instance, or GType')
36
+ }
37
+
14
38
  /*
15
39
  * Exports
16
40
  */
@@ -20,6 +44,7 @@ module.exports = {
20
44
  ...module_,
21
45
  startLoop: loop.start,
22
46
  registerClass: registerClass,
47
+ getGType: getGType,
23
48
  System: internal.System,
24
49
 
25
50
  // Private API
package/lib/loop.js CHANGED
@@ -6,6 +6,7 @@ const internal = require('./native.js')
6
6
 
7
7
  module.exports = {
8
8
  start,
9
+ runLoopEntry,
9
10
  }
10
11
 
11
12
 
@@ -32,6 +33,39 @@ function start() {
32
33
  }
33
34
 
34
35
 
36
+ /**
37
+ * Runs a blocking main-loop entry point (e.g. GLib.MainLoop.run,
38
+ * Gio.Application.run, Gtk.main) so that Promise/async continuations keep
39
+ * draining while it blocks.
40
+ *
41
+ * Under ES modules the top-level body executes as a V8 microtask, so a blocking
42
+ * call made directly from it nests inside V8's microtask drain. V8 refuses
43
+ * nested microtask checkpoints, so any pending Promise/async continuation is
44
+ * starved for the entire lifetime of the loop. Deferring the blocking call to a
45
+ * macrotask lets the module's top-level microtask return first, so the queue
46
+ * drains and the loop integration takes over from a clean (non-nested) state.
47
+ *
48
+ * Under CommonJS (and inside signal callbacks) we are not in a microtask, so we
49
+ * run synchronously and preserve the original blocking semantics exactly.
50
+ *
51
+ * https://github.com/romgrk/node-gtk/issues/442
52
+ *
53
+ * Returns the native call's result when run synchronously (so e.g.
54
+ * `const status = app.run()` keeps working under CommonJS); returns undefined
55
+ * when deferred, since the result is not yet available.
56
+ *
57
+ * @param {Function} run - performs the blocking native call
58
+ * @returns {*} the native return value, or undefined when deferred
59
+ */
60
+ function runLoopEntry(run) {
61
+ if (internal.IsRunningMicrotasks()) {
62
+ setImmediate(run)
63
+ return undefined
64
+ }
65
+ return run()
66
+ }
67
+
68
+
35
69
  // Helpers
36
70
 
37
71
  function wrappedLoopFunction(fn) {
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  const internal = require('../native.js')
6
+ const { runLoopEntry } = require('../loop.js')
6
7
 
7
8
  exports.apply = (GLib) => {
8
9
 
@@ -10,17 +11,19 @@ exports.apply = (GLib) => {
10
11
  GLib.MainLoop.prototype._quit = GLib.MainLoop.prototype.quit
11
12
 
12
13
  GLib.MainLoop.prototype.run = function run() {
13
- /* Run before we enter the loop otherwise pending microtasks
14
- * are not run */
15
- process._tickCallback()
16
-
17
14
  const loopStack = internal.GetLoopStack()
18
-
19
15
  loopStack.push(() => this.quit())
20
- this._run()
21
- if (this._userQuit)
22
- loopStack.pop()
23
- delete this._userQuit
16
+
17
+ return runLoopEntry(() => {
18
+ /* Run before we enter the loop otherwise pending microtasks
19
+ * are not run */
20
+ process._tickCallback()
21
+
22
+ this._run()
23
+ if (this._userQuit)
24
+ loopStack.pop()
25
+ delete this._userQuit
26
+ })
24
27
  }
25
28
  GLib.MainLoop.prototype.quit = function quit() {
26
29
  this._userQuit = true
@@ -0,0 +1,26 @@
1
+ /*
2
+ * Gio-2.0.js
3
+ */
4
+
5
+ const { runLoopEntry } = require('../loop.js')
6
+
7
+ exports.apply = (Gio) => {
8
+
9
+ Gio.Application.prototype._run = Gio.Application.prototype.run
10
+
11
+ /* g_application_run() blocks until the application quits. Under ES modules
12
+ * this would starve Promise/async continuations; runLoopEntry() defers the
13
+ * blocking call when needed so they keep draining. See loop.js / #442.
14
+ *
15
+ * Note: when deferred (ESM), the exit status is not available synchronously,
16
+ * so `app.run()` returns undefined instead of the status code in that case. */
17
+ Gio.Application.prototype.run = function run(...args) {
18
+ return runLoopEntry(() => {
19
+ /* Run before we enter the loop otherwise pending microtasks
20
+ * are not run */
21
+ process._tickCallback()
22
+
23
+ return this._run(...args)
24
+ })
25
+ }
26
+ }
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  const internal = require('../native.js')
6
+ const { runLoopEntry } = require('../loop.js')
6
7
 
7
8
  /**
8
9
  * @typedef {Object} Dimension
@@ -27,10 +28,6 @@ exports.apply = (Gtk) => {
27
28
  let userCallingQuit = false
28
29
 
29
30
  Gtk.main = function main() {
30
- /* Run before we enter the loop otherwise pending microtasks
31
- * are not run */
32
- process._tickCallback()
33
-
34
31
  const loopStack = internal.GetLoopStack()
35
32
 
36
33
  /*
@@ -42,17 +39,25 @@ exports.apply = (Gtk) => {
42
39
 
43
40
  loopStack.push(originalQuit)
44
41
 
45
- originalMain()
42
+ /* runLoopEntry() defers the blocking call under ES modules so pending
43
+ * Promise/async continuations keep draining. See loop.js / #442. */
44
+ return runLoopEntry(() => {
45
+ /* Run before we enter the loop otherwise pending microtasks
46
+ * are not run */
47
+ process._tickCallback()
46
48
 
47
- if (userCallingQuit) {
48
- loopStack.pop()
49
- }
49
+ originalMain()
50
50
 
51
- userCallingQuit = false
51
+ if (userCallingQuit) {
52
+ loopStack.pop()
53
+ }
52
54
 
53
- if (Gtk.mainLevel() === 0) {
54
- placeholderIntervalID = clearInterval(placeholderIntervalID)
55
- }
55
+ userCallingQuit = false
56
+
57
+ if (Gtk.mainLevel() === 0) {
58
+ placeholderIntervalID = clearInterval(placeholderIntervalID)
59
+ }
60
+ })
56
61
  }
57
62
 
58
63
  Gtk.mainQuit = function mainQuit() {
@@ -112,7 +112,8 @@ exports.apply = (Gtk) => {
112
112
  */
113
113
  Gtk.FileChooserDialog.prototype.getFile = function getFile() {
114
114
  const file = originalGetFile.call(this)
115
- file.__proto__= Gio.File.prototype
115
+ if (file)
116
+ file.__proto__= Gio.File.prototype
116
117
  return file
117
118
  }
118
119
 
@@ -122,7 +123,8 @@ exports.apply = (Gtk) => {
122
123
  */
123
124
  Gtk.FileChooserWidget.prototype.getFile = function getFile() {
124
125
  const file = originalGetFile.call(this)
125
- file.__proto__= Gio.File.prototype
126
+ if (file)
127
+ file.__proto__= Gio.File.prototype
126
128
  return file
127
129
  }
128
130
  }