@gtkx/runtime 1.0.0-rc.4 → 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 (156) hide show
  1. package/README.md +5 -5
  2. package/dist/application-class.d.ts +37 -0
  3. package/dist/application-class.d.ts.map +1 -0
  4. package/dist/application-class.js +78 -0
  5. package/dist/application-class.js.map +1 -0
  6. package/dist/arg.d.ts +2 -1
  7. package/dist/arg.d.ts.map +1 -1
  8. package/dist/arg.js +2 -1
  9. package/dist/arg.js.map +1 -1
  10. package/dist/bind.d.ts +11 -0
  11. package/dist/bind.d.ts.map +1 -1
  12. package/dist/bind.js +11 -0
  13. package/dist/bind.js.map +1 -1
  14. package/dist/callback.d.ts +4 -3
  15. package/dist/callback.d.ts.map +1 -1
  16. package/dist/callback.js +124 -29
  17. package/dist/callback.js.map +1 -1
  18. package/dist/closure.d.ts +26 -0
  19. package/dist/closure.d.ts.map +1 -0
  20. package/dist/closure.js +99 -0
  21. package/dist/closure.js.map +1 -0
  22. package/dist/default-application.d.ts +4 -0
  23. package/dist/default-application.d.ts.map +1 -0
  24. package/dist/default-application.js +17 -0
  25. package/dist/default-application.js.map +1 -0
  26. package/dist/descriptors.d.ts +98 -4
  27. package/dist/descriptors.d.ts.map +1 -1
  28. package/dist/descriptors.js +72 -6
  29. package/dist/descriptors.js.map +1 -1
  30. package/dist/error.d.ts +1 -0
  31. package/dist/error.d.ts.map +1 -1
  32. package/dist/error.js.map +1 -1
  33. package/dist/exit-hook.js +13 -0
  34. package/dist/exit-hook.js.map +1 -1
  35. package/dist/fn.d.ts +24 -2
  36. package/dist/fn.d.ts.map +1 -1
  37. package/dist/fn.js +67 -32
  38. package/dist/fn.js.map +1 -1
  39. package/dist/folded-lengths.d.ts +16 -0
  40. package/dist/folded-lengths.d.ts.map +1 -0
  41. package/dist/folded-lengths.js +38 -0
  42. package/dist/folded-lengths.js.map +1 -0
  43. package/dist/index.d.ts +14 -6
  44. package/dist/index.d.ts.map +1 -1
  45. package/dist/index.js +12 -4
  46. package/dist/index.js.map +1 -1
  47. package/dist/internal.d.ts +2 -0
  48. package/dist/internal.d.ts.map +1 -1
  49. package/dist/internal.js +2 -0
  50. package/dist/internal.js.map +1 -1
  51. package/dist/library.d.ts +3 -1
  52. package/dist/library.d.ts.map +1 -1
  53. package/dist/library.js +3 -1
  54. package/dist/library.js.map +1 -1
  55. package/dist/lifecycle.d.ts +58 -11
  56. package/dist/lifecycle.d.ts.map +1 -1
  57. package/dist/lifecycle.js +95 -22
  58. package/dist/lifecycle.js.map +1 -1
  59. package/dist/listeners.d.ts.map +1 -1
  60. package/dist/listeners.js +19 -5
  61. package/dist/listeners.js.map +1 -1
  62. package/dist/mixin.d.ts +3 -2
  63. package/dist/mixin.d.ts.map +1 -1
  64. package/dist/mixin.js +13 -1
  65. package/dist/mixin.js.map +1 -1
  66. package/dist/native-value.d.ts.map +1 -1
  67. package/dist/native-value.js +4 -3
  68. package/dist/native-value.js.map +1 -1
  69. package/dist/object.d.ts +40 -7
  70. package/dist/object.d.ts.map +1 -1
  71. package/dist/object.js +95 -26
  72. package/dist/object.js.map +1 -1
  73. package/dist/param-spec.d.ts +11 -0
  74. package/dist/param-spec.d.ts.map +1 -0
  75. package/dist/param-spec.js +147 -0
  76. package/dist/param-spec.js.map +1 -0
  77. package/dist/promisify.d.ts.map +1 -1
  78. package/dist/promisify.js +3 -2
  79. package/dist/promisify.js.map +1 -1
  80. package/dist/properties.d.ts +39 -7
  81. package/dist/properties.d.ts.map +1 -1
  82. package/dist/properties.js +310 -43
  83. package/dist/properties.js.map +1 -1
  84. package/dist/register-class.d.ts +147 -12
  85. package/dist/register-class.d.ts.map +1 -1
  86. package/dist/register-class.js +129 -83
  87. package/dist/register-class.js.map +1 -1
  88. package/dist/registry.d.ts +80 -21
  89. package/dist/registry.d.ts.map +1 -1
  90. package/dist/registry.js +86 -33
  91. package/dist/registry.js.map +1 -1
  92. package/dist/signal.d.ts +11 -4
  93. package/dist/signal.d.ts.map +1 -1
  94. package/dist/signal.js +85 -25
  95. package/dist/signal.js.map +1 -1
  96. package/dist/t.d.ts +2 -0
  97. package/dist/t.d.ts.map +1 -1
  98. package/dist/t.js +1 -0
  99. package/dist/t.js.map +1 -1
  100. package/dist/tuple.d.ts +6 -3
  101. package/dist/tuple.d.ts.map +1 -1
  102. package/dist/tuple.js +9 -7
  103. package/dist/tuple.js.map +1 -1
  104. package/dist/type.d.ts +2 -1
  105. package/dist/type.d.ts.map +1 -1
  106. package/dist/type.js +2 -0
  107. package/dist/type.js.map +1 -1
  108. package/dist/value.d.ts +10 -1
  109. package/dist/value.d.ts.map +1 -1
  110. package/dist/value.js +99 -24
  111. package/dist/value.js.map +1 -1
  112. package/dist/vfunc-call.d.ts +37 -0
  113. package/dist/vfunc-call.d.ts.map +1 -0
  114. package/dist/vfunc-call.js +172 -0
  115. package/dist/vfunc-call.js.map +1 -0
  116. package/dist/vfunc-seeds.d.ts +12 -0
  117. package/dist/vfunc-seeds.d.ts.map +1 -0
  118. package/dist/vfunc-seeds.js +19 -0
  119. package/dist/vfunc-seeds.js.map +1 -0
  120. package/dist/vfunc.d.ts +8 -0
  121. package/dist/vfunc.d.ts.map +1 -0
  122. package/dist/vfunc.js +29 -0
  123. package/dist/vfunc.js.map +1 -0
  124. package/package.json +14 -4
  125. package/src/application-class.ts +126 -0
  126. package/src/arg.ts +2 -1
  127. package/src/bind.ts +11 -0
  128. package/src/callback.ts +178 -34
  129. package/src/closure.ts +136 -0
  130. package/src/default-application.ts +21 -0
  131. package/src/descriptors.ts +150 -34
  132. package/src/error.ts +1 -0
  133. package/src/exit-hook.ts +18 -0
  134. package/src/fn.ts +107 -36
  135. package/src/folded-lengths.ts +64 -0
  136. package/src/index.ts +21 -6
  137. package/src/internal.ts +2 -0
  138. package/src/library.ts +3 -1
  139. package/src/lifecycle.ts +138 -25
  140. package/src/listeners.ts +25 -6
  141. package/src/mixin.ts +15 -2
  142. package/src/native-value.ts +5 -3
  143. package/src/object.ts +134 -35
  144. package/src/param-spec.ts +234 -0
  145. package/src/promisify.ts +4 -2
  146. package/src/properties.ts +473 -46
  147. package/src/register-class.ts +386 -114
  148. package/src/registry.ts +169 -49
  149. package/src/signal.ts +116 -42
  150. package/src/t.ts +3 -0
  151. package/src/tuple.ts +18 -12
  152. package/src/type.ts +3 -1
  153. package/src/value.ts +143 -23
  154. package/src/vfunc-call.ts +247 -0
  155. package/src/vfunc-seeds.ts +36 -0
  156. package/src/vfunc.ts +38 -0
