@gtkx/runtime 0.0.1 → 1.0.0-rc.1

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 (113) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +171 -3
  3. package/dist/arg.d.ts +12 -0
  4. package/dist/arg.d.ts.map +1 -0
  5. package/dist/arg.js +5 -0
  6. package/dist/arg.js.map +1 -0
  7. package/dist/bind.d.ts +4 -0
  8. package/dist/bind.d.ts.map +1 -0
  9. package/dist/bind.js +17 -0
  10. package/dist/bind.js.map +1 -0
  11. package/dist/callback.d.ts +13 -0
  12. package/dist/callback.d.ts.map +1 -0
  13. package/dist/callback.js +68 -0
  14. package/dist/callback.js.map +1 -0
  15. package/dist/descriptors.d.ts +155 -0
  16. package/dist/descriptors.d.ts.map +1 -0
  17. package/dist/descriptors.js +103 -0
  18. package/dist/descriptors.js.map +1 -0
  19. package/dist/error.d.ts +25 -0
  20. package/dist/error.d.ts.map +1 -0
  21. package/dist/error.js +29 -0
  22. package/dist/error.js.map +1 -0
  23. package/dist/fn.d.ts +10 -0
  24. package/dist/fn.d.ts.map +1 -0
  25. package/dist/fn.js +79 -0
  26. package/dist/fn.js.map +1 -0
  27. package/dist/index.d.ts +15 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +15 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/internal.d.ts +5 -0
  32. package/dist/internal.d.ts.map +1 -0
  33. package/dist/internal.js +5 -0
  34. package/dist/internal.js.map +1 -0
  35. package/dist/library.d.ts +7 -0
  36. package/dist/library.d.ts.map +1 -0
  37. package/dist/library.js +13 -0
  38. package/dist/library.js.map +1 -0
  39. package/dist/lifecycle.d.ts +41 -0
  40. package/dist/lifecycle.d.ts.map +1 -0
  41. package/dist/lifecycle.js +55 -0
  42. package/dist/lifecycle.js.map +1 -0
  43. package/dist/listeners.d.ts +36 -0
  44. package/dist/listeners.d.ts.map +1 -0
  45. package/dist/listeners.js +72 -0
  46. package/dist/listeners.js.map +1 -0
  47. package/dist/mixin.d.ts +19 -0
  48. package/dist/mixin.d.ts.map +1 -0
  49. package/dist/mixin.js +39 -0
  50. package/dist/mixin.js.map +1 -0
  51. package/dist/native-value.d.ts +12 -0
  52. package/dist/native-value.d.ts.map +1 -0
  53. package/dist/native-value.js +72 -0
  54. package/dist/native-value.js.map +1 -0
  55. package/dist/object.d.ts +31 -0
  56. package/dist/object.d.ts.map +1 -0
  57. package/dist/object.js +63 -0
  58. package/dist/object.js.map +1 -0
  59. package/dist/promisify.d.ts +13 -0
  60. package/dist/promisify.d.ts.map +1 -0
  61. package/dist/promisify.js +36 -0
  62. package/dist/promisify.js.map +1 -0
  63. package/dist/register-class.d.ts +17 -0
  64. package/dist/register-class.d.ts.map +1 -0
  65. package/dist/register-class.js +124 -0
  66. package/dist/register-class.js.map +1 -0
  67. package/dist/registry.d.ts +67 -0
  68. package/dist/registry.d.ts.map +1 -0
  69. package/dist/registry.js +167 -0
  70. package/dist/registry.js.map +1 -0
  71. package/dist/signal.d.ts +34 -0
  72. package/dist/signal.d.ts.map +1 -0
  73. package/dist/signal.js +88 -0
  74. package/dist/signal.js.map +1 -0
  75. package/dist/t.d.ts +50 -0
  76. package/dist/t.d.ts.map +1 -0
  77. package/dist/t.js +48 -0
  78. package/dist/t.js.map +1 -0
  79. package/dist/tuple.d.ts +6 -0
  80. package/dist/tuple.d.ts.map +1 -0
  81. package/dist/tuple.js +23 -0
  82. package/dist/tuple.js.map +1 -0
  83. package/dist/type.d.ts +81 -0
  84. package/dist/type.d.ts.map +1 -0
  85. package/dist/type.js +174 -0
  86. package/dist/type.js.map +1 -0
  87. package/dist/value.d.ts +20 -0
  88. package/dist/value.d.ts.map +1 -0
  89. package/dist/value.js +227 -0
  90. package/dist/value.js.map +1 -0
  91. package/package.json +56 -13
  92. package/src/arg.ts +13 -0
  93. package/src/bind.ts +22 -0
  94. package/src/callback.ts +87 -0
  95. package/src/descriptors.ts +208 -0
  96. package/src/error.ts +49 -0
  97. package/src/fn.ts +108 -0
  98. package/src/index.ts +57 -0
  99. package/src/internal.ts +12 -0
  100. package/src/library.ts +15 -0
  101. package/src/lifecycle.ts +73 -0
  102. package/src/listeners.ts +84 -0
  103. package/src/mixin.ts +48 -0
  104. package/src/native-value.ts +78 -0
  105. package/src/object.ts +83 -0
  106. package/src/promisify.ts +40 -0
  107. package/src/register-class.ts +178 -0
  108. package/src/registry.ts +221 -0
  109. package/src/signal.ts +152 -0
  110. package/src/t.ts +88 -0
  111. package/src/tuple.ts +25 -0
  112. package/src/type.ts +200 -0
  113. package/src/value.ts +307 -0
