@gtkx/runtime 1.0.0-rc.4 → 1.0.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 +32 -0
- package/dist/application-class.d.ts.map +1 -0
- package/dist/application-class.js +68 -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 +2 -1
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +118 -20
- 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/descriptors.d.ts +74 -3
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +44 -0
- 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/fn.d.ts +19 -2
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +64 -27
- package/dist/fn.js.map +1 -1
- 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 +1 -0
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -0
- package/dist/internal.js.map +1 -1
- package/dist/library.d.ts +2 -1
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +2 -1
- package/dist/library.js.map +1 -1
- package/dist/lifecycle.d.ts +43 -11
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +62 -19
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +3 -1
- 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 +34 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +89 -25
- 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 +149 -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 +38 -7
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +305 -42
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +117 -12
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +108 -76
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +69 -19
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +70 -26
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +9 -2
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +63 -7
- package/dist/signal.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 +8 -1
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +42 -13
- 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 +168 -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 +23 -0
- package/dist/vfunc.js.map +1 -0
- package/package.json +4 -4
- package/src/application-class.ts +116 -0
- package/src/arg.ts +2 -1
- package/src/bind.ts +11 -0
- package/src/callback.ts +162 -22
- package/src/closure.ts +136 -0
- package/src/descriptors.ts +85 -24
- package/src/error.ts +1 -0
- package/src/fn.ts +92 -31
- package/src/index.ts +21 -6
- package/src/internal.ts +1 -0
- package/src/library.ts +2 -1
- package/src/lifecycle.ts +90 -23
- package/src/listeners.ts +3 -1
- package/src/mixin.ts +15 -2
- package/src/native-value.ts +5 -3
- package/src/object.ts +128 -34
- package/src/param-spec.ts +235 -0
- package/src/promisify.ts +4 -2
- package/src/properties.ts +458 -45
- package/src/register-class.ts +326 -109
- package/src/registry.ts +138 -40
- package/src/signal.ts +84 -14
- package/src/type.ts +3 -1
- package/src/value.ts +55 -10
- package/src/vfunc-call.ts +243 -0
- package/src/vfunc-seeds.ts +36 -0
- package/src/vfunc.ts +31 -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,6 +6,7 @@ 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
|
|
|
@@ -18,49 +18,103 @@ type StaticBase<C, K extends PropertyKey = "new"> = Omit<C, K> &
|
|
|
18
18
|
(C extends new (...args: infer A) => infer R ? new (...args: A) => R : never);
|
|
19
19
|
|
|
20
20
|
/** 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;
|
|
21
|
+
type VfuncDescriptor = {
|
|
24
22
|
/** GIR name of the type struct holding the slot, without its namespace, such as `WidgetClass`. */
|
|
25
23
|
className: string;
|
|
26
24
|
/** Name of the slot's field in that struct. */
|
|
27
25
|
vfuncName: string;
|
|
28
26
|
/** Byte offset of the slot within the struct. */
|
|
29
27
|
byteOffset: number;
|
|
30
|
-
/**
|
|
31
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Byte size of an interface's vtable struct, used to bounds-check `VfuncDescriptor.byteOffset`.
|
|
30
|
+
* A class struct is bounds-checked against the size `g_type_query` reports for it, so a slot in
|
|
31
|
+
* one carries no size of its own.
|
|
32
|
+
*/
|
|
33
|
+
vtableSize?: number;
|
|
32
34
|
/** Descriptor for each argument the slot receives, starting with the instance. */
|
|
33
35
|
argDescriptors: NativeRegisterClassVfunc["argDescriptors"];
|
|
34
36
|
/** Descriptor for the value the slot returns. */
|
|
35
37
|
returnDescriptor: NativeRegisterClassVfunc["returnDescriptor"];
|
|
38
|
+
/**
|
|
39
|
+
* The slot takes a trailing `GError**` that `VfuncDescriptor.argDescriptors` leaves out, the
|
|
40
|
+
* way GIR leaves it out of a callable's parameters. A call through the slot has to append it
|
|
41
|
+
* or it passes one argument fewer than the implementation reads.
|
|
42
|
+
*/
|
|
43
|
+
canThrow?: boolean;
|
|
36
44
|
};
|
|
37
45
|
|
|
38
46
|
/**
|
|
39
47
|
* The vtable slots a wrapper class or interface exposes, keyed by the JavaScript method name that
|
|
40
48
|
* overrides each one.
|
|
41
49
|
*/
|
|
42
|
-
type VfuncRegistry = Record<string, VfuncDescriptor
|
|
50
|
+
type VfuncRegistry = Record<string, VfuncDescriptor>;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How one property an interface declares reaches the vtable, given as the interface's own accessor
|
|
54
|
+
* members. A direction introspection does not route through a vtable slot is left out, and the
|
|
55
|
+
* property owns that direction's state itself.
|
|
56
|
+
*/
|
|
57
|
+
type InterfaceProperty = {
|
|
58
|
+
/** Member reading the slot the property's value comes from, such as `getEnabled`. */
|
|
59
|
+
getter?: string;
|
|
60
|
+
/** Member writing the slot the property's value goes to, such as `setActionName`. */
|
|
61
|
+
setter?: string;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* What an interface's vtable struct looks like, for the classes that adopt the interface.
|
|
66
|
+
* `g_type_query` reports no size for an interface, so each slot's generated metadata carries the
|
|
67
|
+
* struct's byte size to bounds-check the slot's offset; an interface introspection describes no
|
|
68
|
+
* vtable for simply contributes no slots.
|
|
69
|
+
*/
|
|
70
|
+
type InterfaceLayout = {
|
|
71
|
+
/** The slots the struct declares, keyed by the JavaScript method name that fills each one. */
|
|
72
|
+
vfuncs?: VfuncRegistry;
|
|
73
|
+
/**
|
|
74
|
+
* The properties a vtable slot backs, keyed by canonical property name, so a class adopting the
|
|
75
|
+
* interface answers `g_object_get` and `g_object_set` with what the slot holds.
|
|
76
|
+
*/
|
|
77
|
+
properties?: Record<string, InterfaceProperty>;
|
|
78
|
+
};
|
|
43
79
|
|
|
44
80
|
const classRegistry: Map<bigint, AnyClass> = new Map();
|
|
45
81
|
const interfaceMixinRegistry: Map<bigint, Mixin> = new Map();
|
|
46
82
|
const composedClassRegistry: Map<bigint, AnyClass> = new Map();
|
|
47
83
|
const handleMap: WeakMap<object, ExternalObject<Handle>> = new WeakMap();
|
|
48
84
|
const vfuncRegistry: WeakMap<object, VfuncRegistry> = new WeakMap();
|
|
49
|
-
const
|
|
85
|
+
const interfaceLayoutRegistry: Map<bigint, InterfaceLayout> = new Map();
|
|
86
|
+
const wrapperClasses: WeakSet<AnyClass> = new WeakSet();
|
|
87
|
+
const derivedClasses: WeakSet<AnyClass> = new WeakSet();
|
|
50
88
|
|
|
51
89
|
function setClassType(cls: AnyClass, type: bigint): void {
|
|
52
90
|
(cls.prototype as { [K in keyof TypedClass]: TypedClass[K] }).__type__ = type;
|
|
53
91
|
}
|
|
54
92
|
|
|
55
|
-
|
|
56
|
-
|
|
93
|
+
/**
|
|
94
|
+
* Returns the GType a class was registered under, or the invalid type when it carries none. The tag is
|
|
95
|
+
* read off the class's own prototype, so a subclass that never went through `registerClass` reports the
|
|
96
|
+
* invalid type rather than inheriting the one its parent was registered with.
|
|
97
|
+
*/
|
|
98
|
+
function getClassType(cls: AnyClass | undefined): bigint {
|
|
99
|
+
const proto: object | undefined = cls?.prototype;
|
|
100
|
+
|
|
101
|
+
if (proto === undefined || !Object.hasOwn(proto, "__type__")) {
|
|
102
|
+
return TYPE_INVALID;
|
|
103
|
+
}
|
|
57
104
|
|
|
58
|
-
return
|
|
105
|
+
return (proto as TypedClass).__type__;
|
|
59
106
|
}
|
|
60
107
|
|
|
61
|
-
/**
|
|
108
|
+
/**
|
|
109
|
+
* Returns the GType the given instance's handle carries, or the invalid type when it has no handle.
|
|
110
|
+
* This is the object's own type rather than the type of the class it was wrapped as, and the two differ
|
|
111
|
+
* for an object GTK created itself, such as the row widget inside a `Gtk.ListView`, which GTKX wraps as
|
|
112
|
+
* the nearest registered ancestor.
|
|
113
|
+
*/
|
|
62
114
|
function getInstanceType(instance: object): bigint {
|
|
63
|
-
|
|
115
|
+
const handle = handleMap.get(instance);
|
|
116
|
+
|
|
117
|
+
return handle === undefined ? TYPE_INVALID : getType(handle);
|
|
64
118
|
}
|
|
65
119
|
|
|
66
120
|
function registerClassType(cls: AnyClass, type: bigint): void {
|
|
@@ -83,21 +137,47 @@ function registerClassType(cls: AnyClass, type: bigint): void {
|
|
|
83
137
|
function registerWrapperClass(cls: AnyClass, type: bigint, vfuncs?: VfuncRegistry): void {
|
|
84
138
|
registerClassType(cls, type);
|
|
85
139
|
|
|
140
|
+
if (type !== TYPE_INVALID) {
|
|
141
|
+
wrapperClasses.add(cls);
|
|
142
|
+
}
|
|
143
|
+
|
|
86
144
|
if (vfuncs) {
|
|
87
145
|
registerVfuncRegistry(cls, vfuncs);
|
|
88
146
|
}
|
|
89
147
|
}
|
|
90
148
|
|
|
149
|
+
function markDerivedClass(cls: AnyClass): void {
|
|
150
|
+
derivedClasses.add(cls);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function resolveAncestorType(ancestor: AnyClass): bigint | undefined {
|
|
154
|
+
if (derivedClasses.has(ancestor) || !wrapperClasses.has(ancestor)) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return getClassType(ancestor);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function resolveWrapperType(instance: object): bigint {
|
|
162
|
+
const cls = instance.constructor as AnyClass | undefined;
|
|
163
|
+
|
|
164
|
+
if (cls === undefined) {
|
|
165
|
+
return TYPE_INVALID;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return walkClassChain(cls, (ancestor) => resolveAncestorType(ancestor)) ?? TYPE_INVALID;
|
|
169
|
+
}
|
|
170
|
+
|
|
91
171
|
/**
|
|
92
172
|
* Registers a GInterface, associating its GType with a mixin used to compose the
|
|
93
|
-
* interface onto wrapper classes and
|
|
173
|
+
* interface onto wrapper classes and, when introspection describes its vtable, that layout.
|
|
94
174
|
* @param cls Class carrying the interface's GType tag.
|
|
95
175
|
* @param type GType of the interface.
|
|
96
176
|
* @param mixin Mixin that applies the interface to a wrapper class.
|
|
97
|
-
* @param
|
|
98
|
-
* implementing class overrides.
|
|
177
|
+
* @param layout The interface's vtable struct, so `registerClass` can bind the slots an
|
|
178
|
+
* implementing class overrides and take over the ones it leaves alone.
|
|
99
179
|
*/
|
|
100
|
-
function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin,
|
|
180
|
+
function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, layout?: InterfaceLayout): void {
|
|
101
181
|
if (type === TYPE_INVALID) {
|
|
102
182
|
return;
|
|
103
183
|
}
|
|
@@ -105,16 +185,17 @@ function registerInterface(cls: AnyClass, type: bigint, mixin: Mixin, vfuncs?: V
|
|
|
105
185
|
setClassType(cls, type);
|
|
106
186
|
interfaceMixinRegistry.set(type, mixin);
|
|
107
187
|
|
|
108
|
-
if (
|
|
109
|
-
|
|
188
|
+
if (layout) {
|
|
189
|
+
interfaceLayoutRegistry.set(type, layout);
|
|
110
190
|
}
|
|
111
191
|
}
|
|
112
192
|
|
|
113
193
|
/**
|
|
114
194
|
* 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
|
-
*
|
|
195
|
+
* reuses the wrapper for the handle's runtime GType (composing interface mixins),
|
|
196
|
+
* and hands back an instance that already carries a handle unchanged; with an
|
|
197
|
+
* explicit class, creates a bare instance backed by the handle. Returns null for
|
|
198
|
+
* a null or undefined handle.
|
|
118
199
|
* @param handle Native handle to wrap.
|
|
119
200
|
* @param cls Wrapper class to instantiate, or omitted to resolve it from the runtime type.
|
|
120
201
|
*/
|
|
@@ -174,12 +255,16 @@ function resolveWrapperClass(type: bigint): AnyClass | null {
|
|
|
174
255
|
return null;
|
|
175
256
|
}
|
|
176
257
|
|
|
258
|
+
function getInterfaceMixin(type: bigint): Mixin | undefined {
|
|
259
|
+
return interfaceMixinRegistry.get(type);
|
|
260
|
+
}
|
|
261
|
+
|
|
177
262
|
function applyInterfaceMixin(cls: AnyClass, type: bigint, baseType: bigint, applied: Set<bigint>): AnyClass {
|
|
178
263
|
if (applied.has(type) || typeIsA(baseType, type)) {
|
|
179
264
|
return cls;
|
|
180
265
|
}
|
|
181
266
|
|
|
182
|
-
const mixin =
|
|
267
|
+
const mixin = getInterfaceMixin(type);
|
|
183
268
|
|
|
184
269
|
if (mixin === undefined) {
|
|
185
270
|
return cls;
|
|
@@ -228,12 +313,25 @@ function resolveComposedClass(runtimeType: bigint): AnyClass | null {
|
|
|
228
313
|
}
|
|
229
314
|
|
|
230
315
|
setClassType(composed, runtimeType);
|
|
316
|
+
wrapperClasses.add(composed);
|
|
231
317
|
composedClassRegistry.set(runtimeType, composed);
|
|
232
318
|
|
|
233
319
|
return composed;
|
|
234
320
|
}
|
|
235
321
|
|
|
322
|
+
function wrapObject(value: unknown): object | null {
|
|
323
|
+
if (value == null) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return getOrCreateWrapper(value as ExternalObject<Handle>);
|
|
328
|
+
}
|
|
329
|
+
|
|
236
330
|
function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
|
|
331
|
+
if (handleMap.has(handle)) {
|
|
332
|
+
return handle;
|
|
333
|
+
}
|
|
334
|
+
|
|
237
335
|
const existing = getWrapper(handle);
|
|
238
336
|
|
|
239
337
|
if (existing) {
|
|
@@ -258,23 +356,21 @@ function getOrCreateWrapper(handle: ExternalObject<Handle>): object {
|
|
|
258
356
|
return instance;
|
|
259
357
|
}
|
|
260
358
|
|
|
359
|
+
function instanceClassName(instance: object): string {
|
|
360
|
+
return (instance as { constructor?: { name?: string } }).constructor?.name ?? "object";
|
|
361
|
+
}
|
|
362
|
+
|
|
261
363
|
/** Returns the native handle bound to a wrapper instance, throwing if none is set. */
|
|
262
364
|
function getHandle(instance: object): ExternalObject<Handle> {
|
|
263
365
|
const handle = handleMap.get(instance);
|
|
264
366
|
|
|
265
367
|
if (handle === undefined) {
|
|
266
|
-
|
|
267
|
-
throw new Error(`No native handle associated with ${name}`);
|
|
368
|
+
throw new Error(`No native handle associated with ${instanceClassName(instance)}`);
|
|
268
369
|
}
|
|
269
370
|
|
|
270
371
|
return handle;
|
|
271
372
|
}
|
|
272
373
|
|
|
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
374
|
/** Associates a native handle with a wrapper instance. */
|
|
279
375
|
function setHandle(instance: object, handle: ExternalObject<Handle>): void {
|
|
280
376
|
handleMap.set(instance, handle);
|
|
@@ -293,21 +389,18 @@ function getVfuncRegistry(cls: object): VfuncRegistry | undefined {
|
|
|
293
389
|
return vfuncRegistry.get(cls);
|
|
294
390
|
}
|
|
295
391
|
|
|
296
|
-
function
|
|
297
|
-
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
interfaceVfuncRegistry.set(type, registry);
|
|
392
|
+
function getInterfaceVfuncRegistry(type: bigint): VfuncRegistry | undefined {
|
|
393
|
+
return interfaceLayoutRegistry.get(type)?.vfuncs;
|
|
302
394
|
}
|
|
303
395
|
|
|
304
|
-
function
|
|
305
|
-
return
|
|
396
|
+
function getInterfaceProperties(type: bigint): Record<string, InterfaceProperty> | undefined {
|
|
397
|
+
return interfaceLayoutRegistry.get(type)?.properties;
|
|
306
398
|
}
|
|
307
399
|
|
|
308
400
|
export {
|
|
309
401
|
getClassType,
|
|
310
402
|
getInstanceType,
|
|
403
|
+
markDerivedClass,
|
|
311
404
|
registerClassType,
|
|
312
405
|
registerWrapperClass,
|
|
313
406
|
registerInterface,
|
|
@@ -315,11 +408,16 @@ export {
|
|
|
315
408
|
getWrapperClass,
|
|
316
409
|
resolveWrapperClass,
|
|
317
410
|
getHandle,
|
|
318
|
-
tryGetHandle,
|
|
319
411
|
setHandle,
|
|
320
412
|
getVfuncRegistry,
|
|
413
|
+
getInterfaceMixin,
|
|
414
|
+
getInterfaceProperties,
|
|
321
415
|
getInterfaceVfuncRegistry,
|
|
416
|
+
instanceClassName,
|
|
417
|
+
registerWrapper,
|
|
418
|
+
resolveWrapperType,
|
|
419
|
+
wrapObject,
|
|
420
|
+
type InterfaceProperty,
|
|
322
421
|
type StaticBase,
|
|
323
422
|
type VfuncDescriptor,
|
|
324
|
-
type VfuncRegistry,
|
|
325
423
|
};
|
package/src/signal.ts
CHANGED
|
@@ -53,6 +53,7 @@ type EmitArg = Arg & {
|
|
|
53
53
|
};
|
|
54
54
|
|
|
55
55
|
const connectCache = createBindCache();
|
|
56
|
+
const connectionTable: WeakMap<object, Map<string, Set<number>>> = new WeakMap();
|
|
56
57
|
const gQuarkFromString = bind(LIB, "g_quark_from_string", [stringT("borrowed")], uint32T);
|
|
57
58
|
const gSignalLookup = bind(LIB, "g_signal_lookup", [stringT("borrowed"), biguint64T], uint32T);
|
|
58
59
|
|
|
@@ -63,13 +64,6 @@ const gSignalEmitv = bind(
|
|
|
63
64
|
voidT,
|
|
64
65
|
);
|
|
65
66
|
|
|
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
67
|
const gSignalHandlerIsConnected = bind(
|
|
74
68
|
LIB,
|
|
75
69
|
"g_signal_handler_is_connected",
|
|
@@ -77,6 +71,8 @@ const gSignalHandlerIsConnected = bind(
|
|
|
77
71
|
booleanT,
|
|
78
72
|
);
|
|
79
73
|
|
|
74
|
+
const gSignalHandlerDisconnect = bind(LIB, "g_signal_handler_disconnect", [objectT("borrowed"), uint64T], voidT);
|
|
75
|
+
|
|
80
76
|
/** Returns the signal name without its detail suffix (the part after `::`). */
|
|
81
77
|
const getSignalBaseName = (signal: string): string => {
|
|
82
78
|
const detailIndex = signal.indexOf("::");
|
|
@@ -97,6 +93,81 @@ function getSignalDetailQuark(signal: string): number {
|
|
|
97
93
|
const isSignalHandlerConnected = (instance: object, handlerId: number): boolean =>
|
|
98
94
|
gSignalHandlerIsConnected(getHandle(instance), handlerId) as boolean;
|
|
99
95
|
|
|
96
|
+
const trackConnection = (instance: object, signal: string, handlerId: number): void => {
|
|
97
|
+
let bySignal = connectionTable.get(instance);
|
|
98
|
+
|
|
99
|
+
if (!bySignal) {
|
|
100
|
+
bySignal = new Map();
|
|
101
|
+
connectionTable.set(instance, bySignal);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let handlerIds = bySignal.get(signal);
|
|
105
|
+
|
|
106
|
+
if (!handlerIds) {
|
|
107
|
+
handlerIds = new Set();
|
|
108
|
+
bySignal.set(signal, handlerIds);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
handlerIds.add(handlerId);
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const untrackConnection = (instance: object, handlerId: number): void => {
|
|
115
|
+
const bySignal = connectionTable.get(instance);
|
|
116
|
+
|
|
117
|
+
if (bySignal === undefined) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
for (const [name, handlerIds] of bySignal) {
|
|
122
|
+
handlerIds.delete(handlerId);
|
|
123
|
+
|
|
124
|
+
if (handlerIds.size === 0) {
|
|
125
|
+
bySignal.delete(name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Disconnects the handler an instance connected under the given id, and forgets the connection.
|
|
132
|
+
* @param instance Emitter the handler was connected to.
|
|
133
|
+
* @param handlerId Id {@link connectSignal} returned for the handler.
|
|
134
|
+
*/
|
|
135
|
+
const disconnectSignal = (instance: object, handlerId: number): void => {
|
|
136
|
+
untrackConnection(instance, handlerId);
|
|
137
|
+
gSignalHandlerDisconnect(getHandle(instance), handlerId);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const hasLiveConnection = (instance: object, handlerIds: Set<number>): boolean => {
|
|
141
|
+
let isLive = false;
|
|
142
|
+
|
|
143
|
+
for (const handlerId of handlerIds) {
|
|
144
|
+
if (isSignalHandlerConnected(instance, handlerId)) {
|
|
145
|
+
isLive = true;
|
|
146
|
+
} else {
|
|
147
|
+
handlerIds.delete(handlerId);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return isLive;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
function hasSignalListener(instance: object, signals?: string[]): boolean {
|
|
155
|
+
const bySignal = connectionTable.get(instance);
|
|
156
|
+
|
|
157
|
+
if (!bySignal) {
|
|
158
|
+
return false;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const names =
|
|
162
|
+
signals === undefined ? bySignal.keys().toArray() : signals.map((signal) => getSignalBaseName(signal));
|
|
163
|
+
|
|
164
|
+
return names.some((name) => {
|
|
165
|
+
const handlerIds = bySignal.get(name);
|
|
166
|
+
|
|
167
|
+
return handlerIds !== undefined && hasLiveConnection(instance, handlerIds);
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
100
171
|
const getSignalId = (instance: object, signal: string): number => {
|
|
101
172
|
const type: bigint = (instance as TypedClass).__type__;
|
|
102
173
|
|
|
@@ -126,13 +197,10 @@ function connectSignal(instance: object, signal: string, spec: SignalConnectSpec
|
|
|
126
197
|
const wrapped = wrapCallback(handler, callback, "emitter");
|
|
127
198
|
const type: bigint = (instance as TypedClass).__type__;
|
|
128
199
|
const connect = connectBind(type, signal, callback);
|
|
200
|
+
const handlerId = connect(getHandle(instance), signal, wrapped, isAfter ? 1 : 0) as number;
|
|
201
|
+
trackConnection(instance, getSignalBaseName(signal), handlerId);
|
|
129
202
|
|
|
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);
|
|
203
|
+
return handlerId;
|
|
136
204
|
}
|
|
137
205
|
|
|
138
206
|
const createEmitValue = (arg: EmitArg): { value: ExternalObject<Handle>; read?: () => unknown } => {
|
|
@@ -199,8 +267,10 @@ function emitSignal(instance: object, signal: string, args: EmitArg[], returnDes
|
|
|
199
267
|
export {
|
|
200
268
|
getSignalBaseName,
|
|
201
269
|
connectSignal,
|
|
202
|
-
|
|
270
|
+
disconnectSignal,
|
|
203
271
|
emitSignal,
|
|
272
|
+
hasSignalListener,
|
|
204
273
|
isSignalHandlerConnected,
|
|
274
|
+
untrackConnection,
|
|
205
275
|
type SignalHandler,
|
|
206
276
|
};
|
package/src/type.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { LIB } from "./library.js";
|
|
|
15
15
|
|
|
16
16
|
/** Object tagged with its GLib type through a `__type__` GType field. */
|
|
17
17
|
type TypedClass = {
|
|
18
|
+
/** GType the object's class is registered under. */
|
|
18
19
|
__type__: bigint;
|
|
19
20
|
};
|
|
20
21
|
|
|
@@ -119,6 +120,7 @@ const isTypedClass = (value: unknown): value is TypedClass =>
|
|
|
119
120
|
typeof value === "object" && value !== null && "__type__" in value && typeof value.__type__ === "bigint";
|
|
120
121
|
|
|
121
122
|
/** Returns whether `type` is `ancestorType` or descends from it. */
|
|
123
|
+
/* eslint-disable-next-line unicorn/consistent-boolean-name -- mirrors g_type_is_a from the C API */
|
|
122
124
|
function typeIsA(type: bigint, ancestorType: bigint): boolean {
|
|
123
125
|
return gTypeIsA(type, ancestorType) as boolean;
|
|
124
126
|
}
|
|
@@ -150,6 +152,7 @@ function typeName(type: bigint): string | null {
|
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
/** Returns whether `value` is a typed wrapper whose GType is or descends from `gtype`. */
|
|
155
|
+
/* eslint-disable-next-line unicorn/consistent-boolean-name -- mirrors g_type_is_a from the C API */
|
|
153
156
|
function valueIsA(value: unknown, gtype: bigint): boolean {
|
|
154
157
|
return isTypedClass(value) && typeIsA(value.__type__, gtype);
|
|
155
158
|
}
|
|
@@ -284,6 +287,5 @@ export {
|
|
|
284
287
|
resolveBoxedType,
|
|
285
288
|
resolveFundamentalType,
|
|
286
289
|
resolveDescriptorType,
|
|
287
|
-
type ResolvableDescriptor,
|
|
288
290
|
type TypedClass,
|
|
289
291
|
};
|