package/src/registry.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { AnyClass } from "@gtkx/utils";
2
1
  import {
3
2
  type ExternalObject,
4
3
  getType,
@@ -7,60 +6,124 @@ import {
7
6
  type RegisterClassVfunc as NativeRegisterClassVfunc,
8
7
  setWrapper,
9
8
  } from "@gtkx/native";
9
+ import { type AnyClass, walkClassChain } from "@gtkx/utils";
10
10
  import type { Mixin, MixinReceiver } from "./mixin.js";
11
11
  import { TYPE_INVALID, type TypedClass, typeInterfaces, typeIsA, typeName, typeParent } from "./type.js";
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
- type VfuncDescriptor<K extends "class" | "interface"> = {
22
- /** Whether the slot lives in a class struct or in an interface vtable. */
23
- kind: K;
23
+ type VfuncDescriptor = {
24
24
  /** GIR name of the type struct holding the slot, without its namespace, such as `WidgetClass`. */
25
25
  className: string;
26
26
  /** Name of the slot's field in that struct. */
27
27
  vfuncName: string;
28
28
  /** Byte offset of the slot within the struct. */
29
29
  byteOffset: number;
30
- /** Byte size of the struct, used to bounds-check `VfuncDescriptor.byteOffset`. */
31
- vtableSize: number;
30
+ /**
31
+ * Byte size of an interface's vtable struct, used to bounds-check `VfuncDescriptor.byteOffset`.
32
+ * A class struct is bounds-checked against the size `g_type_query` reports for it, so a slot in
33
+ * one carries no size of its own.
34
+ */
35
+ vtableSize?: number;
32
36
  /** Descriptor for each argument the slot receives, starting with the instance. */
33
37
  argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
34
38
  /** Descriptor for the value the slot returns. */
35
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;
46
+ /**
47
+ * The slot takes a trailing `GError**` that `VfuncDescriptor.argDescriptors` leaves out, the
48
+ * way GIR leaves it out of a callable's parameters. A call through the slot has to append it
49
+ * or it passes one argument fewer than the implementation reads.
50
+ */
51
+ canThrow?: boolean;
36
52
  };
37
53
 
38
54
  /**
39
55
  * The vtable slots a wrapper class or interface exposes, keyed by the JavaScript method name that
40
56
  * overrides each one.
41
57
  */
42
- type VfuncRegistry = Record<string, VfuncDescriptor<"class"> | VfuncDescriptor<"interface">>;
58
+ type VfuncRegistry = Record<string, VfuncDescriptor>;
59
+ type WrapperBinding = (handle: ExternalObject<Handle>, instance: object) => void;
60
+
61
+ /**
62
+ * How one property an interface declares reaches the vtable, given as the interface's own accessor
63
+ * members. A direction introspection does not route through a vtable slot is left out, and the
64
+ * property owns that direction's state itself.
65
+ */
66
+ type InterfaceProperty = {
67
+ /** Member reading the slot the property's value comes from, such as `getEnabled`. */
68
+ getter?: string;
69
+ /** Member writing the slot the property's value goes to, such as `setActionName`. */
70
+ setter?: string;
71
+ };
72
+
73
+ /**
74
+ * What an interface's vtable struct looks like, for the classes that adopt the interface.
75
+ * `g_type_query` reports no size for an interface, so each slot's generated metadata carries the
76
+ * struct's byte size to bounds-check the slot's offset; an interface introspection describes no
77
+ * vtable for simply contributes no slots.
78
+ */
79
+ type InterfaceLayout = {
80
+ /** The slots the struct declares, keyed by the JavaScript method name that fills each one. */
81
+ vfuncs?: VfuncRegistry;
82
+ /**
83
+ * The properties a vtable slot backs, keyed by canonical property name, so a class adopting the
84
+ * interface answers `g_object_get` and `g_object_set` with what the slot holds.
85
+ */
86
+ properties?: Record<string, InterfaceProperty>;
87
+ };
43
88
 
44
89
  const classRegistry: Map<bigint, AnyClass> = new Map();
45
90
  const interfaceMixinRegistry: Map<bigint, Mixin> = new Map();
46
91
  const composedClassRegistry: Map<bigint, AnyClass> = new Map();
47
92
  const handleMap: WeakMap<object, ExternalObject<Handle>> = new WeakMap();
48
93
  const vfuncRegistry: WeakMap<object, VfuncRegistry> = new WeakMap();
49
- const interfaceVfuncRegistry: Map<bigint, VfuncRegistry> = new Map();
94
+ const interfaceLayoutRegistry: Map<bigint, InterfaceLayout> = new Map();
95
+ const wrapperClasses: WeakSet<AnyClass> = new WeakSet();
96
+ const derivedClasses: WeakSet<AnyClass> = new WeakSet();
50
97
 
51
98
  function setClassType(cls: AnyClass, type: bigint): void {
52
99
  (cls.prototype as { [K in keyof TypedClass]: TypedClass[K] }).__type__ = type;
53
100
  }
54
101
 
55
- function getClassType(cls: AnyClass): bigint {
56
- const proto: object = cls.prototype;
102
+ /**
103
+ * Returns the GType a class was registered under, or the invalid type when it carries none. The tag is
104
+ * read off the class's own prototype, so a subclass that never went through `registerClass` reports the
105
+ * invalid type rather than inheriting the one its parent was registered with.
106
+ */
107
+ function getClassType(cls: AnyClass | undefined): bigint {
108
+ const proto: object | undefined = cls?.prototype;
109
+
110
+ if (proto === undefined || !Object.hasOwn(proto, "__type__")) {
111
+ return TYPE_INVALID;
112
+ }
57
113
 
58
- return Object.hasOwn(proto, "__type__") ? (proto as TypedClass).__type__ : TYPE_INVALID;
114
+ return (proto as TypedClass).__type__;
59
115
  }
60
116
 
61
- /** Returns the GType tag of the given wrapper instance. */
117
+ /**
118
+ * Returns the GType the given instance's handle carries, or the invalid type when it has no handle.
119
+ * This is the object's own type rather than the type of the class it was wrapped as, and the two differ
120
+ * for an object GTK created itself, such as the row widget inside a `Gtk.ListView`, which GTKX wraps as
121
+ * the nearest registered ancestor.
122
+ */
62
123
  function getInstanceType(instance: object): bigint {
63
- return getClassType(instance.constructor as AnyClass);
124
+ const handle = handleMap.get(instance);
125
+
126
+ return handle === undefined ? TYPE_INVALID : getType(handle);
64
127
  }
65
128
 
66
129
  function registerClassType(cls: AnyClass, type: bigint): void {
@@ -83,21 +146,47 @@ function registerClassType(cls: AnyClass, type: bigint): void {
83
146
  function registerWrapperClass(cls: AnyClass, type: bigint, vfuncs?: VfuncRegistry): void {
84
147
  registerClassType(cls, type);
85
148
 
149
+ if (type !== TYPE_INVALID) {
150
+ wrapperClasses.add(cls);
151
+ }
152
+
86
153
  if (vfuncs) {
87
154
  registerVfuncRegistry(cls, vfuncs);
88
155
  }
89
156
  }
90
157
 
158
+ function markDerivedClass(cls: AnyClass): void {
159
+ derivedClasses.add(cls);
160
+ }
161
+
162
+ function resolveAncestorType(ancestor: AnyClass): bigint | undefined {
163
+ if (derivedClasses.has(ancestor) || !wrapperClasses.has(ancestor)) {
164
+ return undefined;
165
+ }
166
+
167
+ return getClassType(ancestor);
168
+ }
169
+
170
+ function resolveWrapperType(instance: object): bigint {
171
+ const cls = instance.constructor as AnyClass | undefined;
172
+
173
+ if (cls === undefined) {
174
+ return TYPE_INVALID;
175
+ }
176
+
177
+ return walkClassChain(cls, (ancestor) => resolveAncestorType(ancestor)) ?? TYPE_INVALID;
178
+ }
179
+
91
180
  /**
92
181
  * Registers a GInterface, associating its GType with a mixin used to compose the
93
- * interface onto wrapper classes and an optional virtual function registry.
182
+ * interface onto wrapper classes and, when introspection describes its vtable, that layout.
94
183
  * @param cls Class carrying the interface's GType tag.
95
184
  * @param type GType of the interface.
96
185
  * @param mixin Mixin that applies the interface to a wrapper class.
97
- * @param vfuncs Vtable slots the interface exposes, so `registerClass` can bind the ones an
98
- * implementing class overrides.
186
+ * @param layout The interface's vtable struct, so `registerClass` can bind the slots an
187
+ * implementing class overrides and take over the ones it leaves alone.
99
188
  */
100
- function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, vfuncs?: VfuncRegistry): void {
189
+ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, layout?: InterfaceLayout): void {
101
190
  if (type === TYPE_INVALID) {
102
191
  return;
103
192
  }
@@ -105,16 +194,17 @@ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, vfuncs?: V
105
194
  setClassType(cls, type);
106
195
  interfaceMixinRegistry.set(type, mixin);
107
196
 
108
- if (vfuncs) {
109
- registerInterfaceVfuncRegistry(type, vfuncs);
197
+ if (layout) {
198
+ interfaceLayoutRegistry.set(type, layout);
110
199
  }
111
200
  }
112
201
 
113
202
  /**
114
203
  * Wraps a native handle in a JS wrapper instance. With no class, resolves and
115
- * reuses the wrapper for the handle's runtime GType (composing interface mixins);
116
- * with an explicit class, creates a bare instance backed by the handle. Returns
117
- * null for a null or undefined handle.
204
+ * reuses the wrapper for the handle's runtime GType (composing interface mixins),
205
+ * and hands back an instance that already carries a handle unchanged; with an
206
+ * explicit class, creates a bare instance backed by the handle. Returns null for
207
+ * a null or undefined handle.
118
208
  * @param handle Native handle to wrap.
119
209
  * @param cls Wrapper class to instantiate, or omitted to resolve it from the runtime type.
120
210
  */
@@ -174,12 +264,16 @@ function resolveWrapperClass(type: bigint): AnyClass | null {
174
264
  return null;
175
265
  }
176
266
 
267
+ function getInterfaceMixin(type: bigint): Mixin | undefined {
268
+ return interfaceMixinRegistry.get(type);
269
+ }
270
+
177
271
  function applyInterfaceMixin(cls: AnyClass, type: bigint, baseType: bigint, applied: Set<bigint>): AnyClass {
178
272
  if (applied.has(type) || typeIsA(baseType, type)) {
179
273
  return cls;
180
274
  }
181
275
 
182
- const mixin = interfaceMixinRegistry.get(type);
276
+ const mixin = getInterfaceMixin(type);
183
277
 
184
278
  if (mixin === undefined) {
185
279
  return cls;
@@ -228,18 +322,25 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
228
322
  }
229
323
 
230
324
  setClassType(composed, runtimeType);
325
+ wrapperClasses.add(composed);
231
326
  composedClassRegistry.set(runtimeType, composed);
232
327
 
233
328
  return composed;
234
329
  }
235
330
 
236
- function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
237
- const existing = getWrapper(handle);
331
+ function wrapObject(value: unknown): object | null {
332
+ return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>);
333
+ }
238
334
 
239
- if (existing) {
240
- return existing;
241
- }
335
+ function wrapCallScopedObject(value: unknown): object | null {
336
+ return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper);
337
+ }
338
+
339
+ function existingWrapperFor(handle: ExternalObject<Handle>): object | null {
340
+ return handleMap.has(handle) ? handle : getWrapper(handle);
341
+ }
242
342
 
343
+ function createWrapper(handle: ExternalObject<Handle>): object {
243
344
  const runtimeType: bigint = getType(handle);
244
345
 
245
346
  if (runtimeType === TYPE_INVALID) {
@@ -252,34 +353,50 @@ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
252
353
  throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
253
354
  }
254
355
 
255
- const instance: object = Object.create(cls.prototype) as object;
256
- 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);
257
368
 
258
369
  return instance;
259
370
  }
260
371
 
372
+ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
373
+ return wrapperFor(handle, registerWrapper);
374
+ }
375
+
376
+ function instanceClassName(instance: object): string {
377
+ return (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
378
+ }
379
+
261
380
  /** Returns the native handle bound to a wrapper instance, throwing if none is set. */
262
381
  function getHandle(instance: object): ExternalObject<Handle> {
263
382
  const handle = handleMap.get(instance);
264
383
 
265
384
  if (handle === undefined) {
266
- const name = (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
267
- throw new Error(`No native handle associated with ${name}`);
385
+ throw new Error(`No native handle associated with ${instanceClassName(instance)}`);
268
386
  }
269
387
 
270
388
  return handle;
271
389
  }
272
390
 
273
- /** Returns the native handle bound to an instance, or undefined when there is none or the instance is null. */
274
- function tryGetHandle(instance: object | null | undefined): ExternalObject<Handle> | undefined {
275
- return instance == null ? undefined : handleMap.get(instance);
276
- }
277
-
278
391
  /** Associates a native handle with a wrapper instance. */
279
392
  function setHandle(instance: object, handle: ExternalObject<Handle>): void {
280
393
  handleMap.set(instance, handle);
281
394
  }
282
395
 
396
+ function bindCallScopedWrapper(handle: ExternalObject<Handle>, instance: object): void {
397
+ setHandle(instance, handle);
398
+ }
399
+
283
400
  function registerWrapper(handle: ExternalObject<Handle>, instance: object): void {
284
401
  setHandle(instance, handle);
285
402
  setWrapper(handle, instance);
@@ -293,21 +410,18 @@ function getVfuncRegistry(cls: object): VfuncRegistry | undefined {
293
410
  return vfuncRegistry.get(cls);
294
411
  }
295
412
 
296
- function registerInterfaceVfuncRegistry(type: bigint, registry: VfuncRegistry): void {
297
- if (type === TYPE_INVALID) {
298
- return;
299
- }
300
-
301
- interfaceVfuncRegistry.set(type, registry);
413
+ function getInterfaceVfuncRegistry(type: bigint): VfuncRegistry | undefined {
414
+ return interfaceLayoutRegistry.get(type)?.vfuncs;
302
415
  }
303
416
 
304
- function getInterfaceVfuncRegistry(type: bigint): VfuncRegistry | undefined {
305
- return interfaceVfuncRegistry.get(type);
417
+ function getInterfaceProperties(type: bigint): Record<string, InterfaceProperty> | undefined {
418
+ return interfaceLayoutRegistry.get(type)?.properties;
306
419
  }
307
420
 
308
421
  export {
309
422
  getClassType,
310
423
  getInstanceType,
424
+ markDerivedClass,
311
425
  registerClassType,
312
426
  registerWrapperClass,
313
427
  registerInterface,
@@ -315,11 +429,17 @@ export {
315
429
  getWrapperClass,
316
430
  resolveWrapperClass,
317
431
  getHandle,
318
- tryGetHandle,
319
432
  setHandle,
320
433
  getVfuncRegistry,
434
+ getInterfaceMixin,
435
+ getInterfaceProperties,
321
436
  getInterfaceVfuncRegistry,
437
+ instanceClassName,
438
+ registerWrapper,
439
+ resolveWrapperType,
440
+ wrapCallScopedObject,
441
+ wrapObject,
442
+ type InterfaceProperty,
322
443
  type StaticBase,
323
444
  type VfuncDescriptor,
324
- type VfuncRegistry,
325
445
  };
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
  /**
@@ -53,6 +58,7 @@ type EmitArg = Arg & {
53
58
  };
54
59
 
55
60
  const connectCache = createBindCache();
61
+ const connectionTable: WeakMap<object, Map<string, Set<number>>> = new WeakMap();
56
62
  const gQuarkFromString = bind(LIB, "g_quark_from_string", [stringT("borrowed")], uint32T);
57
63
  const gSignalLookup = bind(LIB, "g_signal_lookup", [stringT("borrowed"), biguint64T], uint32T);
58
64
 
@@ -63,13 +69,6 @@ const gSignalEmitv = bind(
63
69
  voidT,
64
70
  );
65
71
 
66
- const gSignalHandlersBlockMatched = bind(
67
- LIB,
68
- "g_signal_handlers_block_matched",
69
- [objectT("borrowed"), uint32T, uint32T, uint32T, objectT("borrowed"), objectT("borrowed"), objectT("borrowed")],
70
- uint32T,
71
- );
72
-
73
72
  const gSignalHandlerIsConnected = bind(
74
73
  LIB,
75
74
  "g_signal_handler_is_connected",
@@ -77,6 +76,8 @@ const gSignalHandlerIsConnected = bind(
77
76
  booleanT,
78
77
  );
79
78
 
79
+ const gSignalHandlerDisconnect = bind(LIB, "g_signal_handler_disconnect", [objectT("borrowed"), uint64T], voidT);
80
+
80
81
  /** Returns the signal name without its detail suffix (the part after `::`). */
81
82
  const getSignalBaseName = (signal: string): string => {
82
83
  const detailIndex = signal.indexOf("::");
@@ -97,6 +98,81 @@ function getSignalDetailQuark(signal: string): number {
97
98
  const isSignalHandlerConnected = (instance: object, handlerId: number): boolean =>
98
99
  gSignalHandlerIsConnected(getHandle(instance), handlerId) as boolean;
99
100
 
101
+ const trackConnection = (instance: object, signal: string, handlerId: number): void => {
102
+ let bySignal = connectionTable.get(instance);
103
+
104
+ if (!bySignal) {
105
+ bySignal = new Map();
106
+ connectionTable.set(instance, bySignal);
107
+ }
108
+
109
+ let handlerIds = bySignal.get(signal);
110
+
111
+ if (!handlerIds) {
112
+ handlerIds = new Set();
113
+ bySignal.set(signal, handlerIds);
114
+ }
115
+
116
+ handlerIds.add(handlerId);
117
+ };
118
+
119
+ const untrackConnection = (instance: object, handlerId: number): void => {
120
+ const bySignal = connectionTable.get(instance);
121
+
122
+ if (bySignal === undefined) {
123
+ return;
124
+ }
125
+
126
+ for (const [name, handlerIds] of bySignal) {
127
+ handlerIds.delete(handlerId);
128
+
129
+ if (handlerIds.size === 0) {
130
+ bySignal.delete(name);
131
+ }
132
+ }
133
+ };
134
+
135
+ /**
136
+ * Disconnects the handler an instance connected under the given id, and forgets the connection.
137
+ * @param instance Emitter the handler was connected to.
138
+ * @param handlerId Id {@link connectSignal} returned for the handler.
139
+ */
140
+ const disconnectSignal = (instance: object, handlerId: number): void => {
141
+ untrackConnection(instance, handlerId);
142
+ gSignalHandlerDisconnect(getHandle(instance), handlerId);
143
+ };
144
+
145
+ const hasLiveConnection = (instance: object, handlerIds: Set<number>): boolean => {
146
+ let isLive = false;
147
+
148
+ for (const handlerId of handlerIds) {
149
+ if (isSignalHandlerConnected(instance, handlerId)) {
150
+ isLive = true;
151
+ } else {
152
+ handlerIds.delete(handlerId);
153
+ }
154
+ }
155
+
156
+ return isLive;
157
+ };
158
+
159
+ function hasSignalListener(instance: object, signals?: string[]): boolean {
160
+ const bySignal = connectionTable.get(instance);
161
+
162
+ if (!bySignal) {
163
+ return false;
164
+ }
165
+
166
+ const names =
167
+ signals === undefined ? bySignal.keys().toArray() : signals.map((signal) => getSignalBaseName(signal));
168
+
169
+ return names.some((name) => {
170
+ const handlerIds = bySignal.get(name);
171
+
172
+ return handlerIds !== undefined && hasLiveConnection(instance, handlerIds);
173
+ });
174
+ }
175
+
100
176
  const getSignalId = (instance: object, signal: string): number => {
101
177
  const type: bigint = (instance as TypedClass).__type__;
102
178
 
@@ -123,16 +199,13 @@ function connectBind(type: bigint, signal: string, callback: CallbackDescriptor)
123
199
  */
124
200
  function connectSignal(instance: object, signal: string, spec: SignalConnectSpec): number {
125
201
  const { callback, handler, isAfter } = spec;
126
- const wrapped = wrapCallback(handler, callback, "emitter");
202
+ const wrapped = wrapCallback(handler, callback, "signal");
127
203
  const type: bigint = (instance as TypedClass).__type__;
128
204
  const connect = connectBind(type, signal, callback);
205
+ const handlerId = connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
206
+ trackConnection(instance, getSignalBaseName(signal), handlerId);
129
207
 
130
- return connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
131
- }
132
-
133
- function blockMatchedSignalHandlers(instance: object, signal: string): void {
134
- const signalId = getSignalId(instance, signal);
135
- gSignalHandlersBlockMatched(getHandle(instance), 1, signalId, 0, undefined, undefined, undefined);
208
+ return handlerId;
136
209
  }
137
210
 
138
211
  const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?: () => unknown } => {
@@ -153,54 +226,55 @@ const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?:
153
226
  return isInoutArg(arg) ? outValueForDescriptor(arg.type, arg.value) : outValueForDescriptor(arg.type);
154
227
  };
155
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
+
156
246
  /**
157
247
  * Emits a signal on an instance with the given arguments and returns its result
158
248
  * combined with any output-argument values.
159
249
  * @param instance Emitter to emit the signal on.
160
250
  * @param signal Signal name, optionally including a `::detail` suffix.
161
251
  * @param args Arguments to pass, including output and inout arguments.
162
- * @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.
163
253
  */
164
- function emitSignal(instance: object, signal: string, args: EmitArg[], returnDescriptor?: Descriptor): unknown {
254
+ function emitSignal(instance: object, signal: string, args: EmitArg[], returns?: Descriptor): unknown {
165
255
  const signalId = getSignalId(instance, signal);
166
256
  const detail = getSignalDetailQuark(signal);
167
- const values: ExternalObject<Handle>[] = [toValue(objectT("full"), instance)];
168
- const reads: (() => unknown)[] = [];
257
+ const { values, reads } = collectEmitValues(instance, args);
169
258
 
170
- for (const arg of args) {
171
- const { value, read } = createEmitValue(arg);
172
- values.push(value);
259
+ if (returns === undefined) {
260
+ gSignalEmitv(values, signalId, detail, undefined);
173
261
 
174
- if (read) {
175
- reads.push(read);
176
- }
262
+ return packTupleResult(readEmitOutputs(reads), undefined, false);
177
263
  }
178
264
 
179
- if (returnDescriptor !== undefined) {
180
- const returnValue = newValueForDescriptor(returnDescriptor);
181
- gSignalEmitv(values, signalId, detail, returnValue);
265
+ const returnValue = newValueForDescriptor(returns);
266
+ gSignalEmitv(values, signalId, detail, returnValue);
182
267
 
183
- return packTupleResult(
184
- reads.map((read) => read()),
185
- fromValue(returnValue),
186
- true,
187
- );
188
- }
189
-
190
- gSignalEmitv(values, signalId, detail, undefined);
191
-
192
- return packTupleResult(
193
- reads.map((read) => read()),
194
- undefined,
195
- false,
196
- );
268
+ return packTupleResult(readEmitOutputs(reads), fromValue(returnValue), true);
197
269
  }
198
270
 
199
271
  export {
200
272
  getSignalBaseName,
201
273
  connectSignal,
202
- blockMatchedSignalHandlers,
274
+ disconnectSignal,
203
275
  emitSignal,
276
+ hasSignalListener,
204
277
  isSignalHandlerConnected,
278
+ untrackConnection,
205
279
  type SignalHandler,
206
280
  };
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 };