node-gtk 3.0.0 → 4.0.1

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
@@ -22,7 +22,7 @@
22
22
 
23
23
  `node-gtk` lets you build native GTK apps on **linux**, **macOS** and **windows**
24
24
  with full **ESM**, **TypeScript** and **CSS hot-reload** support. Prebuilt binaries
25
- are available for Node.js versions **20**, **22** and **24**.
25
+ are available for Node.js versions **22**, **24** and **26**.
26
26
 
27
27
  <p align="center">
28
28
  <img src="./img/browser.png" style="max-width: 500px; height: auto;" alt="A web browser build with node-gtk" />
package/binding.gyp CHANGED
@@ -1,4 +1,24 @@
1
1
  {
2
+ # Node 26's official Windows binary is built with ClangCL + ThinLTO, so its
3
+ # common.gypi injects `-flto=thin` and `/opt:lldltojobs=N` into the MSVC
4
+ # AdditionalOptions of every target. MSVC's link.exe rejects those
5
+ # Clang/lld-only options (LNK1117), which breaks the build on Windows +
6
+ # Node 26. Strip them with gyp's list-exclusion filter. This lives under
7
+ # msvs_settings, so it is inert on the make/xcode generators (Linux/macOS).
8
+ "target_defaults": {
9
+ "configurations": {
10
+ "Release": {
11
+ "msvs_settings": {
12
+ "VCCLCompilerTool": {
13
+ "AdditionalOptions/": [ ["exclude", "flto"] ]
14
+ },
15
+ "VCLinkerTool": {
16
+ "AdditionalOptions/": [ ["exclude", "flto"], ["exclude", "lldltojobs"] ]
17
+ }
18
+ }
19
+ }
20
+ }
21
+ },
2
22
  "targets": [
3
23
  {
4
24
  "target_name": "node_gtk",
@@ -11,6 +31,7 @@
11
31
  "src/debug.cc",
12
32
  "src/error.cc",
13
33
  "src/function.cc",
34
+ "src/fundamental.cc",
14
35
  "src/gi.cc",
15
36
  "src/gobject.cc",
16
37
  "src/loop.cc",
package/lib/bootstrap.js CHANGED
@@ -292,23 +292,55 @@ function makeObject(info) {
292
292
  */
293
293
 
294
294
  forLoopFn(info, GI.object_info_get_n_interfaces, GI.object_info_get_interface, (interfaceInfo) => {
295
- const interface_ = getInterface(interfaceInfo)
296
-
297
- interface_._properties.forEach(description => {
298
- define(constructor, description)
299
- })
300
- interface_._methods.forEach(description => {
301
- define(constructor, description)
302
- })
303
- interface_._constants.forEach(description => {
304
- define(constructor, description)
305
- })
295
+ applyInterface(constructor, getInterface(interfaceInfo))
306
296
  })
307
297
 
308
298
 
309
299
  return constructor;
310
300
  }
311
301
 
302
+ /*
303
+ * Mix an interface's properties/methods/constants (as prepared by
304
+ * makeInterface) into a class constructor. Shared by makeObject() and the
305
+ * `applyInterfaceMethods` callback below.
306
+ */
307
+ function applyInterface(constructor, interface_) {
308
+ interface_._properties.forEach(description => define(constructor, description))
309
+ interface_._methods.forEach(description => define(constructor, description))
310
+ interface_._constants.forEach(description => define(constructor, description))
311
+ }
312
+
313
+ /*
314
+ * Called from C++ (GetClassTemplate) the first time a private/non-introspectable
315
+ * concrete type is wrapped, to install the methods of the interfaces it
316
+ * implements onto its prototype. makeObject() never runs for such types, so
317
+ * without this their instances would only expose the base GObject methods
318
+ * (issue #441). `interfaceRefs` is an array of { namespace, name }.
319
+ */
320
+ function applyInterfaceMethods(constructor, interfaceRefs) {
321
+ const repo = GI.Repository_get_default()
322
+
323
+ interfaceRefs.forEach(({ namespace, name }) => {
324
+ const cache = moduleCache[namespace]
325
+ // The interface's module must be loaded for its constructor to exist; if it
326
+ // isn't, there is nothing meaningful to install.
327
+ if (!cache)
328
+ return
329
+
330
+ let interface_ = cache[name]
331
+ if (!interface_) {
332
+ const info = GI.Repository_find_by_name.call(repo, namespace, name)
333
+ if (!info)
334
+ return
335
+ interface_ = getInterface(info)
336
+ }
337
+
338
+ applyInterface(constructor, interface_)
339
+ })
340
+ }
341
+
342
+ internal.SetInterfaceMethodsApplier(applyInterfaceMethods)
343
+
312
344
  function makeBoxed(info) {
313
345
  if (getType(info) == GI.InfoType.UNION) {
314
346
  return makeUnion(info)
package/lib/loop.js CHANGED
@@ -45,10 +45,22 @@ function start() {
45
45
  * macrotask lets the module's top-level microtask return first, so the queue
46
46
  * drains and the loop integration takes over from a clean (non-nested) state.
47
47
  *
48
+ * We defer with setTimeout, NOT setImmediate. A setImmediate callback runs
49
+ * *inside* Node's immediate-processing machinery (native CheckImmediate ->
50
+ * processImmediate). Because `run` never returns (it blocks in the GLib main
51
+ * loop until the app quits), CheckImmediate never reaches the code that stops
52
+ * Node's private immediate uv_idle handle, so that handle stays active forever.
53
+ * An active idle handle pins uv_backend_timeout() at 0, which makes the nested
54
+ * uv-in-GLib loop busy-spin at 100% CPU (worse on Node 26 / libuv 1.52, where
55
+ * the immediate's wakeup eventfd also stays signalled). A one-shot timer is
56
+ * removed from libuv's timer heap before its callback runs, so blocking inside
57
+ * it leaves no libuv state active and the loop can sleep normally. See #477.
58
+ *
48
59
  * Under CommonJS (and inside signal callbacks) we are not in a microtask, so we
49
60
  * run synchronously and preserve the original blocking semantics exactly.
50
61
  *
51
62
  * https://github.com/romgrk/node-gtk/issues/442
63
+ * https://github.com/romgrk/node-gtk/issues/477
52
64
  *
53
65
  * Returns the native call's result when run synchronously (so e.g.
54
66
  * `const status = app.run()` keeps working under CommonJS); returns undefined
@@ -64,7 +76,7 @@ function runLoopEntry(run) {
64
76
  start()
65
77
 
66
78
  if (internal.IsRunningMicrotasks()) {
67
- setImmediate(run)
79
+ setTimeout(run, 0)
68
80
  return undefined
69
81
  }
70
82
  return run()
@@ -2,10 +2,6 @@
2
2
  * Gtk-4.0.js
3
3
  */
4
4
 
5
- const internal = require('../native.js')
6
- const Module = require('../module')
7
- const Gio = Module.require('Gio')
8
-
9
5
  exports.apply = (Gtk) => {
10
6
 
11
7
  Gtk.EVENT_CONTINUE = false
@@ -104,29 +100,12 @@ exports.apply = (Gtk) => {
104
100
  return tickId
105
101
  }
106
102
 
107
- const originalGetFile = Gtk.FileChooser.prototype.getFile
108
-
109
- /**
110
- * Gtk.FileChooserDialog.prototype.getFile
111
- * @returns {GFile} - The file
112
- */
113
- Gtk.FileChooserDialog.prototype.getFile = function getFile() {
114
- const file = originalGetFile.call(this)
115
- if (file)
116
- file.__proto__= Gio.File.prototype
117
- return file
118
- }
119
-
120
- /**
121
- * Gtk.FileChooserWidget.prototype.getFile
122
- * @returns {GFile} - The file
123
- */
124
- Gtk.FileChooserWidget.prototype.getFile = function getFile() {
125
- const file = originalGetFile.call(this)
126
- if (file)
127
- file.__proto__= Gio.File.prototype
128
- return file
129
- }
103
+ /* getFile() used to need a manual `file.__proto__ = Gio.File.prototype` fixup
104
+ * because the returned GLocalFile is a private type whose GFile interface
105
+ * methods weren't mixed into its prototype. That is now handled generically
106
+ * for every private type at wrap time (see issue #441), so the override is no
107
+ * longer necessary — the introspected getFile() returns a fully-usable file
108
+ * (or null when nothing is selected). */
130
109
  }
131
110
 
132
111
  function easeOutCubic(t) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-gtk",
3
- "version": "3.0.0",
3
+ "version": "4.0.1",
4
4
  "description": "GNOME Gtk+ bindings for NodeJS",
5
5
  "main": "lib/index.js",
6
6
  "exports": {
@@ -64,7 +64,6 @@
64
64
  "assert": "^1.5.0",
65
65
  "aws-sdk": "^2.452.0",
66
66
  "chalk": "^2.4.2",
67
- "deasync": "^0.1.30",
68
67
  "mocha": "^7.1.0",
69
68
  "nid-parser": "0.0.5",
70
69
  "node-pre-gyp-github": "^1.4.5"
package/src/boxed.cc CHANGED
@@ -6,6 +6,7 @@
6
6
  #include "debug.h"
7
7
  #include "error.h"
8
8
  #include "function.h"
9
+ #include "fundamental.h"
9
10
  #include "gi.h"
10
11
  #include "gobject.h"
11
12
  #include "macros.h"
@@ -258,8 +259,8 @@ static void BoxedConstructor(const Nan::FunctionCallbackInfo<Value> &info) {
258
259
  box->persistent = new Nan::Persistent<Object>(self);
259
260
  box->persistent->SetWeak(box, BoxedDestroyed, Nan::WeakCallbackType::kParameter);
260
261
 
261
- self->SetAlignedPointerInInternalField (0, boxed);
262
- self->SetAlignedPointerInInternalField (1, box);
262
+ Nan::SetInternalFieldPointer(self, 0, boxed);
263
+ Nan::SetInternalFieldPointer(self, 1, box);
263
264
 
264
265
  SET_OBJECT_GTYPE (self, gtype);
265
266
 
@@ -414,6 +415,13 @@ Local<Function> GetBoxedFunction(GIBaseInfo *info, GType gtype) {
414
415
  Local<Function> MakeBoxedClass(GIBaseInfo *info) {
415
416
  GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *) info);
416
417
 
418
+ // GVariant is a StructInfo but a refcounted fundamental (G_TYPE_VARIANT),
419
+ // not a boxed value: give it the fundamental ref/unref wrapper instead of
420
+ // the boxed copy/free one. JS still attaches its struct methods to this
421
+ // class via makeBoxed() (#465).
422
+ if (gtype == G_TYPE_VARIANT)
423
+ return MakeVariantClass (info);
424
+
417
425
  if (gtype == G_TYPE_NONE) {
418
426
  auto moduleCache = GNodeJS::GetModuleCache();
419
427
  auto ns = UTF8 (g_base_info_get_namespace (info));
@@ -454,7 +462,7 @@ Local<Value> WrapperFromBoxed(GIBaseInfo *info, void *data, ResourceOwnership ow
454
462
  void* PointerFromWrapper(Local<Value> value) {
455
463
  Local<Object> object = TO_OBJECT (value);
456
464
  g_assert(object->InternalFieldCount() > 0);
457
- void *boxed = object->GetAlignedPointerFromInternalField(0);
465
+ void *boxed = Nan::GetInternalFieldPointer(object, 0);
458
466
  return boxed;
459
467
  }
460
468
 
@@ -467,12 +475,12 @@ void DisownBoxed(Local<Value> value) {
467
475
  if (object->InternalFieldCount() < 2)
468
476
  return;
469
477
 
470
- Boxed *box = static_cast<Boxed *>(object->GetAlignedPointerFromInternalField(1));
478
+ Boxed *box = static_cast<Boxed *>(Nan::GetInternalFieldPointer(object, 1));
471
479
  if (box != NULL) {
472
480
  box->owns_memory = false;
473
481
  box->data = NULL;
474
482
  }
475
- object->SetAlignedPointerInInternalField(0, NULL);
483
+ Nan::SetInternalFieldPointer(object, 0, NULL);
476
484
  }
477
485
 
478
486
  };
package/src/function.cc CHANGED
@@ -5,6 +5,7 @@
5
5
  #include "callback.h"
6
6
  #include "debug.h"
7
7
  #include "error.h"
8
+ #include "fundamental.h"
8
9
  #include "function.h"
9
10
  #include "gobject.h"
10
11
  #include "macros.h"
@@ -522,11 +523,14 @@ Local<Value> FunctionCall (
522
523
  // Boxed: hand it a copy so the JS wrapper's own memory isn't
523
524
  // double-freed when finalized (#409). GObject: add the reference
524
525
  // the callee will own, so it isn't finalized out from under the
525
- // callee once the wrapper is GC'd (#439).
526
+ // callee once the wrapper is GC'd (#439). Fundamental (e.g.
527
+ // GskRenderNode): add the reference the callee will own (#468).
526
528
  else if (direction == GI_DIRECTION_IN
527
529
  && g_arg_info_get_ownership_transfer(&arg_info) == GI_TRANSFER_EVERYTHING) {
528
530
  CopyBoxedForTransferFullIn(&type_info, &callable_arg_values[i], param.length);
529
531
  RefObjectForTransferFullIn(&type_info, &callable_arg_values[i]);
532
+ RefFundamentalForTransferFullIn(&type_info, &callable_arg_values[i]);
533
+ RefVariantForTransferFullIn(&type_info, &callable_arg_values[i]);
530
534
  }
531
535
  }
532
536
 
@@ -708,7 +712,8 @@ Local<Value> FunctionInfo::JsReturnValue (
708
712
  ADD_RETURN (isReturningSelf ? self :
709
713
  GIArgumentToV8 (
710
714
  return_type, return_value, length,
711
- return_transfer == GI_TRANSFER_EVERYTHING ? kTransfer : kNone))
715
+ return_transfer == GI_TRANSFER_EVERYTHING ? kTransfer : kNone,
716
+ g_callable_info_may_return_null (info)))
712
717
  }
713
718
 
714
719
  for (int i = 0; i < n_callable_args; i++) {
@@ -742,20 +747,22 @@ Local<Value> FunctionInfo::JsReturnValue (
742
747
  &callable_arg_values[length_i],
743
748
  IsDirectionOut(length_direction));
744
749
 
745
- Local<Value> result = ArrayToV8(&arg_type, *(void**)arg_value.v_pointer, param.length);
750
+ bool may_be_null = g_arg_info_may_be_null(&arg_info);
751
+ Local<Value> result = ArrayToV8(&arg_type, *(void**)arg_value.v_pointer, param.length, may_be_null);
746
752
 
747
753
  ADD_RETURN (result)
748
754
 
749
755
  } else if (param.type == ParameterType::kNORMAL) {
750
756
  GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info);
751
757
  ResourceOwnership ownership = transfer == GI_TRANSFER_EVERYTHING ? kTransfer : kNone;
758
+ bool may_be_null = g_arg_info_may_be_null(&arg_info);
752
759
 
753
760
  if (IsPointerType(&arg_type) && g_arg_info_is_caller_allocates(&arg_info)) {
754
761
  void *pointer = &arg_value.v_pointer;
755
- ADD_RETURN (GIArgumentToV8(&arg_type, (GIArgument*) pointer, -1, ownership))
762
+ ADD_RETURN (GIArgumentToV8(&arg_type, (GIArgument*) pointer, -1, ownership, may_be_null))
756
763
  }
757
764
  else {
758
- ADD_RETURN (GIArgumentToV8(&arg_type, (GIArgument*) arg_value.v_pointer, -1, ownership))
765
+ ADD_RETURN (GIArgumentToV8(&arg_type, (GIArgument*) arg_value.v_pointer, -1, ownership, may_be_null))
759
766
  }
760
767
  }
761
768
  }