@gtkx/runtime 1.0.0 → 1.2.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 (112) 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/closure.js +1 -1
  11. package/dist/closure.js.map +1 -1
  12. package/dist/default-application.d.ts +4 -0
  13. package/dist/default-application.d.ts.map +1 -0
  14. package/dist/default-application.js +17 -0
  15. package/dist/default-application.js.map +1 -0
  16. package/dist/descriptors.d.ts +34 -9
  17. package/dist/descriptors.d.ts.map +1 -1
  18. package/dist/descriptors.js +42 -17
  19. package/dist/descriptors.js.map +1 -1
  20. package/dist/exit-hook.js +13 -0
  21. package/dist/exit-hook.js.map +1 -1
  22. package/dist/fn.d.ts +5 -0
  23. package/dist/fn.d.ts.map +1 -1
  24. package/dist/fn.js +11 -13
  25. package/dist/fn.js.map +1 -1
  26. package/dist/folded-lengths.d.ts +16 -0
  27. package/dist/folded-lengths.d.ts.map +1 -0
  28. package/dist/folded-lengths.js +38 -0
  29. package/dist/folded-lengths.js.map +1 -0
  30. package/dist/internal.d.ts +1 -0
  31. package/dist/internal.d.ts.map +1 -1
  32. package/dist/internal.js +1 -0
  33. package/dist/internal.js.map +1 -1
  34. package/dist/library.d.ts +2 -1
  35. package/dist/library.d.ts.map +1 -1
  36. package/dist/library.js +2 -1
  37. package/dist/library.js.map +1 -1
  38. package/dist/lifecycle.d.ts +16 -1
  39. package/dist/lifecycle.d.ts.map +1 -1
  40. package/dist/lifecycle.js +44 -14
  41. package/dist/lifecycle.js.map +1 -1
  42. package/dist/listeners.d.ts.map +1 -1
  43. package/dist/listeners.js +16 -4
  44. package/dist/listeners.js.map +1 -1
  45. package/dist/native-value.js +2 -2
  46. package/dist/native-value.js.map +1 -1
  47. package/dist/object.d.ts +7 -2
  48. package/dist/object.d.ts.map +1 -1
  49. package/dist/object.js +7 -2
  50. package/dist/object.js.map +1 -1
  51. package/dist/param-spec.d.ts.map +1 -1
  52. package/dist/param-spec.js +18 -20
  53. package/dist/param-spec.js.map +1 -1
  54. package/dist/properties.d.ts +2 -1
  55. package/dist/properties.d.ts.map +1 -1
  56. package/dist/properties.js +13 -9
  57. package/dist/properties.js.map +1 -1
  58. package/dist/register-class.d.ts +33 -3
  59. package/dist/register-class.d.ts.map +1 -1
  60. package/dist/register-class.js +21 -7
  61. package/dist/register-class.js.map +1 -1
  62. package/dist/registry.d.ts +14 -4
  63. package/dist/registry.d.ts.map +1 -1
  64. package/dist/registry.js +24 -15
  65. package/dist/registry.js.map +1 -1
  66. package/dist/signal.d.ts +2 -2
  67. package/dist/signal.d.ts.map +1 -1
  68. package/dist/signal.js +22 -18
  69. package/dist/signal.js.map +1 -1
  70. package/dist/t.d.ts +3 -1
  71. package/dist/t.d.ts.map +1 -1
  72. package/dist/t.js +1 -0
  73. package/dist/t.js.map +1 -1
  74. package/dist/tuple.d.ts +6 -3
  75. package/dist/tuple.d.ts.map +1 -1
  76. package/dist/tuple.js +9 -7
  77. package/dist/tuple.js.map +1 -1
  78. package/dist/value.d.ts +3 -1
  79. package/dist/value.d.ts.map +1 -1
  80. package/dist/value.js +64 -18
  81. package/dist/value.js.map +1 -1
  82. package/dist/vfunc-call.d.ts.map +1 -1
  83. package/dist/vfunc-call.js +5 -1
  84. package/dist/vfunc-call.js.map +1 -1
  85. package/dist/vfunc.d.ts.map +1 -1
  86. package/dist/vfunc.js +9 -3
  87. package/dist/vfunc.js.map +1 -1
  88. package/package.json +14 -4
  89. package/src/application-class.ts +12 -2
  90. package/src/callback.ts +70 -66
  91. package/src/closure.ts +1 -1
  92. package/src/default-application.ts +21 -0
  93. package/src/descriptors.ts +98 -29
  94. package/src/exit-hook.ts +18 -0
  95. package/src/fn.ts +23 -13
  96. package/src/folded-lengths.ts +64 -0
  97. package/src/internal.ts +1 -0
  98. package/src/library.ts +2 -1
  99. package/src/lifecycle.ts +60 -14
  100. package/src/listeners.ts +22 -5
  101. package/src/native-value.ts +2 -2
  102. package/src/object.ts +7 -2
  103. package/src/param-spec.ts +21 -22
  104. package/src/properties.ts +23 -9
  105. package/src/register-class.ts +66 -11
  106. package/src/registry.ts +43 -20
  107. package/src/signal.ts +32 -28
  108. package/src/t.ts +4 -1
  109. package/src/tuple.ts +18 -12
  110. package/src/value.ts +94 -19
  111. package/src/vfunc-call.ts +5 -1
  112. package/src/vfunc.ts +10 -3
