@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/callback.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { copy, type Descriptor } from "@gtkx/native";
2
2
  import type { CallbackDescriptor, RefDescriptor } from "./descriptors.js";
3
+ import { foldedLengthSources, type LengthSource, type LengthSources } from "./folded-lengths.js";
3
4
  import { fromNative, toNative } from "./native-value.js";
4
5
  import { getHandle } from "./registry.js";
5
6
  import { splitTupleResult } from "./tuple.js";
@@ -7,10 +8,12 @@ import { copyValue } from "./value.js";
7
8
  import { popSeedFrame, pushSeedFrame, type RefSeeds } from "./vfunc-seeds.js";
8
9
 
9
10
  type Callback = (...args: unknown[]) => unknown;
10
- type CallbackReceiver = "this" | "emitter" | "none";
11
+ type CallbackKind = "vfunc" | "signal" | "callback";
12
+ type CallbackTraits = { isInstanceBound: boolean; hasInstanceArg: boolean; hasFoldedLengths: boolean };
11
13
  type OutParam = { value: unknown; descriptor: Descriptor; argIndex: number };
12
- type LengthLink = { target: OutParam; sourceIndex: number };
14
+ type LengthLink = { target: OutParam; sources: LengthSource[] };
13
15
  type OutParamGroups = { lengthLinks: LengthLink[]; valueParams: OutParam[] };
16
+ type OutValues = Map<number, unknown>;
14
17
 
