@gtkx/runtime 2.0.0-beta.1 → 2.0.0-beta.10

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 (74) hide show
  1. package/README.md +43 -40
  2. package/dist/descriptors.d.ts +2 -1
  3. package/dist/descriptors.d.ts.map +1 -1
  4. package/dist/descriptors.js +5 -1
  5. package/dist/descriptors.js.map +1 -1
  6. package/dist/index.d.ts +1 -1
  7. package/dist/index.d.ts.map +1 -1
  8. package/dist/index.js +1 -1
  9. package/dist/index.js.map +1 -1
  10. package/dist/internal.d.ts +17 -0
  11. package/dist/internal.d.ts.map +1 -1
  12. package/dist/internal.js +13 -0
  13. package/dist/internal.js.map +1 -1
  14. package/dist/lifecycle.js +1 -1
  15. package/dist/lifecycle.js.map +1 -1
  16. package/dist/listeners.d.ts +3 -13
  17. package/dist/listeners.d.ts.map +1 -1
  18. package/dist/listeners.js +12 -12
  19. package/dist/listeners.js.map +1 -1
  20. package/dist/mixin.d.ts +5 -3
  21. package/dist/mixin.d.ts.map +1 -1
  22. package/dist/mixin.js +36 -9
  23. package/dist/mixin.js.map +1 -1
  24. package/dist/object.d.ts +14 -3
  25. package/dist/object.d.ts.map +1 -1
  26. package/dist/object.js +20 -27
  27. package/dist/object.js.map +1 -1
  28. package/dist/properties.d.ts +4 -2
  29. package/dist/properties.d.ts.map +1 -1
  30. package/dist/properties.js +69 -16
  31. package/dist/properties.js.map +1 -1
  32. package/dist/property-brand.d.ts +5 -0
  33. package/dist/property-brand.d.ts.map +1 -0
  34. package/dist/property-brand.js +5 -0
  35. package/dist/property-brand.js.map +1 -0
  36. package/dist/register-class.d.ts +102 -20
  37. package/dist/register-class.d.ts.map +1 -1
  38. package/dist/register-class.js +19 -37
  39. package/dist/register-class.js.map +1 -1
  40. package/dist/registry.d.ts +1 -1
  41. package/dist/registry.d.ts.map +1 -1
  42. package/dist/registry.js +4 -4
  43. package/dist/registry.js.map +1 -1
  44. package/dist/signal-brand.d.ts +12 -0
  45. package/dist/signal-brand.d.ts.map +1 -0
  46. package/dist/signal-brand.js +6 -0
  47. package/dist/signal-brand.js.map +1 -0
  48. package/dist/signal.d.ts +39 -2
  49. package/dist/signal.d.ts.map +1 -1
  50. package/dist/signal.js +75 -5
  51. package/dist/signal.js.map +1 -1
  52. package/dist/type.d.ts.map +1 -1
  53. package/dist/type.js +1 -1
  54. package/dist/type.js.map +1 -1
  55. package/dist/value.d.ts +2 -1
  56. package/dist/value.d.ts.map +1 -1
  57. package/dist/value.js +28 -7
  58. package/dist/value.js.map +1 -1
  59. package/package.json +7 -5
  60. package/src/descriptors.ts +7 -0
  61. package/src/index.ts +2 -2
  62. package/src/internal.ts +42 -0
  63. package/src/lifecycle.ts +1 -1
  64. package/src/listeners.ts +21 -26
  65. package/src/mixin.ts +55 -9
  66. package/src/object.ts +57 -7
  67. package/src/properties.ts +94 -17
  68. package/src/property-brand.ts +5 -0
  69. package/src/register-class.ts +296 -95
  70. package/src/registry.ts +4 -4
  71. package/src/signal-brand.ts +29 -0
  72. package/src/signal.ts +177 -6
  73. package/src/type.ts +1 -1
  74. package/src/value.ts +40 -5
package/src/signal.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import type { Descriptor, ExternalObject, Handle } from "@gtkx/native";
2
- import { toCamelIdentifier, upperFirst } from "@gtkx/utils";
2
+ import { type AnyClass, toCamelIdentifier, upperFirst } from "@gtkx/utils";
3
+ import type { ResolvedSignalEmitMap, ResolvedSignalMap } from "./signal-brand.js";
3
4
  import { type Arg, isCallerAllocatedArg, isInoutArg, isOutputArg } from "./arg.js";
