@gtkx/runtime 1.0.0-rc.4 → 1.1.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.
- package/README.md +5 -5
- package/dist/application-class.d.ts +37 -0
- package/dist/application-class.d.ts.map +1 -0
- package/dist/application-class.js +78 -0
- package/dist/application-class.js.map +1 -0
- package/dist/arg.d.ts +2 -1
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +2 -1
- package/dist/arg.js.map +1 -1
- package/dist/bind.d.ts +11 -0
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +11 -0
- package/dist/bind.js.map +1 -1
- package/dist/callback.d.ts +4 -3
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +124 -29
- package/dist/callback.js.map +1 -1
- package/dist/closure.d.ts +26 -0
- package/dist/closure.d.ts.map +1 -0
- package/dist/closure.js +99 -0
- package/dist/closure.js.map +1 -0
- package/dist/default-application.d.ts +4 -0
- package/dist/default-application.d.ts.map +1 -0
- package/dist/default-application.js +17 -0
- package/dist/default-application.js.map +1 -0
- package/dist/descriptors.d.ts +98 -4
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +72 -6
- package/dist/descriptors.js.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js.map +1 -1
- package/dist/exit-hook.js +13 -0
- package/dist/exit-hook.js.map +1 -1
- package/dist/fn.d.ts +24 -2
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +67 -32
- package/dist/fn.js.map +1 -1
- package/dist/folded-lengths.d.ts +16 -0
- package/dist/folded-lengths.d.ts.map +1 -0
- package/dist/folded-lengths.js +38 -0
- package/dist/folded-lengths.js.map +1 -0
- package/dist/index.d.ts +14 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +2 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +2 -0
- package/dist/internal.js.map +1 -1
- package/dist/library.d.ts +3 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +3 -1
- package/dist/library.js.map +1 -1
- package/dist/lifecycle.d.ts +58 -11
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +95 -22
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +19 -5
- package/dist/listeners.js.map +1 -1
- package/dist/mixin.d.ts +3 -2
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +13 -1
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +4 -3
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +40 -7
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +95 -26
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts +11 -0
- package/dist/param-spec.d.ts.map +1 -0
- package/dist/param-spec.js +147 -0
- package/dist/param-spec.js.map +1 -0
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +3 -2
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +39 -7
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +310 -43
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +147 -12
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +129 -83
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +80 -21
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +86 -33
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +11 -4
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +85 -25
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +2 -0
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js +1 -0
- package/dist/t.js.map +1 -1
- package/dist/tuple.d.ts +6 -3
- package/dist/tuple.d.ts.map +1 -1
- package/dist/tuple.js +9 -7
- package/dist/tuple.js.map +1 -1
- package/dist/type.d.ts +2 -1
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +2 -0
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +10 -1
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +99 -24
- package/dist/value.js.map +1 -1
- package/dist/vfunc-call.d.ts +37 -0
- package/dist/vfunc-call.d.ts.map +1 -0
- package/dist/vfunc-call.js +172 -0
- package/dist/vfunc-call.js.map +1 -0
- package/dist/vfunc-seeds.d.ts +12 -0
- package/dist/vfunc-seeds.d.ts.map +1 -0
- package/dist/vfunc-seeds.js +19 -0
- package/dist/vfunc-seeds.js.map +1 -0
- package/dist/vfunc.d.ts +8 -0
- package/dist/vfunc.d.ts.map +1 -0
- package/dist/vfunc.js +29 -0
- package/dist/vfunc.js.map +1 -0
- package/package.json +14 -4
- package/src/application-class.ts +126 -0
- package/src/arg.ts +2 -1
- package/src/bind.ts +11 -0
- package/src/callback.ts +178 -34
- package/src/closure.ts +136 -0
- package/src/default-application.ts +21 -0
- package/src/descriptors.ts +150 -34
- package/src/error.ts +1 -0
- package/src/exit-hook.ts +18 -0
- package/src/fn.ts +107 -36
- package/src/folded-lengths.ts +64 -0
- package/src/index.ts +21 -6
- package/src/internal.ts +2 -0
- package/src/library.ts +3 -1
- package/src/lifecycle.ts +138 -25
- package/src/listeners.ts +25 -6
- package/src/mixin.ts +15 -2
- package/src/native-value.ts +5 -3
- package/src/object.ts +134 -35
- package/src/param-spec.ts +234 -0
- package/src/promisify.ts +4 -2
- package/src/properties.ts +473 -46
- package/src/register-class.ts +386 -114
- package/src/registry.ts +169 -49
- package/src/signal.ts +116 -42
- package/src/t.ts +3 -0
- package/src/tuple.ts +18 -12
- package/src/type.ts +3 -1
- package/src/value.ts +143 -23
- package/src/vfunc-call.ts +247 -0
- package/src/vfunc-seeds.ts +36 -0
- package/src/vfunc.ts +38 -0
package/src/fn.ts
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import type { Descriptor, Ref } from "@gtkx/native";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import type { CallDescriptor, Descriptor, ExternalObject, Ref } from "@gtkx/native";
|
|
2
|
+
import { call, bind as nativeBind } from "@gtkx/native";
|
|
3
|
+
import type { RefSeeds } from "./vfunc-seeds.js";
|
|
4
|
+
import { type Arg, isCallerAllocatedArg, isOutputArg, isRefArg, requiresInputArg } from "./arg.js";
|
|
4
5
|
import { wrapCallbackValue } from "./callback.js";
|
|
5
6
|
import { boxedT, refT } from "./descriptors.js";
|
|
6
7
|
import { checkError } from "./error.js";
|
|
7
8
|
import { LIB } from "./library.js";
|
|
8
9
|
import { fromNative } from "./native-value.js";
|
|
9
10
|
import { getHandle } from "./registry.js";
|
|
10
|
-
import { packTupleResult } from "./tuple.js";
|
|
11
|
+
import { hasSurfacedPrimary, packTupleResult } from "./tuple.js";
|
|
11
12
|
|
|
12
13
|
/** The signature a native function is bound against. */
|
|
13
14
|
type FnSpec = {
|
|
@@ -15,12 +16,24 @@ type FnSpec = {
|
|
|
15
16
|
args: Arg[];
|
|
16
17
|
/** Descriptor for the C return value, packed first when output arguments are also returned. */
|
|
17
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;
|
|
18
24
|
/** The function takes a trailing `GError**`, whose contents are thrown as an error on return. */
|
|
19
25
|
canThrow?: boolean;
|
|
26
|
+
/**
|
|
27
|
+
* How many of `args` precede the callee's ellipsis, marking it variadic. Omitting it binds a
|
|
28
|
+
* fixed-arity call, which passes the wrong argument classes to a variadic callee on some
|
|
29
|
+
* architectures.
|
|
30
|
+
*/
|
|
31
|
+
fixedArgCount?: number;
|
|
20
32
|
};
|
|
21
33
|
|
|
22
34
|
type ArgSpec = {
|
|
23
35
|
arg: Arg;
|
|
36
|
+
index: number;
|
|
24
37
|
isRef: boolean;
|
|
25
38
|
isCallerAllocated: boolean;
|
|
26
39
|
requiresInput: boolean;
|
|
@@ -28,6 +41,8 @@ type ArgSpec = {
|
|
|
28
41
|
isOutParam: boolean;
|
|
29
42
|
};
|
|
30
43
|
|
|
44
|
+
const NO_OUT_PARAMS: unknown[] = [];
|
|
45
|
+
|
|
31
46
|
const buildNativeArgTypes = (args: Arg[], canThrow: boolean): Descriptor[] => {
|
|
32
47
|
const nativeArgTypes = args.map((argSpec) =>
|
|
33
48
|
argSpec.direction !== undefined && argSpec.isCallerAllocated !== true ? refT(argSpec.type) : argSpec.type,
|
|
@@ -45,13 +60,14 @@ const buildNativeArgTypes = (args: Arg[], canThrow: boolean): Descriptor[] => {
|
|
|
45
60
|
const buildArgSpecs = (args: Arg[]): ArgSpec[] => {
|
|
46
61
|
let inputCursor = 0;
|
|
47
62
|
|
|
48
|
-
return args.map((arg) => {
|
|
63
|
+
return args.map((arg, index) => {
|
|
49
64
|
const isRef = isRefArg(arg);
|
|
50
|
-
const requiresInput =
|
|
65
|
+
const requiresInput = requiresInputArg(arg);
|
|
51
66
|
const isOutParam = isOutputArg(arg) && arg.isConsumed !== true;
|
|
52
67
|
|
|
53
68
|
return {
|
|
54
69
|
arg,
|
|
70
|
+
index,
|
|
55
71
|
isRef,
|
|
56
72
|
isCallerAllocated: isCallerAllocatedArg(arg),
|
|
57
73
|
requiresInput,
|
|
@@ -67,19 +83,19 @@ const resolveCallerAllocated = (inputs: unknown[], inputIndex: number): unknown
|
|
|
67
83
|
return wrapper == null ? wrapper : getHandle(wrapper);
|
|
68
84
|
};
|
|
69
85
|
|
|
70
|
-
const buildRefValue = (
|
|
71
|
-
value: requiresInput ? inputs[inputIndex] : null,
|
|
86
|
+
const buildRefValue = (spec: ArgSpec, inputs: unknown[], seeds: RefSeeds | undefined): Ref => ({
|
|
87
|
+
value: spec.requiresInput ? inputs[spec.inputIndex] : (seeds?.get(spec.index) ?? null),
|
|
72
88
|
});
|
|
73
89
|
|
|
74
|
-
const buildNativeValue = (spec: ArgSpec, inputs: unknown[]): unknown => {
|
|
75
|
-
const { arg, isRef, isCallerAllocated,
|
|
90
|
+
const buildNativeValue = (spec: ArgSpec, inputs: unknown[], seeds: RefSeeds | undefined): unknown => {
|
|
91
|
+
const { arg, isRef, isCallerAllocated, inputIndex } = spec;
|
|
76
92
|
|
|
77
93
|
if (isCallerAllocated) {
|
|
78
94
|
return resolveCallerAllocated(inputs, inputIndex);
|
|
79
95
|
}
|
|
80
96
|
|
|
81
97
|
if (isRef) {
|
|
82
|
-
return buildRefValue(
|
|
98
|
+
return buildRefValue(spec, inputs, seeds);
|
|
83
99
|
}
|
|
84
100
|
|
|
85
101
|
if (arg.type.kind === "callback") {
|
|
@@ -89,44 +105,84 @@ const buildNativeValue = (spec: ArgSpec, inputs: unknown[]): unknown => {
|
|
|
89
105
|
return inputs[inputIndex];
|
|
90
106
|
};
|
|
91
107
|
|
|
92
|
-
const buildNativeValues = (plans: ArgSpec[], inputs: unknown[]): unknown[] =>
|
|
93
|
-
plans.map((plan) => buildNativeValue(plan, inputs));
|
|
108
|
+
const buildNativeValues = (plans: ArgSpec[], inputs: unknown[], seeds: RefSeeds | undefined): unknown[] =>
|
|
109
|
+
plans.map((plan) => buildNativeValue(plan, inputs, seeds));
|
|
110
|
+
|
|
111
|
+
const readOutParams = (outPlans: ArgSpec[], inputs: unknown[], nativeValues: unknown[]): unknown[] => {
|
|
112
|
+
if (outPlans.length === 0) {
|
|
113
|
+
return NO_OUT_PARAMS;
|
|
114
|
+
}
|
|
94
115
|
|
|
95
|
-
|
|
96
|
-
|
|
116
|
+
return Array.from(outPlans, (plan) =>
|
|
117
|
+
plan.isCallerAllocated
|
|
118
|
+
? inputs[plan.inputIndex]
|
|
119
|
+
: fromNative(plan.arg.type, (nativeValues[plan.index] as Ref).value));
|
|
120
|
+
};
|
|
97
121
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
122
|
+
const isPassThroughPlan = (plan: ArgSpec, index: number): boolean =>
|
|
123
|
+
plan.inputIndex === index &&
|
|
124
|
+
!plan.isRef &&
|
|
125
|
+
!plan.isCallerAllocated &&
|
|
126
|
+
plan.arg.type.kind !== "callback";
|
|
102
127
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
);
|
|
128
|
+
const resizeInputs = (inputs: unknown[], argCount: number): unknown[] => {
|
|
129
|
+
while (inputs.length < argCount) {
|
|
130
|
+
inputs.push(undefined);
|
|
106
131
|
}
|
|
107
132
|
|
|
108
|
-
|
|
133
|
+
if (inputs.length > argCount) {
|
|
134
|
+
inputs.length = argCount;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return inputs;
|
|
109
138
|
};
|
|
110
139
|
|
|
111
|
-
|
|
140
|
+
const directCallable = (
|
|
141
|
+
descriptor: ExternalObject<CallDescriptor>,
|
|
142
|
+
returnDescriptor: Descriptor,
|
|
143
|
+
hasPrimary: boolean,
|
|
144
|
+
argCount: number,
|
|
145
|
+
): ((...inputs: unknown[]) => unknown) => {
|
|
146
|
+
const marshal = (inputs: unknown[]): unknown =>
|
|
147
|
+
fromNative(returnDescriptor, call(descriptor, resizeInputs(inputs, argCount)));
|
|
148
|
+
|
|
149
|
+
if (hasPrimary) {
|
|
150
|
+
return (...inputs) => marshal(inputs);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (...inputs) => {
|
|
154
|
+
marshal(inputs);
|
|
155
|
+
};
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
function fromNativeCallable(
|
|
159
|
+
descriptor: ExternalObject<CallDescriptor>,
|
|
160
|
+
spec: FnSpec,
|
|
161
|
+
getRefSeeds?: () => RefSeeds | undefined,
|
|
162
|
+
): (...inputs: unknown[]) => unknown {
|
|
112
163
|
const { args, returns: returnDescriptor, canThrow = false } = spec;
|
|
113
|
-
const
|
|
114
|
-
const nativeFn = bind(sharedLibrary, symbol, nativeArgTypes, returnDescriptor);
|
|
115
|
-
const hasPrimary = returnDescriptor.kind !== "void";
|
|
164
|
+
const hasPrimary = hasSurfacedPrimary(returnDescriptor, spec.isReturnSkipped);
|
|
116
165
|
const plans = buildArgSpecs(args);
|
|
166
|
+
const outPlans = plans.filter((plan) => plan.isOutParam);
|
|
167
|
+
const arePassThrough = plans.every((plan, index) => isPassThroughPlan(plan, index));
|
|
117
168
|
|
|
118
|
-
|
|
119
|
-
|
|
169
|
+
if (!canThrow && arePassThrough) {
|
|
170
|
+
return directCallable(descriptor, returnDescriptor, hasPrimary, plans.length);
|
|
171
|
+
}
|
|
120
172
|
|
|
121
|
-
|
|
122
|
-
|
|
173
|
+
const shape = (inputs: unknown[], nativeValues: unknown[], nativeResult: unknown): unknown =>
|
|
174
|
+
packTupleResult(
|
|
175
|
+
readOutParams(outPlans, inputs, nativeValues),
|
|
176
|
+
fromNative(returnDescriptor, nativeResult),
|
|
177
|
+
hasPrimary,
|
|
178
|
+
);
|
|
123
179
|
|
|
124
180
|
if (canThrow) {
|
|
125
181
|
return (...inputs) => {
|
|
126
|
-
const nativeValues = buildNativeValues(plans, inputs);
|
|
182
|
+
const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
|
|
127
183
|
const errorRef: Ref = { value: null };
|
|
128
184
|
nativeValues.push(errorRef);
|
|
129
|
-
const nativeResult =
|
|
185
|
+
const nativeResult = call(descriptor, nativeValues);
|
|
130
186
|
checkError(errorRef);
|
|
131
187
|
|
|
132
188
|
return shape(inputs, nativeValues, nativeResult);
|
|
@@ -134,10 +190,25 @@ function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inputs: un
|
|
|
134
190
|
}
|
|
135
191
|
|
|
136
192
|
return (...inputs) => {
|
|
137
|
-
const nativeValues = buildNativeValues(plans, inputs);
|
|
193
|
+
const nativeValues = buildNativeValues(plans, inputs, getRefSeeds?.());
|
|
138
194
|
|
|
139
|
-
return shape(inputs, nativeValues,
|
|
195
|
+
return shape(inputs, nativeValues, call(descriptor, nativeValues));
|
|
140
196
|
};
|
|
141
197
|
}
|
|
142
198
|
|
|
143
|
-
|
|
199
|
+
/**
|
|
200
|
+
* Binds a symbol in a shared library to a callable that marshals its inputs and packs output
|
|
201
|
+
* arguments into the result. When the spec sets `canThrow`, the reported `GError` is thrown.
|
|
202
|
+
*
|
|
203
|
+
* @param sharedLibrary Shared library the symbol is looked up in.
|
|
204
|
+
* @param symbol Name of the C symbol to bind.
|
|
205
|
+
* @param spec Argument and return descriptors the call is marshalled through.
|
|
206
|
+
*/
|
|
207
|
+
function fn(sharedLibrary: string, symbol: string, spec: FnSpec): (...inputs: unknown[]) => unknown {
|
|
208
|
+
const nativeArgTypes = buildNativeArgTypes(spec.args, spec.canThrow ?? false);
|
|
209
|
+
const descriptor = nativeBind(sharedLibrary, symbol, nativeArgTypes, spec.returns, spec.fixedArgCount);
|
|
210
|
+
|
|
211
|
+
return fromNativeCallable(descriptor, spec);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export { buildNativeArgTypes, fn, fromNativeCallable };
|
|
@@ -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 };
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import "./exit-hook.js";
|
|
2
2
|
|
|
3
|
+
/** @public */
|
|
4
|
+
export { type ApplicationClass, type CommandLineApplication, createApplication } from "./application-class.js";
|
|
5
|
+
/** @public */
|
|
6
|
+
export { type ClosureCallback, ClosureMarshalError, toClosure, tryToClosure } from "./closure.js";
|
|
3
7
|
/** @public */
|
|
4
8
|
export { createErrorDomain, type ErrorDomain } from "./error.js";
|
|
5
9
|
/** @public */
|
|
6
|
-
export { onExit, quit, quitApplication, runApplication } from "./lifecycle.js";
|
|
10
|
+
export { onExit, quit, quitApplication, runApplication, type RunApplicationResult } from "./lifecycle.js";
|
|
7
11
|
/** @public */
|
|
8
12
|
export { offSignal, onceSignal, onSignal } from "./listeners.js";
|
|
9
13
|
/** @public */
|
|
@@ -11,13 +15,21 @@ export { installMixins, type Mixin } from "./mixin.js";
|
|
|
11
15
|
/** @public */
|
|
12
16
|
export { fromNative, toNative } from "./native-value.js";
|
|
13
17
|
/** @public */
|
|
14
|
-
export {
|
|
18
|
+
export {
|
|
19
|
+
type ConstructBinding,
|
|
20
|
+
type ConstructBindings,
|
|
21
|
+
getObjectProperty,
|
|
22
|
+
newObjectWithProperties,
|
|
23
|
+
registerConstructProperties,
|
|
24
|
+
setObjectProperty,
|
|
25
|
+
} from "./object.js";
|
|
15
26
|
/** @public */
|
|
16
27
|
export { promisify } from "./promisify.js";
|
|
17
28
|
/** @public */
|
|
18
|
-
export { registerClass } from "./register-class.js";
|
|
29
|
+
export { type Interface, registerClass } from "./register-class.js";
|
|
19
30
|
/** @public */
|
|
20
31
|
export {
|
|
32
|
+
getClassType,
|
|
21
33
|
getHandle,
|
|
22
34
|
getInstanceType,
|
|
23
35
|
getWrapperClass,
|
|
@@ -25,11 +37,10 @@ export {
|
|
|
25
37
|
registerWrapperClass,
|
|
26
38
|
type StaticBase,
|
|
27
39
|
setHandle,
|
|
28
|
-
tryGetHandle,
|
|
29
40
|
wrapHandle,
|
|
30
41
|
} from "./registry.js";
|
|
31
42
|
/** @public */
|
|
32
|
-
export { connectSignal, emitSignal, getSignalBaseName, type SignalHandler } from "./signal.js";
|
|
43
|
+
export { connectSignal, disconnectSignal, emitSignal, getSignalBaseName, type SignalHandler } from "./signal.js";
|
|
33
44
|
/** @public */
|
|
34
45
|
export { t } from "./t.js";
|
|
35
46
|
/** @public */
|
|
@@ -70,4 +81,8 @@ export {
|
|
|
70
81
|
/** @public */
|
|
71
82
|
export { getBoxedValue, setBoxedValue } from "./value.js";
|
|
72
83
|
/** @public */
|
|
73
|
-
export {
|
|
84
|
+
export { callParent, callVfunc } from "./vfunc-call.js";
|
|
85
|
+
/** @public */
|
|
86
|
+
export { alloc, type ExternalObject, type Handle, read, write } from "@gtkx/native";
|
|
87
|
+
/** @public */
|
|
88
|
+
export { type AnyClass } from "@gtkx/utils";
|
package/src/internal.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
export { checkError, createErrorDomain } from "./error.js";
|
|
2
|
+
export { type ApplicationInstance, getApplicationInstance } from "./lifecycle.js";
|
|
2
3
|
export { registerClassType, resolveWrapperClass, wrapHandle } from "./registry.js";
|
|
4
|
+
export { hasSignalListener } from "./signal.js";
|
|
3
5
|
export { resolveType } from "./type.js";
|
|
4
6
|
export {
|
|
5
7
|
fromValue,
|
package/src/library.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { type BoxedDescriptor, boxedT, type FundamentalDescriptor, fundamentalT } from "./descriptors.js";
|
|
2
2
|
|
|
3
3
|
const LIB = "libgobject-2.0.so.0,libglib-2.0.so.0";
|
|
4
|
+
const GIO_LIB = "libgio-2.0.so.0";
|
|
4
5
|
const VALUE_SIZE = 24;
|
|
6
|
+
const CLOSURE_SIZE = 32;
|
|
5
7
|
const VALUE_T: BoxedDescriptor = boxedT("GValue", { sharedLibrary: LIB, getTypeFnName: "g_value_get_type" });
|
|
6
8
|
|
|
7
9
|
const PARAM_T: FundamentalDescriptor = fundamentalT(LIB, "g_param_spec_ref", "g_param_spec_unref", {
|
|
@@ -14,4 +16,4 @@ const VARIANT_T: FundamentalDescriptor = fundamentalT(LIB, "g_variant_ref", "g_v
|
|
|
14
16
|
typeName: "GVariant",
|
|
15
17
|
});
|
|
16
18
|
|
|
17
|
-
export { LIB, VALUE_SIZE, VALUE_T, PARAM_T, VARIANT_T };
|
|
19
|
+
export { CLOSURE_SIZE, GIO_LIB, LIB, VALUE_SIZE, VALUE_T, PARAM_T, VARIANT_T };
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { keepAlive, quit as nativeQuit } from "@gtkx/native";
|
|
2
|
-
import {
|
|
2
|
+
import { isDerivedApplication, type LocalCommandLineApplication, shutDownThroughRun } from "./application-class.js";
|
|
3
|
+
import { claimDefaultApplication, releaseDefaultApplication } from "./default-application.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The GIO and GTK application surface {@link runApplication} and {@link quitApplication} drive, so any
|
|
@@ -9,13 +10,16 @@ import { blockMatchedSignalHandlers } from "./signal.js";
|
|
|
9
10
|
type ApplicationLike = {
|
|
10
11
|
/** Returns whether the application has already been registered. */
|
|
11
12
|
getIsRegistered(): boolean;
|
|
13
|
+
/** Returns whether another process owns the application ID, making this one a remote instance. */
|
|
14
|
+
getIsRemote?(): boolean;
|
|
12
15
|
/** Registers the application with the session, returning whether it succeeded. */
|
|
13
16
|
register(cancellable: null): boolean;
|
|
14
17
|
/** Emits `activate`, bringing up the application's initial user interface. */
|
|
15
18
|
activate(): void;
|
|
16
|
-
/**
|
|
17
|
-
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Runs GLib's own `g_application_run`, whose tail emits `shutdown`, destroys the application
|
|
21
|
+
* implementation and clears the registration.
|
|
22
|
+
*/
|
|
19
23
|
run(argv: string[]): number;
|
|
20
24
|
/** Returns the windows currently attached to the application. */
|
|
21
25
|
getWindows?(): object[];
|
|
@@ -23,9 +27,24 @@ type ApplicationLike = {
|
|
|
23
27
|
removeWindow?(window: object): void;
|
|
24
28
|
/** Connects a handler to the application's `activate` or `shutdown` signal. */
|
|
25
29
|
on(signal: "activate" | "shutdown", handler: () => void): unknown;
|
|
30
|
+
/** Emits one of the application's own signals. */
|
|
31
|
+
emit(signal: "shutdown"): unknown;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type ApplicationInstance = "primary" | "remote" | "shutDown" | "unregistered";
|
|
35
|
+
|
|
36
|
+
/** What {@link runApplication} reports about the process it just started. */
|
|
37
|
+
type RunApplicationResult = {
|
|
38
|
+
/** Whether this process owns the application ID and may build a user interface. */
|
|
39
|
+
isPrimary: boolean;
|
|
40
|
+
/** The status GLib determined for the command line, which the process should exit with. */
|
|
41
|
+
exitStatus: number;
|
|
26
42
|
};
|
|
27
43
|
|
|
28
44
|
const shutdownCallbacks: (() => void)[] = [];
|
|
45
|
+
const startedApplications: WeakSet<object> = new WeakSet();
|
|
46
|
+
const registeredApplications: WeakSet<object> = new WeakSet();
|
|
47
|
+
const shutDownApplications: WeakSet<object> = new WeakSet();
|
|
29
48
|
/**
|
|
30
49
|
* Runs every registered exit callback and shuts down the native runtime. Safe to
|
|
31
50
|
* call more than once; only the first call takes effect.
|
|
@@ -60,13 +79,9 @@ const onExit = (callback: () => void): void => {
|
|
|
60
79
|
shutdownCallbacks.push(callback);
|
|
61
80
|
};
|
|
62
81
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
*
|
|
67
|
-
* @param application The application to register and activate.
|
|
68
|
-
*/
|
|
69
|
-
const runApplication = (application: ApplicationLike): void => {
|
|
82
|
+
const startApplication = (application: ApplicationLike & LocalCommandLineApplication, argv: string[]): number => {
|
|
83
|
+
startedApplications.add(application);
|
|
84
|
+
|
|
70
85
|
application.on("activate", () => {
|
|
71
86
|
keepAlive(true);
|
|
72
87
|
});
|
|
@@ -75,22 +90,90 @@ const runApplication = (application: ApplicationLike): void => {
|
|
|
75
90
|
keepAlive(false);
|
|
76
91
|
});
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
|
|
93
|
+
return application.runLocalCommandLine(argv)[2];
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const restartApplication = (application: ApplicationLike): number => {
|
|
97
|
+
shutDownApplications.delete(application);
|
|
98
|
+
|
|
99
|
+
if (!application.register(null)) {
|
|
100
|
+
return 1;
|
|
80
101
|
}
|
|
81
102
|
|
|
82
103
|
application.activate();
|
|
104
|
+
|
|
105
|
+
return 0;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const getApplicationInstance = (application: ApplicationLike): ApplicationInstance => {
|
|
109
|
+
if (!application.getIsRegistered()) {
|
|
110
|
+
return registeredApplications.has(application) ? "shutDown" : "unregistered";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return application.getIsRemote?.() === true ? "remote" : "primary";
|
|
83
114
|
};
|
|
84
115
|
|
|
85
116
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
117
|
+
* Hands `argv` to GLib's own command line handling, which parses the application's options, prints
|
|
118
|
+
* `--help`, runs `handle-local-options`, registers the application, and either activates it or
|
|
119
|
+
* forwards the command line to the process that already owns the application ID. The runtime is
|
|
120
|
+
* held alive while the application is active and released on shutdown.
|
|
89
121
|
*
|
|
90
|
-
*
|
|
122
|
+
* `g_application_run()` is not what starts the application, because it would drive its own main loop
|
|
123
|
+
* and freeze Node; only the local command line handling it delegates to runs here. {@link
|
|
124
|
+
* quitApplication} calls it once no window holds the application open, to reach the teardown only it
|
|
125
|
+
* performs.
|
|
126
|
+
*
|
|
127
|
+
* GLib parses a given application's command line at most once, so starting an application that has
|
|
128
|
+
* already run registers and activates it instead of reading `argv` again.
|
|
129
|
+
*
|
|
130
|
+
* When another process already owns the application ID, this one registers as a remote instance: no
|
|
131
|
+
* user interface may be built here, because a remote application has no `GtkApplicationImpl` and
|
|
132
|
+
* attaching a window to it crashes.
|
|
133
|
+
*
|
|
134
|
+
* The application also claims the process-wide default `Gio.Application.getDefault()` returns, before
|
|
135
|
+
* its command line is parsed, so an `activate` handler and anything the host reads afterwards see the
|
|
136
|
+
* application being started rather than an earlier one. That holds whether the start ends registered,
|
|
137
|
+
* remote or unregistered: a refused command line still has to be reportable through the default, which
|
|
138
|
+
* is how `gtkx dev` learns which application its entry mounted. {@link quitApplication} gives the
|
|
139
|
+
* default back up, since GLib drops it only at finalize.
|
|
140
|
+
*
|
|
141
|
+
* @param application The application to start.
|
|
142
|
+
* @param argv The command line, whose first entry names the program as `--help` should print it.
|
|
143
|
+
* @returns Whether this process may build a user interface, and the status to exit with.
|
|
91
144
|
*/
|
|
92
|
-
const
|
|
93
|
-
if (!application
|
|
145
|
+
const runApplication = (application: ApplicationLike, argv: string[]): RunApplicationResult => {
|
|
146
|
+
if (!isDerivedApplication(application)) {
|
|
147
|
+
throw new Error(
|
|
148
|
+
"runApplication: this application was not built by GTKX, so its command line cannot be " +
|
|
149
|
+
"parsed and it cannot be shut down safely; render <GtkApplication> or <AdwApplication>, " +
|
|
150
|
+
"or construct it with createApplication from @gtkx/runtime",
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
claimDefaultApplication(application);
|
|
155
|
+
|
|
156
|
+
const exitStatus = startedApplications.has(application)
|
|
157
|
+
? restartApplication(application)
|
|
158
|
+
: startApplication(application, argv);
|
|
159
|
+
|
|
160
|
+
const instance = getApplicationInstance(application);
|
|
161
|
+
|
|
162
|
+
if (instance !== "unregistered") {
|
|
163
|
+
registeredApplications.add(application);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const isPrimary = instance === "primary";
|
|
167
|
+
|
|
168
|
+
if (isPrimary) {
|
|
169
|
+
keepAlive(true);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { isPrimary, exitStatus };
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const tearDownApplication = (application: ApplicationLike): void => {
|
|
176
|
+
if (!application.getIsRegistered() || shutDownApplications.has(application)) {
|
|
94
177
|
return;
|
|
95
178
|
}
|
|
96
179
|
|
|
@@ -100,12 +183,42 @@ const quitApplication = (application: ApplicationLike): void => {
|
|
|
100
183
|
application.removeWindow?.(window);
|
|
101
184
|
}
|
|
102
185
|
|
|
103
|
-
application
|
|
104
|
-
|
|
105
|
-
|
|
186
|
+
shutDownThroughRun(application);
|
|
187
|
+
|
|
188
|
+
if (application.getIsRegistered()) {
|
|
189
|
+
shutDownApplications.add(application);
|
|
190
|
+
application.emit("shutdown");
|
|
191
|
+
}
|
|
192
|
+
};
|
|
106
193
|
|
|
107
|
-
|
|
108
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Detaches every window from the application and runs GLib's own shutdown, which emits `shutdown`,
|
|
196
|
+
* destroys the application implementation and releases the D-Bus registration. Does nothing for an
|
|
197
|
+
* application that is not registered, so a repeated call is a no-op.
|
|
198
|
+
*
|
|
199
|
+
* GLib's own shutdown is reachable once per application: reaching it marks the application as
|
|
200
|
+
* quitting, which GLib never undoes. An application that has already quit falls back to emitting
|
|
201
|
+
* `shutdown`, which releases the runtime and leaves the registration for GLib to drop at finalize.
|
|
202
|
+
*
|
|
203
|
+
* The process-wide default {@link runApplication} claimed is given up whichever of those paths the
|
|
204
|
+
* application took, including the ones that have no teardown left to do, so
|
|
205
|
+
* `Gio.Application.getDefault()` never hands back an application that has been torn down or that
|
|
206
|
+
* never registered in the first place. GLib clears that default only at finalize, which a
|
|
207
|
+
* garbage-collected wrapper reaches arbitrarily late or never.
|
|
208
|
+
*
|
|
209
|
+
* @param application The application to shut down.
|
|
210
|
+
*/
|
|
211
|
+
const quitApplication = (application: ApplicationLike): void => {
|
|
212
|
+
tearDownApplication(application);
|
|
213
|
+
releaseDefaultApplication(application);
|
|
109
214
|
};
|
|
110
215
|
|
|
111
|
-
export {
|
|
216
|
+
export {
|
|
217
|
+
getApplicationInstance,
|
|
218
|
+
onExit,
|
|
219
|
+
quit,
|
|
220
|
+
runApplication,
|
|
221
|
+
quitApplication,
|
|
222
|
+
type ApplicationInstance,
|
|
223
|
+
type RunApplicationResult,
|
|
224
|
+
};
|
package/src/listeners.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isSignalHandlerConnected, type SignalHandler } from "./signal.js";
|
|
1
|
+
import { isSignalHandlerConnected, type SignalHandler, untrackConnection } from "./signal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* The connect and disconnect surface {@link onSignal}, {@link onceSignal} and {@link offSignal}
|
|
@@ -11,10 +11,10 @@ type SignalConnectable = {
|
|
|
11
11
|
disconnect(handlerId: number): void;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number>>> = new WeakMap();
|
|
14
|
+
const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number[]>>> = new WeakMap();
|
|
15
15
|
|
|
16
16
|
const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
|
|
17
|
-
listenerTable.get(instance)?.get(signal)?.get(handler);
|
|
17
|
+
listenerTable.get(instance)?.get(signal)?.get(handler)?.at(-1);
|
|
18
18
|
|
|
19
19
|
const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
|
|
20
20
|
let bySignal = listenerTable.get(instance);
|
|
@@ -31,7 +31,24 @@ const trackListener = (instance: object, signal: string, handler: SignalHandler,
|
|
|
31
31
|
bySignal.set(signal, byHandler);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
byHandler.
|
|
34
|
+
let handlerIds = byHandler.get(handler);
|
|
35
|
+
|
|
36
|
+
if (!handlerIds) {
|
|
37
|
+
handlerIds = [];
|
|
38
|
+
byHandler.set(handler, handlerIds);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handlerIds.push(handlerId);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const removeTrackedHandlerId = (handlerIds: number[], handlerId: number): number => {
|
|
45
|
+
const index = handlerIds.lastIndexOf(handlerId);
|
|
46
|
+
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
handlerIds.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return handlerIds.length;
|
|
35
52
|
};
|
|
36
53
|
|
|
37
54
|
const untrackHandlerId = (instance: object, signal: string, handlerId: number): void => {
|
|
@@ -42,8 +59,8 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
|
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
for (const [handler,
|
|
46
|
-
if (
|
|
62
|
+
for (const [handler, handlerIds] of byHandler) {
|
|
63
|
+
if (removeTrackedHandlerId(handlerIds, handlerId) === 0) {
|
|
47
64
|
byHandler.delete(handler);
|
|
48
65
|
}
|
|
49
66
|
}
|
|
@@ -81,6 +98,7 @@ function onceSignal(instance: SignalConnectable, signal: string, handler: Signal
|
|
|
81
98
|
|
|
82
99
|
const wrapped: SignalHandler = (...args) => {
|
|
83
100
|
untrackHandlerId(instance, signal, handlerId);
|
|
101
|
+
untrackConnection(instance, handlerId);
|
|
84
102
|
instance.disconnect(handlerId);
|
|
85
103
|
|
|
86
104
|
return handler(...args);
|
|
@@ -107,6 +125,7 @@ function offSignal(instance: SignalConnectable, signal: string, handler: SignalH
|
|
|
107
125
|
}
|
|
108
126
|
|
|
109
127
|
untrackHandlerId(instance, signal, handlerId);
|
|
128
|
+
untrackConnection(instance, handlerId);
|
|
110
129
|
|
|
111
130
|
if (!isSignalHandlerConnected(instance, handlerId)) {
|
|
112
131
|
return;
|