@gjsify/node-gi 0.63.0 → 0.64.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/src/object.cc CHANGED
@@ -444,17 +444,53 @@ bool JsToGValue(Napi::Env env, Napi::Value js, GValue* v) {
444
444
  // napi failure mid worker.terminate() must degrade to a zero, not cascade
445
445
  // into Error::New(nullptr)'s fatal sites (the terminate-mid-`newObject` path).
446
446
  case G_TYPE_BOOLEAN: g_value_set_boolean(v, NodeGiToBool(js)); return true;
447
- case G_TYPE_CHAR: g_value_set_schar(v, static_cast<gint8>(NodeGiToInt32(js))); return true;
448
- case G_TYPE_UCHAR: g_value_set_uchar(v, static_cast<guchar>(NodeGiToUint32(js))); return true;
449
- case G_TYPE_INT: g_value_set_int(v, NodeGiToInt32(js)); return true;
450
- case G_TYPE_UINT: g_value_set_uint(v, NodeGiToUint32(js)); return true;
451
- // 64-bit (glong/gulong are 64-bit on LP64): accept a BigInt losslessly, else a
452
- // truncated Number — never let a BigInt reach ToNumber(). Shared with the GI
453
- // scalar marshaller via JsValueTo{Int,Uint}64 (common.h).
447
+ // char/uchar/int64/uint64 refuse an out-of-range value and int/uint wrap, as gjs's
448
+ // gjs_value_to_g_value does (value.cpp:602-690) — the same rule the constructor
449
+ // properties and signal arguments reach. A BigInt on a 32-bit tag is ToNumber's
450
+ // TypeError, left pending.
451
+ case G_TYPE_CHAR:
452
+ case G_TYPE_UCHAR:
453
+ case G_TYPE_INT64:
454
+ case G_TYPE_UINT64: {
455
+ GITypeTag tag = ft == G_TYPE_CHAR ? GI_TYPE_TAG_INT8
456
+ : ft == G_TYPE_UCHAR ? GI_TYPE_TAG_UINT8
457
+ : ft == G_TYPE_INT64 ? GI_TYPE_TAG_INT64
458
+ : GI_TYPE_TAG_UINT64;
459
+ GIArgument a;
460
+ memset(&a, 0, sizeof(a));
461
+ switch (NodeGiJsToInteger(js, tag, &a, nullptr)) {
462
+ case NodeGiIntResult::kPending: return false;
463
+ case NodeGiIntResult::kOutOfRange:
464
+ NodeGiThrowGValueOutOfRange(env, js,
465
+ ft == G_TYPE_CHAR ? "char"
466
+ : ft == G_TYPE_UCHAR ? "unsigned char"
467
+ : ft == G_TYPE_INT64 ? "64-bit integer"
468
+ : "unsigned 64-bit integer");
469
+ return false;
470
+ case NodeGiIntResult::kOk: break;
471
+ }
472
+ if (ft == G_TYPE_CHAR) g_value_set_schar(v, a.v_int8);
473
+ else if (ft == G_TYPE_UCHAR) g_value_set_uchar(v, a.v_uint8);
474
+ else if (ft == G_TYPE_INT64) g_value_set_int64(v, a.v_int64);
475
+ else g_value_set_uint64(v, a.v_uint64);
476
+ return true;
477
+ }
478
+ case G_TYPE_INT:
479
+ case G_TYPE_UINT: {
480
+ GIArgument a;
481
+ memset(&a, 0, sizeof(a));
482
+ if (NodeGiJsToIntegerWrap(js, ft == G_TYPE_INT ? GI_TYPE_TAG_INT32 : GI_TYPE_TAG_UINT32,
483
+ &a) == NodeGiIntResult::kPending)
484
+ return false;
485
+ if (ft == G_TYPE_INT) g_value_set_int(v, a.v_int32);
486
+ else g_value_set_uint(v, a.v_uint32);
487
+ return true;
488
+ }
489
+ // glong/gulong are 64-bit on LP64: accept a BigInt losslessly, else a truncated
490
+ // Number — never let a BigInt reach ToNumber(). Shared with the GI scalar marshaller
491
+ // via JsValueTo{Int,Uint}64 (common.h). gjs has no G_TYPE_LONG case here at all.
454
492
  case G_TYPE_LONG: g_value_set_long(v, static_cast<glong>(JsValueToInt64(js))); return true;
455
493
  case G_TYPE_ULONG: g_value_set_ulong(v, static_cast<gulong>(JsValueToUint64(js))); return true;
456
- case G_TYPE_INT64: g_value_set_int64(v, JsValueToInt64(js)); return true;
457
- case G_TYPE_UINT64: g_value_set_uint64(v, JsValueToUint64(js)); return true;
458
494
  case G_TYPE_FLOAT: g_value_set_float(v, static_cast<float>(NodeGiToDouble(js))); return true;
459
495
  case G_TYPE_DOUBLE: g_value_set_double(v, NodeGiToDouble(js)); return true;
460
496
  case G_TYPE_ENUM: g_value_set_enum(v, NodeGiToInt32(js)); return true;
@@ -611,7 +647,16 @@ Napi::Value ConstructGObject(Napi::Env env, GType gtype, Napi::Object props,
611
647
  }
612
648
  g_value_init(&values[i], pspec->value_type);
613
649
  initialised = i + 1;
614
- if (!JsToGValue(env, props.Get(nameStorage[i]), &values[i])) {
650
+ Napi::Value propValue = props.Get(nameStorage[i]);
651
+ if (!propValue.IsEmpty() && propValue.IsUndefined()) {
652
+ // gjs refuses `undefined` for every property type (object.cpp:2090).
653
+ Napi::Error::New(env, "Invalid value 'undefined' for property " + std::string(pspec->name) +
654
+ " in object initializer.")
655
+ .ThrowAsJavaScriptException();
656
+ ok = false;
657
+ break;
658
+ }
659
+ if (!JsToGValue(env, propValue, &values[i])) {
615
660
  ok = false;
616
661
  break;
617
662
  }
@@ -734,6 +779,30 @@ Napi::Value SetProperty(const Napi::CallbackInfo& info) {
734
779
  Napi::TypeError::New(env, "no such property '" + name + "'").ThrowAsJavaScriptException();
735
780
  return env.Undefined();
736
781
  }
782
+ // gjs refuses an out-of-range value for these property types on assignment
783
+ // (object.cpp, with its own text); JsToGValue below now refuses them too, with the
784
+ // gjs_value_to_g_value text.
785
+ GITypeTag intTag = GI_TYPE_TAG_VOID;
786
+ switch (G_TYPE_FUNDAMENTAL(pspec->value_type)) {
787
+ case G_TYPE_CHAR: intTag = GI_TYPE_TAG_INT8; break;
788
+ case G_TYPE_UCHAR: intTag = GI_TYPE_TAG_UINT8; break;
789
+ case G_TYPE_UINT: intTag = GI_TYPE_TAG_UINT32; break;
790
+ case G_TYPE_INT64: intTag = GI_TYPE_TAG_INT64; break;
791
+ case G_TYPE_UINT64: intTag = GI_TYPE_TAG_UINT64; break;
792
+ default: break;
793
+ }
794
+ if (intTag != GI_TYPE_TAG_VOID) {
795
+ GIArgument probe;
796
+ std::string shown;
797
+ NodeGiIntResult r = NodeGiJsToInteger(info[2], intTag, &probe, &shown);
798
+ if (r == NodeGiIntResult::kPending) return env.Undefined();
799
+ if (r == NodeGiIntResult::kOutOfRange) {
800
+ Napi::Error::New(env, "value " + shown + " is out of range for " + pspec->name +
801
+ " (type " + NodeGiIntTagName(intTag) + ")")
802
+ .ThrowAsJavaScriptException();
803
+ return env.Undefined();
804
+ }
805
+ }
737
806
  GValue v = G_VALUE_INIT;
738
807
  g_value_init(&v, pspec->value_type);
739
808
  if (JsToGValue(env, info[2], &v)) {
package/src/private.cc CHANGED
@@ -25,6 +25,10 @@
25
25
 
26
26
  #include "common.h"
27
27
 
28
+ #ifdef _WIN32
29
+ #include <windows.h>
30
+ #endif
31
+
28
32
  namespace nodegi {
29
33
 
30
34
  // ---- GLib.log_set_writer_func ----------------------------------------------
@@ -557,10 +561,39 @@ Napi::Value Bindtextdomain(const Napi::CallbackInfo& info) {
557
561
  }
558
562
  location = info[1].As<Napi::String>().Utf8Value();
559
563
  }
564
+ #ifdef _WIN32
565
+ // bindtextdomain() reads its path in the ANSI code page, so a UTF-8 JS string with
566
+ // a non-ASCII directory (".../Steuererklärung/share/locale") binds a path that
567
+ // does not exist and every lookup silently answers the msgid (measured in a Windows
568
+ // 11 VM: the same catalog under an ASCII path translated). GNU gettext >= 0.21
569
+ // ships wbindtextdomain() for exactly this; both directions go through UTF-16.
570
+ std::wstring wlocation;
571
+ if (!query) {
572
+ int n = MultiByteToWideChar(CP_UTF8, 0, location.c_str(), -1, nullptr, 0);
573
+ if (n <= 0) {
574
+ Napi::TypeError::New(env, "bindtextdomain: location is not valid UTF-8")
575
+ .ThrowAsJavaScriptException();
576
+ return env.Null();
577
+ }
578
+ wlocation.resize(n);
579
+ MultiByteToWideChar(CP_UTF8, 0, location.c_str(), -1, wlocation.data(), n);
580
+ wlocation.resize(n - 1);
581
+ }
582
+ const wchar_t* wresult = wbindtextdomain(domain.c_str(), query ? nullptr : wlocation.c_str());
583
+ if (!query) bind_textdomain_codeset(domain.c_str(), "UTF-8");
584
+ if (wresult == nullptr) return env.Null();
585
+ int len = WideCharToMultiByte(CP_UTF8, 0, wresult, -1, nullptr, 0, nullptr, nullptr);
586
+ if (len <= 0) return env.Null();
587
+ std::string utf8(len, '\0');
588
+ WideCharToMultiByte(CP_UTF8, 0, wresult, -1, utf8.data(), len, nullptr, nullptr);
589
+ utf8.resize(len - 1);
590
+ return Napi::String::New(env, utf8);
591
+ #else
560
592
  const char* result = bindtextdomain(domain.c_str(), query ? nullptr : location.c_str());
561
593
  if (!query) bind_textdomain_codeset(domain.c_str(), "UTF-8");
562
594
  if (result == nullptr) return env.Null();
563
595
  return Napi::String::New(env, result);
596
+ #endif
564
597
  }
565
598
 
566
599
  // localeCategories() -> { ALL, COLLATE, CTYPE, MESSAGES, MONETARY, NUMERIC, TIME }
package/src/repo.cc CHANGED
@@ -13,7 +13,6 @@ void NodeGiEnvDataFinalize(napi_env env, void* data, void* /*hint*/) {
13
13
  NodeGiEnvData* d = static_cast<NodeGiEnvData*>(data);
14
14
  if (d == nullptr) return;
15
15
  if (d->errorBuilder != nullptr) napi_delete_reference(env, d->errorBuilder);
16
- if (d->errorClass != nullptr) napi_delete_reference(env, d->errorClass);
17
16
  if (d->templateCallbackResolver != nullptr)
18
17
  napi_delete_reference(env, d->templateCallbackResolver);
19
18
  if (d->cairoWrappers != nullptr) napi_delete_reference(env, d->cairoWrappers);
@@ -260,17 +259,52 @@ Napi::Value GetErrorDomain(const Napi::CallbackInfo& info) {
260
259
  return result;
261
260
  }
262
261
 
263
- // setErrorBuilder(builder: (domainName, domainQuark, code, message) => Error,
264
- // errorClass?: typeof GLib.Error) -> void
265
- // Register the L1 GLib.Error factory the engine calls when a GI invoke fails, so a
266
- // failed sync call throws a real GLib.Error, together with the class it builds —
267
- // which the GError IN-arg path needs to recognise one coming back. Both stored
268
- // per-env (a napi_ref is env-specific; both paths are gated on the same env), in
269
- // ONE call so a registration cannot supply the builder and forget the class.
262
+ // errorDomainInfo(quark: number) -> { namespace: string, name: string } | null
263
+ // Which introspected enum registered a GError domain quark, so L1 can resolve a
264
+ // thrown error to its DOMAIN CLASS (Gio.IOErrorEnum) by reading that member.
265
+ // getErrorDomain below answers the same relation the other way round, and L1's
266
+ // quark→class map is only populated by it — which is not enough on its own: an
267
+ // application that never named `Gio.IOErrorEnum` has nothing in the map, and the
268
+ // error it catches would land on the base class, breaking exactly the
269
+ // `catch (e) { if (e instanceof Gio.IOErrorEnum) }` idiom. Null for a quark no
270
+ // typelib registers (a private `GLib.quark_from_string` domain).
271
+ Napi::Value ErrorDomainInfo(const Napi::CallbackInfo& info) {
272
+ Napi::Env env = info.Env();
273
+ if (info.Length() < 1 || !info[0].IsNumber()) {
274
+ Napi::TypeError::New(env, "errorDomainInfo(quark: number)").ThrowAsJavaScriptException();
275
+ return env.Null();
276
+ }
277
+ GQuark quark = static_cast<GQuark>(NodeGiToUint32(info[0]));
278
+ if (quark == 0) return env.Null();
279
+ GIRepository* repo = DupDefaultRepository();
280
+ GIEnumInfo* enumInfo = gi_repository_find_by_error_domain(repo, quark);
281
+ Napi::Value result = env.Null();
282
+ if (enumInfo != nullptr) {
283
+ GIBaseInfo* base = reinterpret_cast<GIBaseInfo*>(enumInfo);
284
+ const char* ns = gi_base_info_get_namespace(base);
285
+ const char* name = gi_base_info_get_name(base);
286
+ if (ns != nullptr && name != nullptr) {
287
+ Napi::Object obj = Napi::Object::New(env);
288
+ obj.Set("namespace", Napi::String::New(env, ns));
289
+ obj.Set("name", Napi::String::New(env, name));
290
+ result = obj;
291
+ }
292
+ gi_base_info_unref(base);
293
+ }
294
+ g_object_unref(repo);
295
+ return result;
296
+ }
297
+
298
+ // setErrorBuilder(builder: (handle) => GLib.Error) -> void
299
+ // Register the L1 error factory the engine calls when a GI invoke fails, so a
300
+ // failed sync call throws the error object of its domain's class. Stored per-env
301
+ // (a napi_ref is env-specific). It takes the GError's boxed HANDLE — the L1 error
302
+ // IS that handle, which is why nothing else needs registering here: the GError
303
+ // IN-arg direction reads the same handle back off the object.
270
304
  Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info) {
271
305
  Napi::Env env = info.Env();
272
306
  if (info.Length() < 1 || !info[0].IsFunction()) {
273
- Napi::TypeError::New(env, "setErrorBuilder(builder: function, errorClass?: function)")
307
+ Napi::TypeError::New(env, "setErrorBuilder(builder: function)")
274
308
  .ThrowAsJavaScriptException();
275
309
  return env.Undefined();
276
310
  }
@@ -281,12 +315,6 @@ Napi::Value SetErrorBuilder(const Napi::CallbackInfo& info) {
281
315
  d->errorBuilder = nullptr;
282
316
  }
283
317
  napi_create_reference(env, info[0], 1, &d->errorBuilder);
284
- if (d->errorClass != nullptr) {
285
- napi_delete_reference(env, d->errorClass);
286
- d->errorClass = nullptr;
287
- }
288
- if (info.Length() >= 2 && info[1].IsFunction())
289
- napi_create_reference(env, info[1], 1, &d->errorClass);
290
318
  return env.Undefined();
291
319
  }
292
320