@gtkx/runtime 1.2.1 → 1.3.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 (91) hide show
  1. package/README.md +2 -1
  2. package/dist/arg.d.ts +6 -1
  3. package/dist/arg.d.ts.map +1 -1
  4. package/dist/arg.js +2 -1
  5. package/dist/arg.js.map +1 -1
  6. package/dist/closure.d.ts.map +1 -1
  7. package/dist/closure.js +2 -11
  8. package/dist/closure.js.map +1 -1
  9. package/dist/descriptors.d.ts +21 -3
  10. package/dist/descriptors.d.ts.map +1 -1
  11. package/dist/descriptors.js +50 -21
  12. package/dist/descriptors.js.map +1 -1
  13. package/dist/fn.d.ts +2 -0
  14. package/dist/fn.d.ts.map +1 -1
  15. package/dist/fn.js +50 -10
  16. package/dist/fn.js.map +1 -1
  17. package/dist/index.d.ts +11 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +10 -4
  20. package/dist/index.js.map +1 -1
  21. package/dist/mixin.d.ts +2 -1
  22. package/dist/mixin.d.ts.map +1 -1
  23. package/dist/mixin.js +1 -1
  24. package/dist/mixin.js.map +1 -1
  25. package/dist/native-value.d.ts +9 -3
  26. package/dist/native-value.d.ts.map +1 -1
  27. package/dist/native-value.js +86 -9
  28. package/dist/native-value.js.map +1 -1
  29. package/dist/object.d.ts +14 -6
  30. package/dist/object.d.ts.map +1 -1
  31. package/dist/object.js +21 -10
  32. package/dist/object.js.map +1 -1
  33. package/dist/param-spec.d.ts +31 -1
  34. package/dist/param-spec.d.ts.map +1 -1
  35. package/dist/param-spec.js +66 -5
  36. package/dist/param-spec.js.map +1 -1
  37. package/dist/promisify.d.ts +13 -1
  38. package/dist/promisify.d.ts.map +1 -1
  39. package/dist/promisify.js +14 -1
  40. package/dist/promisify.js.map +1 -1
  41. package/dist/properties.d.ts +28 -1
  42. package/dist/properties.d.ts.map +1 -1
  43. package/dist/properties.js +139 -8
  44. package/dist/properties.js.map +1 -1
  45. package/dist/regex.d.ts +25 -0
  46. package/dist/regex.d.ts.map +1 -0
  47. package/dist/regex.js +59 -0
  48. package/dist/regex.js.map +1 -0
  49. package/dist/register-class.d.ts +154 -11
  50. package/dist/register-class.d.ts.map +1 -1
  51. package/dist/register-class.js +157 -19
  52. package/dist/register-class.js.map +1 -1
  53. package/dist/registry.d.ts +48 -4
  54. package/dist/registry.d.ts.map +1 -1
  55. package/dist/registry.js +147 -3
  56. package/dist/registry.js.map +1 -1
  57. package/dist/signal.d.ts +8 -1
  58. package/dist/signal.d.ts.map +1 -1
  59. package/dist/signal.js +41 -3
  60. package/dist/signal.js.map +1 -1
  61. package/dist/type.d.ts +2 -1
  62. package/dist/type.d.ts.map +1 -1
  63. package/dist/type.js +5 -1
  64. package/dist/type.js.map +1 -1
  65. package/dist/value.d.ts +48 -4
  66. package/dist/value.d.ts.map +1 -1
  67. package/dist/value.js +172 -16
  68. package/dist/value.js.map +1 -1
  69. package/dist/variant.d.ts +75 -26
  70. package/dist/variant.d.ts.map +1 -1
  71. package/dist/variant.js +44 -29
  72. package/dist/variant.js.map +1 -1
  73. package/package.json +4 -4
  74. package/src/arg.ts +6 -1
  75. package/src/closure.ts +2 -14
  76. package/src/descriptors.ts +74 -21
  77. package/src/fn.ts +73 -17
  78. package/src/index.ts +31 -4
  79. package/src/mixin.ts +1 -1
  80. package/src/native-value.ts +146 -14
  81. package/src/object.ts +23 -11
  82. package/src/param-spec.ts +94 -8
  83. package/src/promisify.ts +27 -1
  84. package/src/properties.ts +184 -5
  85. package/src/regex.ts +92 -0
  86. package/src/register-class.ts +446 -38
  87. package/src/registry.ts +219 -4
  88. package/src/signal.ts +77 -0
  89. package/src/type.ts +7 -0
  90. package/src/value.ts +268 -14
  91. package/src/variant.ts +184 -53