15
18
  type CallbackSpec = {
16
19
  argDescriptors: Descriptor[];
@@ -22,12 +25,20 @@ type CallbackPlan = {
22
25
  fn: Callback;
23
26
  effectiveTypes: Descriptor[];
24
27
  returnDescriptor: Descriptor;
28
+ hasPrimary: boolean;
25
29
  start: number;
26
- receiver: CallbackReceiver;
30
+ isInstanceBound: boolean;
31
+ lengthSources: LengthSources;
27
32
  hasOutParams: boolean;
28
33
  hasRefOutParams: boolean;
29
34
  };
30
35
 
36
+ const CALLBACK_TRAITS: Record<CallbackKind, CallbackTraits> = {
37
+ callback: { isInstanceBound: false, hasInstanceArg: false, hasFoldedLengths: true },
38
+ signal: { isInstanceBound: false, hasInstanceArg: true, hasFoldedLengths: false },
39
+ vfunc: { isInstanceBound: true, hasInstanceArg: true, hasFoldedLengths: true },
40
+ };
41
+
31
42
  const fillCallerAllocatedBuffer = (descriptor: Descriptor, target: object, source: object): void => {
32
43
  if (descriptor.kind === "boxed" && descriptor.typeName === "GValue") {
33
44
  copyValue(getHandle(target), getHandle(source));
@@ -108,74 +119,58 @@ const haveOutParamArgs = (effectiveTypes: Descriptor[], start: number): boolean
108
119
  const haveRefOutParamArgs = (effectiveTypes: Descriptor[], start: number): boolean =>
109
120
  effectiveTypes.slice(start).some((descriptor) => hasRefOutParamArg(descriptor));
110
121
 
111
- const sizeParamIndexFor = (descriptor: Descriptor): number | undefined => {
112
- if (descriptor.kind !== "ref") {
113
- return undefined;
114
- }
115
-
116
- const { innerDescriptor } = descriptor;
117
-
118
- return innerDescriptor.kind === "array" ? innerDescriptor.sizeParamIndex : undefined;
119
- };
120
-
121
- const lengthOutParamIndices = (outParams: OutParam[]): Map<number, number> => {
122
- const indices: Map<number, number> = new Map();
122
+ const groupOutParams = (outParams: OutParam[], lengthSources: LengthSources): OutParamGroups => {
123
+ const groups: OutParamGroups = { lengthLinks: [], valueParams: [] };
123
124
 
124
125
  for (const outParam of outParams) {
125
- const sizeParamIndex = sizeParamIndexFor(outParam.descriptor);
126
+ const sources = lengthSources.get(outParam.argIndex);
126
127
 
127
- if (sizeParamIndex !== undefined) {
128
- indices.set(sizeParamIndex, outParam.argIndex);
128
+ if (sources === undefined) {
129
+ groups.valueParams.push(outParam);
130
+ } else {
131
+ groups.lengthLinks.push({ target: outParam, sources });
129
132
  }
130
133
  }
131
134
 
132
- return indices;
135
+ return groups;
133
136
  };
134
137
 
135
- const writeOutParams = (outParams: OutParam[], outValues: unknown[]): Map<number, unknown> => {
136
- const written: Map<number, unknown> = new Map();
138
+ const getLength = (value: unknown): number => {
139
+ const length = (value as { length?: unknown } | null | undefined)?.length;
137
140
 
138
- for (const [position, outParam] of outParams.entries()) {
139
- const outValue = outValues[position];
140
- const { descriptor } = outParam;
141
- written.set(outParam.argIndex, outValue);
141
+ return typeof length === "number" ? length : 0;
142
+ };
142
143
 
143
- if (descriptor.kind === "ref") {
144
- (outParam.value as { value: unknown }).value = toNative(descriptor.innerDescriptor, outValue);
145
- } else if (outValue != null && outParam.value != null) {
146
- fillCallerAllocatedBuffer(descriptor, outParam.value, outValue);
147
- }
148
- }
144
+ const lengthSourceValue = (source: LengthSource, outValues: OutValues, primary: unknown): unknown =>
145
+ source.kind === "return" ? primary : outValues.get(source.argIndex);
149
146
 
150
- return written;
151
- };
147
+ const foldedLength = (sources: LengthSource[], outValues: OutValues, primary: unknown): number =>
148
+ Math.min(...sources.map((source) => getLength(lengthSourceValue(source, outValues, primary))));
152
149
 
153
- const groupOutParams = (outParams: OutParam[]): OutParamGroups => {
154
- const lengths = lengthOutParamIndices(outParams);
155
- const groups: OutParamGroups = { lengthLinks: [], valueParams: [] };
150
+ const resolveOutValues = (groups: OutParamGroups, values: unknown[], primary: unknown): OutValues => {
151
+ const outValues: OutValues = new Map();
156
152
 
157
- for (const outParam of outParams) {
158
- const sourceIndex = lengths.get(outParam.argIndex);
153
+ for (const [position, outParam] of groups.valueParams.entries()) {
154
+ outValues.set(outParam.argIndex, values[position]);
155
+ }
159
156
 
160
- if (sourceIndex === undefined) {
161
- groups.valueParams.push(outParam);
162
- } else {
163
- groups.lengthLinks.push({ target: outParam, sourceIndex });
164
- }
157
+ for (const link of groups.lengthLinks) {
158
+ outValues.set(link.target.argIndex, foldedLength(link.sources, outValues, primary));
165
159
  }
166
160
 
167
- return groups;
161
+ return outValues;
168
162
  };
169
163
 
170
- const getFoldedLength = (source: unknown): number => {
171
- const length = (source as { length?: unknown } | null | undefined)?.length;
172
-
173
- return typeof length === "number" ? length : 0;
174
- };
164
+ const writeOutParams = (outParams: OutParam[], outValues: OutValues): void => {
165
+ for (const outParam of outParams) {
166
+ const { descriptor } = outParam;
167
+ const outValue = outValues.get(outParam.argIndex);
175
168
 
176
- const writeFoldedLengths = (lengthLinks: LengthLink[], written: Map<number, unknown>): void => {
177
- for (const link of lengthLinks) {
178
- (link.target.value as { value: unknown }).value = getFoldedLength(written.get(link.sourceIndex));
169
+ if (descriptor.kind === "ref") {
170
+ (outParam.value as { value: unknown }).value = toNative(descriptor.innerDescriptor, outValue);
171
+ } else if (outValue != null && outParam.value != null) {
172
+ fillCallerAllocatedBuffer(descriptor, outParam.value, outValue);
173
+ }
179
174
  }
180
175
  };
181
176
 
@@ -207,8 +202,8 @@ const applyCallback = (plan: CallbackPlan, thisArg: unknown, inputs: unknown[],
207
202
  }
208
203
  };
209
204
 
210
- const getThisArg = (receiver: CallbackReceiver, wrapped: unknown[]): unknown =>
211
- receiver === "this" ? (wrapped[0] ?? null) : null;
205
+ const getThisArg = (isInstanceBound: boolean, wrapped: unknown[]): unknown =>
206
+ isInstanceBound ? (wrapped[0] ?? null) : null;
212
207
 
213
208
  const wrapCallbackArgs = (effectiveTypes: Descriptor[], rawArgs: unknown[]): void => {
214
209
  let index = 0;
@@ -233,29 +228,35 @@ const trimCallbackInputs = (plan: CallbackPlan, wrapped: unknown[]): unknown[] =
233
228
  return wrapped;
234
229
  };
235
230
 
231
+ const nativeReturn = (plan: CallbackPlan, primary: unknown): unknown =>
232
+ toNative(plan.returnDescriptor, plan.hasPrimary ? primary : undefined);
233
+
236
234
  const runCallback = (plan: CallbackPlan, rawArgs: unknown[]): unknown => {
237
- const { effectiveTypes, returnDescriptor } = plan;
235
+ const { effectiveTypes } = plan;
238
236
  wrapCallbackArgs(effectiveTypes, rawArgs);
239
- const thisArg = getThisArg(plan.receiver, rawArgs);
237
+ const thisArg = getThisArg(plan.isInstanceBound, rawArgs);
240
238
 
241
239
  if (!plan.hasOutParams) {
242
- return toNative(returnDescriptor, plan.fn.apply(thisArg, trimCallbackInputs(plan, rawArgs)));
240
+ return nativeReturn(plan, plan.fn.apply(thisArg, trimCallbackInputs(plan, rawArgs)));
243
241
  }
244
242
 
245
243
  const { inputs, outParams } = partitionCallbackArgs(effectiveTypes, rawArgs, plan.start);
246
244
  const result = applyCallback(plan, thisArg, inputs, outParams);
247
245
 
248
246
  if (outParams.length === 0) {
249
- return toNative(returnDescriptor, result);
247
+ return nativeReturn(plan, result);
250
248
  }
251
249
 
252
- const { lengthLinks, valueParams } = groupOutParams(outParams);
253
- const { primary, outValues } = splitTupleResult(result, returnDescriptor.kind !== "void", valueParams.length);
254
- writeFoldedLengths(lengthLinks, writeOutParams(valueParams, outValues));
250
+ const groups = groupOutParams(outParams, plan.lengthSources);
251
+ const { primary, outValues } = splitTupleResult(result, plan.hasPrimary, groups.valueParams.length);
252
+ writeOutParams(outParams, resolveOutValues(groups, outValues, primary));
255
253
 
256
- return toNative(returnDescriptor, primary);
254
+ return nativeReturn(plan, primary);
257
255
  };
258
256
 
257
+ const planLengthSources = (spec: CallbackSpec, hasFoldedLengths: boolean): LengthSources =>
258
+ hasFoldedLengths ? foldedLengthSources(spec) : new Map<number, LengthSource[]>();
259
+
259
260
  const getEffectiveTypes = (spec: CallbackSpec): Descriptor[] => {
260
261
  const { userDataIndex } = spec;
261
262
 
@@ -267,18 +268,21 @@ const getEffectiveTypes = (spec: CallbackSpec): Descriptor[] => {
267
268
  };
268
269
 
269
270
  const wrapCallbackValue = (spec: CallbackDescriptor, callback: unknown): unknown =>
270
- callback == null ? callback : wrapCallback(callback as Callback, spec, "none");
271
+ callback == null ? callback : wrapCallback(callback as Callback, spec, "callback");
271
272
 
272
- function wrapCallback(fn: Callback, spec: CallbackSpec, receiver: CallbackReceiver): Callback {
273
+ function wrapCallback(fn: Callback, spec: CallbackSpec, kind: CallbackKind): Callback {
273
274
  const effectiveTypes = getEffectiveTypes(spec);
274
- const start = receiver === "none" ? 0 : 1;
275
+ const { isInstanceBound, hasInstanceArg, hasFoldedLengths } = CALLBACK_TRAITS[kind];
276
+ const start = hasInstanceArg ? 1 : 0;
275
277
 
276
278
  const plan: CallbackPlan = {
277
279
  fn,
278
280
  effectiveTypes,
279
281
  returnDescriptor: spec.returnDescriptor,
282
+ hasPrimary: spec.returnDescriptor.kind !== "void",
280
283
  start,
281
- receiver,
284
+ isInstanceBound,
285
+ lengthSources: planLengthSources(spec, hasFoldedLengths),
282
286
  hasOutParams: haveOutParamArgs(effectiveTypes, start),
283
287
  hasRefOutParams: haveRefOutParamArgs(effectiveTypes, start),
284
288
  };
package/src/closure.ts CHANGED
@@ -40,7 +40,7 @@ const MARSHAL_T = callbackT(
40
40
  CLOSURE_T,
41
41
  MARSHAL_VALUE_T,
42
42
  uint32T,
43
- sizedArrayT(MARSHAL_VALUE_T, N_PARAM_VALUES_INDEX, "borrowed", VALUE_SIZE),
43
+ sizedArrayT(MARSHAL_VALUE_T, N_PARAM_VALUES_INDEX, "borrowed", { elementSize: VALUE_SIZE }),
44
44
  uint64T,
45
45
  uint64T,
46
46
  ],
@@ -0,0 +1,21 @@
1
+ import { bind } from "./bind.js";
2
+ import { objectT, voidT } from "./descriptors.js";
3
+ import { GIO_LIB } from "./library.js";
4
+ import { getHandle } from "./registry.js";
5
+
6
+ const readDefaultApplication = bind(GIO_LIB, "g_application_get_default", [], objectT("borrowed"));
7
+ const writeDefaultApplication = bind(GIO_LIB, "g_application_set_default", [objectT("borrowed")], voidT);
8
+
9
+ const claimDefaultApplication = (application: object): void => {
10
+ writeDefaultApplication(getHandle(application));
11
+ };
12
+
13
+ const releaseDefaultApplication = (application: object): void => {
14
+ if (readDefaultApplication() !== application) {
15
+ return;
16
+ }
17
+
18
+ writeDefaultApplication(null);
19
+ };
20
+
21
+ export { claimDefaultApplication, releaseDefaultApplication };
@@ -89,7 +89,10 @@ type BoxedOptions = {
89
89
  size?: number;
90
90
  };
91
91
 
92
- /** How the callee takes a callback's closure, and how long that closure has to stay alive. */
92
+ /**
93
+ * What the bindings do with a callback's return value, how the callee takes its closure, and how
94
+ * long that closure has to stay alive.
95
+ */
93
96
  type CallbackOptions = {
94
97
  /** The callee also takes a destroy notify, which frees the closure once it is done with it. */
95
98
  hasDestroy?: boolean;
@@ -107,10 +110,22 @@ type CallbackOptions = {
107
110
  type ArrayOptions = {
108
111
  /** Stride in bytes between elements stored inline in the array. */
109
112
  elementSize?: number | undefined;
113
+ /** Position of the argument whose buffer a cursor array points into. */
114
+ baseParamIndex?: number | undefined;
110
115
  /** Position of the argument carrying the element count, for a length-bounded array. */
111
116
  sizeParamIndex?: number | undefined;
112
117
  /** Element count of a fixed-length array. */
113
118
  fixedSize?: number | undefined;
119
+ /** Whether the array carries raw bytes, and so decodes to a `Uint8Array` rather than to numbers. */
120
+ isBytes?: boolean | undefined;
121
+ };
122
+
123
+ /** Where a cursor array's base buffer and total length come from. */
124
+ type CursorBounds = {
125
+ /** Position of the argument holding the buffer the cursor points into. */
126
+ baseParamIndex: number;
127
+ /** Position of the argument carrying that buffer's element count. */
128
+ sizeParamIndex: number;
114
129
  };
115
130
 
116
131
  /** How a ref-counted fundamental value is named, wrapped and stored. */
@@ -125,6 +140,12 @@ type FundamentalOptions = {
125
140
  isInline?: boolean;
126
141
  };
127
142
 
143
+ type FundamentalLifecycle = {
144
+ sharedLibrary: string;
145
+ refFnName: string;
146
+ unrefFnName: string;
147
+ };
148
+
128
149
  /** How a plain C struct is stored and wrapped. */
129
150
  type StructOptions = {
130
151
  /** The caller owns the storage the callee fills, so a decoded value is borrowed instead of copied. */
@@ -171,6 +192,7 @@ const voidT: VoidDescriptor = { kind: "void" };
171
192
  const unicharT: UnicharDescriptor = { kind: "unichar" };
172
193
  /** Descriptor for an opaque `gpointer`, taken from a typed array's memory or a numeric address. */
173
194
  const bufferT: BufferDescriptor = { kind: "buffer" };
195
+ const fundamentalLifecycles: Map<string, FundamentalLifecycle> = new Map();
174
196
 
175
197
  /**
176
198
  * Builds a descriptor for a C string, whose optional length sizes the caller-allocated buffer
@@ -280,6 +302,15 @@ const structT = (ownership: Ownership = "borrowed", options: StructOptions = {})
280
302
  return result;
281
303
  };
282
304
 
305
+ const recordFundamentalLifecycle = (typeName: string, lifecycle: FundamentalLifecycle): void => {
306
+ if (!fundamentalLifecycles.has(typeName)) {
307
+ fundamentalLifecycles.set(typeName, lifecycle);
308
+ }
309
+ };
310
+
311
+ const fundamentalLifecycleFor = (typeName: string): FundamentalLifecycle | undefined =>
312
+ fundamentalLifecycles.get(typeName);
313
+
283
314
  /** Builds a descriptor for a fundamental type whose lifetime is managed by named ref and unref functions. */
284
315
  const fundamentalT = (
285
316
  sharedLibrary: string,
@@ -292,6 +323,7 @@ const fundamentalT = (
292
323
 
293
324
  if (options.typeName !== undefined) {
294
325
  result.typeName = options.typeName;
326
+ recordFundamentalLifecycle(options.typeName, { sharedLibrary, refFnName, unrefFnName });
295
327
  }
296
328
 
297
329
  if (options.wrapperClass !== undefined) {
@@ -318,6 +350,10 @@ const arrayT = (
318
350
  result.elementSize = options.elementSize;
319
351
  }
320
352
 
353
+ if (options?.baseParamIndex !== undefined) {
354
+ result.baseParamIndex = options.baseParamIndex;
355
+ }
356
+
321
357
  if (options?.sizeParamIndex !== undefined) {
322
358
  result.sizeParamIndex = options.sizeParamIndex;
323
359
  }
@@ -326,76 +362,107 @@ const arrayT = (
326
362
  result.fixedSize = options.fixedSize;
327
363
  }
328
364
 
365
+ if (options?.isBytes === true) {
366
+ result.isBytes = true;
367
+ }
368
+
329
369
  return result;
330
370
  };
331
371
 
332
372
  /** Builds a descriptor for a `GList` of items. */
333
- const listT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
334
- arrayT(itemDescriptor, "glist", ownership);
373
+ const listT = (
374
+ itemDescriptor: Descriptor,
375
+ ownership: Ownership = "borrowed",
376
+ options: ArrayOptions = {},
377
+ ): ArrayDescriptor => arrayT(itemDescriptor, "glist", ownership, options);
335
378
 
336
379
  /** Builds a descriptor for a `GSList` of items. */
337
- const slistT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
338
- arrayT(itemDescriptor, "gslist", ownership);
380
+ const slistT = (
381
+ itemDescriptor: Descriptor,
382
+ ownership: Ownership = "borrowed",
383
+ options: ArrayOptions = {},
384
+ ): ArrayDescriptor => arrayT(itemDescriptor, "gslist", ownership, options);
339
385
 
340
386
  /** Builds a descriptor for a `GPtrArray` of items. */
341
- const ptrArrayT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
342
- arrayT(itemDescriptor, "gptrarray", ownership);
387
+ const ptrArrayT = (
388
+ itemDescriptor: Descriptor,
389
+ ownership: Ownership = "borrowed",
390
+ options: ArrayOptions = {},
391
+ ): ArrayDescriptor => arrayT(itemDescriptor, "gptrarray", ownership, options);
343
392
 
344
- /** Builds a descriptor for a `GArray` of items, optionally with an explicit element size. */
393
+ /** Builds a descriptor for a `GArray` of items. */
345
394
  const gArrayT = (
346
395
  itemDescriptor: Descriptor,
347
396
  ownership: Ownership = "borrowed",
348
- elementSize?: number,
349
- ): ArrayDescriptor =>
350
- arrayT(itemDescriptor, "garray", ownership, elementSize === undefined ? undefined : { elementSize });
397
+ options: ArrayOptions = {},
398
+ ): ArrayDescriptor => arrayT(itemDescriptor, "garray", ownership, options);
351
399
 
352
400
  /** Builds a descriptor for a `GByteArray`. */
353
401
  const byteArrayT = (ownership: Ownership = "borrowed"): ArrayDescriptor =>
354
- arrayT(uint8T, "gbytearray", ownership);
402
+ arrayT(uint8T, "gbytearray", ownership, { isBytes: true });
355
403
 
356
404
  /** Builds a descriptor for a C array whose length is carried by another argument. */
357
405
  const sizedArrayT = (
358
406
  itemDescriptor: Descriptor,
359
407
  sizeParamIndex: number,
360
408
  ownership: Ownership = "borrowed",
361
- elementSize?: number,
362
- ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { sizeParamIndex, elementSize });
409
+ options: ArrayOptions = {},
410
+ ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { ...options, sizeParamIndex });
411
+
412
+ /**
413
+ * Builds a descriptor for an out pointer into the buffer another argument supplied, decoded as the
414
+ * elements from where it points to the end of that buffer.
415
+ */
416
+ const cursorArrayT = (
417
+ itemDescriptor: Descriptor,
418
+ bounds: CursorBounds,
419
+ ownership: Ownership = "borrowed",
420
+ options: ArrayOptions = {},
421
+ ): ArrayDescriptor => arrayT(itemDescriptor, "cursor", ownership, { ...options, ...bounds });
363
422
 
364
423
  /** Builds a descriptor for a C array of a fixed length. */
365
424
  const fixedArrayT = (
366
425
  itemDescriptor: Descriptor,
367
426
  fixedSize: number,
368
427
  ownership: Ownership = "borrowed",
369
- elementSize?: number,
370
- ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { fixedSize, elementSize });
428
+ options: ArrayOptions = {},
429
+ ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { ...options, fixedSize });
371
430
 
372
- /** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */
373
- const callbackT = (
374
- argDescriptors: Descriptor[],
375
- returnDescriptor: Descriptor,
376
- options?: CallbackOptions,
377
- ): CallbackDescriptor => {
378
- const result: CallbackDescriptor = { kind: "callback", argDescriptors, returnDescriptor };
379
-
380
- if (options?.hasDestroy !== undefined) {
431
+ const applyClosureOptions = (result: CallbackDescriptor, options: CallbackOptions): void => {
432
+ if (options.hasDestroy !== undefined) {
381
433
  result.hasDestroy = options.hasDestroy;
382
434
  }
383
435
 
384
- if (options?.destroyKind !== undefined) {
436
+ if (options.destroyKind !== undefined) {
385
437
  result.destroyKind = options.destroyKind;
386
438
  }
387
439
 
388
- if (options?.hasUserData !== undefined) {
440
+ if (options.hasUserData !== undefined) {
389
441
  result.hasUserData = options.hasUserData;
390
442
  }
391
443
 
392
- if (options?.userDataIndex !== undefined) {
444
+ if (options.userDataIndex !== undefined) {
393
445
  result.userDataIndex = options.userDataIndex;
394
446
  }
395
447
 
396
- if (options?.scope !== undefined) {
448
+ if (options.scope !== undefined) {
397
449
  result.scope = options.scope;
398
450
  }
451
+ };
452
+
453
+ /** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */
454
+ const callbackT = (
455
+ argDescriptors: Descriptor[],
456
+ returnDescriptor: Descriptor,
457
+ options?: CallbackOptions,
458
+ ): CallbackDescriptor => {
459
+ const result: CallbackDescriptor = { kind: "callback", argDescriptors, returnDescriptor };
460
+
461
+ if (options === undefined) {
462
+ return result;
463
+ }
464
+
465
+ applyClosureOptions(result, options);
399
466
 
400
467
  return result;
401
468
  };
@@ -426,6 +493,7 @@ export {
426
493
  flagsT,
427
494
  boxedT,
428
495
  structT,
496
+ fundamentalLifecycleFor,
429
497
  fundamentalT,
430
498
  arrayT,
431
499
  listT,
@@ -435,6 +503,7 @@ export {
435
503
  byteArrayT,
436
504
  sizedArrayT,
437
505
  fixedArrayT,
506
+ cursorArrayT,
438
507
  callbackT,
439
508
  type BoxedDescriptor,
440
509
  type StructDescriptor,
package/src/exit-hook.ts CHANGED
@@ -1,3 +1,21 @@
1
1
  import { quit } from "./lifecycle.js";
2
2
 
3
+ const TERMINATION_SIGNALS: NodeJS.Signals[] = ["SIGHUP", "SIGINT", "SIGTERM"];
4
+
5
+ const isSoleHandler = (signal: NodeJS.Signals): boolean => process.listenerCount(signal) === 1;
6
+
7
+ const quitOnSignal = (signal: NodeJS.Signals): void => {
8
+ if (!isSoleHandler(signal)) {
9
+ return;
10
+ }
11
+
12
+ quit();
13
+ process.removeAllListeners(signal);
14
+ process.kill(process.pid, signal);
15
+ };
16
+
3
17
  process.on("exit", quit);
18
+
19
+ for (const signal of TERMINATION_SIGNALS) {
20
+ process.on(signal, quitOnSignal);
21
+ }
package/src/fn.ts CHANGED
@@ -8,7 +8,7 @@ import { checkError } from "./error.js";
8
8
  import { LIB } from "./library.js";
9
9
  import { fromNative } from "./native-value.js";
10
10
  import { getHandle } from "./registry.js";
11
- import { packTupleResult } from "./tuple.js";
11
+ import { hasSurfacedPrimary, packTupleResult } from "./tuple.js";
12
12
 
13
13
  /** The signature a native function is bound against. */
14
14
  type FnSpec = {
@@ -16,6 +16,11 @@ type FnSpec = {
16
16
  args: Arg[];
17
17
  /** Descriptor for the C return value, packed first when output arguments are also returned. */
18
18
  returns: Descriptor;
19
+ /**
20
+ * The callee returns a value the bindings do not surface, so the call is made against the
21
+ * declared return type and the value is dropped instead of being packed into the result.
22
+ */
23
+ isReturnSkipped?: boolean;
19
24
  /** The function takes a trailing `GError**`, whose contents are thrown as an error on return. */
20
25
  canThrow?: boolean;
21
26
  /**
@@ -135,15 +140,19 @@ const resizeInputs = (inputs: unknown[], argCount: number): unknown[] => {
135
140
  const directCallable = (
136
141
  descriptor: ExternalObject<CallDescriptor>,
137
142
  returnDescriptor: Descriptor,
143
+ hasPrimary: boolean,
138
144
  argCount: number,
139
145
  ): ((...inputs: unknown[]) => unknown) => {
140
- if (returnDescriptor.kind === "void") {
141
- return (...inputs) => {
142
- call(descriptor, resizeInputs(inputs, argCount));
143
- };
146
+ const marshal = (inputs: unknown[]): unknown =>
147
+ fromNative(returnDescriptor, call(descriptor, resizeInputs(inputs, argCount)));
148
+
149
+ if (hasPrimary) {
150
+ return (...inputs) => marshal(inputs);
144
151
  }
145
152
 
146
- return (...inputs) => fromNative(returnDescriptor, call(descriptor, resizeInputs(inputs, argCount)));
153
+ return (...inputs) => {
154
+ marshal(inputs);
155
+ };
147
156
  };
148
157
 
149
158
  function fromNativeCallable(
@@ -152,20 +161,21 @@ function fromNativeCallable(
152
161
  getRefSeeds?: () => RefSeeds | undefined,
153
162
  ): (...inputs: unknown[]) => unknown {
154
163
  const { args, returns: returnDescriptor, canThrow = false } = spec;
155
- const hasPrimary = returnDescriptor.kind !== "void";
164
+ const hasPrimary = hasSurfacedPrimary(returnDescriptor, spec.isReturnSkipped);
156
165
  const plans = buildArgSpecs(args);
157
166
  const outPlans = plans.filter((plan) => plan.isOutParam);
158
167
  const arePassThrough = plans.every((plan, index) => isPassThroughPlan(plan, index));
159
168
 
160
169
  if (!canThrow && arePassThrough) {
161
- return directCallable(descriptor, returnDescriptor, plans.length);
170
+ return directCallable(descriptor, returnDescriptor, hasPrimary, plans.length);
162
171
  }
163
172
 
164
- const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown => {
165
- const primary = hasPrimary ? fromNative(returnDescriptor, nativeResult) : undefined;
166
-
167
- return packTupleResult(readOutParams(outPlans, inputs, nativeValues), primary, hasPrimary);
168
- };
173
+ const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown =>
174
+ packTupleResult(
175
+ readOutParams(outPlans, inputs, nativeValues),
176
+ fromNative(returnDescriptor, nativeResult),
177
+ hasPrimary,
178
+ );
169
179
 
170
180
  if (canThrow) {
171
181
  return (...inputs) => {
@@ -0,0 +1,64 @@
1
+ import type { Descriptor } from "@gtkx/native";
2
+
3
+ type LengthSource = { kind: "return" } | { kind: "outArg"; argIndex: number };
4
+ type LengthSources = Map<number, LengthSource[]>;
5
+
6
+ type FoldedLengthSpec = {
7
+ argDescriptors: Descriptor[];
8
+ returnDescriptor: Descriptor;
9
+ userDataIndex?: number | undefined;
10
+ };
11
+
12
+ const sizedArrayLengthIndex = (descriptor: Descriptor): number | undefined =>
13
+ descriptor.kind === "array" && descriptor.arrayKind === "sized" ? descriptor.sizeParamIndex : undefined;
14
+
15
+ const outArgLengthIndex = (descriptor: Descriptor | undefined): number | undefined =>
16
+ descriptor?.kind === "ref" ? sizedArrayLengthIndex(descriptor.innerDescriptor) : undefined;
17
+
18
+ const effectiveArgIndex = (argIndex: number | undefined, userDataIndex: number | undefined): number | undefined => {
19
+ if (argIndex === undefined || userDataIndex === undefined || argIndex < userDataIndex) {
20
+ return argIndex;
21
+ }
22
+
23
+ return argIndex === userDataIndex ? undefined : argIndex - 1;
24
+ };
25
+
26
+ const addLengthSource = (sources: LengthSources, index: number | undefined, source: LengthSource): void => {
27
+ if (index === undefined) {
28
+ return;
29
+ }
30
+
31
+ const existing = sources.get(index);
32
+
33
+ if (existing === undefined) {
34
+ sources.set(index, [source]);
35
+
36
+ return;
37
+ }
38
+
39
+ existing.push(source);
40
+ };
41
+
42
+ const addOutArgLengthSources = (sources: LengthSources, spec: FoldedLengthSpec): void => {
43
+ const { userDataIndex } = spec;
44
+
45
+ for (const [declaredIndex, descriptor] of spec.argDescriptors.entries()) {
46
+ const argIndex = effectiveArgIndex(declaredIndex, userDataIndex);
47
+
48
+ if (argIndex !== undefined) {
49
+ const lengthIndex = effectiveArgIndex(outArgLengthIndex(descriptor), userDataIndex);
50
+ addLengthSource(sources, lengthIndex, { kind: "outArg", argIndex });
51
+ }
52
+ }
53
+ };
54
+
55
+ const foldedLengthSources = (spec: FoldedLengthSpec): LengthSources => {
56
+ const sources: LengthSources = new Map();
57
+ const returnLengthIndex = effectiveArgIndex(sizedArrayLengthIndex(spec.returnDescriptor), spec.userDataIndex);
58
+ addLengthSource(sources, returnLengthIndex, { kind: "return" });
59
+ addOutArgLengthSources(sources, spec);
60
+
61
+ return sources;
62
+ };
63
+
64
+ export { foldedLengthSources, type LengthSource, type LengthSources };