package/src/registry.ts CHANGED
@@ -11,11 +11,14 @@ 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
- * Static side of class `C` with its construct signature preserved but the member
15
- * named `K` (default `"new"`) removed.
14
+ * Static side of class `C` with its construct signature preserved but the members named `K`
15
+ * removed. A generated class lists every static it declares itself, so a static it narrows never
16
+ * has to stay assignable to the one it shadows. The signature is kept abstract so an abstract `C`
17
+ * survives it: a subclass extends the result, and only the subclass's own declarations decide
18
+ * which statics reach it.
16
19
  */
17
- 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
+ type StaticBase<C, K extends PropertyKey> = Omit<C, K> &
21
+ (C extends abstract new (...args: infer A) => infer R ? abstract new (...args: A) => R : never);
19
22
 
20
23
  /** One overridable vtable slot: where it sits in the vtable struct and how it is marshalled. */
21
24
  type VfuncDescriptor = {
@@ -35,6 +38,12 @@ type VfuncDescriptor = {
35
38
  argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
36
39
  /** Descriptor for the value the slot returns. */
37
40
  returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"];
41
+ /**
42
+ * GIR marks the slot's return value as one the bindings do not surface, so a call through the
43
+ * slot drops it and an implementation of the slot reports success in its place, while
44
+ * `VfuncDescriptor.returnDescriptor` keeps the C type the slot is called against either way.
45
+ */
46
+ isReturnSkipped?: boolean;
38
47
  /**
39
48
  * The slot takes a trailing `GError**` that `VfuncDescriptor.argDescriptors` leaves out, the
40
49
  * way GIR leaves it out of a callable's parameters. A call through the slot has to append it
@@ -48,6 +57,7 @@ type VfuncDescriptor = {
48
57
  * overrides each one.
49
58
  */
50
59
  type VfuncRegistry = Record<string, VfuncDescriptor>;
60
+ type WrapperBinding = (handle: ExternalObject<Handle>, instance: object) => void;
51
61
 
52
62
  /**
53
63
  * How one property an interface declares reaches the vtable, given as the interface's own accessor
@@ -320,24 +330,18 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
320
330
  }
321
331
 
322
332
  function wrapObject(value: unknown): object | null {
323
- if (value == null) {
324
- return null;
325
- }
326
-
327
- return getOrCreateWrapper(value as ExternalObject<Handle>);
333
+ return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>);
328
334
  }
329
335
 
330
- function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
331
- if (handleMap.has(handle)) {
332
- return handle;
333
- }
334
-
335
- const existing = getWrapper(handle);
336
+ function wrapCallScopedObject(value: unknown): object | null {
337
+ return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper);
338
+ }
336
339
 
337
- if (existing) {
338
- return existing;
339
- }
340
+ function existingWrapperFor(handle: ExternalObject<Handle>): object | null {
341
+ return handleMap.has(handle) ? handle : getWrapper(handle);
342
+ }
340
343
 
344
+ function createWrapper(handle: ExternalObject<Handle>): object {
341
345
  const runtimeType: bigint = getType(handle);
342
346
 
343
347
  if (runtimeType === TYPE_INVALID) {
@@ -350,12 +354,26 @@ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
350
354
  throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
351
355
  }
352
356
 
353
- const instance: object = Object.create(cls.prototype) as object;
354
- registerWrapper(handle, instance);
357
+ return Object.create(cls.prototype) as object;
358
+ }
359
+
360
+ function wrapperFor(handle: ExternalObject<Handle>, bind: WrapperBinding): object {
361
+ const existing = existingWrapperFor(handle);
362
+
363
+ if (existing) {
364
+ return existing;
365
+ }
366
+
367
+ const instance = createWrapper(handle);
368
+ bind(handle, instance);
355
369
 
356
370
  return instance;
357
371
  }
358
372
 
373
+ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
374
+ return wrapperFor(handle, registerWrapper);
375
+ }
376
+
359
377
  function instanceClassName(instance: object): string {
360
378
  return (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
361
379
  }
@@ -376,6 +394,10 @@ function setHandle(instance: object, handle: ExternalObject<Handle>): void {
376
394
  handleMap.set(instance, handle);
377
395
  }
378
396
 
397
+ function bindCallScopedWrapper(handle: ExternalObject<Handle>, instance: object): void {
398
+ setHandle(instance, handle);
399
+ }
400
+
379
401
  function registerWrapper(handle: ExternalObject<Handle>, instance: object): void {
380
402
  setHandle(instance, handle);
381
403
  setWrapper(handle, instance);
@@ -416,6 +438,7 @@ export {
416
438
  instanceClassName,
417
439
  registerWrapper,
418
440
  resolveWrapperType,
441
+ wrapCallScopedObject,
419
442
  wrapObject,
420
443
  type InterfaceProperty,
421
444
  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
@@ -69,7 +69,7 @@ type T = {
69
69
  slist: typeof helpers.slistT;
70
70
  /** Builds a descriptor for a `GPtrArray` of items. */
71
71
  ptrArray: typeof helpers.ptrArrayT;
72
- /** Builds a descriptor for a `GArray` of items, optionally with an explicit element size. */
72
+ /** Builds a descriptor for a `GArray` of items. */
73
73
  gArray: typeof helpers.gArrayT;
74
74
  /** Builds a descriptor for a `GByteArray`. */
75
75
  byteArray: typeof helpers.byteArrayT;
@@ -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 };
package/src/value.ts CHANGED
@@ -9,6 +9,9 @@ import {
9
9
  boxedT,
10
10
  float32T,
11
11
  float64T,
12
+ type FundamentalDescriptor,
13
+ fundamentalLifecycleFor,
14
+ fundamentalT,
12
15
  int8T,
13
16
  int32T,
14
17
  objectT,
@@ -61,10 +64,13 @@ type ValueType = {
61
64
 
62
65
  type ValueGetter = (value: ExternalObject<Handle>) => unknown;
63
66
  type ValueWriter = ValueType["set"];
67
+ type ValueNarrower = (jsValue: unknown) => unknown;
64
68
 
65
69
  const setBoxedCache = createBindCache();
66
70
  const setStaticBoxedCache = createBindCache();
67
71
  const dupBoxedCache = createBindCache();
72
+ const setInstanceCache = createBindCache();
73
+ const peekPointerCache = createBindCache();
68
74
  const gValueInit = bind(LIB, "g_value_init", [VALUE_T, biguint64T], voidT);
69
75
  const gValueCopy = bind(LIB, "g_value_copy", [VALUE_T, VALUE_T], voidT);
70
76
  const booleanValueType = bindValueType("boolean", booleanT);
@@ -86,9 +92,10 @@ const objectValueType = bindValueType("object", objectT("borrowed"));
86
92
  const paramValueType = bindValueType("param", PARAM_T);
87
93
  const variantValueType = bindValueType("variant", VARIANT_T);
88
94
  const pointerValueType = bindValueType("pointer", uint64T);
95
+ const setStrvBoxed = bind(LIB, "g_value_set_boxed", [VALUE_T, arrayT(stringT("borrowed"))], voidT);
89
96
 
90
97
  const strvValueType: ValueType = {
91
- set: bind(LIB, "g_value_set_boxed", [VALUE_T, arrayT(stringT("borrowed"))], voidT),
98
+ set: setStrvValue,
92
99
  get: bind(LIB, "g_value_get_boxed", [VALUE_T], arrayT(stringT("borrowed"))),
93
100
  };
94
101
 
@@ -112,7 +119,6 @@ const PLAIN_VALUE_TYPES: Partial<Record<Descriptor["kind"], ValueType>> = {
112
119
 
113
120
  const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
114
121
  [TYPE_BOOLEAN, booleanValueType.get],
115
- [TYPE_GTYPE, typeValueType.get],
116
122
  [TYPE_CHAR, scharValueType.get],
117
123
  [TYPE_UCHAR, ucharValueType.get],
118
124
  [TYPE_INT, intValueType.get],
@@ -129,7 +135,6 @@ const PLAIN_VALUE_GETTERS: Map<bigint, ValueGetter> = new Map([
129
135
 
130
136
  const PLAIN_VALUE_SETTERS: Map<bigint, ValueType["set"]> = new Map([
131
137
  [TYPE_BOOLEAN, booleanValueType.set],
132
- [TYPE_GTYPE, typeValueType.set],
133
138
  [TYPE_CHAR, scharValueType.set],
134
139
  [TYPE_UCHAR, ucharValueType.set],
135
140
  [TYPE_INT, intValueType.set],
@@ -182,6 +187,46 @@ const boxedValueType = (type: bigint): ValueType => {
182
187
  const enumOrFlagsValueType = (type: bigint): ValueType =>
183
188
  typeFundamental(type) === TYPE_FLAGS ? flagsValueType : enumValueType;
184
189
 
190
+ function unsupportedFundamental(type: bigint): never {
191
+ throw new Error(`Unsupported fundamental type '${typeName(type) ?? String(type)}' for value`);
192
+ }
193
+
194
+ const setInstanceBind = (fundamental: bigint, descriptor: FundamentalDescriptor) =>
195
+ setInstanceCache(String(fundamental), LIB, "g_value_set_instance", [VALUE_T, descriptor], voidT);
196
+
197
+ const peekPointerBind = (fundamental: bigint, descriptor: FundamentalDescriptor) =>
198
+ peekPointerCache(String(fundamental), LIB, "g_value_peek_pointer", [VALUE_T], descriptor);
199
+
200
+ const customFundamentalDescriptor = (fundamental: bigint): FundamentalDescriptor | undefined => {
201
+ const name = typeName(fundamental);
202
+
203
+ if (name === null) {
204
+ return undefined;
205
+ }
206
+
207
+ const lifecycle = fundamentalLifecycleFor(name);
208
+
209
+ if (lifecycle === undefined) {
210
+ return undefined;
211
+ }
212
+
213
+ return fundamentalT(lifecycle.sharedLibrary, lifecycle.refFnName, lifecycle.unrefFnName, {
214
+ ownership: "borrowed",
215
+ typeName: name,
216
+ });
217
+ };
218
+
219
+ const customFundamentalValueType = (type: bigint): ValueType | undefined => {
220
+ const fundamental = typeFundamental(type);
221
+ const descriptor = customFundamentalDescriptor(fundamental);
222
+
223
+ if (descriptor === undefined) {
224
+ return undefined;
225
+ }
226
+
227
+ return { set: setInstanceBind(fundamental, descriptor), get: peekPointerBind(fundamental, descriptor) };
228
+ };
229
+
185
230
  const fundamentalValueType = (type: bigint): ValueType => {
186
231
  switch (typeFundamental(type)) {
187
232
  case TYPE_PARAM: {
@@ -194,7 +239,7 @@ const fundamentalValueType = (type: bigint): ValueType => {
194
239
  return boxedValueType(type);
195
240
  }
196
241
  default: {
197
- throw new Error(`Unsupported fundamental type '${typeName(type) ?? String(type)}' for value`);
242
+ return customFundamentalValueType(type) ?? unsupportedFundamental(type);
198
243
  }
199
244
  }
200
245
  };
@@ -332,6 +377,10 @@ function setStringValue(value: ExternalObject<Handle>, nativeValue: unknown): vo
332
377
  stringValueType.set(value, nativeValue ?? null);
333
378
  }
334
379
 
380
+ function setStrvValue(value: ExternalObject<Handle>, nativeValue: unknown): void {
381
+ setStrvBoxed(value, nativeValue ?? null);
382
+ }
383
+
335
384
  function setPointerValue(value: ExternalObject<Handle>, nativeValue: unknown): void {
336
385
  if (nativeValue != null) {
337
386
  throw new Error("G_TYPE_POINTER non-null values cannot be marshalled from JS");
@@ -350,22 +399,51 @@ function setBoxedFromValue(value: ExternalObject<Handle>, nativeValue: unknown):
350
399
  setBoxedValue(value, nativeValue ?? null);
351
400
  }
352
401
 
353
- const resolveValueGetter = (fundamental: bigint): ValueGetter | undefined =>
402
+ const customFundamentalGetter = (type: bigint): ValueGetter | undefined => {
403
+ const valueType = customFundamentalValueType(type);
404
+
405
+ if (valueType === undefined) {
406
+ return undefined;
407
+ }
408
+
409
+ return (value) => wrapHandle(valueType.get(value) as ExternalObject<Handle> | null, getWrapperClass(type));
410
+ };
411
+
412
+ const customFundamentalSetter = (type: bigint): ValueType["set"] | undefined => {
413
+ const valueType = customFundamentalValueType(type);
414
+
415
+ return valueType === undefined ? undefined : handleSetter(valueType);
416
+ };
417
+
418
+ const builtInValueGetter = (fundamental: bigint): ValueGetter | undefined =>
354
419
  PLAIN_VALUE_GETTERS.get(fundamental) ?? wrappedValueGetter(fundamental);
355
420
 
356
- const resolveValueSetter = (fundamental: bigint): ValueType["set"] | undefined =>
421
+ const builtInValueSetter = (fundamental: bigint): ValueType["set"] | undefined =>
357
422
  PLAIN_VALUE_SETTERS.get(fundamental) ?? WRAPPED_VALUE_SETTERS.get(fundamental);
358
423
 
359
- function setStrvValue(value: ExternalObject<Handle>, jsValue: unknown): void {
360
- strvValueType.set(value, jsValue ?? null);
424
+ const exactValueType = (type: bigint): ValueType | undefined => {
425
+ if (type === TYPE_GTYPE) {
426
+ return typeValueType;
427
+ }
428
+
429
+ return type === getStrvType() ? strvValueType : undefined;
430
+ };
431
+
432
+ const resolveValueGetter = (type: bigint): ValueGetter | undefined =>
433
+ exactValueType(type)?.get ?? builtInValueGetter(typeFundamental(type)) ?? customFundamentalGetter(type);
434
+
435
+ const resolveValueSetter = (type: bigint): ValueType["set"] | undefined =>
436
+ exactValueType(type)?.set ?? builtInValueSetter(typeFundamental(type)) ?? customFundamentalSetter(type);
437
+
438
+ const holdsUnchanged: ValueNarrower = (jsValue) => jsValue;
439
+ const holdsAsFloat: ValueNarrower = (jsValue) => Math.fround(jsValue as number);
440
+
441
+ function valueNarrowerFor(type: bigint): ValueNarrower {
442
+ return typeFundamental(type) === TYPE_FLOAT ? holdsAsFloat : holdsUnchanged;
361
443
  }
362
444
 
363
445
  function valueWriterFor(type: bigint): ValueWriter {
364
- if (type === getStrvType()) {
365
- return setStrvValue;
366
- }
367
-
368
- const set = resolveValueSetter(typeFundamental(type));
446
+ const set = resolveValueSetter(type);
369
447
 
370
448
  if (set === undefined) {
371
449
  throw new Error(`Unsupported type for intoValue: ${typeName(type) ?? String(type)}`);
@@ -407,12 +485,7 @@ function toValue(descriptor: Descriptor, value: unknown): ExternalObject<Handle>
407
485
 
408
486
  function fromValue(value: ExternalObject<Handle>): unknown {
409
487
  const type = getValueType(value);
410
-
411
- if (type === getStrvType()) {
412
- return strvValueType.get(value);
413
- }
414
-
415
- const get = resolveValueGetter(typeFundamental(type));
488
+ const get = resolveValueGetter(type);
416
489
 
417
490
  if (get === undefined) {
418
491
  throw new Error(`Unsupported type for fromValue: ${typeName(type) ?? String(type)}`);
@@ -463,6 +536,8 @@ export {
463
536
  outValueForDescriptor,
464
537
  outValueForBoxedDescriptor,
465
538
  inoutValueForBoxedDescriptor,
539
+ type ValueNarrower,
540
+ valueNarrowerFor,
466
541
  type ValueWriter,
467
542
  valueWriterFor,
468
543
  };
package/src/vfunc-call.ts CHANGED
@@ -82,7 +82,11 @@ function buildInvoker(slot: ResolvedSlot, instanceType: bigint | undefined, call
82
82
 
83
83
  const shaped = fromNativeCallable(
84
84
  bindVfunc(bindOptionsFor(slot, instanceType, args, label)),
85
- { args, returns: descriptor.returnDescriptor, canThrow },
85
+ {
86
+ args,
87
+ returns: descriptor.returnDescriptor,
88
+ canThrow,
89
+ },
86
90
  takeRefSeeds,
87
91
  );
88
92
 
package/src/vfunc.ts CHANGED
@@ -2,6 +2,7 @@ import type { Descriptor } from "@gtkx/native";
2
2
  import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
3
3
  import type { Arg } from "./arg.js";
4
4
  import { isCallerAllocatedOut } from "./callback.js";
5
+ import { foldedLengthSources } from "./folded-lengths.js";
5
6
  import { getInterfaceVfuncRegistry, getVfuncRegistry, type VfuncDescriptor } from "./registry.js";
6
7
 
7
8
  function findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDescriptor | null {
@@ -12,9 +13,13 @@ function findInterfaceVfuncDescriptor(interfaceGtype: bigint, methodName: string
12
13
  return getInterfaceVfuncRegistry(interfaceGtype)?.[methodName];
13
14
  }
14
15
 
15
- function vfuncArg(descriptor: Descriptor): Arg {
16
+ function vfuncArg(descriptor: Descriptor, isFoldedLength: boolean): Arg {
16
17
  if (descriptor.kind === "ref") {
17
- return { type: descriptor.innerDescriptor, direction: descriptor.inout === true ? "inout" : "out" };
18
+ return {
19
+ type: descriptor.innerDescriptor,
20
+ direction: descriptor.inout === true ? "inout" : "out",
21
+ isConsumed: isFoldedLength,
22
+ };
18
23
  }
19
24
 
20
25
  if (isCallerAllocatedOut(descriptor)) {
@@ -25,7 +30,9 @@ function vfuncArg(descriptor: Descriptor): Arg {
25
30
  }
26
31
 
27
32
  function vfuncArgs(descriptor: VfuncDescriptor): Arg[] {
28
- return descriptor.argDescriptors.map((argDescriptor) => vfuncArg(argDescriptor));
33
+ const lengths = foldedLengthSources(descriptor);
34
+
35
+ return descriptor.argDescriptors.map((argDescriptor, index) => vfuncArg(argDescriptor, lengths.has(index)));
29
36
  }
30
37
 
31
38
  export { findClassVfuncDescriptor, findInterfaceVfuncDescriptor, vfuncArgs };