@@ -102,6 +102,11 @@ type CallbackOptions = {
102
102
  hasUserData?: boolean;
103
103
  /** Position of `user_data` among the callback's own arguments, dropped before the closure is called. */
104
104
  userDataIndex?: number;
105
+ /**
106
+ * The callback's C signature ends with a `GError**`, which receives a `GError` built from
107
+ * whatever the JavaScript function throws while the callback returns its failure value.
108
+ */
109
+ canThrow?: boolean;
105
110
  /** Lifetime of the closure; defaults to `notified` when `hasDestroy` is set and `call` otherwise. */
106
111
  scope?: CallbackDescriptor["scope"];
107
112
  };
@@ -118,6 +123,12 @@ type ArrayOptions = {
118
123
  fixedSize?: number | undefined;
119
124
  /** Whether the array carries raw bytes, and so decodes to a `Uint8Array` rather than to numbers. */
120
125
  isBytes?: boolean | undefined;
126
+ /**
127
+ * Whether the caller supplies the storage for a fixed-length out array: the runtime allocates
128
+ * a buffer of the element stride times the fixed element count, passes its pointer, and
129
+ * decodes the elements the callee wrote into it.
130
+ */
131
+ isCallerAllocated?: boolean | undefined;
121
132
  };
122
133
 
123
134
  /** Where a cursor array's base buffer and total length come from. */
@@ -136,6 +147,8 @@ type FundamentalOptions = {
136
147
  typeName?: string;
137
148
  /** Class a decoded value is wrapped in, instead of the one registered for `typeName`. */
138
149
  wrapperClass?: AnyClass;
150
+ /** The caller owns the storage the callee fills, so a decoded value is borrowed instead of acquired. */
151
+ isCallerAllocated?: boolean;
139
152
  /** The value is embedded in the containing struct rather than reached through a pointer. */
140
153
  isInline?: boolean;
141
154
  };
@@ -194,6 +207,9 @@ const unicharT: UnicharDescriptor = { kind: "unichar" };
194
207
  const bufferT: BufferDescriptor = { kind: "buffer" };
195
208
  const fundamentalLifecycles: Map<string, FundamentalLifecycle> = new Map();
196
209
 
210
+ const isGtypeDescriptor = (descriptor: Descriptor): descriptor is TypeDescriptor =>
211
+ descriptor.kind === "biguint64" && "type" in descriptor;
212
+
197
213
  /**
198
214
  * Builds a descriptor for a C string, whose optional length sizes the caller-allocated buffer
199
215
  * used when the string is passed by reference.
@@ -228,13 +244,25 @@ const enumT = (sharedLibrary: string, typeFnName: string, isSigned: boolean): En
228
244
  isSigned,
229
245
  });
230
246
 
231
- /** Builds a descriptor for a flags type, resolving its GType from the named `get_type` function. */
232
- const flagsT = (sharedLibrary: string, typeFnName: string, isSigned: boolean): FlagsDescriptor => ({
233
- kind: "flags",
234
- sharedLibrary,
235
- getTypeFnName: typeFnName,
236
- isSigned,
237
- });
247
+ /**
248
+ * Builds a descriptor for a flags type, resolving its GType from the named `get_type` function.
249
+ * For flags without a registered GType, pass empty library and function names and supply `mask`,
250
+ * the union of all valid bits, which invalid combinations are rejected against.
251
+ */
252
+ const flagsT = (sharedLibrary: string, typeFnName: string, isSigned: boolean, mask?: number): FlagsDescriptor => {
253
+ const result: FlagsDescriptor = {
254
+ kind: "flags",
255
+ sharedLibrary,
256
+ getTypeFnName: typeFnName,
257
+ isSigned,
258
+ };
259
+
260
+ if (mask !== undefined) {
261
+ result.mask = mask;
262
+ }
263
+
264
+ return result;
265
+ };
238
266
 
