@gtkx/runtime 1.0.0 → 1.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.
Files changed (109) hide show
  1. package/README.md +1 -1
  2. package/dist/application-class.d.ts +5 -0
  3. package/dist/application-class.d.ts.map +1 -1
  4. package/dist/application-class.js +11 -1
  5. package/dist/application-class.js.map +1 -1
  6. package/dist/callback.d.ts +2 -2
  7. package/dist/callback.d.ts.map +1 -1
  8. package/dist/callback.js +51 -54
  9. package/dist/callback.js.map +1 -1
  10. package/dist/default-application.d.ts +4 -0
  11. package/dist/default-application.d.ts.map +1 -0
  12. package/dist/default-application.js +17 -0
  13. package/dist/default-application.js.map +1 -0
  14. package/dist/descriptors.d.ts +25 -2
  15. package/dist/descriptors.d.ts.map +1 -1
  16. package/dist/descriptors.js +31 -9
  17. package/dist/descriptors.js.map +1 -1
  18. package/dist/exit-hook.js +13 -0
  19. package/dist/exit-hook.js.map +1 -1
  20. package/dist/fn.d.ts +5 -0
  21. package/dist/fn.d.ts.map +1 -1
  22. package/dist/fn.js +11 -13
  23. package/dist/fn.js.map +1 -1
  24. package/dist/folded-lengths.d.ts +16 -0
  25. package/dist/folded-lengths.d.ts.map +1 -0
  26. package/dist/folded-lengths.js +38 -0
  27. package/dist/folded-lengths.js.map +1 -0
  28. package/dist/internal.d.ts +1 -0
  29. package/dist/internal.d.ts.map +1 -1
  30. package/dist/internal.js +1 -0
  31. package/dist/internal.js.map +1 -1
  32. package/dist/library.d.ts +2 -1
  33. package/dist/library.d.ts.map +1 -1
  34. package/dist/library.js +2 -1
  35. package/dist/library.js.map +1 -1
  36. package/dist/lifecycle.d.ts +16 -1
  37. package/dist/lifecycle.d.ts.map +1 -1
  38. package/dist/lifecycle.js +44 -14
  39. package/dist/lifecycle.js.map +1 -1
  40. package/dist/listeners.d.ts.map +1 -1
  41. package/dist/listeners.js +16 -4
  42. package/dist/listeners.js.map +1 -1
  43. package/dist/native-value.js +2 -2
  44. package/dist/native-value.js.map +1 -1
  45. package/dist/object.d.ts +7 -2
  46. package/dist/object.d.ts.map +1 -1
  47. package/dist/object.js +7 -2
  48. package/dist/object.js.map +1 -1
  49. package/dist/param-spec.d.ts.map +1 -1
  50. package/dist/param-spec.js +18 -20
  51. package/dist/param-spec.js.map +1 -1
  52. package/dist/properties.d.ts +2 -1
  53. package/dist/properties.d.ts.map +1 -1
  54. package/dist/properties.js +13 -9
  55. package/dist/properties.js.map +1 -1
  56. package/dist/register-class.d.ts +30 -0
  57. package/dist/register-class.d.ts.map +1 -1
  58. package/dist/register-class.js +21 -7
  59. package/dist/register-class.js.map +1 -1
  60. package/dist/registry.d.ts +12 -3
  61. package/dist/registry.d.ts.map +1 -1
  62. package/dist/registry.js +24 -15
  63. package/dist/registry.js.map +1 -1
  64. package/dist/signal.d.ts +2 -2
  65. package/dist/signal.d.ts.map +1 -1
  66. package/dist/signal.js +22 -18
  67. package/dist/signal.js.map +1 -1
  68. package/dist/t.d.ts +2 -0
  69. package/dist/t.d.ts.map +1 -1
  70. package/dist/t.js +1 -0
  71. package/dist/t.js.map +1 -1
  72. package/dist/tuple.d.ts +6 -3
  73. package/dist/tuple.d.ts.map +1 -1
  74. package/dist/tuple.js +9 -7
  75. package/dist/tuple.js.map +1 -1
  76. package/dist/value.d.ts +3 -1
  77. package/dist/value.d.ts.map +1 -1
  78. package/dist/value.js +64 -18
  79. package/dist/value.js.map +1 -1
  80. package/dist/vfunc-call.d.ts.map +1 -1
  81. package/dist/vfunc-call.js +5 -1
  82. package/dist/vfunc-call.js.map +1 -1
  83. package/dist/vfunc.d.ts.map +1 -1
  84. package/dist/vfunc.js +9 -3
  85. package/dist/vfunc.js.map +1 -1
  86. package/package.json +14 -4
  87. package/src/application-class.ts +12 -2
  88. package/src/callback.ts +70 -66
  89. package/src/default-application.ts +21 -0
  90. package/src/descriptors.ts +69 -14
  91. package/src/exit-hook.ts +18 -0
  92. package/src/fn.ts +23 -13
  93. package/src/folded-lengths.ts +64 -0
  94. package/src/internal.ts +1 -0
  95. package/src/library.ts +2 -1
  96. package/src/lifecycle.ts +60 -14
  97. package/src/listeners.ts +22 -5
  98. package/src/native-value.ts +2 -2
  99. package/src/object.ts +7 -2
  100. package/src/param-spec.ts +21 -22
  101. package/src/properties.ts +23 -9
  102. package/src/register-class.ts +63 -8
  103. package/src/registry.ts +40 -18
  104. package/src/signal.ts +32 -28
  105. package/src/t.ts +3 -0
  106. package/src/tuple.ts +18 -12
  107. package/src/value.ts +94 -19
  108. package/src/vfunc-call.ts +5 -1
  109. package/src/vfunc.ts +10 -3
