@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/registry.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { AnyClass } from "@gtkx/utils";
|
|
2
1
|
import {
|
|
3
2
|
type ExternalObject,
|
|
4
3
|
getType,
|
|
@@ -7,60 +6,124 @@ import {
|
|
|
7
6
|
type RegisterClassVfunc as NativeRegisterClassVfunc,
|
|
8
7
|
setWrapper,
|
|
9
8
|
} from "@gtkx/native";
|
|
9
|
+
import { type AnyClass, walkClassChain } from "@gtkx/utils";
|
|
10
10
|
import type { Mixin, MixinReceiver } from "./mixin.js";
|
|
11
11
|
import { TYPE_INVALID, type TypedClass, typeInterfaces, typeIsA, typeName, typeParent } from "./type.js";
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Static side of class `C` with its construct signature preserved but the member
|
|
15
|
-
* named `K` (default `"new"`) removed.
|
|
15
|
+
* named `K` (default `"new"`) removed. The signature is kept abstract so an abstract
|
|
16
|
+
* `C` survives it: a subclass extends the result, and only the subclass's own
|
|
17
|
+
* declaration decides whether `new` reaches it.
|
|
16
18
|
*/
|
|
17
19
|
type StaticBase<C, K extends PropertyKey = "new"> = Omit<C, K> &
|
|
18
|
-
(C extends new (...args: infer A) => infer R ? new (...args: A) => R : never);
|
|
20
|
+
(C extends abstract new (...args: infer A) => infer R ? abstract new (...args: A) => R : never);
|
|
19
21
|
|
|
20
22
|
/** One overridable vtable slot: where it sits in the vtable struct and how it is marshalled. */
|
|
21
|
-
type VfuncDescriptor
|
|
22
|
-
/** Whether the slot lives in a class struct or in an interface vtable. */
|
|
23
|
-
kind: K;
|
|
23
|
+
type VfuncDescriptor = {
|
|
24
24
|
/** GIR name of the type struct holding the slot, without its namespace, such as `WidgetClass`. */
|
|
25
25
|
className: string;
|
|
26
26
|
/** Name of the slot's field in that struct. */
|
|
27
27
|
vfuncName: string;
|
|
28
28
|
/** Byte offset of the slot within the struct. */
|
|
29
29
|
byteOffset: number;
|
|
30
|
-
/**
|
|
31
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Byte size of an interface's vtable struct, used to bounds-check `VfuncDescriptor.byteOffset`.
|
|
32
|
+
* A class struct is bounds-checked against the size `g_type_query` reports for it, so a slot in
|
|
33
|
+
* one carries no size of its own.
|
|
34
|
+
*/
|
|
35
|
+
vtableSize?: number;
|
|
32
36
|
/** Descriptor for each argument the slot receives, starting with the instance. */
|
|
33
37
|
argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
|
|
34
38
|
/** Descriptor for the value the slot returns. */
|
|
35
39
|
returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"];
|
|
40
|
+
/**
|
|
41
|
+
* GIR marks the slot's return value as one the bindings do not surface, so a call through the
|
|
42
|
+
* slot drops it and an implementation of the slot reports success in its place, while
|
|
43
|
+
* `VfuncDescriptor.returnDescriptor` keeps the C type the slot is called against either way.
|
|
44
|
+
*/
|
|
45
|
+
isReturnSkipped?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* The slot takes a trailing `GError**` that `VfuncDescriptor.argDescriptors` leaves out, the
|
|
48
|
+
* way GIR leaves it out of a callable's parameters. A call through the slot has to append it
|
|
49
|
+
* or it passes one argument fewer than the implementation reads.
|
|
50
|
+
*/
|
|
51
|
+
canThrow?: boolean;
|
|
36
52
|
};
|
|
37
53
|
|
|
38
54
|
/**
|
|
39
55
|
* The vtable slots a wrapper class or interface exposes, keyed by the JavaScript method name that
|
|
40
56
|
* overrides each one.
|
|
41
57
|
*/
|
|
42
|
-
type VfuncRegistry = Record<string, VfuncDescriptor
|
|
58
|
+
type VfuncRegistry = Record<string, VfuncDescriptor>;
|
|
59
|
+
type WrapperBinding = (handle: ExternalObject<Handle>, instance: object) => void;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How one property an interface declares reaches the vtable, given as the interface's own accessor
|
|
63
|
+
* members. A direction introspection does not route through a vtable slot is left out, and the
|
|
64
|
+
* property owns that direction's state itself.
|
|
65
|
+
*/
|
|
66
|
+
type InterfaceProperty = {
|
|
67
|
+
/** Member reading the slot the property's value comes from, such as `getEnabled`. */
|
|
68
|
+
getter?: string;
|
|
69
|
+
/** Member writing the slot the property's value goes to, such as `setActionName`. */
|
|
70
|
+
setter?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* What an interface's vtable struct looks like, for the classes that adopt the interface.
|
|
75
|
+
* `g_type_query` reports no size for an interface, so each slot's generated metadata carries the
|
|
76
|
+
* struct's byte size to bounds-check the slot's offset; an interface introspection describes no
|
|
77
|
+
* vtable for simply contributes no slots.
|
|
78
|
+
*/
|
|
79
|
+
type InterfaceLayout = {
|
|
80
|
+
/** The slots the struct declares, keyed by the JavaScript method name that fills each one. */
|
|
81
|
+
vfuncs?: VfuncRegistry;
|
|
82
|
+
/**
|
|
83
|
+
* The properties a vtable slot backs, keyed by canonical property name, so a class adopting the
|
|
84
|
+
* interface answers `g_object_get` and `g_object_set` with what the slot holds.
|
|
85
|
+
*/
|
|
86
|
+
properties?: Record<string, InterfaceProperty>;
|
|
87
|
+
};
|
|
43
88
|
|
|
44
89
|
const classRegistry: Map<bigint, AnyClass> = new Map();
|
|
45
90
|
const interfaceMixinRegistry: Map<bigint, Mixin> = new Map();
|
|
46
91
|
const composedClassRegistry: Map<bigint, AnyClass> = new Map();
|
|
47
92
|
const handleMap: WeakMap<object, ExternalObject<Handle>> = new WeakMap();
|
|
48
93
|
const vfuncRegistry: WeakMap<object, VfuncRegistry> = new WeakMap();
|
|
49
|
-
const
|
|
94
|
+
const interfaceLayoutRegistry: Map<bigint, InterfaceLayout> = new Map();
|
|
95
|
+
const wrapperClasses: WeakSet<AnyClass> = new WeakSet();
|
|
96
|
+
const derivedClasses: WeakSet<AnyClass> = new WeakSet();
|
|
50
97
|
|
|
51
98
|
function setClassType(cls: AnyClass, type: bigint): void {
|
|
52
99
|
(cls.prototype as { [K in keyof TypedClass]: TypedClass[K] }).__type__ = type;
|
|
53
100
|
}
|
|
54
101
|
|
|
55
|
-
|
|
56
|
-
|
|
102
|
+
/**
|
|
103
|
+
* Returns the GType a class was registered under, or the invalid type when it carries none. The tag is
|
|
104
|
+
* read off the class's own prototype, so a subclass that never went through `registerClass` reports the
|
|
105
|
+
* invalid type rather than inheriting the one its parent was registered with.
|
|
106
|
+
*/
|
|
107
|
+
function getClassType(cls: AnyClass | undefined): bigint {
|
|
108
|
+
const proto: object | undefined = cls?.prototype;
|
|
109
|
+
|
|
110
|
+
if (proto === undefined || !Object.hasOwn(proto, "__type__")) {
|
|
111
|
+
return TYPE_INVALID;
|
|
112
|
+
}
|
|
57
113
|
|
|
58
|
-
return
|
|
114
|
+
return (proto as TypedClass).__type__;
|
|
59
115
|
}
|
|
60
116
|
|
|
61
|
-
/**
|
|
117
|
+
/**
|
|
118
|
+
* Returns the GType the given instance's handle carries, or the invalid type when it has no handle.
|
|
119
|
+
* This is the object's own type rather than the type of the class it was wrapped as, and the two differ
|
|
120
|
+
* for an object GTK created itself, such as the row widget inside a `Gtk.ListView`, which GTKX wraps as
|
|
121
|
+
* the nearest registered ancestor.
|
|
122
|
+
*/
|
|
62
123
|
function getInstanceType(instance: object): bigint {
|
|
63
|
-
|
|
124
|
+
const handle = handleMap.get(instance);
|
|
125
|
+
|
|
126
|
+
return handle === undefined ? TYPE_INVALID : getType(handle);
|
|
64
127
|
}
|
|
65
128
|
|
|
66
129
|
function registerClassType(cls: AnyClass, type: bigint): void {
|
|
@@ -83,21 +146,47 @@ function registerClassType(cls: AnyClass, type: bigint): void {
|
|
|
83
146
|
function registerWrapperClass(cls: AnyClass, type: bigint, vfuncs?: VfuncRegistry): void {
|
|
84
147
|
registerClassType(cls, type);
|
|
85
148
|
|
|
149
|
+
if (type !== TYPE_INVALID) {
|
|
150
|
+
wrapperClasses.add(cls);
|
|
151
|
+
}
|
|
152
|
+
|
|
86
153
|
if (vfuncs) {
|
|
87
154
|
registerVfuncRegistry(cls, vfuncs);
|
|
88
155
|
}
|
|
89
156
|
}
|
|
90
157
|
|
|
158
|
+
function markDerivedClass(cls: AnyClass): void {
|
|
159
|
+
derivedClasses.add(cls);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function resolveAncestorType(ancestor: AnyClass): bigint | undefined {
|
|
163
|
+
if (derivedClasses.has(ancestor) || !wrapperClasses.has(ancestor)) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return getClassType(ancestor);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function resolveWrapperType(instance: object): bigint {
|
|
171
|
+
const cls = instance.constructor as AnyClass | undefined;
|
|
172
|
+
|
|
173
|
+
if (cls === undefined) {
|
|
174
|
+
return TYPE_INVALID;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return walkClassChain(cls, (ancestor) => resolveAncestorType(ancestor)) ?? TYPE_INVALID;
|
|
178
|
+
}
|
|
179
|
+
|
|
91
180
|
/**
|
|
92
181
|
* Registers a GInterface, associating its GType with a mixin used to compose the
|
|
93
|
-
* interface onto wrapper classes and
|
|
182
|
+
* interface onto wrapper classes and, when introspection describes its vtable, that layout.
|
|
94
183
|
* @param cls Class carrying the interface's GType tag.
|
|
95
184
|
* @param type GType of the interface.
|
|
96
185
|
* @param mixin Mixin that applies the interface to a wrapper class.
|
|
97
|
-
* @param
|
|
98
|
-
* implementing class overrides.
|
|
186
|
+
* @param layout The interface's vtable struct, so `registerClass` can bind the slots an
|
|
187
|
+
* implementing class overrides and take over the ones it leaves alone.
|
|
99
188
|
*/
|
|
100
|
-
function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin,
|
|
189
|
+
function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, layout?: InterfaceLayout): void {
|
|
101
190
|
if (type === TYPE_INVALID) {
|
|
102
191
|
return;
|
|
103
192
|
}
|
|
@@ -105,16 +194,17 @@ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, vfuncs?: V
|
|
|
105
194
|
setClassType(cls, type);
|
|
106
195
|
interfaceMixinRegistry.set(type, mixin);
|
|
107
196
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
197
|
+
if (layout) {
|
|
198
|
+
interfaceLayoutRegistry.set(type, layout);
|
|
110
199
|
}
|
|
111
200
|
}
|
|
112
201
|
|
|
113
202
|
/**
|
|
114
203
|
* Wraps a native handle in a JS wrapper instance. With no class, resolves and
|
|
115
|
-
* reuses the wrapper for the handle's runtime GType (composing interface mixins)
|
|
116
|
-
*
|
|
117
|
-
*
|
|
204
|
+
* reuses the wrapper for the handle's runtime GType (composing interface mixins),
|
|
205
|
+
* and hands back an instance that already carries a handle unchanged; with an
|
|
206
|
+
* explicit class, creates a bare instance backed by the handle. Returns null for
|
|
207
|
+
* a null or undefined handle.
|
|
118
208
|
* @param handle Native handle to wrap.
|
|
119
209
|
* @param cls Wrapper class to instantiate, or omitted to resolve it from the runtime type.
|
|
120
210
|
*/
|
|
@@ -174,12 +264,16 @@ function resolveWrapperClass(type: bigint): AnyClass | null {
|
|
|
174
264
|
return null;
|
|
175
265
|
}
|
|
176
266
|
|
|
267
|
+
function getInterfaceMixin(type: bigint): Mixin | undefined {
|
|
268
|
+
return interfaceMixinRegistry.get(type);
|
|
269
|
+
}
|
|
270
|
+
|
|
177
271
|
function applyInterfaceMixin(cls: AnyClass, type: bigint, baseType: bigint, applied: Set<bigint>): AnyClass {
|
|
178
272
|
if (applied.has(type) || typeIsA(baseType, type)) {
|
|
179
273
|
return cls;
|
|
180
274
|
}
|
|
181
275
|
|
|
182
|
-
const mixin =
|
|
276
|
+
const mixin = getInterfaceMixin(type);
|
|
183
277
|
|
|
184
278
|
if (mixin === undefined) {
|
|
185
279
|
return cls;
|
|
@@ -228,18 +322,25 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
|
|
|
228
322
|
}
|
|
229
323
|
|
|
230
324
|
setClassType(composed, runtimeType);
|
|
325
|
+
wrapperClasses.add(composed);
|
|
231
326
|
composedClassRegistry.set(runtimeType, composed);
|
|
232
327
|
|
|
233
328
|
return composed;
|
|
234
329
|
}
|
|
235
330
|
|
|
236
|
-
function
|
|
237
|
-
|
|
331
|
+
function wrapObject(value: unknown): object | null {
|
|
332
|
+
return value == null ? null : getOrCreateWrapper(value as ExternalObject<Handle>);
|
|
333
|
+
}
|
|
238
334
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
335
|
+
function wrapCallScopedObject(value: unknown): object | null {
|
|
336
|
+
return value == null ? null : wrapperFor(value as ExternalObject<Handle>, bindCallScopedWrapper);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
function existingWrapperFor(handle: ExternalObject<Handle>): object | null {
|
|
340
|
+
return handleMap.has(handle) ? handle : getWrapper(handle);
|
|
341
|
+
}
|
|
242
342
|
|
|
343
|
+
function createWrapper(handle: ExternalObject<Handle>): object {
|
|
243
344
|
const runtimeType: bigint = getType(handle);
|
|
244
345
|
|
|
245
346
|
if (runtimeType === TYPE_INVALID) {
|
|
@@ -252,34 +353,50 @@ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
|
|
|
252
353
|
throw new Error(`Expected registered GLib type, got type ${String(runtimeType)}`);
|
|
253
354
|
}
|
|
254
355
|
|
|
255
|
-
|
|
256
|
-
|
|
356
|
+
return Object.create(cls.prototype) as object;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function wrapperFor(handle: ExternalObject<Handle>, bind: WrapperBinding): object {
|
|
360
|
+
const existing = existingWrapperFor(handle);
|
|
361
|
+
|
|
362
|
+
if (existing) {
|
|
363
|
+
return existing;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const instance = createWrapper(handle);
|
|
367
|
+
bind(handle, instance);
|
|
257
368
|
|
|
258
369
|
return instance;
|
|
259
370
|
}
|
|
260
371
|
|
|
372
|
+
function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
|
|
373
|
+
return wrapperFor(handle, registerWrapper);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function instanceClassName(instance: object): string {
|
|
377
|
+
return (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
|
|
378
|
+
}
|
|
379
|
+
|
|
261
380
|
/** Returns the native handle bound to a wrapper instance, throwing if none is set. */
|
|
262
381
|
function getHandle(instance: object): ExternalObject<Handle> {
|
|
263
382
|
const handle = handleMap.get(instance);
|
|
264
383
|
|
|
265
384
|
if (handle === undefined) {
|
|
266
|
-
|
|
267
|
-
throw new Error(`No native handle associated with ${name}`);
|
|
385
|
+
throw new Error(`No native handle associated with ${instanceClassName(instance)}`);
|
|
268
386
|
}
|
|
269
387
|
|
|
270
388
|
return handle;
|
|
271
389
|
}
|
|
272
390
|
|
|
273
|
-
/** Returns the native handle bound to an instance, or undefined when there is none or the instance is null. */
|
|
274
|
-
function tryGetHandle(instance: object | null | undefined): ExternalObject<Handle> | undefined {
|
|
275
|
-
return instance == null ? undefined : handleMap.get(instance);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
391
|
/** Associates a native handle with a wrapper instance. */
|
|
279
392
|
function setHandle(instance: object, handle: ExternalObject<Handle>): void {
|
|
280
393
|
handleMap.set(instance, handle);
|
|
281
394
|
}
|
|
282
395
|
|
|
396
|
+
function bindCallScopedWrapper(handle: ExternalObject<Handle>, instance: object): void {
|
|
397
|
+
setHandle(instance, handle);
|
|
398
|
+
}
|
|
399
|
+
|
|
283
400
|
function registerWrapper(handle: ExternalObject<Handle>, instance: object): void {
|
|
284
401
|
setHandle(instance, handle);
|
|
285
402
|
setWrapper(handle, instance);
|
|
@@ -293,21 +410,18 @@ function getVfuncRegistry(cls: object): VfuncRegistry | undefined {
|
|
|
293
410
|
return vfuncRegistry.get(cls);
|
|
294
411
|
}
|
|
295
412
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
interfaceVfuncRegistry.set(type, registry);
|
|
413
|
+
function getInterfaceVfuncRegistry(type: bigint): VfuncRegistry | undefined {
|
|
414
|
+
return interfaceLayoutRegistry.get(type)?.vfuncs;
|
|
302
415
|
}
|
|
303
416
|
|
|
304
|
-
function
|
|
305
|
-
return
|
|
417
|
+
function getInterfaceProperties(type: bigint): Record<string, InterfaceProperty> | undefined {
|
|
418
|
+
return interfaceLayoutRegistry.get(type)?.properties;
|
|
306
419
|
}
|
|
307
420
|
|
|
308
421
|
export {
|
|
309
422
|
getClassType,
|
|
310
423
|
getInstanceType,
|
|
424
|
+
markDerivedClass,
|
|
311
425
|
registerClassType,
|
|
312
426
|
registerWrapperClass,
|
|
313
427
|
registerInterface,
|
|
@@ -315,11 +429,17 @@ export {
|
|
|
315
429
|
getWrapperClass,
|
|
316
430
|
resolveWrapperClass,
|
|
317
431
|
getHandle,
|
|
318
|
-
tryGetHandle,
|
|
319
432
|
setHandle,
|
|
320
433
|
getVfuncRegistry,
|
|
434
|
+
getInterfaceMixin,
|
|
435
|
+
getInterfaceProperties,
|
|
321
436
|
getInterfaceVfuncRegistry,
|
|
437
|
+
instanceClassName,
|
|
438
|
+
registerWrapper,
|
|
439
|
+
resolveWrapperType,
|
|
440
|
+
wrapCallScopedObject,
|
|
441
|
+
wrapObject,
|
|
442
|
+
type InterfaceProperty,
|
|
322
443
|
type StaticBase,
|
|
323
444
|
type VfuncDescriptor,
|
|
324
|
-
type VfuncRegistry,
|
|
325
445
|
};
|
package/src/signal.ts
CHANGED
|
@@ -43,6 +43,11 @@ type SignalConnectSpec = {
|
|
|
43
43
|
isAfter: boolean;
|
|
44
44
|
};
|
|
45
45
|
|
|
46
|
+
type EmitValues = {
|
|
47
|
+
values: ExternalObject<Handle>[];
|
|
48
|
+
reads: (() => unknown)[];
|
|
49
|
+
};
|
|
50
|
+
|
|
46
51
|
/** One argument of a signal emission: how to marshal it, plus the value to marshal. */
|
|
47
52
|
type EmitArg = Arg & {
|
|
48
53
|
/**
|
|
@@ -53,6 +58,7 @@ type EmitArg = Arg & {
|
|
|
53
58
|
};
|
|
54
59
|
|
|
55
60
|
const connectCache = createBindCache();
|
|
61
|
+
const connectionTable: WeakMap<object, Map<string, Set<number>>> = new WeakMap();
|
|
56
62
|
const gQuarkFromString = bind(LIB, "g_quark_from_string", [stringT("borrowed")], uint32T);
|
|
57
63
|
const gSignalLookup = bind(LIB, "g_signal_lookup", [stringT("borrowed"), biguint64T], uint32T);
|
|
58
64
|
|
|
@@ -63,13 +69,6 @@ const gSignalEmitv = bind(
|
|
|
63
69
|
voidT,
|
|
64
70
|
);
|
|
65
71
|
|
|
66
|
-
const gSignalHandlersBlockMatched = bind(
|
|
67
|
-
LIB,
|
|
68
|
-
"g_signal_handlers_block_matched",
|
|
69
|
-
[objectT("borrowed"), uint32T, uint32T, uint32T, objectT("borrowed"), objectT("borrowed"), objectT("borrowed")],
|
|
70
|
-
uint32T,
|
|
71
|
-
);
|
|
72
|
-
|
|
73
72
|
const gSignalHandlerIsConnected = bind(
|
|
74
73
|
LIB,
|
|
75
74
|
"g_signal_handler_is_connected",
|
|
@@ -77,6 +76,8 @@ const gSignalHandlerIsConnected = bind(
|
|
|
77
76
|
booleanT,
|
|
78
77
|
);
|
|
79
78
|
|
|
79
|
+
const gSignalHandlerDisconnect = bind(LIB, "g_signal_handler_disconnect", [objectT("borrowed"), uint64T], voidT);
|
|
80
|
+
|
|
80
81
|
/** Returns the signal name without its detail suffix (the part after `::`). */
|
|
81
82
|
const getSignalBaseName = (signal: string): string => {
|
|
82
83
|
const detailIndex = signal.indexOf("::");
|
|
@@ -97,6 +98,81 @@ function getSignalDetailQuark(signal: string): number {
|
|
|
97
98
|
const isSignalHandlerConnected = (instance: object, handlerId: number): boolean =>
|
|
98
99
|
gSignalHandlerIsConnected(getHandle(instance), handlerId) as boolean;
|
|
99
100
|
|
|
101
|
+
const trackConnection = (instance: object, signal: string, handlerId: number): void => {
|
|
102
|
+
let bySignal = connectionTable.get(instance);
|
|
103
|
+
|
|
104
|
+
if (!bySignal) {
|
|
105
|
+
bySignal = new Map();
|
|
106
|
+
connectionTable.set(instance, bySignal);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let handlerIds = bySignal.get(signal);
|
|
110
|
+
|
|
111
|
+
if (!handlerIds) {
|
|
112
|
+
handlerIds = new Set();
|
|
113
|
+
bySignal.set(signal, handlerIds);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
handlerIds.add(handlerId);
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const untrackConnection = (instance: object, handlerId: number): void => {
|
|
120
|
+
const bySignal = connectionTable.get(instance);
|
|
121
|
+
|
|
122
|
+
if (bySignal === undefined) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
for (const [name, handlerIds] of bySignal) {
|
|
127
|
+
handlerIds.delete(handlerId);
|
|
128
|
+
|
|
129
|
+
if (handlerIds.size === 0) {
|
|
130
|
+
bySignal.delete(name);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Disconnects the handler an instance connected under the given id, and forgets the connection.
|
|
137
|
+
* @param instance Emitter the handler was connected to.
|
|
138
|
+
* @param handlerId Id {@link connectSignal} returned for the handler.
|
|
139
|
+
*/
|
|
140
|
+
const disconnectSignal = (instance: object, handlerId: number): void => {
|
|
141
|
+
untrackConnection(instance, handlerId);
|
|
142
|
+
gSignalHandlerDisconnect(getHandle(instance), handlerId);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
const hasLiveConnection = (instance: object, handlerIds: Set<number>): boolean => {
|
|
146
|
+
let isLive = false;
|
|
147
|
+
|
|
148
|
+
for (const handlerId of handlerIds) {
|
|
149
|
+
if (isSignalHandlerConnected(instance, handlerId)) {
|
|
150
|
+
isLive = true;
|
|
151
|
+
} else {
|
|
152
|
+
handlerIds.delete(handlerId);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return isLive;
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
function hasSignalListener(instance: object, signals?: string[]): boolean {
|
|
160
|
+
const bySignal = connectionTable.get(instance);
|
|
161
|
+
|
|
162
|
+
if (!bySignal) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const names =
|
|
167
|
+
signals === undefined ? bySignal.keys().toArray() : signals.map((signal) => getSignalBaseName(signal));
|
|
168
|
+
|
|
169
|
+
return names.some((name) => {
|
|
170
|
+
const handlerIds = bySignal.get(name);
|
|
171
|
+
|
|
172
|
+
return handlerIds !== undefined && hasLiveConnection(instance, handlerIds);
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
100
176
|
const getSignalId = (instance: object, signal: string): number => {
|
|
101
177
|
const type: bigint = (instance as TypedClass).__type__;
|
|
102
178
|
|
|
@@ -123,16 +199,13 @@ function connectBind(type: bigint, signal: string, callback: CallbackDescriptor)
|
|
|
123
199
|
*/
|
|
124
200
|
function connectSignal(instance: object, signal: string, spec: SignalConnectSpec): number {
|
|
125
201
|
const { callback, handler, isAfter } = spec;
|
|
126
|
-
const wrapped = wrapCallback(handler, callback, "
|
|
202
|
+
const wrapped = wrapCallback(handler, callback, "signal");
|
|
127
203
|
const type: bigint = (instance as TypedClass).__type__;
|
|
128
204
|
const connect = connectBind(type, signal, callback);
|
|
205
|
+
const handlerId = connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
|
|
206
|
+
trackConnection(instance, getSignalBaseName(signal), handlerId);
|
|
129
207
|
|
|
130
|
-
return
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
function blockMatchedSignalHandlers(instance: object, signal: string): void {
|
|
134
|
-
const signalId = getSignalId(instance, signal);
|
|
135
|
-
gSignalHandlersBlockMatched(getHandle(instance), 1, signalId, 0, undefined, undefined, undefined);
|
|
208
|
+
return handlerId;
|
|
136
209
|
}
|
|
137
210
|
|
|
138
211
|
const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?: () => unknown } => {
|
|
@@ -153,54 +226,55 @@ const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?:
|
|
|
153
226
|
return isInoutArg(arg) ? outValueForDescriptor(arg.type, arg.value) : outValueForDescriptor(arg.type);
|
|
154
227
|
};
|
|
155
228
|
|
|
229
|
+
const collectEmitValues = (instance: object, args: EmitArg[]): EmitValues => {
|
|
230
|
+
const collected: EmitValues = { values: [toValue(objectT("full"), instance)], reads: [] };
|
|
231
|
+
|
|
232
|
+
for (const arg of args) {
|
|
233
|
+
const { value, read } = createEmitValue(arg);
|
|
234
|
+
collected.values.push(value);
|
|
235
|
+
|
|
236
|
+
if (read) {
|
|
237
|
+
collected.reads.push(read);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return collected;
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const readEmitOutputs = (reads: (() => unknown)[]): unknown[] => reads.map((read) => read());
|
|
245
|
+
|
|
156
246
|
/**
|
|
157
247
|
* Emits a signal on an instance with the given arguments and returns its result
|
|
158
248
|
* combined with any output-argument values.
|
|
159
249
|
* @param instance Emitter to emit the signal on.
|
|
160
250
|
* @param signal Signal name, optionally including a `::detail` suffix.
|
|
161
251
|
* @param args Arguments to pass, including output and inout arguments.
|
|
162
|
-
* @param
|
|
252
|
+
* @param returns Descriptor for the signal's return value, omitted when it returns void.
|
|
163
253
|
*/
|
|
164
|
-
function emitSignal(instance: object, signal: string, args: EmitArg[],
|
|
254
|
+
function emitSignal(instance: object, signal: string, args: EmitArg[], returns?: Descriptor): unknown {
|
|
165
255
|
const signalId = getSignalId(instance, signal);
|
|
166
256
|
const detail = getSignalDetailQuark(signal);
|
|
167
|
-
const values
|
|
168
|
-
const reads: (() => unknown)[] = [];
|
|
257
|
+
const { values, reads } = collectEmitValues(instance, args);
|
|
169
258
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
values.push(value);
|
|
259
|
+
if (returns === undefined) {
|
|
260
|
+
gSignalEmitv(values, signalId, detail, undefined);
|
|
173
261
|
|
|
174
|
-
|
|
175
|
-
reads.push(read);
|
|
176
|
-
}
|
|
262
|
+
return packTupleResult(readEmitOutputs(reads), undefined, false);
|
|
177
263
|
}
|
|
178
264
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
gSignalEmitv(values, signalId, detail, returnValue);
|
|
265
|
+
const returnValue = newValueForDescriptor(returns);
|
|
266
|
+
gSignalEmitv(values, signalId, detail, returnValue);
|
|
182
267
|
|
|
183
|
-
|
|
184
|
-
reads.map((read) => read()),
|
|
185
|
-
fromValue(returnValue),
|
|
186
|
-
true,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
gSignalEmitv(values, signalId, detail, undefined);
|
|
191
|
-
|
|
192
|
-
return packTupleResult(
|
|
193
|
-
reads.map((read) => read()),
|
|
194
|
-
undefined,
|
|
195
|
-
false,
|
|
196
|
-
);
|
|
268
|
+
return packTupleResult(readEmitOutputs(reads), fromValue(returnValue), true);
|
|
197
269
|
}
|
|
198
270
|
|
|
199
271
|
export {
|
|
200
272
|
getSignalBaseName,
|
|
201
273
|
connectSignal,
|
|
202
|
-
|
|
274
|
+
disconnectSignal,
|
|
203
275
|
emitSignal,
|
|
276
|
+
hasSignalListener,
|
|
204
277
|
isSignalHandlerConnected,
|
|
278
|
+
untrackConnection,
|
|
205
279
|
type SignalHandler,
|
|
206
280
|
};
|
package/src/t.ts
CHANGED
|
@@ -77,6 +77,8 @@ type T = {
|
|
|
77
77
|
sizedArray: typeof helpers.sizedArrayT;
|
|
78
78
|
/** Builds a descriptor for a C array of a fixed length. */
|
|
79
79
|
fixedArray: typeof helpers.fixedArrayT;
|
|
80
|
+
/** Builds a descriptor for an out pointer into the buffer another argument supplied. */
|
|
81
|
+
cursorArray: typeof helpers.cursorArrayT;
|
|
80
82
|
/** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */
|
|
81
83
|
callback: typeof helpers.callbackT;
|
|
82
84
|
/**
|
|
@@ -127,6 +129,7 @@ const t: T = {
|
|
|
127
129
|
byteArray: helpers.byteArrayT,
|
|
128
130
|
sizedArray: helpers.sizedArrayT,
|
|
129
131
|
fixedArray: helpers.fixedArrayT,
|
|
132
|
+
cursorArray: helpers.cursorArrayT,
|
|
130
133
|
callback: helpers.callbackT,
|
|
131
134
|
fn,
|
|
132
135
|
};
|
package/src/tuple.ts
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import type { Descriptor } from "@gtkx/native";
|
|
2
|
+
|
|
3
|
+
type SplitResult = { primary: unknown; outValues: unknown[] };
|
|
4
|
+
|
|
5
|
+
const hasSurfacedPrimary = (returnDescriptor: Descriptor, isReturnSkipped: boolean | undefined): boolean =>
|
|
6
|
+
returnDescriptor.kind !== "void" && isReturnSkipped !== true;
|
|
7
|
+
|
|
1
8
|
const packTupleResult = (outs: unknown[], primary: unknown, hasPrimary: boolean): unknown => {
|
|
2
9
|
if (hasPrimary) {
|
|
3
10
|
return outs.length === 0 ? primary : [primary, ...outs];
|
|
@@ -14,19 +21,15 @@ const packTupleResult = (outs: unknown[], primary: unknown, hasPrimary: boolean)
|
|
|
14
21
|
return outs;
|
|
15
22
|
};
|
|
16
23
|
|
|
17
|
-
const
|
|
18
|
-
result
|
|
19
|
-
|
|
20
|
-
outCount: number,
|
|
21
|
-
): { primary: unknown; outValues: unknown[] } => {
|
|
22
|
-
if (hasPrimary) {
|
|
23
|
-
if (Array.isArray(result)) {
|
|
24
|
-
return { primary: result[0], outValues: result.slice(1) };
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
return { primary: result, outValues: [] };
|
|
24
|
+
const splitPrimaryResult = (result: unknown, outCount: number): SplitResult => {
|
|
25
|
+
if (outCount > 0 && Array.isArray(result)) {
|
|
26
|
+
return { primary: result[0], outValues: result.slice(1) };
|
|
28
27
|
}
|
|
29
28
|
|
|
29
|
+
return { primary: result, outValues: [] };
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const splitOutResult = (result: unknown, outCount: number): SplitResult => {
|
|
30
33
|
if (outCount === 1) {
|
|
31
34
|
return { primary: undefined, outValues: [result] };
|
|
32
35
|
}
|
|
@@ -34,4 +37,7 @@ const splitTupleResult = (
|
|
|
34
37
|
return { primary: undefined, outValues: Array.isArray(result) ? result : [] };
|
|
35
38
|
};
|
|
36
39
|
|
|
37
|
-
|
|
40
|
+
const splitTupleResult = (result: unknown, hasPrimary: boolean, outCount: number): SplitResult =>
|
|
41
|
+
hasPrimary ? splitPrimaryResult(result, outCount) : splitOutResult(result, outCount);
|
|
42
|
+
|
|
43
|
+
export { hasSurfacedPrimary, packTupleResult, splitTupleResult };
|