4
5
  import { bind } from "./bind.js";
5
6
  import { wrapCallback } from "./callback.js";
@@ -19,7 +20,7 @@ import {
19
20
  voidT,
20
21
  } from "./descriptors.js";
21
22
  import { LIB, VALUE_SIZE, VALUE_T } from "./library.js";
22
- import { getHandle } from "./registry.js";
23
+ import { getClassType, getHandle, getInstanceType } from "./registry.js";
23
24
  import { packTupleResult } from "./tuple.js";
24
25
  import { TYPE_INVALID, type TypedClass, typeInterfaces, typeParent } from "./type.js";
25
26
  import {
@@ -37,6 +38,55 @@ import {
37
38
  /** Function invoked when a connected GObject signal is emitted. */
38
39
  type SignalHandler = (...args: unknown[]) => unknown;
39
40
 
41
+ const isSignalHandler = (value: unknown): value is SignalHandler => typeof value === "function";
42
+
43
+ type DeclaredSignalMap<T> = T extends { __signals__?: infer TSignals } ? NonNullable<TSignals> : never;
44
+ type SignalMap<T> = ResolvedSignalMap<T, DeclaredSignalMap<T>>;
45
+ type SignalName<T> = Extract<keyof SignalMap<T>, string>;
46
+ type DeclaredSignalEmitMap<T> = T extends { __signalEmit__?: infer TSignals }
47
+ ? NonNullable<TSignals>
48
+ : T extends { __signals__?: infer TSignals }
49
+ ? {
50
+ [K in keyof NonNullable<TSignals>]: NonNullable<TSignals>[K] extends (
51
+ ...args: infer TArgs
52
+ ) => infer TResult
53
+ ? { args: TArgs; result: TResult }
54
+ : never;
55
+ }
56
+ : never;
57
+ type SignalEmitMap<T> = ResolvedSignalEmitMap<T, DeclaredSignalEmitMap<T>>;
58
+ type SignalEmitName<T> = Extract<keyof SignalEmitMap<T>, string>;
59
+ type SignalEmitArguments<T, K extends SignalEmitName<T>> = SignalEmitMap<T>[K] extends {
60
+ args: infer TArgs extends unknown[];
61
+ }
62
+ ? TArgs
63
+ : never;
64
+ type SignalEmitResult<T, K extends SignalEmitName<T>> = SignalEmitMap<T>[K] extends {
65
+ result: infer TResult;
66
+ }
67
+ ? TResult
68
+ : never;
69
+
70
+ type SignalConnector = (
71
+ instance: object,
72
+ signal: string,
73
+ handler: SignalHandler,
74
+ isAfter?: boolean,
75
+ ) => number;
76
+ type SignalEmitter = (instance: object, signal: string, args: unknown[]) => unknown;
77
+ type SignalDispatch = {
78
+ connect: SignalConnector;
79
+ emit: SignalEmitter;
80
+ };
81
+ type SignalDispatchSpec = {
82
+ connect: SignalConnector;
83
+ emit: SignalEmitter;
84
+ };
85
+ type PendingSignalDispatch = {
86
+ ownerType: bigint;
87
+ spec: SignalDispatchSpec;
88
+ };
89
+
40
90
  type DeclaredSignalTypes = {
41
91
  paramTypes: bigint[];
42
92
  returnType?: bigint;
@@ -70,6 +120,8 @@ type EmitArg = Arg & {
70
120
  };
71
121
 
72
122
  const connectionTable: WeakMap<object, Map<string, Set<number>>> = new WeakMap();
123
+ const signalDispatchTable: Map<number, SignalDispatch> = new Map();
124
+ const pendingSignalDispatches: Map<string, PendingSignalDispatch[]> = new Map();
73
125
  const gQuarkFromString = bind(LIB, "g_quark_from_string", [stringT("borrowed")], uint32T);
74
126
  const gSignalLookup = bind(LIB, "g_signal_lookup", [stringT("borrowed"), biguint64T], uint32T);
75
127
  const gSignalName = bind(LIB, "g_signal_name", [uint32T], stringT("borrowed"));
@@ -114,6 +166,15 @@ const getSignalBaseName = (signal: string): string => {
114
166
  return detailIndex === -1 ? signal : signal.slice(0, detailIndex);
115
167
  };
116
168
 
169
+ const canonicalSignalName = (signal: string): string => getSignalBaseName(signal).replaceAll("_", "-");
170
+
171
+ const canonicalDetailedSignalName = (signal: string): string => {
172
+ const detailIndex = signal.indexOf("::");
173
+ const base = canonicalSignalName(signal);
174
+
175
+ return detailIndex === -1 ? base : `${base}${signal.slice(detailIndex)}`;
176
+ };
177
+
117
178
  function getSignalDetailQuark(signal: string): number {
118
179
  const detailIndex = signal.indexOf("::");
119
180
 
@@ -180,7 +241,7 @@ function hasSignalListener(instance: object, signals?: string[]): boolean {
180
241
  }
181
242
 
182
243
  const names =
183
- signals === undefined ? bySignal.keys().toArray() : signals.map((signal) => getSignalBaseName(signal));
244
+ signals === undefined ? bySignal.keys().toArray() : signals.map((signal) => canonicalSignalName(signal));
184
245
 
185
246
  return names.some((name) => {
186
247
  const handlerIds = bySignal.get(name);
@@ -242,7 +303,7 @@ function connectSignal(instance: object, signal: string, spec: SignalConnectSpec
242
303
  const key = `${String(type)}\0${getSignalBaseName(signal)}`;
243
304
  const closure = newCCallbackClosure(key, callback, wrapped);
244
305
  const handlerId = gSignalConnectClosure(getHandle(instance), signal, closure, isAfter) as number;
245
- trackConnection(instance, getSignalBaseName(signal), handlerId);
306
+ trackConnection(instance, canonicalSignalName(signal), handlerId);
246
307
 
247
308
  return handlerId;
248
309
  }
@@ -254,7 +315,7 @@ function overrideSignalClassClosure(type: bigint, signalId: number, handler: Sig
254
315
  function connectClosureSignal(instance: object, signal: string, handler: SignalHandler, isAfter: boolean): number {
255
316
  const closure = toClosure((...args: unknown[]) => Reflect.apply(handler, null, args.slice(1)));
256
317
  const handlerId = gSignalConnectClosure(getHandle(instance), signal, closure, isAfter) as number;
257
- trackConnection(instance, getSignalBaseName(signal), handlerId);
318
+ trackConnection(instance, canonicalSignalName(signal), handlerId);
258
319
 
259
320
  return handlerId;
260
321
  }
@@ -350,19 +411,129 @@ function emitSignal(instance: object, signal: string, args: EmitArg[], returns?:
350
411
  return packTupleResult(readEmitOutputs(reads), fromValue(returnValue), true);
351
412
  }
352
413
 
414
+ function installSignalDispatch(
415
+ klass: AnyClass,
416
+ names: readonly string[],
417
+ spec: SignalDispatchSpec,
418
+ ): void {
419
+ const ownerType = getClassType(klass);
420
+
421
+ for (const rawName of names) {
422
+ const name = canonicalSignalName(rawName);
423
+ pendingSignalDispatches.getOrInsertComputed(name, () => []).push({ ownerType, spec });
424
+ }
425
+ }
426
+
427
+ const resolvePendingSignalDispatch = (signalId: number, name: string): SignalDispatch | undefined => {
428
+ const pending = pendingSignalDispatches.get(name) ?? [];
429
+
430
+ for (let index = pending.length - 1; index >= 0; index -= 1) {
431
+ const entry = pending[index];
432
+
433
+ if (
434
+ entry !== undefined &&
435
+ entry.ownerType !== TYPE_INVALID &&
436
+ signalIdFor(entry.ownerType, name) === signalId
437
+ ) {
438
+ signalDispatchTable.set(signalId, entry.spec);
439
+
440
+ return entry.spec;
441
+ }
442
+ }
443
+
444
+ return undefined;
445
+ };
446
+
447
+ const signalDispatchFor = (instance: object, signal: string): SignalDispatch | undefined => {
448
+ const name = canonicalSignalName(signal);
449
+ const instanceType = getInstanceType(instance);
450
+
451
+ if (instanceType === TYPE_INVALID) {
452
+ return undefined;
453
+ }
454
+
455
+ const signalId = signalIdFor(instanceType, name);
456
+
457
+ if (signalId === 0) {
458
+ return undefined;
459
+ }
460
+
461
+ return signalDispatchTable.get(signalId) ?? resolvePendingSignalDispatch(signalId, name);
462
+ };
463
+
464
+ const connectDispatcherFor = (instance: object, signal: string): SignalConnector => {
465
+ const dispatch = signalDispatchFor(instance, signal);
466
+
467
+ if (dispatch === undefined) {
468
+ throw new TypeError("connectSignal: unknown signal");
469
+ }
470
+
471
+ return dispatch.connect;
472
+ };
473
+
474
+ const emitDispatcherFor = (instance: object, signal: string): SignalEmitter => {
475
+ const dispatch = signalDispatchFor(instance, signal);
476
+
477
+ if (dispatch === undefined) {
478
+ throw new TypeError("emitSignal: unknown signal");
479
+ }
480
+
481
+ return dispatch.emit;
482
+ };
483
+
484
+ function signalConnect<T extends object, K extends SignalName<NoInfer<T>>>(
485
+ instance: T,
486
+ signal: K,
487
+ handler: SignalMap<NoInfer<T>>[K],
488
+ isAfter?: boolean,
489
+ ): number {
490
+ return connectSignalByName(instance, signal, handler, isAfter);
491
+ }
492
+
493
+ function connectSignalByName(
494
+ instance: object,
495
+ signal: string,
496
+ handler: unknown,
497
+ isAfter?: boolean,
498
+ ): number {
499
+ if (!isSignalHandler(handler)) {
500
+ throw new TypeError("connectSignal: handler must be a function");
501
+ }
502
+
503
+ return connectDispatcherFor(instance, signal)(instance, signal, handler, isAfter);
504
+ }
505
+
506
+ function emitSignalByName(instance: object, signal: string, args: unknown[]): unknown {
507
+ return emitDispatcherFor(instance, signal)(instance, signal, args);
508
+ }
509
+
510
+ function signalEmit<T extends object, K extends SignalEmitName<NoInfer<T>>>(
511
+ instance: T,
512
+ signal: K,
513
+ ...args: SignalEmitArguments<NoInfer<T>, K>
514
+ ): SignalEmitResult<NoInfer<T>, K> {
515
+ return emitSignalByName(instance, signal, args) as SignalEmitResult<NoInfer<T>, K>;
516
+ }
517
+
353
518
  export {
519
+ canonicalDetailedSignalName,
520
+ canonicalSignalName,
354
521
  signalForHandlerName,
355
522
  getSignalBaseName,
356
523
  signalIdFor,
357
524
  connectClosureSignal,
358
525
  connectSignal,
526
+ connectSignalByName,
359
527
  type DeclaredSignalTypes,
360
528
  disconnectSignal,
361
529
  emitDeclaredSignal,
362
530
  emitSignal,
531
+ emitSignalByName,
363
532
  hasSignalListener,
364
533
  isSignalHandlerConnected,
534
+ installSignalDispatch,
365
535
  overrideSignalClassClosure,
366
- untrackConnection,
536
+ signalConnect,
537
+ signalEmit,
367
538
  type SignalHandler,
368
539
  };
package/src/type.ts CHANGED
@@ -86,7 +86,7 @@ const TYPE_VARIANT: bigint = typeFromName("GVariant");
86
86
  /** GType tag for a Unicode character stored as `guint`. */
87
87
  const TYPE_UNICHAR: bigint = typeFromName("guint");
88
88
  const getErrorType: () => bigint = lazyType("GError");
89
- const getStrvType: () => bigint = lazyType("GStrv");
89
+ const getStrvType = (): bigint => resolveType(LIB, "g_strv_get_type");
90
90
 
91
91
  const PLAIN_DESCRIPTOR_TYPES: Partial<Record<Descriptor["kind"], bigint>> = {
92
92
  boolean: TYPE_BOOLEAN,
package/src/value.ts CHANGED
@@ -17,6 +17,7 @@ import {
17
17
  int32T,
18
18
  type ObjectDescriptor,
19
19
  objectT,
20
+ preserveArrayNull,
20
21
  stringT,
21
22
  uint8T,
22
23
  uint32T,
@@ -133,11 +134,25 @@ const objectValueFundamentals: Set<bigint> = new Set([TYPE_OBJECT, TYPE_INTERFAC
133
134
  const paramValueType = bindValueType("param", PARAM_T);
134
135
  const variantValueType = bindValueType("variant", VARIANT_T);
135
136
  const pointerValueType = bindValueType("pointer", uint64T);
136
- const setStrvBoxed = bind(LIB, "g_value_set_boxed", [VALUE_T, arrayT(stringT("borrowed"))], voidT);
137
+ const strvDescriptor = arrayT(stringT("borrowed"));
138
+ const nullableStrvDescriptor = preserveArrayNull(arrayT(stringT("borrowed")));
139
+ const setStrvBoxed = bind(LIB, "g_value_set_boxed", [VALUE_T, strvDescriptor], voidT);
140
+ const getStrvBoxed = bind(LIB, "g_value_get_boxed", [VALUE_T], strvDescriptor);
141
+ const getNullableStrvBoxed = bind(
142
+ LIB,
143
+ "g_value_get_boxed",
144
+ [VALUE_T],
145
+ nullableStrvDescriptor,
146
+ );
137
147
 
138
148
  const strvValueType: ValueType = {
139
149
  set: setStrvValue,
140
- get: bind(LIB, "g_value_get_boxed", [VALUE_T], arrayT(stringT("borrowed"))),
150
+ get: getStrvBoxed,
151
+ };
152
+
153
+ const nullableStrvValueType: ValueType = {
154
+ set: setStrvValue,
155
+ get: getNullableStrvBoxed,
141
156
  };
142
157
 
143
158
  const setByteArrayBoxed = bind(LIB, "g_value_set_boxed", [VALUE_T, byteArrayT()], voidT);
@@ -412,7 +427,7 @@ function setWrappedBoxedValue(value: ExternalObject<Handle>, type: bigint, boxed
412
427
 
413
428
  const arrayValueType = (descriptor: ArrayDescriptor): ValueType => {
414
429
  if (descriptor.itemDescriptor.kind === "string" && descriptor.arrayKind === "array") {
415
- return strvValueType;
430
+ return descriptor.preserveNull === true ? nullableStrvValueType : strvValueType;
416
431
  }
417
432
 
418
433
  if (descriptor.arrayKind === "gbytearray") {
@@ -728,6 +743,9 @@ function fromValue(value: ExternalObject<Handle>): unknown {
728
743
  return get(value);
729
744
  }
730
745
 
746
+ const fromObjectPropertyValue = (value: ExternalObject<Handle>): unknown =>
747
+ getValueType(value) === getStrvType() ? nullableStrvValueType.get(value) : fromValue(value);
748
+
731
749
  const objectValueWithFallback = (
732
750
  descriptor: ObjectDescriptor,
733
751
  value: ExternalObject<Handle>,
@@ -739,9 +757,25 @@ const objectValueWithFallback = (
739
757
  return { wrapped: wrapObject(objectValueType.get(value), descriptor.fallbackClass) };
740
758
  };
741
759
 
760
+ const arrayValueGetterForDescriptor = (descriptor: ArrayDescriptor): ValueGetter | undefined => {
761
+ if (descriptor.arrayKind === "gbytearray") {
762
+ return byteArrayValueGetterFor(descriptor.isBytes === true);
763
+ }
764
+
765
+ return descriptor.itemDescriptor.kind === "string" && descriptor.arrayKind === "array"
766
+ ? arrayValueType(descriptor).get
767
+ : undefined;
768
+ };
769
+
770
+ const fromArrayValueForDescriptor = (descriptor: ArrayDescriptor, value: ExternalObject<Handle>): unknown => {
771
+ const get = arrayValueGetterForDescriptor(descriptor);
772
+
773
+ return get === undefined ? fromValue(value) : get(value);
774
+ };
775
+
742
776
  const fromValueForDescriptor = (descriptor: Descriptor, value: ExternalObject<Handle>): unknown => {
743
- if (descriptor.kind === "array" && descriptor.arrayKind === "gbytearray") {
744
- return byteArrayValueGetterFor(descriptor.isBytes === true)(value);
777
+ if (descriptor.kind === "array") {
778
+ return fromArrayValueForDescriptor(descriptor, value);
745
779
  }
746
780
 
747
781
  if (descriptor.kind === "object") {
@@ -815,6 +849,7 @@ export {
815
849
  tryToValueHandle,
816
850
  ValueMarshalError,
817
851
  fromValue,
852
+ fromObjectPropertyValue,
818
853
  fromValueForDescriptor,
819
854
  valueGuardOverrideFor,
820
855
  newValueForDescriptor,