package/src/param-spec.ts CHANGED
@@ -13,6 +13,7 @@ import {
13
13
  TYPE_ENUM,
14
14
  TYPE_FLAGS,
15
15
  TYPE_FLOAT,
16
+ TYPE_GTYPE,
16
17
  TYPE_INT,
17
18
  TYPE_INT64,
18
19
  TYPE_INTERFACE,
@@ -51,7 +52,6 @@ const UINT32_MAXIMUM = 4_294_967_295;
51
52
  const INT64_MINIMUM = -(2n ** 63n);
52
53
  const INT64_MAXIMUM = 2n ** 63n - 1n;
53
54
  const UINT64_MAXIMUM = 2n ** 64n - 1n;
54
- const FLOAT_MAXIMUM = 3.4028234663852886e38;
55
55
 
56
56
  const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
57
57
  TYPE_BOXED,
@@ -61,6 +61,8 @@ const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
61
61
  TYPE_VARIANT,
62
62
  ]);
63
63
 
64
+ const isWideUnsignedValue: ValueGuard = wideIntegerGuardFor(0n, UINT64_MAXIMUM);
65
+
64
66
  const SCALAR_GUARDS: Map<bigint, ValueGuard> = new Map([
65
67
  [TYPE_BOOLEAN, isBooleanValue],
66
68
  [TYPE_STRING, isStringValue],
@@ -71,11 +73,11 @@ const SCALAR_GUARDS: Map<bigint, ValueGuard> = new Map([
71
73
  [TYPE_ENUM, integerGuardFor(INT32_MINIMUM, INT32_MAXIMUM)],
72
74
  [TYPE_FLAGS, integerGuardFor(0, UINT32_MAXIMUM)],
73
75
  [TYPE_LONG, wideIntegerGuardFor(INT64_MINIMUM, INT64_MAXIMUM)],
74
- [TYPE_ULONG, wideIntegerGuardFor(0n, UINT64_MAXIMUM)],
76
+ [TYPE_ULONG, isWideUnsignedValue],
75
77
  [TYPE_INT64, wideIntegerGuardFor(INT64_MINIMUM, INT64_MAXIMUM)],
76
- [TYPE_UINT64, wideIntegerGuardFor(0n, UINT64_MAXIMUM)],
77
- [TYPE_FLOAT, isFloatValue],
78
- [TYPE_DOUBLE, isDoubleValue],
78
+ [TYPE_UINT64, isWideUnsignedValue],
79
+ [TYPE_FLOAT, isNumberValue],
80
+ [TYPE_DOUBLE, isNumberValue],
79
81
  [TYPE_POINTER, isNullValue],
80
82
  ]);
81
83
 
@@ -115,12 +117,8 @@ function isStrvValue(value: unknown): boolean {
115
117
  return value == null || (Array.isArray(value) && value.every((item) => typeof item === "string"));
116
118
  }
117
119
 
118
- function isDoubleValue(value: unknown): boolean {
119
- return value == null || (typeof value === "number" && Number.isFinite(value));
120
- }
121
-
122
- function isFloatValue(value: unknown): boolean {
123
- return isDoubleValue(value) && (value == null || Math.abs(value as number) <= FLOAT_MAXIMUM);
120
+ function isNumberValue(value: unknown): boolean {
121
+ return typeof value === "number";
124
122
  }
125
123
 
126
124
  function isAnyValue(): boolean {
@@ -131,12 +129,8 @@ function isNullValue(value: unknown): boolean {
131
129
  return value == null;
132
130
  }
133
131
 
134
- function isIntegerWithin(value: unknown, minimum: number, maximum: number): boolean {
135
- return Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
136
- }
137
-
138
132
  function integerGuardFor(minimum: number, maximum: number): ValueGuard {
139
- return (value) => value == null || isIntegerWithin(value, minimum, maximum);
133
+ return (value) => Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
140
134
  }
141
135
 
142
136
  function toWideInteger(value: unknown): bigint | undefined {
@@ -149,10 +143,6 @@ function toWideInteger(value: unknown): bigint | undefined {
149
143
 
150
144
  function wideIntegerGuardFor(minimum: bigint, maximum: bigint): ValueGuard {
151
145
  return (value) => {
152
- if (value == null) {
153
- return true;
154
- }
155
-
156
146
  const wide = toWideInteger(value);
157
147
 
158
148
  return wide !== undefined && wide >= minimum && wide <= maximum;
@@ -173,11 +163,20 @@ function wrappedGuardFor(valueType: bigint): ValueGuard {
173
163
  return (value) => value == null || typeIsA(resolveGtype(value), valueType);
174
164
  }
175
165
 
166
+ function exactGuardFor(valueType: bigint): ValueGuard | undefined {
167
+ if (valueType === TYPE_GTYPE) {
168
+ return isWideUnsignedValue;
169
+ }
170
+
171
+ return valueType === getStrvType() ? isStrvValue : undefined;
172
+ }
173
+
176
174
  function valueGuardFor(valueType: bigint): ValueGuard {
177
175
  assertParamLayout();
176
+ const exact = exactGuardFor(valueType);
178
177
 
179
- if (valueType === getStrvType()) {
180
- return isStrvValue;
178
+ if (exact !== undefined) {
179
+ return exact;
181
180
  }
182
181
 
183
182
  const fundamental = typeFundamental(valueType);
package/src/properties.ts CHANGED
@@ -16,7 +16,14 @@ import {
16
16
  } from "./param-spec.js";
17
17
  import { getHandle, getInterfaceProperties, instanceClassName, type InterfaceProperty } from "./registry.js";
18
18
  import { typeName } from "./type.js";
19
- import { fromValue, intoValue, newValueForType, type ValueWriter, valueWriterFor } from "./value.js";
19
+ import {
20
+ fromValue,
21
+ newValueForType,
22
+ type ValueNarrower,
23
+ valueNarrowerFor,
24
+ type ValueWriter,
25
+ valueWriterFor,
26
+ } from "./value.js";
20
27
 
21
28
  type PropertyCheck = {
22
29
  name: string;
@@ -25,6 +32,7 @@ type PropertyCheck = {
25
32
  flags: number;
26
33
  valueType: bigint;
27
34
  canHoldValue: ValueGuard;
35
+ narrowValue: ValueNarrower;
28
36
  write?: ValueWriter;
29
37
  scratch?: ExternalObject<Handle>;
30
38
  };
@@ -80,6 +88,8 @@ const typeLabel = (type: bigint): string => typeName(type) ?? String(type);
80
88
  const defaultValueFor = (handle: ExternalObject<Handle>): ExternalObject<Handle> =>
81
89
  paramSpecDefaultValue(handle) as ExternalObject<Handle>;
82
90
 
91
+ const heldValue = (value: unknown): unknown => value ?? null;
92
+
83
93
  const holdsReason = (check: PropertyCheck): string =>
84
94
  `the property holds values of type '${typeLabel(check.valueType)}'`;
85
95
 
@@ -93,6 +103,7 @@ function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
93
103
  flags: getParamFlags(handle),
94
104
  valueType,
95
105
  canHoldValue: valueGuardFor(valueType),
106
+ narrowValue: valueNarrowerFor(valueType),
96
107
  };
97
108
  }
98
109
 
@@ -168,6 +179,11 @@ function assertWritable(instance: object, check: PropertyCheck, value: unknown):
168
179
  throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
169
180
  }
170
181
 
182
+ function writeHeld(check: PropertyCheck, gValue: ExternalObject<Handle>, value: unknown): void {
183
+ check.write ??= valueWriterFor(check.valueType);
184
+ check.write(gValue, check.narrowValue(value));
185
+ }
186
+
171
187
  function fillCheckedValue(
172
188
  instance: object,
173
189
  check: PropertyCheck,
@@ -175,8 +191,7 @@ function fillCheckedValue(
175
191
  value: unknown,
176
192
  ): void {
177
193
  assertValueFits(instance, check, value);
178
- check.write ??= valueWriterFor(check.valueType);
179
- check.write(gValue, value);
194
+ writeHeld(check, gValue, value);
180
195
  assertValueValidates(instance, check, value, gValue);
181
196
  }
182
197
 
@@ -224,11 +239,11 @@ function readStored(instance: Record<symbol, unknown>, accessor: PropertyAccesso
224
239
  }
225
240
 
226
241
  function storeValue(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
227
- instance[accessor.storage] = value;
242
+ instance[accessor.storage] = heldValue(value);
228
243
  }
229
244
 
230
245
  function writeStored(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
231
- if (readStored(instance, accessor) === value) {
246
+ if (readStored(instance, accessor) === heldValue(value)) {
232
247
  return;
233
248
  }
234
249
 
@@ -239,13 +254,12 @@ function writeStored(instance: Record<symbol, unknown>, accessor: PropertyAccess
239
254
  function writeProperty(instance: object, accessor: PropertyAccessor, value: unknown): void {
240
255
  const stored = instance as Record<symbol, unknown>;
241
256
 
242
- if (readStored(stored, accessor) === value) {
257
+ if (readStored(stored, accessor) === heldValue(value)) {
243
258
  return;
244
259
  }
245
260
 
246
261
  assertValueAccepted(instance, accessor, value);
247
- storeValue(stored, accessor, value);
248
- (instance as NotifyingObject).notify?.(accessor.propertyName);
262
+ writeStored(stored, accessor, accessor.narrowValue(value));
249
263
  }
250
264
 
251
265
  function storedGetter(accessor: PropertyAccessor): (this: object) => unknown {
@@ -431,7 +445,7 @@ function makeGetProperty(dispatch: PropertyDispatch) {
431
445
  const accessor = resolveAccessor(dispatch, propertyId, pspec);
432
446
  const current = readCurrent(this, accessor);
433
447
  assertValueServes(this, accessor, current);
434
- intoValue(getHandle(value), current);
448
+ writeHeld(accessor, getHandle(value), current);
435
449
  };
436
450
  }
437
451
 
@@ -134,6 +134,36 @@ type RegisterClassOptions<TInstance extends object, TProperties extends Record<s
134
134
  * which a `freeze_notify` batch collects; a write of the value the property already holds is
135
135
  * dropped and emits none.
136
136
  *
137
+ * `null` and `undefined` both mean NULL, and mean it only where the ParamSpec's own type holds
138
+ * NULL, which is a string, string-array, boxed, object, interface, param, variant or pointer
139
+ * property. Such a property holds the `null` either spelling wrote, so the member, the type's
140
+ * `get_property` slot and `g_object_get_property` serve the same thing, and writing the other
141
+ * spelling over it emits no `notify`. Every other property, so every integer, floating-point,
142
+ * boolean, enum, flags and GType one, refuses both with the same `TypeError` it refuses a
143
+ * string with, and keeps the value it already holds. It refuses them for what it holds rather
144
+ * than for its range, whatever that range is: the type is checked before the range, so a
145
+ * `gint` whose range excludes 0 answers a nullish with that same `TypeError` and never with
146
+ * the `RangeError` that names the value GObject would put in its place. The one place
147
+ * `undefined` means something else is the constructor, which reads it as the property not
148
+ * being given at all and leaves it at the ParamSpec's default, so a property is never handed a
149
+ * value it cannot serve back.
150
+ *
151
+ * A floating-point property takes every JavaScript number, `NaN` and both infinities
152
+ * included, and its ParamSpec alone rules on which of them the range admits: a `gdouble`
153
+ * bounded by `-Infinity` and `Infinity` holds either infinity, and a magnitude a bounded
154
+ * one excludes, like any `NaN`, comes back as the `RangeError` that names what GObject
155
+ * would put in its place rather than as the `TypeError` a type the property cannot hold
156
+ * earns. A `gfloat` property holds what GObject narrows the double to, so it serves `0.1`
157
+ * back as `0.10000000149011612` and a finite magnitude no `gfloat` reaches as an infinity,
158
+ * which the range then rules on in turn. That narrowing belongs to the property alone: the
159
+ * same magnitude written to a `gfloat` through a generated binding, a signal argument or a
160
+ * closure return is refused outright rather than narrowed.
161
+ *
162
+ * A generated property of a wrapped type answers a nullish differently, and the two halves of
163
+ * the API disagree here: that property marshals what it is written through its descriptor
164
+ * rather than through the checks above, so `new Gtk.Label({ widthRequest: null })` and a later
165
+ * write of `null` to that member both land 0, where a `gint` installed here refuses both.
166
+ *
137
167
  * A class that defines the camelCase member itself owns the property: its own accessor decides
138
168
  * what a write means, the other two spellings forward to it, and the type's property slots read
139
169
  * and write it rather than the generated storage.
@@ -166,7 +196,11 @@ type PropertyVfuncSpec = {
166
196
  makeDispatch: (dispatch: PropertyDispatch) => VfuncFn;
167
197
  };
168
198
 
199
+ type ArgPatch = { isCallerAllocated: true } | { isCallScoped: true };
200
+
201
+ const INSTANCE_ARG_INDEX = 0;
169
202
  const VALUE_ARG_INDEX = 2;
203
+ const TEARDOWN_VFUNC_NAMES: Set<string> = new Set(["dispose", "finalize"]);
170
204
 
171
205
  const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
172
206
  { methodName: GET_PROPERTY_VFUNC, isValueOut: true, makeDispatch: makeGetProperty },
@@ -345,10 +379,13 @@ function buildDiscoveredVfunc(
345
379
  return undefined;
346
380
  }
347
381
 
382
+ const argDescriptors = slotArgDescriptors(descriptor);
383
+
348
384
  return {
349
385
  ...descriptor,
350
386
  methodName,
351
- fn: wrapVfunc(fn, descriptor.argDescriptors, descriptor.returnDescriptor),
387
+ argDescriptors,
388
+ fn: wrapVfunc(fn, argDescriptors, descriptor),
352
389
  };
353
390
  }
354
391
 
@@ -381,9 +418,13 @@ function discoverClassVfuncs(klass: AnyClass, methods: MethodTable): DiscoveredV
381
418
  function wrapVfunc(
382
419
  fn: VfuncFn,
383
420
  argDescriptors: NativeRegisterClassVfunc["argDescriptors"],
384
- returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"],
421
+ descriptor: VfuncDescriptor,
385
422
  ): VfuncFn {
386
- return wrapCallback(fn as (...args: unknown[]) => unknown, { argDescriptors, returnDescriptor }, "this");
423
+ return wrapCallback(
424
+ fn as (...args: unknown[]) => unknown,
425
+ { argDescriptors, returnDescriptor: descriptor.returnDescriptor },
426
+ "vfunc",
427
+ );
387
428
  }
388
429
 
389
430
  function discoverInterfaceBindings(
@@ -443,9 +484,23 @@ function propertyVfuncs(source: PropertyVfuncSource): DiscoveredVfunc[] {
443
484
  );
444
485
  }
445
486
 
446
- function markValueCallerAllocated(argDescriptors: Descriptor[]): Descriptor[] {
447
- return argDescriptors.map((arg, index) =>
448
- index === VALUE_ARG_INDEX ? { ...arg, isCallerAllocated: true } : arg);
487
+ function markArg(argDescriptors: Descriptor[], index: number, patch: ArgPatch): Descriptor[] {
488
+ return argDescriptors.map((arg, at) => (at === index ? { ...arg, ...patch } : arg));
489
+ }
490
+
491
+ function isTeardownSlot(descriptor: VfuncDescriptor): boolean {
492
+ return (
493
+ TEARDOWN_VFUNC_NAMES.has(descriptor.vfuncName) &&
494
+ descriptor.argDescriptors[INSTANCE_ARG_INDEX]?.kind === "object"
495
+ );
496
+ }
497
+
498
+ function slotArgDescriptors(descriptor: VfuncDescriptor): Descriptor[] {
499
+ if (!isTeardownSlot(descriptor)) {
500
+ return descriptor.argDescriptors;
501
+ }
502
+
503
+ return markArg(descriptor.argDescriptors, INSTANCE_ARG_INDEX, { isCallScoped: true });
449
504
  }
450
505
 
451
506
  function buildPropertyVfunc(
@@ -461,14 +516,14 @@ function buildPropertyVfunc(
461
516
  }
462
517
 
463
518
  const argDescriptors = isValueOut
464
- ? markValueCallerAllocated(descriptor.argDescriptors)
519
+ ? markArg(descriptor.argDescriptors, VALUE_ARG_INDEX, { isCallerAllocated: true })
465
520
  : descriptor.argDescriptors;
466
521
 
467
522
  return {
468
523
  ...descriptor,
469
524
  methodName,
470
525
  argDescriptors,
471
- fn: wrapVfunc(fn, argDescriptors, descriptor.returnDescriptor),
526
+ fn: wrapVfunc(fn, argDescriptors, descriptor),
472
527
  };
473
528
  }
474
529
 
package/src/registry.ts CHANGED
@@ -12,10 +12,12 @@ import { TYPE_INVALID, type TypedClass, typeInterfaces, typeIsA, typeName, typeP
12
12
 
13
13
  /**
14
14
  * Static side of class `C` with its construct signature preserved but the member
15
- * named `K` (default `"new"`) removed.
15
+ * named `K` (default `"new"`) removed. The signature is kept abstract so an abstract
16
+ * `C` survives it: a subclass extends the result, and only the subclass's own
17
+ * declaration decides whether `new` reaches it.
16
18
  */
17
19
  type StaticBase<C, K extends PropertyKey = "new"> = Omit<C, K> &
18
- (C extends new (...args: infer A) => infer R ? new (...args: A) => R : never);
20
+ (C extends abstract new (...args: infer A) => infer R ? abstract new (...args: A) => R : never);
19
21
 
20
22
  /** One overridable vtable slot: where it sits in the vtable struct and how it is marshalled. */
21
23
  type VfuncDescriptor = {
@@ -35,6 +37,12 @@ type VfuncDescriptor = {
35
37
  argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
36
38
  /** Descriptor for the value the slot returns. */
37
39
  returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"];
40
+ /**
41
+ * GIR marks the slot's return value as one the bindings do not surface, so a call through the
42
+ * slot drops it and an implementation of the slot reports success in its place, while
43
+ * `VfuncDescriptor.returnDescriptor` keeps the C type the slot is called against either way.
44
+ */
45
+ isReturnSkipped?: boolean;
38
46
  /**
39
47
  * The slot takes a trailing `GError**` that `VfuncDescriptor.argDescriptors` leaves out, the
40
48
  * way GIR leaves it out of a callable's parameters. A call through the slot has to append it
@@ -48,6 +56,7 @@ type VfuncDescriptor = {
48
56
  * overrides each one.
49
57
  */
50
58
  type VfuncRegistry = Record<string, VfuncDescriptor>;
59
+ type WrapperBinding = (handle: ExternalObject<Handle>, instance: object) => void;
51
60
 
52
61
  /**
53
62
  * How one property an interface declares reaches the vtable, given as the interface's own accessor
@@ -320,24 +329,18 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
320
329
  }
321
330
 
322
331
  function wrapObject(value: unknown): object | null {
323
- if (value == null) {
324
- return null;
325
- }
326
-
327
- return getOrCreateWrapper(value as ExternalObject<Handle>);
332
+ return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>);
328
333
  }
329
334
 
330
- function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
331
- if (handleMap.has(handle)) {
332
- return handle;
333
- }
334
-
335
- const existing = getWrapper(handle);
335
+ function wrapCallScopedObject(value: unknown): object | null {
336
+ return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper);
337
+ }
336
338
 
337
- if (existing) {
338
- return existing;
339
- }
339
+ function existingWrapperFor(handle: ExternalObject<Handle>): object | null {
340
+ return handleMap.has(handle) ? handle : getWrapper(handle);
341
+ }
340
342
 
343
+ function createWrapper(handle: ExternalObject<Handle>): object {
341
344
  const runtimeType: bigint = getType(handle);
342
345
 
343
346
  if (runtimeType === TYPE_INVALID) {
@@ -350,12 +353,26 @@ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
350
353
  throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
351
354
  }
352
355
 
353
- const instance: object = Object.create(cls.prototype) as object;
354
- registerWrapper(handle, instance);
356
+ return Object.create(cls.prototype) as object;
357
+ }
358
+
359
+ function wrapperFor(handle: ExternalObject<Handle>, bind: WrapperBinding): object {
360
+ const existing = existingWrapperFor(handle);
361
+
362
+ if (existing) {
363
+ return existing;
364
+ }
365
+
366
+ const instance = createWrapper(handle);
367
+ bind(handle, instance);
355
368
 
356
369
  return instance;
357
370
  }
358
371
 
372
+ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
373
+ return wrapperFor(handle, registerWrapper);
374
+ }
375
+
359
376
  function instanceClassName(instance: object): string {
360
377
  return (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
361
378
  }
@@ -376,6 +393,10 @@ function setHandle(instance: object, handle: ExternalObject<Handle>): void {
376
393
  handleMap.set(instance, handle);
377
394
  }
378
395
 
396
+ function bindCallScopedWrapper(handle: ExternalObject<Handle>, instance: object): void {
397
+ setHandle(instance, handle);
398
+ }
399
+
379
400
  function registerWrapper(handle: ExternalObject<Handle>, instance: object): void {
380
401
  setHandle(instance, handle);
381
402
  setWrapper(handle, instance);
@@ -416,6 +437,7 @@ export {
416
437
  instanceClassName,
417
438
  registerWrapper,
418
439
  resolveWrapperType,
440
+ wrapCallScopedObject,
419
441
  wrapObject,
420
442
  type InterfaceProperty,
421
443
  type StaticBase,
package/src/signal.ts CHANGED
@@ -43,6 +43,11 @@ type SignalConnectSpec = {
43
43
  isAfter: boolean;
44
44
  };
45
45
 
46
+ type EmitValues = {
47
+ values: ExternalObject<Handle>[];
48
+ reads: (() => unknown)[];
49
+ };
50
+
46
51
  /** One argument of a signal emission: how to marshal it, plus the value to marshal. */
47
52
  type EmitArg = Arg & {
48
53
  /**
@@ -194,7 +199,7 @@ function connectBind(type: bigint, signal: string, callback: CallbackDescriptor)
194
199
  */
195
200
  function connectSignal(instance: object, signal: string, spec: SignalConnectSpec): number {
196
201
  const { callback, handler, isAfter } = spec;
197
- const wrapped = wrapCallback(handler, callback, "emitter");
202
+ const wrapped = wrapCallback(handler, callback, "signal");
198
203
  const type: bigint = (instance as TypedClass).__type__;
199
204
  const connect = connectBind(type, signal, callback);
200
205
  const handlerId = connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
@@ -221,47 +226,46 @@ const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?:
221
226
  return isInoutArg(arg) ? outValueForDescriptor(arg.type, arg.value) : outValueForDescriptor(arg.type);
222
227
  };
223
228
 
229
+ const collectEmitValues = (instance: object, args: EmitArg[]): EmitValues => {
230
+ const collected: EmitValues = { values: [toValue(objectT("full"), instance)], reads: [] };
231
+
232
+ for (const arg of args) {
233
+ const { value, read } = createEmitValue(arg);
234
+ collected.values.push(value);
235
+
236
+ if (read) {
237
+ collected.reads.push(read);
238
+ }
239
+ }
240
+
241
+ return collected;
242
+ };
243
+
244
+ const readEmitOutputs = (reads: (() => unknown)[]): unknown[] => reads.map((read) => read());
245
+
224
246
  /**
225
247
  * Emits a signal on an instance with the given arguments and returns its result
226
248
  * combined with any output-argument values.
227
249
  * @param instance Emitter to emit the signal on.
228
250
  * @param signal Signal name, optionally including a `::detail` suffix.
229
251
  * @param args Arguments to pass, including output and inout arguments.
230
- * @param returnDescriptor Descriptor for the signal's return value, omitted when it returns void.
252
+ * @param returns Descriptor for the signal's return value, omitted when it returns void.
231
253
  */
232
- function emitSignal(instance: object, signal: string, args: EmitArg[], returnDescriptor?: Descriptor): unknown {
254
+ function emitSignal(instance: object, signal: string, args: EmitArg[], returns?: Descriptor): unknown {
233
255
  const signalId = getSignalId(instance, signal);
234
256
  const detail = getSignalDetailQuark(signal);
235
- const values: ExternalObject<Handle>[] = [toValue(objectT("full"), instance)];
236
- const reads: (() => unknown)[] = [];
237
-
238
- for (const arg of args) {
239
- const { value, read } = createEmitValue(arg);
240
- values.push(value);
257
+ const { values, reads } = collectEmitValues(instance, args);
241
258
 
242
- if (read) {
243
- reads.push(read);
244
- }
245
- }
259
+ if (returns === undefined) {
260
+ gSignalEmitv(values, signalId, detail, undefined);
246
261
 
247
- if (returnDescriptor !== undefined) {
248
- const returnValue = newValueForDescriptor(returnDescriptor);
249
- gSignalEmitv(values, signalId, detail, returnValue);
250
-
251
- return packTupleResult(
252
- reads.map((read) => read()),
253
- fromValue(returnValue),
254
- true,
255
- );
262
+ return packTupleResult(readEmitOutputs(reads), undefined, false);
256
263
  }
257
264
 
258
- gSignalEmitv(values, signalId, detail, undefined);
265
+ const returnValue = newValueForDescriptor(returns);
266
+ gSignalEmitv(values, signalId, detail, returnValue);
259
267
 
260
- return packTupleResult(
261
- reads.map((read) => read()),
262
- undefined,
263
- false,
264
- );
268
+ return packTupleResult(readEmitOutputs(reads), fromValue(returnValue), true);
265
269
  }
266
270
 
267
271
  export {
package/src/t.ts CHANGED
@@ -77,6 +77,8 @@ type T = {
77
77
  sizedArray: typeof helpers.sizedArrayT;
78
78
  /** Builds a descriptor for a C array of a fixed length. */
79
79
  fixedArray: typeof helpers.fixedArrayT;
80
+ /** Builds a descriptor for an out pointer into the buffer another argument supplied. */
81
+ cursorArray: typeof helpers.cursorArrayT;
80
82
  /** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */
81
83
  callback: typeof helpers.callbackT;
82
84
  /**
@@ -127,6 +129,7 @@ const t: T = {
127
129
  byteArray: helpers.byteArrayT,
128
130
  sizedArray: helpers.sizedArrayT,
129
131
  fixedArray: helpers.fixedArrayT,
132
+ cursorArray: helpers.cursorArrayT,
130
133
  callback: helpers.callbackT,
131
134
  fn,
132
135
  };
package/src/tuple.ts CHANGED
@@ -1,3 +1,10 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+
3
+ type SplitResult = { primary: unknown; outValues: unknown[] };
4
+
5
+ const hasSurfacedPrimary = (returnDescriptor: Descriptor, isReturnSkipped: boolean | undefined): boolean =>
6
+ returnDescriptor.kind !== "void" && isReturnSkipped !== true;
7
+
1
8
  const packTupleResult = (outs: unknown[], primary: unknown, hasPrimary: boolean): unknown => {
2
9
  if (hasPrimary) {
3
10
  return outs.length === 0 ? primary : [primary, ...outs];
@@ -14,19 +21,15 @@ const packTupleResult = (outs: unknown[], primary: unknown, hasPrimary: boolean)
14
21
  return outs;
15
22
  };
16
23
 
17
- const splitTupleResult = (
18
- result: unknown,
19
- hasPrimary: boolean,
20
- outCount: number,
21
- ): { primary: unknown; outValues: unknown[] } => {
22
- if (hasPrimary) {
23
- if (Array.isArray(result)) {
24
- return { primary: result[0], outValues: result.slice(1) };
25
- }
26
-
27
- return { primary: result, outValues: [] };
24
+ const splitPrimaryResult = (result: unknown, outCount: number): SplitResult => {
25
+ if (outCount > 0 && Array.isArray(result)) {
26
+ return { primary: result[0], outValues: result.slice(1) };
28
27
  }
29
28
 
29
+ return { primary: result, outValues: [] };
30
+ };
31
+
32
+ const splitOutResult = (result: unknown, outCount: number): SplitResult => {
30
33
  if (outCount === 1) {
31
34
  return { primary: undefined, outValues: [result] };
32
35
  }
@@ -34,4 +37,7 @@ const splitTupleResult = (
34
37
  return { primary: undefined, outValues: Array.isArray(result) ? result : [] };
35
38
  };
36
39
 
37
- export { packTupleResult, splitTupleResult };
40
+ const splitTupleResult = (result: unknown, hasPrimary: boolean, outCount: number): SplitResult =>
41
+ hasPrimary ? splitPrimaryResult(result, outCount) : splitOutResult(result, outCount);
42
+
43
+ export { hasSurfacedPrimary, packTupleResult, splitTupleResult };