@@ -0,0 +1,208 @@
1
+ import type { ArrayKind, Descriptor, Ownership } from "@gtkx/native";
2
+ import type { AnyClass } from "@gtkx/utils";
3
+
4
+ export type Int8Descriptor = Extract<Descriptor, { kind: "int8" }>;
5
+ export type Uint8Descriptor = Extract<Descriptor, { kind: "uint8" }>;
6
+ export type Int16Descriptor = Extract<Descriptor, { kind: "int16" }>;
7
+ export type Uint16Descriptor = Extract<Descriptor, { kind: "uint16" }>;
8
+ export type Int32Descriptor = Extract<Descriptor, { kind: "int32" }>;
9
+ export type Uint32Descriptor = Extract<Descriptor, { kind: "uint32" }>;
10
+ export type Int64Descriptor = Extract<Descriptor, { kind: "int64" }>;
11
+ export type Uint64Descriptor = Extract<Descriptor, { kind: "uint64" }>;
12
+ export type BigInt64Descriptor = Extract<Descriptor, { kind: "bigint64" }>;
13
+ export type BigUint64Descriptor = Extract<Descriptor, { kind: "biguint64" }>;
14
+ export type Float32Descriptor = Extract<Descriptor, { kind: "float32" }>;
15
+ export type Float64Descriptor = Extract<Descriptor, { kind: "float64" }>;
16
+ export type EnumDescriptor = Extract<Descriptor, { kind: "enum" }>;
17
+ export type FlagsDescriptor = Extract<Descriptor, { kind: "flags" }>;
18
+ export type BooleanDescriptor = Extract<Descriptor, { kind: "boolean" }>;
19
+ export type StringDescriptor = Extract<Descriptor, { kind: "string" }>;
20
+ export type ObjectDescriptor = Extract<Descriptor, { kind: "object" }>;
21
+ export type UnicharDescriptor = Extract<Descriptor, { kind: "unichar" }>;
22
+ export type VoidDescriptor = Extract<Descriptor, { kind: "void" }>;
23
+ export type BufferDescriptor = Extract<Descriptor, { kind: "buffer" }>;
24
+ export type BoxedDescriptor = Extract<Descriptor, { kind: "boxed" }>;
25
+ export type StructDescriptor = Extract<Descriptor, { kind: "struct" }> & { wrapperClass?: AnyClass };
26
+ export type FundamentalDescriptor = Extract<Descriptor, { kind: "fundamental" }> & { wrapperClass?: AnyClass };
27
+ export type ArrayDescriptor = Extract<Descriptor, { kind: "array" }>;
28
+ export type HashTableDescriptor = Extract<Descriptor, { kind: "hashtable" }>;
29
+ export type CallbackDescriptor = Extract<Descriptor, { kind: "callback" }>;
30
+ export type RefDescriptor = Extract<Descriptor, { kind: "ref" }>;
31
+ export type TypeDescriptor = BigUint64Descriptor & { type: true };
32
+
33
+ type BoxedOptions = {
34
+ callerAllocated?: boolean;
35
+ ownership?: Ownership;
36
+ sharedLibrary?: string;
37
+ getTypeFnName?: string;
38
+ freeFnName?: string;
39
+ size?: number;
40
+ };
41
+
42
+ type CallbackOptions = {
43
+ hasDestroy?: boolean;
44
+ userDataIndex?: number;
45
+ scope?: CallbackDescriptor["scope"];
46
+ };
47
+
48
+ type ArrayOptions = {
49
+ elementSize?: number | undefined;
50
+ sizeParamIndex?: number | undefined;
51
+ fixedSize?: number | undefined;
52
+ };
53
+
54
+ type FundamentalOptions = {
55
+ ownership?: Ownership;
56
+ typeName?: string;
57
+ wrapperClass?: AnyClass;
58
+ };
59
+
60
+ type StructOptions = {
61
+ callerAllocated?: boolean;
62
+ size?: number;
63
+ wrapperClass?: AnyClass;
64
+ };
65
+
66
+ export const int8T: Int8Descriptor = { kind: "int8" };
67
+ export const uint8T: Uint8Descriptor = { kind: "uint8" };
68
+ export const int16T: Int16Descriptor = { kind: "int16" };
69
+ export const uint16T: Uint16Descriptor = { kind: "uint16" };
70
+ export const int32T: Int32Descriptor = { kind: "int32" };
71
+ export const uint32T: Uint32Descriptor = { kind: "uint32" };
72
+ export const int64T: Int64Descriptor = { kind: "int64" };
73
+ export const uint64T: Uint64Descriptor = { kind: "uint64" };
74
+ export const bigint64T: BigInt64Descriptor = { kind: "bigint64" };
75
+ export const biguint64T: BigUint64Descriptor = { kind: "biguint64" };
76
+ export const gtypeT: TypeDescriptor = { kind: "biguint64", type: true };
77
+ export const float32T: Float32Descriptor = { kind: "float32" };
78
+ export const float64T: Float64Descriptor = { kind: "float64" };
79
+ export const booleanT: BooleanDescriptor = { kind: "boolean" };
80
+ export const voidT: VoidDescriptor = { kind: "void" };
81
+ export const unicharT: UnicharDescriptor = { kind: "unichar" };
82
+ export const bufferT: BufferDescriptor = { kind: "buffer" };
83
+
84
+ export const stringT = (ownership: Ownership = "borrowed", length?: number): StringDescriptor =>
85
+ length === undefined ? { kind: "string", ownership } : { kind: "string", ownership, length };
86
+
87
+ export const objectT = (ownership: Ownership = "borrowed"): ObjectDescriptor => ({ kind: "object", ownership });
88
+
89
+ export const refT = (innerDescriptor: Descriptor, inout = false): RefDescriptor =>
90
+ inout ? { kind: "ref", innerDescriptor, inout: true } : { kind: "ref", innerDescriptor };
91
+
92
+ export const hashTableT = (
93
+ keyDescriptor: Descriptor,
94
+ valueDescriptor: Descriptor,
95
+ ownership: Ownership = "borrowed",
96
+ ): HashTableDescriptor => ({
97
+ kind: "hashtable",
98
+ keyDescriptor,
99
+ valueDescriptor,
100
+ ownership,
101
+ });
102
+
103
+ export const enumT = (sharedLibrary: string, getTypeFnName: string, signed: boolean): EnumDescriptor => ({
104
+ kind: "enum",
105
+ sharedLibrary,
106
+ getTypeFnName,
107
+ signed,
108
+ });
109
+
110
+ export const flagsT = (sharedLibrary: string, getTypeFnName: string, signed: boolean): FlagsDescriptor => ({
111
+ kind: "flags",
112
+ sharedLibrary,
113
+ getTypeFnName,
114
+ signed,
115
+ });
116
+
117
+ export const boxedT = (typeName: string, options: BoxedOptions = {}): BoxedDescriptor => {
118
+ const result: BoxedDescriptor = {
119
+ kind: "boxed",
120
+ ownership: options.ownership ?? "borrowed",
121
+ typeName,
122
+ };
123
+ if (options.sharedLibrary !== undefined) result.sharedLibrary = options.sharedLibrary;
124
+ if (options.getTypeFnName !== undefined) result.getTypeFnName = options.getTypeFnName;
125
+ if (options.freeFnName !== undefined) result.freeFnName = options.freeFnName;
126
+ if (options.callerAllocated) result.callerAllocated = true;
127
+ if (options.size !== undefined) result.size = options.size;
128
+ return result;
129
+ };
130
+
131
+ export const structT = (ownership: Ownership = "borrowed", options: StructOptions = {}): StructDescriptor => {
132
+ const result: StructDescriptor = { kind: "struct", ownership };
133
+ if (options.size !== undefined) result.size = options.size;
134
+ if (options.wrapperClass !== undefined) result.wrapperClass = options.wrapperClass;
135
+ if (options.callerAllocated) result.callerAllocated = true;
136
+ return result;
137
+ };
138
+
139
+ export const fundamentalT = (
140
+ sharedLibrary: string,
141
+ refFnName: string,
142
+ unrefFnName: string,
143
+ options: FundamentalOptions = {},
144
+ ): FundamentalDescriptor => {
145
+ const ownership = options.ownership ?? "borrowed";
146
+ const result: FundamentalDescriptor = { kind: "fundamental", ownership, sharedLibrary, refFnName, unrefFnName };
147
+ if (options.typeName !== undefined) result.typeName = options.typeName;
148
+ if (options.wrapperClass !== undefined) result.wrapperClass = options.wrapperClass;
149
+ return result;
150
+ };
151
+
152
+ export const arrayT = (
153
+ itemDescriptor: Descriptor,
154
+ arrayKind: ArrayKind = "array",
155
+ ownership: Ownership = "borrowed",
156
+ options?: ArrayOptions,
157
+ ): ArrayDescriptor => {
158
+ const result: ArrayDescriptor = { kind: "array", itemDescriptor, arrayKind, ownership };
159
+ if (options?.elementSize !== undefined) result.elementSize = options.elementSize;
160
+ if (options?.sizeParamIndex !== undefined) result.sizeParamIndex = options.sizeParamIndex;
161
+ if (options?.fixedSize !== undefined) result.fixedSize = options.fixedSize;
162
+ return result;
163
+ };
164
+
165
+ export const listT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
166
+ arrayT(itemDescriptor, "glist", ownership);
167
+
168
+ export const slistT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
169
+ arrayT(itemDescriptor, "gslist", ownership);
170
+
171
+ export const ptrArrayT = (itemDescriptor: Descriptor, ownership: Ownership = "borrowed"): ArrayDescriptor =>
172
+ arrayT(itemDescriptor, "gptrarray", ownership);
173
+
174
+ export const gArrayT = (
175
+ itemDescriptor: Descriptor,
176
+ ownership: Ownership = "borrowed",
177
+ elementSize?: number,
178
+ ): ArrayDescriptor =>
179
+ arrayT(itemDescriptor, "garray", ownership, elementSize === undefined ? undefined : { elementSize });
180
+
181
+ export const byteArrayT = (ownership: Ownership = "borrowed"): ArrayDescriptor =>
182
+ arrayT(uint8T, "gbytearray", ownership);
183
+
184
+ export const sizedArrayT = (
185
+ itemDescriptor: Descriptor,
186
+ sizeParamIndex: number,
187
+ ownership: Ownership = "borrowed",
188
+ elementSize?: number,
189
+ ): ArrayDescriptor => arrayT(itemDescriptor, "sized", ownership, { sizeParamIndex, elementSize });
190
+
191
+ export const fixedArrayT = (
192
+ itemDescriptor: Descriptor,
193
+ fixedSize: number,
194
+ ownership: Ownership = "borrowed",
195
+ elementSize?: number,
196
+ ): ArrayDescriptor => arrayT(itemDescriptor, "fixed", ownership, { fixedSize, elementSize });
197
+
198
+ export const callbackT = (
199
+ argDescriptors: Descriptor[],
200
+ returnDescriptor: Descriptor,
201
+ options?: CallbackOptions,
202
+ ): CallbackDescriptor => {
203
+ const result: CallbackDescriptor = { kind: "callback", argDescriptors, returnDescriptor };
204
+ if (options?.hasDestroy !== undefined) result.hasDestroy = options.hasDestroy;
205
+ if (options?.userDataIndex !== undefined) result.userDataIndex = options.userDataIndex;
206
+ if (options?.scope !== undefined) result.scope = options.scope;
207
+ return result;
208
+ };
package/src/error.ts ADDED
@@ -0,0 +1,49 @@
1
+ import type { ExternalObject, Handle, Ref } from "@gtkx/native";
2
+ import { getWrapperClass, wrapHandle } from "./registry.js";
3
+ import { getErrorType, isTypedClass } from "./type.js";
4
+
5
+ type ErrorLike = {
6
+ domain: number;
7
+ code: number;
8
+ message: string;
9
+ };
10
+
11
+ /**
12
+ * An error enum object carrying its member codes plus an `instanceof` check that
13
+ * matches wrapped GLib errors belonging to a specific error domain.
14
+ */
15
+ export type ErrorDomain<T extends Record<string, number>> = T & {
16
+ [Symbol.hasInstance]: (value: unknown) => value is ErrorLike;
17
+ };
18
+
19
+ export function checkError(error: Ref): void {
20
+ if (error.value !== null) {
21
+ const gerror = wrapHandle<ErrorLike>(error.value as ExternalObject<Handle>, getWrapperClass(getErrorType()));
22
+ Error.captureStackTrace(gerror, checkError);
23
+ throw gerror;
24
+ }
25
+ }
26
+
27
+ const isError = (value: unknown): value is ErrorLike => isTypedClass(value) && value.__type__ === getErrorType();
28
+
29
+ /**
30
+ * Builds an error domain enum object from its members and a lazy resolver for the
31
+ * domain quark, giving it an `instanceof` check that matches wrapped GLib errors of
32
+ * that domain.
33
+ *
34
+ * @param resolveDomain Called once, on first `instanceof` check, to obtain the domain quark.
35
+ * @param members The error code enum members to expose on the returned object.
36
+ */
37
+ export function createErrorDomain<const T extends Record<string, number>>(
38
+ resolveDomain: () => number,
39
+ members: T,
40
+ ): ErrorDomain<T> {
41
+ let domain: number | undefined;
42
+ const hasInstance = (value: unknown): value is ErrorLike => {
43
+ domain ??= resolveDomain();
44
+ return isError(value) && value.domain === domain;
45
+ };
46
+ const enumObject: Record<string, unknown> = { ...members };
47
+ Object.defineProperty(enumObject, Symbol.hasInstance, { value: hasInstance });
48
+ return enumObject as ErrorDomain<T>;
49
+ }
package/src/fn.ts ADDED
@@ -0,0 +1,108 @@
1
+ import type { Descriptor, Ref } from "@gtkx/native";
2
+ import { type Arg, isCallerAllocatedArg, isInoutArg, isOutputArg, isRefArg } from "./arg.js";
3
+ import { bind } from "./bind.js";
4
+ import { wrapCallbackValue } from "./callback.js";
5
+ import { boxedT, refT } from "./descriptors.js";
6
+ import { checkError } from "./error.js";
7
+ import { LIB } from "./library.js";
8
+ import { fromNative } from "./native-value.js";
9
+ import { getHandle } from "./registry.js";
10
+ import { packTupleResult } from "./tuple.js";
11
+
12
+ type FnSpec = {
13
+ args: Arg[];
14
+ returns: Descriptor;
15
+ throws?: boolean;
16
+ };
17
+
18
+ type ArgSpec = {
19
+ arg: Arg;
20
+ isRef: boolean;
21
+ isCallerAllocated: boolean;
22
+ consumesInput: boolean;
23
+ inputIndex: number;
24
+ isOutParam: boolean;
25
+ };
26
+
27
+ const buildNativeArgTypes = (args: Arg[], throws: boolean): Descriptor[] => {
28
+ const nativeArgTypes = args.map((argSpec) =>
29
+ argSpec.direction !== undefined && argSpec.callerAllocated !== true ? refT(argSpec.type) : argSpec.type,
30
+ );
31
+ if (throws)
32
+ nativeArgTypes.push(
33
+ refT(boxedT("GError", { ownership: "full", sharedLibrary: LIB, getTypeFnName: "g_error_get_type" })),
34
+ );
35
+ return nativeArgTypes;
36
+ };
37
+
38
+ const buildArgSpecs = (args: Arg[]): ArgSpec[] => {
39
+ let inputCursor = 0;
40
+ return args.map((arg) => {
41
+ const isRef = isRefArg(arg);
42
+ const consumesInput = !isRef || isInoutArg(arg);
43
+ const isOutParam = isOutputArg(arg) && arg.consumed !== true;
44
+ return {
45
+ arg,
46
+ isRef,
47
+ isCallerAllocated: isCallerAllocatedArg(arg),
48
+ consumesInput,
49
+ inputIndex: consumesInput ? inputCursor++ : -1,
50
+ isOutParam,
51
+ };
52
+ });
53
+ };
54
+
55
+ const buildNativeValues = (plans: ArgSpec[], inputs: unknown[]): unknown[] =>
56
+ plans.map(({ arg, isRef, isCallerAllocated, consumesInput, inputIndex }) => {
57
+ if (isCallerAllocated) {
58
+ const wrapper = inputs[inputIndex];
59
+ return wrapper == null ? wrapper : getHandle(wrapper as object);
60
+ }
61
+ if (isRef) {
62
+ return { value: consumesInput ? inputs[inputIndex] : null };
63
+ }
64
+ if (arg.type.kind === "callback") {
65
+ return wrapCallbackValue(arg.type, inputs[inputIndex]);
66
+ }
67
+ return inputs[inputIndex];
68
+ });
69
+
70
+ const readOutParams = (plans: ArgSpec[], inputs: unknown[], nativeValues: unknown[]): unknown[] => {
71
+ const outParams: unknown[] = [];
72
+ plans.forEach(({ arg, isCallerAllocated, inputIndex, isOutParam }, index) => {
73
+ if (!isOutParam) return;
74
+ outParams.push(
75
+ isCallerAllocated ? inputs[inputIndex] : fromNative(arg.type, (nativeValues[index] as Ref).value),
76
+ );
77
+ });
78
+ return outParams;
79
+ };
80
+
81
+ export function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inputs: unknown[]) => unknown {
82
+ const { args, returns: returnDescriptor, throws = false } = spec;
83
+ const nativeArgTypes = buildNativeArgTypes(args, throws);
84
+ const nativeFn = bind(sharedLibrary, symbol, nativeArgTypes, returnDescriptor);
85
+ const hasPrimary = returnDescriptor.kind !== "void";
86
+ const plans = buildArgSpecs(args);
87
+
88
+ const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown => {
89
+ const primary = hasPrimary ? fromNative(returnDescriptor, nativeResult) : undefined;
90
+ return packTupleResult(readOutParams(plans, inputs, nativeValues), primary, hasPrimary);
91
+ };
92
+
93
+ if (throws) {
94
+ return (...inputs) => {
95
+ const nativeValues = buildNativeValues(plans, inputs);
96
+ const errorRef: Ref = { value: null };
97
+ nativeValues.push(errorRef);
98
+ const nativeResult = nativeFn(...nativeValues);
99
+ checkError(errorRef);
100
+ return shape(inputs, nativeValues, nativeResult);
101
+ };
102
+ }
103
+
104
+ return (...inputs) => {
105
+ const nativeValues = buildNativeValues(plans, inputs);
106
+ return shape(inputs, nativeValues, nativeFn(...nativeValues));
107
+ };
108
+ }
package/src/index.ts ADDED
@@ -0,0 +1,57 @@
1
+ export { alloc, type ExternalObject, type Handle, read, setWrapper, write } from "@gtkx/native";
2
+ export { createErrorDomain, type ErrorDomain } from "./error.js";
3
+ export { type ApplicationLike, onExit, quit, quitApplication, runApplication } from "./lifecycle.js";
4
+ export { offSignal, onceSignal, onSignal } from "./listeners.js";
5
+ export { installMixins, type Mixin } from "./mixin.js";
6
+ export { fromNative } from "./native-value.js";
7
+ export { getObjectProperty, newObjectWithProperties, setObjectProperty } from "./object.js";
8
+ export { promisify } from "./promisify.js";
9
+ export { registerClass } from "./register-class.js";
10
+ export {
11
+ getHandle,
12
+ getInstanceType,
13
+ getWrapperClass,
14
+ registerInterface,
15
+ registerWrapperClass,
16
+ type StaticBase,
17
+ setHandle,
18
+ tryGetHandle,
19
+ wrapHandle,
20
+ } from "./registry.js";
21
+ export { connectSignal, emitSignal, getSignalBaseName, type SignalHandler } from "./signal.js";
22
+ export { t } from "./t.js";
23
+ export {
24
+ resolveType,
25
+ TYPE_BOOLEAN,
26
+ TYPE_BOXED,
27
+ TYPE_CHAR,
28
+ TYPE_DOUBLE,
29
+ TYPE_ENUM,
30
+ TYPE_FLAGS,
31
+ TYPE_FLOAT,
32
+ TYPE_GTYPE,
33
+ TYPE_INT,
34
+ TYPE_INT64,
35
+ TYPE_INTERFACE,
36
+ TYPE_INVALID,
37
+ TYPE_LONG,
38
+ TYPE_NONE,
39
+ TYPE_OBJECT,
40
+ TYPE_PARAM,
41
+ TYPE_POINTER,
42
+ TYPE_STRING,
43
+ TYPE_UCHAR,
44
+ TYPE_UINT,
45
+ TYPE_UINT64,
46
+ TYPE_ULONG,
47
+ TYPE_UNICHAR,
48
+ TYPE_VARIANT,
49
+ type TypedClass,
50
+ typeFromName,
51
+ typeInterfaces,
52
+ typeIsA,
53
+ typeName,
54
+ typeParent,
55
+ valueIsA,
56
+ } from "./type.js";
57
+ export { getBoxedValue, setBoxedValue } from "./value.js";
@@ -0,0 +1,12 @@
1
+ export { checkError, createErrorDomain } from "./error.js";
2
+ export { registerClassType, resolveWrapperClass, wrapHandle } from "./registry.js";
3
+ export { resolveType } from "./type.js";
4
+ export {
5
+ fromValue,
6
+ getBoxedValue,
7
+ getValueType,
8
+ inoutValueForBoxedDescriptor,
9
+ newValueForDescriptor,
10
+ outValueForBoxedDescriptor,
11
+ toValue,
12
+ } from "./value.js";
package/src/library.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { type BoxedDescriptor, boxedT, type FundamentalDescriptor, fundamentalT } from "./descriptors.js";
2
+
3
+ export const LIB = "libgobject-2.0.so.0,libglib-2.0.so.0";
4
+ export const VALUE_SIZE = 24;
5
+ export const VALUE_T: BoxedDescriptor = boxedT("GValue", { sharedLibrary: LIB, getTypeFnName: "g_value_get_type" });
6
+
7
+ export const PARAM_T: FundamentalDescriptor = fundamentalT(LIB, "g_param_spec_ref", "g_param_spec_unref", {
8
+ ownership: "borrowed",
9
+ typeName: "GParam",
10
+ });
11
+
12
+ export const VARIANT_T: FundamentalDescriptor = fundamentalT(LIB, "g_variant_ref", "g_variant_unref", {
13
+ ownership: "borrowed",
14
+ typeName: "GVariant",
15
+ });
@@ -0,0 +1,73 @@
1
+ import { keepAlive, quit as nativeQuit } from "@gtkx/native";
2
+ import { blockMatchedSignalHandlers } from "./signal.js";
3
+
4
+ /**
5
+ * Minimal structural interface of a GTK4/GIO application needed to register, run,
6
+ * and shut it down, plus the signals its lifecycle helpers connect to.
7
+ */
8
+ export type ApplicationLike = {
9
+ getIsRegistered(): boolean;
10
+ register(cancellable: null): boolean;
11
+ activate(): void;
12
+ quit(): void;
13
+ run(argv: string[]): number;
14
+ getWindows?(): object[];
15
+ removeWindow?(window: object): void;
16
+ on(signal: "activate" | "shutdown", handler: () => void): unknown;
17
+ };
18
+
19
+ const shutdownCallbacks: (() => void)[] = [];
20
+ let hasQuit = false;
21
+
22
+ /**
23
+ * Registers a callback to run once when the process quits, before the native
24
+ * runtime is torn down.
25
+ *
26
+ * @param callback Invoked during shutdown.
27
+ */
28
+ export const onExit = (callback: () => void): void => {
29
+ shutdownCallbacks.push(callback);
30
+ };
31
+
32
+ /**
33
+ * Runs every registered exit callback and shuts down the native runtime. Safe to
34
+ * call more than once; only the first call takes effect.
35
+ */
36
+ export const quit = (): void => {
37
+ if (hasQuit) return;
38
+ hasQuit = true;
39
+
40
+ for (const callback of shutdownCallbacks) callback();
41
+
42
+ nativeQuit();
43
+ };
44
+
45
+ process.on("exit", quit);
46
+
47
+ /**
48
+ * Registers the application if needed and activates it, keeping the runtime alive
49
+ * while it is active and releasing it on shutdown.
50
+ *
51
+ * @param application The application to register and activate.
52
+ */
53
+ export const runApplication = (application: ApplicationLike): void => {
54
+ application.on("activate", () => keepAlive(true));
55
+ application.on("shutdown", () => keepAlive(false));
56
+
57
+ if (!application.getIsRegistered()) application.register(null);
58
+ application.activate();
59
+ };
60
+
61
+ /**
62
+ * Closes all of the application's windows and drives its main loop until it quits,
63
+ * so pending shutdown work can complete before the process exits.
64
+ *
65
+ * @param application The application to shut down.
66
+ */
67
+ export const quitApplication = (application: ApplicationLike): void => {
68
+ if (!application.getIsRegistered()) return;
69
+ for (const window of application.getWindows?.() ?? []) application.removeWindow?.(window);
70
+ application.on("shutdown", () => application.quit());
71
+ blockMatchedSignalHandlers(application, "activate");
72
+ application.run([]);
73
+ };
@@ -0,0 +1,84 @@
1
+ import type { SignalHandler } from "./signal.js";
2
+
3
+ type SignalConnectable = {
4
+ connect(signal: string, handler: SignalHandler, after?: boolean): number;
5
+ disconnect(handlerId: number): void;
6
+ };
7
+
8
+ const listenerTable = new WeakMap<object, Map<string, Map<SignalHandler, number>>>();
9
+
10
+ const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
11
+ listenerTable.get(instance)?.get(signal)?.get(handler);
12
+
13
+ const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
14
+ let bySignal = listenerTable.get(instance);
15
+ if (!bySignal) {
16
+ bySignal = new Map();
17
+ listenerTable.set(instance, bySignal);
18
+ }
19
+ let byHandler = bySignal.get(signal);
20
+ if (!byHandler) {
21
+ byHandler = new Map();
22
+ bySignal.set(signal, byHandler);
23
+ }
24
+ byHandler.set(handler, handlerId);
25
+ };
26
+
27
+ const untrackListener = (instance: object, signal: string, handler: SignalHandler): void => {
28
+ const bySignal = listenerTable.get(instance);
29
+ const byHandler = bySignal?.get(signal);
30
+ byHandler?.delete(handler);
31
+ if (byHandler?.size === 0) bySignal?.delete(signal);
32
+ };
33
+
34
+ /**
35
+ * Connects a handler to a signal and tracks it so it can later be removed with
36
+ * {@link offSignal}.
37
+ *
38
+ * @param instance The object emitting the signal.
39
+ * @param signal The signal name to connect to.
40
+ * @param handler The callback invoked on each emission.
41
+ * @param after When true, run the handler after the default handler.
42
+ */
43
+ export function onSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, after?: boolean): void {
44
+ const handlerId = instance.connect(signal, handler, after);
45
+ trackListener(instance, signal, handler, handlerId);
46
+ }
47
+
48
+ /**
49
+ * Connects a handler that runs at most once, disconnecting itself after the first
50
+ * emission.
51
+ *
52
+ * @param instance The object emitting the signal.
53
+ * @param signal The signal name to connect to.
54
+ * @param handler The callback invoked on the first emission.
55
+ * @param after When true, run the handler after the default handler.
56
+ */
57
+ export function onceSignal(instance: SignalConnectable, signal: string, handler: SignalHandler, after?: boolean): void {
58
+ let handlerId = 0;
59
+ const wrapped: SignalHandler = (...args) => {
60
+ untrackListener(instance, signal, wrapped);
61
+ untrackListener(instance, signal, handler);
62
+ instance.disconnect(handlerId);
63
+ return handler(...args);
64
+ };
65
+ handlerId = instance.connect(signal, wrapped, after);
66
+ trackListener(instance, signal, wrapped, handlerId);
67
+ trackListener(instance, signal, handler, handlerId);
68
+ }
69
+
70
+ /**
71
+ * Disconnects a handler previously connected with {@link onSignal} or
72
+ * {@link onceSignal}. Does nothing if the handler is not currently connected.
73
+ *
74
+ * @param instance The object the handler was connected to.
75
+ * @param signal The signal name the handler was connected to.
76
+ * @param handler The handler to disconnect.
77
+ */
78
+ export function offSignal(instance: SignalConnectable, signal: string, handler: SignalHandler): void {
79
+ const handlerId = findListenerHandlerId(instance, signal, handler);
80
+ if (handlerId !== undefined) {
81
+ instance.disconnect(handlerId);
82
+ untrackListener(instance, signal, handler);
83
+ }
84
+ }
package/src/mixin.ts ADDED
@@ -0,0 +1,48 @@
1
+ import type { AnyClass } from "@gtkx/utils";
2
+
3
+ export type MixinReceiver = {
4
+ connect(signal: string, handler: (...args: unknown[]) => unknown, after?: boolean): number;
5
+ emit(signal: string, ...args: unknown[]): unknown;
6
+ };
7
+
8
+ /**
9
+ * A factory that, given a base class, returns a subclass adding extra members to
10
+ * be merged onto a target prototype.
11
+ */
12
+ export type Mixin = (base: AnyClass<MixinReceiver>) => AnyClass;
13
+
14
+ function definedInClassChain(prototype: object, key: string): boolean {
15
+ let current: object | null = prototype;
16
+ while (current !== null && current !== Object.prototype) {
17
+ if (Object.hasOwn(current, key)) return true;
18
+ current = Object.getPrototypeOf(current);
19
+ }
20
+ return false;
21
+ }
22
+
23
+ /**
24
+ * Copies each mixin's prototype members onto the target class prototype, skipping
25
+ * any member already defined anywhere in the target's class chain.
26
+ *
27
+ * @param target The class whose prototype receives the mixin members.
28
+ * @param mixins The mixins to apply, in order.
29
+ */
30
+ export function installMixins(target: AnyClass, mixins: Mixin[]): void {
31
+ const empty: AnyClass<MixinReceiver> = class {
32
+ connect(): number {
33
+ return 0;
34
+ }
35
+ emit(): unknown {
36
+ return undefined;
37
+ }
38
+ };
39
+ for (const mixin of mixins) {
40
+ const layer: object = mixin(empty).prototype;
41
+ for (const key of Object.getOwnPropertyNames(layer)) {
42
+ if (key === "constructor") continue;
43
+ if (definedInClassChain(target.prototype, key)) continue;
44
+ const descriptor = Object.getOwnPropertyDescriptor(layer, key);
45
+ if (descriptor !== undefined) Object.defineProperty(target.prototype, key, descriptor);
46
+ }
47
+ }
48
+ }