239
267
  const applyBoxedNames = (result: BoxedDescriptor, options: BoxedOptions): void => {
240
268
  if (options.sharedLibrary !== undefined) {
@@ -330,6 +358,10 @@ const fundamentalT = (
330
358
  result.wrapperClass = options.wrapperClass;
331
359
  }
332
360
 
361
+ if (options.isCallerAllocated) {
362
+ result.isCallerAllocated = true;
363
+ }
364
+
333
365
  if (options.isInline) {
334
366
  result.isInline = true;
335
367
  }
@@ -337,6 +369,20 @@ const fundamentalT = (
337
369
  return result;
338
370
  };
339
371
 
372
+ const applyArrayBounds = (result: ArrayDescriptor, options: ArrayOptions): void => {
373
+ if (options.baseParamIndex !== undefined) {
374
+ result.baseParamIndex = options.baseParamIndex;
375
+ }
376
+
377
+ if (options.sizeParamIndex !== undefined) {
378
+ result.sizeParamIndex = options.sizeParamIndex;
379
+ }
380
+
381
+ if (options.fixedSize !== undefined) {
382
+ result.fixedSize = options.fixedSize;
383
+ }
384
+ };
385
+
340
386
  /** Builds a descriptor for an array of items in one of the supported container layouts. */
341
387
  const arrayT = (
342
388
  itemDescriptor: Descriptor,
@@ -346,24 +392,22 @@ const arrayT = (
346
392
  ): ArrayDescriptor => {
347
393
  const result: ArrayDescriptor = { kind: "array", itemDescriptor, arrayKind, ownership };
348
394
 
349
- if (options?.elementSize !== undefined) {
350
- result.elementSize = options.elementSize;
395
+ if (options === undefined) {
396
+ return result;
351
397
  }
352
398
 
353
- if (options?.baseParamIndex !== undefined) {
354
- result.baseParamIndex = options.baseParamIndex;
355
- }
399
+ applyArrayBounds(result, options);
356
400
 
357
- if (options?.sizeParamIndex !== undefined) {
358
- result.sizeParamIndex = options.sizeParamIndex;
401
+ if (options.elementSize !== undefined) {
402
+ result.elementSize = options.elementSize;
359
403
  }
360
404
 
361
- if (options?.fixedSize !== undefined) {
362
- result.fixedSize = options.fixedSize;
405
+ if (options.isBytes === true) {
406
+ result.isBytes = true;
363
407
  }
364
408
 
365
- if (options?.isBytes === true) {
366
- result.isBytes = true;
409
+ if (options.isCallerAllocated === true) {
410
+ result.isCallerAllocated = true;
367
411
  }
368
412
 
369
413
  return result;
@@ -428,7 +472,7 @@ const fixedArrayT = (
428
472
  options: ArrayOptions = {},
429
473
  ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { ...options, fixedSize });
430
474
 
431
- const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOptions): void => {
475
+ const applyClosureLifetimeOptions = (result: CallbackDescriptor, options: CallbackOptions): void => {
432
476
  if (options.hasDestroy !== undefined) {
433
477
  result.hasDestroy = options.hasDestroy;
434
478
  }
@@ -437,6 +481,14 @@ const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOption
437
481
  result.destroyKind = options.destroyKind;
438
482
  }
439
483
 
484
+ if (options.scope !== undefined) {
485
+ result.scope = options.scope;
486
+ }
487
+ };
488
+
489
+ const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOptions): void => {
490
+ applyClosureLifetimeOptions(result, options);
491
+
440
492
  if (options.hasUserData !== undefined) {
441
493
  result.hasUserData = options.hasUserData;
442
494
  }
@@ -445,8 +497,8 @@ const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOption
445
497
  result.userDataIndex = options.userDataIndex;
446
498
  }
447
499
 
448
- if (options.scope !== undefined) {
449
- result.scope = options.scope;
500
+ if (options.canThrow !== undefined) {
501
+ result.canThrow = options.canThrow;
450
502
  }
451
503
  };
452
504
 
@@ -479,6 +531,7 @@ export {
479
531
  bigint64T,
480
532
  biguint64T,
481
533
  gtypeT,
534
+ isGtypeDescriptor,
482
535
  float32T,
483
536
  float64T,
484
537
  booleanT,
package/src/fn.ts CHANGED
@@ -1,14 +1,16 @@
1
- import type { CallDescriptor, Descriptor, ExternalObject, Ref } from "@gtkx/native";
1
+ import type { CallDescriptor, Descriptor, ExternalObject, Handle, Ref } from "@gtkx/native";
2
2
  import { call, bind as nativeBind } from "@gtkx/native";
3
3
  import type { RefSeeds } from "./vfunc-seeds.js";
4
- import { type Arg, isCallerAllocatedArg, isOutputArg, isRefArg, requiresInputArg } from "./arg.js";
4
+ import { type Arg, isCallerAllocatedArg, isOutputArg, isRefArg, isUnpackedArg, requiresInputArg } from "./arg.js";
5
5
  import { wrapCallbackValue } from "./callback.js";
6
- import { boxedT, refT } from "./descriptors.js";
6
+ import { boxedT, isGtypeDescriptor, refT } from "./descriptors.js";
7
7
  import { checkError } from "./error.js";
8
8
  import { LIB } from "./library.js";
9
- import { fromNative } from "./native-value.js";
9
+ import { fromNative, toNative } from "./native-value.js";
10
10
  import { getHandle } from "./registry.js";
11
11
  import { hasSurfacedPrimary, packTupleResult } from "./tuple.js";
12
+ import { TYPE_INVALID } from "./type.js";
13
+ import { fromValue, getValueType } from "./value.js";
12
14
 
13
15
  /** The signature a native function is bound against. */
14
16
  type FnSpec = {
@@ -29,6 +31,8 @@ type FnSpec = {
29
31
  * architectures.
30
32
  */
31
33
  fixedArgCount?: number;
34
+ /** The callee returns a `GValue`, surfaced as what it holds rather than as the value itself. */
35
+ isReturnUnpacked?: boolean;
32
36
  };
33
37
 
34
38
  type ArgSpec = {
@@ -36,13 +40,18 @@ type ArgSpec = {
36
40
  index: number;
37
41
  isRef: boolean;
38
42
  isCallerAllocated: boolean;
43
+ isUnpacked: boolean;
39
44
  requiresInput: boolean;
40
45
  inputIndex: number;
41
46
  isOutParam: boolean;
47
+ carriesGtype: boolean;
42
48
  };
43
49
 
44
50
  const NO_OUT_PARAMS: unknown[] = [];
45
51
 
52
+ const hasGtypeInput = (descriptor: Descriptor): boolean =>
53
+ isGtypeDescriptor(descriptor) || (descriptor.kind === "array" && isGtypeDescriptor(descriptor.itemDescriptor));
54
+
46
55
  const buildNativeArgTypes = (args: Arg[], canThrow: boolean): Descriptor[] => {
47
56
  const nativeArgTypes = args.map((argSpec) =>
48
57
  argSpec.direction !== undefined && argSpec.isCallerAllocated !== true ? refT(argSpec.type) : argSpec.type,
@@ -70,9 +79,11 @@ const buildArgSpecs = (args: Arg[]): ArgSpec[] => {
70
79
  index,
71
80
  isRef,
72
81
  isCallerAllocated: isCallerAllocatedArg(arg),
82
+ isUnpacked: isUnpackedArg(arg),
73
83
  requiresInput,
74
84
  inputIndex: requiresInput ? inputCursor++ : -1,
75
85
  isOutParam,
86
+ carriesGtype: hasGtypeInput(arg.type),
76
87
  };
77
88
  });
78
89
  };
@@ -83,8 +94,11 @@ const resolveCallerAllocated = (inputs: unknown[], inputIndex: number): unknown
83
94
  return wrapper == null ? wrapper : getHandle(wrapper);
84
95
  };
85
96
 
97
+ const inputValueFor = (spec: ArgSpec, inputs: unknown[]): unknown =>
98
+ spec.carriesGtype ? toNative(spec.arg.type, inputs[spec.inputIndex]) : inputs[spec.inputIndex];
99
+
86
100
  const buildRefValue = (spec: ArgSpec, inputs: unknown[], seeds: RefSeeds | undefined): Ref => ({
87
- value: spec.requiresInput ? inputs[spec.inputIndex] : (seeds?.get(spec.index) ?? null),
101
+ value: spec.requiresInput ? inputValueFor(spec, inputs) : (seeds?.get(spec.index) ?? null),
88
102
  });
89
103
 
90
104
  const buildNativeValue = (spec: ArgSpec, inputs: unknown[], seeds: RefSeeds | undefined): unknown => {
@@ -102,12 +116,30 @@ const buildNativeValue = (spec: ArgSpec, inputs: unknown[], seeds: RefSeeds | un
102
116
  return wrapCallbackValue(arg.type, inputs[inputIndex]);
103
117
  }
104
118
 
105
- return inputs[inputIndex];
119
+ return inputValueFor(spec, inputs);
106
120
  };
107
121
 
108
122
  const buildNativeValues = (plans: ArgSpec[], inputs: unknown[], seeds: RefSeeds | undefined): unknown[] =>
109
123
  plans.map((plan) => buildNativeValue(plan, inputs, seeds));
110
124
 
125
+ const unpackValueHandle = (handle: ExternalObject<Handle> | null): unknown => {
126
+ if (handle === null || getValueType(handle) === TYPE_INVALID) {
127
+ return null;
128
+ }
129
+
130
+ return fromValue(handle);
131
+ };
132
+
133
+ const readCallerAllocated = (plan: ArgSpec, inputs: unknown[]): unknown => {
134
+ const wrapper = inputs[plan.inputIndex];
135
+
136
+ if (!plan.isUnpacked) {
137
+ return wrapper;
138
+ }
139
+
140
+ return unpackValueHandle(wrapper == null ? null : getHandle(wrapper));
141
+ };
142
+
111
143
  const readOutParams = (outPlans: ArgSpec[], inputs: unknown[], nativeValues: unknown[]): unknown[] => {
112
144
  if (outPlans.length === 0) {
113
145
  return NO_OUT_PARAMS;
@@ -115,7 +147,7 @@ const readOutParams = (outPlans: ArgSpec[], inputs: unknown[], nativeValues: unk
115
147
 
116
148
  return Array.from(outPlans, (plan) =>
117
149
  plan.isCallerAllocated
118
- ? inputs[plan.inputIndex]
150
+ ? readCallerAllocated(plan, inputs)
119
151
  : fromNative(plan.arg.type, (nativeValues[plan.index] as Ref).value));
120
152
  };
121
153
 
@@ -123,8 +155,20 @@ const isPassThroughPlan = (plan: ArgSpec, index: number): boolean =>
123
155
  plan.inputIndex === index &&
124
156
  !plan.isRef &&
125
157
  !plan.isCallerAllocated &&
158
+ !plan.carriesGtype &&
126
159
  plan.arg.type.kind !== "callback";
127
160
 
161
+ const requiredInputIndices = (plans: ArgSpec[]): number[] =>
162
+ plans.filter((plan) => plan.requiresInput && plan.arg.isRequired === true).map((plan) => plan.inputIndex);
163
+
164
+ const assertRequiredInputs = (requiredIndices: number[], inputs: unknown[]): void => {
165
+ for (const index of requiredIndices) {
166
+ if (inputs[index] === undefined) {
167
+ throw new TypeError(`Missing required argument at position ${String(index + 1)}`);
168
+ }
169
+ }
170
+ };
171
+
128
172
  const resizeInputs = (inputs: unknown[], argCount: number): unknown[] => {
129
173
  while (inputs.length < argCount) {
130
174
  inputs.push(undefined);
@@ -137,14 +181,25 @@ const resizeInputs = (inputs: unknown[], argCount: number): unknown[] => {
137
181
  return inputs;
138
182
  };
139
183
 
184
+ const returnReader = (spec: FnSpec): ((nativeResult: unknown) => unknown) =>
185
+ spec.isReturnUnpacked === true
186
+ ? (nativeResult) => unpackValueHandle((nativeResult ?? null) as ExternalObject<Handle> | null)
187
+ : (nativeResult) => fromNative(spec.returns, nativeResult);
188
+
140
189
  const directCallable = (
141
190
  descriptor: ExternalObject<CallDescriptor>,
142
- returnDescriptor: Descriptor,
191
+ readReturn: (nativeResult: unknown) => unknown,
143
192
  hasPrimary: boolean,
144
- argCount: number,
193
+ plans: ArgSpec[],
145
194
  ): ((...inputs: unknown[]) => unknown) => {
146
- const marshal = (inputs: unknown[]): unknown =>
147
- fromNative(returnDescriptor, call(descriptor, resizeInputs(inputs, argCount)));
195
+ const argCount = plans.length;
196
+ const requiredIndices = requiredInputIndices(plans);
197
+
198
+ const marshal = (inputs: unknown[]): unknown => {
199
+ assertRequiredInputs(requiredIndices, inputs);
200
+
201
+ return readReturn(call(descriptor, resizeInputs(inputs, argCount)));
202
+ };
148
203
 
149
204
  if (hasPrimary) {
150
205
  return (...inputs) => marshal(inputs);
@@ -165,20 +220,20 @@ function fromNativeCallable(
165
220
  const plans = buildArgSpecs(args);
166
221
  const outPlans = plans.filter((plan) => plan.isOutParam);
167
222
  const arePassThrough = plans.every((plan, index) => isPassThroughPlan(plan, index));
223
+ const readReturn = returnReader(spec);
168
224
 
169
225
  if (!canThrow && arePassThrough) {
170
- return directCallable(descriptor, returnDescriptor, hasPrimary, plans.length);
226
+ return directCallable(descriptor, readReturn, hasPrimary, plans);
171
227
  }
172
228
 
229
+ const requiredIndices = requiredInputIndices(plans);
230
+
173
231
  const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown =>
174
- packTupleResult(
175
- readOutParams(outPlans, inputs, nativeValues),
176
- fromNative(returnDescriptor, nativeResult),
177
- hasPrimary,
178
- );
232
+ packTupleResult(readOutParams(outPlans, inputs, nativeValues), readReturn(nativeResult), hasPrimary);
179
233
 
180
234
  if (canThrow) {
181
235
  return (...inputs) => {
236
+ assertRequiredInputs(requiredIndices, inputs);
182
237
  const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
183
238
  const errorRef: Ref = { value: null };
184
239
  nativeValues.push(errorRef);
@@ -190,6 +245,7 @@ function fromNativeCallable(
190
245
  }
191
246
 
192
247
  return (...inputs) => {
248
+ assertRequiredInputs(requiredIndices, inputs);
193
249
  const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
194
250
 
195
251
  return shape(inputs, nativeValues, call(descriptor, nativeValues));
package/src/index.ts CHANGED
@@ -24,20 +24,30 @@ export {
24
24
  setObjectProperty,
25
25
  } from "./object.js";
26
26
  /** @public */
27
- export { promisify } from "./promisify.js";
27
+ export { getParamSpecFlags, getParamSpecOwnerType, getParamSpecValueType } from "./param-spec.js";
28
28
  /** @public */
29
- export { type Interface, registerClass } from "./register-class.js";
29
+ export { promisify, trimFinish } from "./promisify.js";
30
+ /** @public */
31
+ export { coerceObjectProperty, newParamSpecOverride } from "./properties.js";
32
+ /** @public */
33
+ export { matchAllRegex, matchRegex } from "./regex.js";
34
+ /** @public */
35
+ export { type Interface, registerClass, type SignalGType, type SignalSpec } from "./register-class.js";
30
36
  /** @public */
31
37
  export {
32
38
  getClassType,
33
39
  getHandle,
34
40
  getInstanceType,
35
41
  getWrapperClass,
42
+ peekTypeClass,
43
+ registerClassStruct,
36
44
  registerInterface,
37
45
  registerWrapperClass,
46
+ registerWrapperClassResolver,
38
47
  type StaticBase,
39
48
  setHandle,
40
49
  wrapHandle,
50
+ type WrapperClassResolver,
41
51
  } from "./registry.js";
42
52
  /** @public */
43
53
  export { connectSignal, disconnectSignal, emitSignal, getSignalBaseName, type SignalHandler } from "./signal.js";
@@ -79,9 +89,26 @@ export {
79
89
  valueIsA,
80
90
  } from "./type.js";
81
91
  /** @public */
82
- export { getBoxedValue, setBoxedValue } from "./value.js";
92
+ export {
93
+ fromValue,
94
+ getBoxedValue,
95
+ type JsValue,
96
+ setBoxedValue,
97
+ toValueHandle,
98
+ tryToValueHandle,
99
+ ValueMarshalError,
100
+ } from "./value.js";
83
101
  /** @public */
84
- export { fromVariant, toVariant, type VariantValue } from "./variant.js";
102
+ export {
103
+ type ByteArray,
104
+ type FromVariantOptions,
105
+ fromVariant,
106
+ type RecursiveFromVariantOptions,
107
+ type RecursiveVariantValue,
108
+ toVariant,
109
+ type VariantInput,
110
+ type VariantValue,
111
+ } from "./variant.js";
85
112
  /** @public */
86
113
  export { callParent, callVfunc } from "./vfunc-call.js";
87
114
  /** @public */
package/src/mixin.ts CHANGED
@@ -89,4 +89,4 @@ function insertMixinLayer(target: AnyClass, mixin: Mixin, inheritedNames: Set<st
89
89
  Object.setPrototypeOf(target, layer);
90
90
  }
91
91
 
92
- export { insertMixinLayer, installMixins, type MixinReceiver, type Mixin };
92
+ export { copyLayerMembers, insertMixinLayer, installMixins, type MixinReceiver, type Mixin };
@@ -1,10 +1,41 @@
1
- import type { Descriptor, ExternalObject, Handle } from "@gtkx/native";
2
- import type { ArrayDescriptor, FundamentalDescriptor, HashTableDescriptor, StructDescriptor } from "./descriptors.js";
3
- import { getHandle, getWrapperClass, wrapCallScopedObject, wrapHandle, wrapObject } from "./registry.js";
1
+ import type { AnyClass } from "@gtkx/utils";
2
+ import {
3
+ bindFunctionPointer,
4
+ call,
5
+ type CallDescriptor,
6
+ type Descriptor,
7
+ type ExternalObject,
8
+ getType,
9
+ type Handle,
10
+ type Ref,
11
+ } from "@gtkx/native";
12
+ import {
13
+ type ArrayDescriptor,
14
+ boxedT,
15
+ type CallbackDescriptor,
16
+ type FundamentalDescriptor,
17
+ type HashTableDescriptor,
18
+ isGtypeDescriptor,
19
+ refT,
20
+ type StructDescriptor,
21
+ } from "./descriptors.js";
22
+ import { checkError } from "./error.js";
23
+ import { LIB } from "./library.js";
24
+ import {
25
+ coerceGType,
26
+ getHandle,
27
+ getWrapperClass,
28
+ resolveWrapperClass,
29
+ wrapCallScopedObject,
30
+ wrapFundamentalHandle,
31
+ wrapHandle,
32
+ wrapObject,
33
+ } from "./registry.js";
4
34
  import { resolveDescriptorType } from "./type.js";
5
35
 
6
36
  type MarshalledKind = "object" | "struct" | "boxed" | "fundamental" | "array" | "hashtable";
7
37
  type MarshalledDescriptor = Extract<Descriptor, { kind: MarshalledKind }>;
38
+ type DecodedCallbackTarget = { fnPtr: bigint; userData?: bigint | undefined };
8
39
 
9
40
  const MARSHALLED_KINDS: Set<Descriptor["kind"]> = new Set<MarshalledKind>([
10
41
  "object",
@@ -15,6 +46,8 @@ const MARSHALLED_KINDS: Set<Descriptor["kind"]> = new Set<MarshalledKind>([
15
46
  "hashtable",
16
47
  ]);
17
48
 
49
+ const NULL_POINTER = 0n;
50
+
18
51
  function isMarshalledDescriptor(descriptor: Descriptor): descriptor is MarshalledDescriptor {
19
52
  return MARSHALLED_KINDS.has(descriptor.kind);
20
53
  }
@@ -32,18 +65,103 @@ function collectionFromNative(descriptor: ArrayDescriptor, value: unknown): unkn
32
65
  }
33
66
 
34
67
  function boxedFromNative(descriptor: Descriptor, value: unknown): unknown {
35
- return value == null
36
- ? null
37
- : wrapHandle(value as ExternalObject<Handle>, getWrapperClass(resolveDescriptorType(descriptor)));
68
+ if (value == null) {
69
+ return null;
70
+ }
71
+
72
+ const handle = value as ExternalObject<Handle>;
73
+ const type = resolveDescriptorType(descriptor);
74
+
75
+ return wrapHandle(handle, getWrapperClass(type));
76
+ }
77
+
78
+ function fundamentalWrapperClass(descriptor: FundamentalDescriptor, handle: ExternalObject<Handle>): AnyClass {
79
+ if (descriptor.wrapperClass !== undefined) {
80
+ return descriptor.wrapperClass;
81
+ }
82
+
83
+ const declaredType = resolveDescriptorType(descriptor);
84
+
85
+ return resolveWrapperClass(getType(handle, declaredType)) ?? getWrapperClass(declaredType);
38
86
  }
39
87
 
40
88
  function fundamentalFromNative(descriptor: FundamentalDescriptor, value: unknown): unknown {
41
- return value == null
42
- ? null
43
- : wrapHandle(
44
- value as ExternalObject<Handle>,
45
- descriptor.wrapperClass ?? getWrapperClass(resolveDescriptorType(descriptor)),
46
- );
89
+ if (value == null) {
90
+ return null;
91
+ }
92
+
93
+ const handle = value as ExternalObject<Handle>;
94
+
95
+ return wrapFundamentalHandle(handle, fundamentalWrapperClass(descriptor, handle));
96
+ }
97
+
98
+ const errorRefDescriptor = (): Descriptor =>
99
+ refT(boxedT("GError", { ownership: "full", sharedLibrary: LIB, getTypeFnName: "g_error_get_type" }));
100
+
101
+ function decodedCallbackValues(
102
+ descriptor: CallbackDescriptor,
103
+ target: DecodedCallbackTarget,
104
+ inputs: unknown[],
105
+ ): unknown[] {
106
+ const values: unknown[] = [];
107
+ let cursor = 0;
108
+
109
+ for (const [index, argDescriptor] of descriptor.argDescriptors.entries()) {
110
+ if (index === descriptor.userDataIndex) {
111
+ values.push(target.userData ?? NULL_POINTER);
112
+ } else {
113
+ values.push(toNative(argDescriptor, inputs[cursor]));
114
+ cursor += 1;
115
+ }
116
+ }
117
+
118
+ return values;
119
+ }
120
+
121
+ function decodedCallbackCallable(
122
+ descriptor: CallbackDescriptor,
123
+ target: DecodedCallbackTarget,
124
+ ): (...inputs: unknown[]) => unknown {
125
+ const canThrow = descriptor.canThrow === true;
126
+ const isOneShot = descriptor.scope === "async";
127
+ const argDescriptors = canThrow ? [...descriptor.argDescriptors, errorRefDescriptor()] : descriptor.argDescriptors;
128
+ let bound: ExternalObject<CallDescriptor> | undefined;
129
+ let isSpent = false;
130
+
131
+ return (...inputs) => {
132
+ if (isSpent) {
133
+ throw new Error("An async-scoped callback was already invoked; its native caller has released it");
134
+ }
135
+
136
+ bound ??= bindFunctionPointer(target.fnPtr, argDescriptors, descriptor.returnDescriptor, "decoded callback");
137
+ const values = decodedCallbackValues(descriptor, target, inputs);
138
+ isSpent = isOneShot;
139
+
140
+ if (!canThrow) {
141
+ return fromNative(descriptor.returnDescriptor, call(bound, values));
142
+ }
143
+
144
+ const errorRef: Ref = { value: null };
145
+ values.push(errorRef);
146
+ const result = call(bound, values);
147
+ checkError(errorRef);
148
+
149
+ return fromNative(descriptor.returnDescriptor, result);
150
+ };
151
+ }
152
+
153
+ function callbackFromNative(descriptor: CallbackDescriptor, value: unknown): unknown {
154
+ if (value == null) {
155
+ return value;
156
+ }
157
+
158
+ const target = value as Partial<DecodedCallbackTarget>;
159
+
160
+ if (typeof target.fnPtr !== "bigint") {
161
+ return value;
162
+ }
163
+
164
+ return decodedCallbackCallable(descriptor, { fnPtr: target.fnPtr, userData: target.userData });
47
165
  }
48
166
 
49
167
  function hashTableFromNative(descriptor: HashTableDescriptor, value: unknown): unknown {
@@ -64,12 +182,21 @@ function hashTableFromNative(descriptor: HashTableDescriptor, value: unknown): u
64
182
  /**
65
183
  * Converts a raw value returned from native code into its JavaScript form,
66
184
  * wrapping object, struct, boxed, and fundamental handles and recursively
67
- * converting arrays and hash tables according to the descriptor.
185
+ * converting arrays and hash tables according to the descriptor. A callback
186
+ * decoded from a native function pointer and its bound user data becomes a
187
+ * callable function that invokes the native callback; it stays valid only as
188
+ * long as the native caller keeps the pointer pair alive, which for an
189
+ * async-scoped callback means until the first invocation — calling one a
190
+ * second time throws instead of reaching the released native closure.
68
191
  *
69
192
  * @param descriptor Describes the native type of the value.
70
193
  * @param value The raw native value to convert.
71
194
  */
72
195
  function fromNative(descriptor: Descriptor, value: unknown): unknown {
196
+ if (descriptor.kind === "callback") {
197
+ return callbackFromNative(descriptor, value);
198
+ }
199
+
73
200
  if (!isMarshalledDescriptor(descriptor)) {
74
201
  return value;
75
202
  }
@@ -113,13 +240,18 @@ function hashTableToNative(descriptor: HashTableDescriptor, value: unknown): unk
113
240
 
114
241
  /**
115
242
  * Converts a JavaScript value into the raw form native code expects, unwrapping
116
- * object, struct, boxed, and fundamental wrappers back to their handles and
243
+ * object, struct, boxed, and fundamental wrappers back to their handles,
244
+ * resolving a class passed for a GType to the GType it was registered under, and
117
245
  * recursively converting arrays and maps according to the descriptor.
118
246
  *
119
247
  * @param descriptor Describes the native type to convert to.
120
248
  * @param value The JavaScript value to convert.
121
249
  */
122
250
  function toNative(descriptor: Descriptor, value: unknown): unknown {
251
+ if (isGtypeDescriptor(descriptor)) {
252
+ return coerceGType(value);
253
+ }
254
+
123
255
  if (!isMarshalledDescriptor(descriptor)) {
124
256
  return value;
125
257
  }