@gtkx/runtime 1.2.2 → 1.3.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 +2 -1
- package/dist/arg.d.ts +6 -1
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +2 -1
- package/dist/arg.js.map +1 -1
- package/dist/closure.d.ts.map +1 -1
- package/dist/closure.js +2 -11
- package/dist/closure.js.map +1 -1
- package/dist/descriptors.d.ts +21 -3
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +50 -21
- package/dist/descriptors.js.map +1 -1
- package/dist/fn.d.ts +2 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +50 -10
- package/dist/fn.js.map +1 -1
- package/dist/index.d.ts +11 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -4
- package/dist/index.js.map +1 -1
- package/dist/mixin.d.ts +2 -1
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +1 -1
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts +9 -3
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +86 -9
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +14 -6
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +21 -10
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts +31 -1
- package/dist/param-spec.d.ts.map +1 -1
- package/dist/param-spec.js +66 -5
- package/dist/param-spec.js.map +1 -1
- package/dist/promisify.d.ts +13 -1
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +14 -1
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +28 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +139 -8
- package/dist/properties.js.map +1 -1
- package/dist/regex.d.ts +25 -0
- package/dist/regex.d.ts.map +1 -0
- package/dist/regex.js +59 -0
- package/dist/regex.js.map +1 -0
- package/dist/register-class.d.ts +154 -11
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +157 -19
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +48 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +147 -3
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +8 -1
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +41 -3
- 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 +5 -1
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +48 -4
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +172 -16
- package/dist/value.js.map +1 -1
- package/dist/variant.d.ts +75 -26
- package/dist/variant.d.ts.map +1 -1
- package/dist/variant.js +44 -29
- package/dist/variant.js.map +1 -1
- package/package.json +4 -4
- package/src/arg.ts +6 -1
- package/src/closure.ts +2 -14
- package/src/descriptors.ts +74 -21
- package/src/fn.ts +73 -17
- package/src/index.ts +31 -4
- package/src/mixin.ts +1 -1
- package/src/native-value.ts +146 -14
- package/src/object.ts +23 -11
- package/src/param-spec.ts +94 -8
- package/src/promisify.ts +27 -1
- package/src/properties.ts +184 -5
- package/src/regex.ts +92 -0
- package/src/register-class.ts +446 -38
- package/src/registry.ts +219 -4
- package/src/signal.ts +77 -0
- package/src/type.ts +7 -0
- package/src/value.ts +268 -14
- package/src/variant.ts +184 -53
package/src/register-class.ts
CHANGED
|
@@ -3,9 +3,10 @@ import {
|
|
|
3
3
|
registerClass as nativeRegisterClass,
|
|
4
4
|
type RegisterClassInterface as NativeRegisterClassInterface,
|
|
5
5
|
type RegisterClassOptions as NativeRegisterClassOptions,
|
|
6
|
+
type RegisterClassSignal as NativeRegisterClassSignal,
|
|
6
7
|
type RegisterClassVfunc as NativeRegisterClassVfunc,
|
|
7
8
|
} from "@gtkx/native";
|
|
8
|
-
import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
|
|
9
|
+
import { type AnyClass, getParentClass, kebabCase, walkClassChain } from "@gtkx/utils";
|
|
9
10
|
import { wrapCallback } from "./callback.js";
|
|
10
11
|
import { insertMixinLayer } from "./mixin.js";
|
|
11
12
|
import {
|
|
@@ -19,13 +20,33 @@ import {
|
|
|
19
20
|
toNativeProperties,
|
|
20
21
|
} from "./properties.js";
|
|
21
22
|
import {
|
|
23
|
+
getClassStructClass,
|
|
22
24
|
getClassType,
|
|
23
25
|
getInterfaceMixin,
|
|
26
|
+
getTypeClassHandle,
|
|
24
27
|
markDerivedClass,
|
|
25
28
|
registerClassType,
|
|
26
29
|
type VfuncDescriptor,
|
|
30
|
+
wrapHandle,
|
|
27
31
|
} from "./registry.js";
|
|
28
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
connectClosureSignal,
|
|
34
|
+
type DeclaredSignalTypes,
|
|
35
|
+
emitDeclaredSignal,
|
|
36
|
+
getSignalBaseName,
|
|
37
|
+
overrideSignalClassClosure,
|
|
38
|
+
type SignalHandler,
|
|
39
|
+
} from "./signal.js";
|
|
40
|
+
import {
|
|
41
|
+
TYPE_INTERFACE,
|
|
42
|
+
TYPE_INVALID,
|
|
43
|
+
TYPE_NONE,
|
|
44
|
+
type TypedClass,
|
|
45
|
+
typeFundamental,
|
|
46
|
+
typeInterfaces,
|
|
47
|
+
typeIsA,
|
|
48
|
+
typeName,
|
|
49
|
+
} from "./type.js";
|
|
29
50
|
import { findClassVfuncDescriptor, findInterfaceVfuncDescriptor } from "./vfunc.js";
|
|
30
51
|
|
|
31
52
|
/**
|
|
@@ -69,16 +90,58 @@ type InstalledNames<TProperties> = string extends keyof TProperties
|
|
|
69
90
|
? never
|
|
70
91
|
: Camelized<Dashed<keyof TProperties & string>>;
|
|
71
92
|
|
|
93
|
+
/**
|
|
94
|
+
* The base names the signal surface takes for the signals `RegisterClassOptions.signals` declares:
|
|
95
|
+
* each name as written, plus its canonical spelling with underscores turned into dashes, which
|
|
96
|
+
* GObject knows the signal by too.
|
|
97
|
+
*/
|
|
98
|
+
type DeclaredSignalBase<TSignals> = (keyof TSignals & string) | Dashed<keyof TSignals & string>;
|
|
99
|
+
/**
|
|
100
|
+
* The names `connect` and `emit` take for one of the signals `RegisterClassOptions.signals`
|
|
101
|
+
* declares: each spelling of each name, plus its detailed form for a signal emitted with a
|
|
102
|
+
* `::detail` suffix.
|
|
103
|
+
*/
|
|
104
|
+
type DeclaredSignalName<TSignals> = DeclaredSignalBase<TSignals> | `${DeclaredSignalBase<TSignals>}::${string}`;
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* The `connect` and `emit` signatures instances gain for the signals
|
|
108
|
+
* `RegisterClassOptions.signals` declares, widening the inherited ones, which take only the names
|
|
109
|
+
* introspection knows. A class declaring no signals gains nothing, since no name reaches the
|
|
110
|
+
* added signatures.
|
|
111
|
+
*/
|
|
112
|
+
type DeclaredSignalMethods<TSignals> = {
|
|
113
|
+
/**
|
|
114
|
+
* Type-level map from declared signal name to handler signature, feeding the hooks that
|
|
115
|
+
* address a signal by name, such as `useSignal` from `@gtkx/react`; no value ever carries it.
|
|
116
|
+
*/
|
|
117
|
+
__signals__?: Record<DeclaredSignalBase<TSignals>, (...args: never[]) => unknown>;
|
|
118
|
+
/** Connects a handler to a signal `RegisterClassOptions.signals` declared. */
|
|
119
|
+
connect(
|
|
120
|
+
signal: DeclaredSignalName<TSignals>,
|
|
121
|
+
handler: (...args: never[]) => unknown,
|
|
122
|
+
isAfter?: boolean,
|
|
123
|
+
): number;
|
|
124
|
+
/** Emits a signal `RegisterClassOptions.signals` declared. */
|
|
125
|
+
emit(sigName: DeclaredSignalName<TSignals>, ...args: unknown[]): unknown;
|
|
126
|
+
/** Connects a handler to a signal `RegisterClassOptions.signals` declared, for `off` to take off. */
|
|
127
|
+
on(sigName: DeclaredSignalName<TSignals>, callback: (...args: unknown[]) => unknown, isAfter?: boolean): unknown;
|
|
128
|
+
/** Connects a handler to a signal `RegisterClassOptions.signals` declared for one emission. */
|
|
129
|
+
once(sigName: DeclaredSignalName<TSignals>, callback: (...args: unknown[]) => unknown, isAfter?: boolean): unknown;
|
|
130
|
+
/** Disconnects a handler `on` or `once` connected to a signal `RegisterClassOptions.signals` declared. */
|
|
131
|
+
off(sigName: DeclaredSignalName<TSignals>, callback: (...args: unknown[]) => unknown): unknown;
|
|
132
|
+
};
|
|
133
|
+
|
|
72
134
|
/**
|
|
73
135
|
* An instance of a registered class: everything the class itself declares, plus the property map the
|
|
74
136
|
* hooks that address a property by name, such as `useProperty` from `@gtkx/react`, read the installed
|
|
75
137
|
* names off. Each name is typed with the value type the class declares for the member of that name,
|
|
76
138
|
* so a property the class does not `declare` contributes nothing.
|
|
77
139
|
*/
|
|
78
|
-
type RegisteredInstance<TInstance, TProperties> = TInstance &
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
140
|
+
type RegisteredInstance<TInstance, TProperties, TSignals> = TInstance &
|
|
141
|
+
DeclaredSignalMethods<TSignals> & {
|
|
142
|
+
/** Type-level map from installed property name to value type; no value ever carries it. */
|
|
143
|
+
__properties__: Pick<TInstance, InstalledNames<TProperties> & keyof TInstance>;
|
|
144
|
+
};
|
|
82
145
|
|
|
83
146
|
/**
|
|
84
147
|
* The construct signature and prototype a registered class carries, both giving
|
|
@@ -92,20 +155,91 @@ type RegisteredConstructor<TClass, TArgs extends unknown[], TInstance> = {
|
|
|
92
155
|
? new (...args: TArgs) => TInstance
|
|
93
156
|
: abstract new (...args: TArgs) => TInstance);
|
|
94
157
|
|
|
158
|
+
/** The statics a registered class keeps from the class it was, joined with {@link RegisteredConstructor}. */
|
|
159
|
+
type RegisteredParts<TClass, TArgs extends unknown[], TInstance> = Omit<TClass, "prototype"> &
|
|
160
|
+
RegisteredConstructor<TClass, TArgs, TInstance>;
|
|
161
|
+
|
|
95
162
|
/**
|
|
96
163
|
* The class {@link registerClass} hands back: the same class, with the same statics, whose instances
|
|
97
|
-
* carry the properties `RegisterClassOptions.properties` installed
|
|
98
|
-
*
|
|
164
|
+
* carry the properties `RegisterClassOptions.properties` installed and take the signals
|
|
165
|
+
* `RegisterClassOptions.signals` declares by name. Binding the call to a name, rather than
|
|
166
|
+
* discarding it, is what carries those names into the type system.
|
|
99
167
|
*/
|
|
100
|
-
type RegisteredClass<TClass extends AnyClass, TProperties> =
|
|
168
|
+
type RegisteredClass<TClass extends AnyClass, TProperties, TSignals> =
|
|
101
169
|
TClass extends abstract new (...args: infer TArgs) => infer TInstance
|
|
102
|
-
?
|
|
170
|
+
? RegisteredParts<TClass, TArgs, RegisteredInstance<TInstance, TProperties, TSignals>>
|
|
103
171
|
: never;
|
|
104
172
|
|
|
173
|
+
/**
|
|
174
|
+
* A GType in the form `RegisterClassOptions.signals` takes one: the numeric GType itself, such as
|
|
175
|
+
* `TYPE_STRING` from `@gtkx/gi/gobject`, or a class carrying one, which is any generated wrapper
|
|
176
|
+
* class and any class an earlier {@link registerClass} call registered.
|
|
177
|
+
*/
|
|
178
|
+
type SignalGType = bigint | AnyClass<TypedClass>;
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* One signal `RegisterClassOptions.signals` creates on the new type, sitting under the signal's
|
|
182
|
+
* name. Every part is optional: `{}` declares a signal with no arguments and no return value that
|
|
183
|
+
* runs its handlers in the default `RUN_FIRST` stage.
|
|
184
|
+
*/
|
|
185
|
+
type SignalSpec = {
|
|
186
|
+
/**
|
|
187
|
+
* `GObject.SignalFlags` bit mask for the signal, defaulting to `RUN_FIRST`. `DETAILED` lets
|
|
188
|
+
* handlers connect to and emissions name a `::detail` suffix.
|
|
189
|
+
*/
|
|
190
|
+
flags?: number;
|
|
191
|
+
/** GType of each argument an emission carries, defaulting to none. */
|
|
192
|
+
paramTypes?: SignalGType[];
|
|
193
|
+
/** GType of the value an emission returns, defaulting to none. */
|
|
194
|
+
returnType?: SignalGType;
|
|
195
|
+
/**
|
|
196
|
+
* How the emission combines what its handlers return, limited to the two accumulators GObject
|
|
197
|
+
* ships: `"first-wins"` stops the emission at the first handler and keeps its result, and
|
|
198
|
+
* `"true-handled"` runs handlers until one returns `true`, which requires a boolean
|
|
199
|
+
* `returnType`. Without one, every handler runs and the last result stands.
|
|
200
|
+
*/
|
|
201
|
+
accumulator?: "first-wins" | "true-handled";
|
|
202
|
+
};
|
|
203
|
+
|
|
105
204
|
/** What {@link registerClass} adds to the new GType beyond the vtable slots it discovers on the class. */
|
|
106
|
-
type RegisterClassOptions<
|
|
107
|
-
|
|
205
|
+
type RegisterClassOptions<
|
|
206
|
+
TInstance extends object,
|
|
207
|
+
TProperties extends Record<string, PropertySpec>,
|
|
208
|
+
TSignals extends Record<string, SignalSpec>,
|
|
209
|
+
> = {
|
|
210
|
+
/**
|
|
211
|
+
* Name to register the new GType under, defaulting to the class's own name. Either way the
|
|
212
|
+
* name has to be a valid GType name: at least three characters, starting with a letter or
|
|
213
|
+
* underscore, the rest letters, digits, `-`, `_` or `+`. Any other name throws a `TypeError`.
|
|
214
|
+
*/
|
|
108
215
|
typeName?: string;
|
|
216
|
+
/**
|
|
217
|
+
* Registers the new GType abstract, the way `G_TYPE_FLAG_ABSTRACT` marks a C type: the class
|
|
218
|
+
* still serves as a parent for further registered subclasses, which instantiate as usual, but
|
|
219
|
+
* constructing it directly throws, whether from JavaScript or from a native caller.
|
|
220
|
+
*/
|
|
221
|
+
abstract?: boolean;
|
|
222
|
+
/**
|
|
223
|
+
* Name instances of the new type carry in CSS, applied through `gtk_widget_class_set_css_name`
|
|
224
|
+
* from inside the type's `class_init`, so every instance is born with it, wherever it is
|
|
225
|
+
* created from. Requires the class to extend `Gtk.Widget`; registering a non-widget with a
|
|
226
|
+
* `cssName` throws.
|
|
227
|
+
*/
|
|
228
|
+
cssName?: string;
|
|
229
|
+
/**
|
|
230
|
+
* Hook run once, synchronously, while `registerClass` registers the type, after its
|
|
231
|
+
* `class_init` has installed the vfuncs, properties and signals declared here. It receives the
|
|
232
|
+
* new type's class struct wrapped in its generated GTypeStruct wrapper, so class-level setup
|
|
233
|
+
* calls such as `Gtk.WidgetClass.installAction`, `Gtk.WidgetClass.addShortcut` and
|
|
234
|
+
* `Gtk.WidgetClass.setLayoutManagerType` have somewhere to land. The wrapper serves the
|
|
235
|
+
* members of every struct in the parent chain on one object: a widget subclass sees
|
|
236
|
+
* `Gtk.WidgetClass` and `GObject.ObjectClass` members alike, so the parameter can be declared
|
|
237
|
+
* as whichever of those types the hook needs. The class struct belongs to the type system for
|
|
238
|
+
* the life of the process, so keeping the wrapper around past the hook is safe, if rarely
|
|
239
|
+
* useful. An exception the hook throws propagates out of `registerClass`, with the type
|
|
240
|
+
* already registered: GObject offers no way to unregister a static type.
|
|
241
|
+
*/
|
|
242
|
+
classInit?(typeStruct: object): void;
|
|
109
243
|
/**
|
|
110
244
|
* Interfaces the new type implements on top of the ones it inherits, given as the interface values
|
|
111
245
|
* themselves, such as `Gio.ListModel`. Their vtable slots are filled from the `vfunc`-prefixed methods on
|
|
@@ -169,10 +303,32 @@ type RegisterClassOptions<TInstance extends object, TProperties extends Record<s
|
|
|
169
303
|
* and write it rather than the generated storage.
|
|
170
304
|
*/
|
|
171
305
|
properties?: TProperties;
|
|
306
|
+
/**
|
|
307
|
+
* Signals to create on the new type, keyed by signal name and valued with the
|
|
308
|
+
* {@link SignalSpec} describing each one. A name has to start with a letter, continue in
|
|
309
|
+
* letters, digits, `-` and `_`, and be new to the type: one an ancestor type or a listed
|
|
310
|
+
* interface already carries throws. The two word separators spell the same signal, so a
|
|
311
|
+
* signal declared as `data_changed` is connected to and emitted as `data-changed` too.
|
|
312
|
+
*
|
|
313
|
+
* Instances connect and emit by name through the same `connect`, `on`, `once`, `off` and
|
|
314
|
+
* `emit` surface inherited signals use: `registerClass` wraps `connect` and `emit` on the
|
|
315
|
+
* class's prototype to serve the declared names, unless the class defines the member itself,
|
|
316
|
+
* and hands every other name to the inherited implementation. A handler receives the
|
|
317
|
+
* emission's arguments without the leading emitter, matching a generated signal, and what it
|
|
318
|
+
* returns becomes the emission's return value when the signal declares one.
|
|
319
|
+
*
|
|
320
|
+
* The declared parameter GTypes rule the emission: `emit` takes exactly one argument per
|
|
321
|
+
* declared parameter, throwing a `TypeError` for any other count, and converts each argument
|
|
322
|
+
* into a `GValue` of the declared type, throwing for a value that type cannot hold. The signals
|
|
323
|
+
* are created with no class closure of their own, but a method named `on<SignalName>`
|
|
324
|
+
* becomes the signal's default handler, the way every `on`-prefixed method that names a
|
|
325
|
+
* signal the type carries does; see {@link registerClass}.
|
|
326
|
+
*/
|
|
327
|
+
signals?: TSignals;
|
|
172
328
|
};
|
|
173
329
|
|
|
174
330
|
/** {@link RegisterClassOptions} with the widest instance and property types {@link registerClass} accepts. */
|
|
175
|
-
type AnyRegisterClassOptions = RegisterClassOptions<object, Record<string, PropertySpec>>;
|
|
331
|
+
type AnyRegisterClassOptions = RegisterClassOptions<object, Record<string, PropertySpec>, Record<string, SignalSpec>>;
|
|
176
332
|
type VfuncFn = NativeRegisterClassVfunc["fn"];
|
|
177
333
|
type DiscoveredVfunc = VfuncDescriptor & { methodName: string; fn: VfuncFn };
|
|
178
334
|
type MethodTable = Map<string, VfuncFn>;
|
|
@@ -197,10 +353,18 @@ type PropertyVfuncSpec = {
|
|
|
197
353
|
};
|
|
198
354
|
|
|
199
355
|
type ArgPatch = { isCallerAllocated: true } | { isCallScoped: true };
|
|
356
|
+
type DeclaredSignals = { native: NativeRegisterClassSignal[]; table: Map<string, DeclaredSignalTypes> };
|
|
357
|
+
|
|
358
|
+
type SignalMethodHost = {
|
|
359
|
+
connect?: (signal: string, handler: SignalHandler, isAfter?: boolean) => number;
|
|
360
|
+
emit?: (sigName: string, ...args: unknown[]) => unknown;
|
|
361
|
+
};
|
|
200
362
|
|
|
201
363
|
const INSTANCE_ARG_INDEX = 0;
|
|
202
364
|
const VALUE_ARG_INDEX = 2;
|
|
203
365
|
const TEARDOWN_VFUNC_NAMES: Set<string> = new Set(["dispose", "finalize"]);
|
|
366
|
+
const ASYNC_INITABLE_TYPE_NAME = "GAsyncInitable";
|
|
367
|
+
const INIT_ASYNC_METHOD_NAME = "vfuncInitAsync";
|
|
204
368
|
|
|
205
369
|
const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
|
|
206
370
|
{ methodName: GET_PROPERTY_VFUNC, isValueOut: true, makeDispatch: makeGetProperty },
|
|
@@ -208,6 +372,8 @@ const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
|
|
|
208
372
|
];
|
|
209
373
|
|
|
210
374
|
const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec) => spec.methodName));
|
|
375
|
+
const TYPE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9\-_+]{2,}$/;
|
|
376
|
+
const SIGNAL_OVERRIDE_PATTERN = /^on[A-Z]/;
|
|
211
377
|
|
|
212
378
|
/**
|
|
213
379
|
* Registers a subclass of a wrapper class as a new GType, wiring up any class and interface
|
|
@@ -215,10 +381,19 @@ const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec
|
|
|
215
381
|
* `RegisterClassOptions.implements` names.
|
|
216
382
|
*
|
|
217
383
|
* Throws when the class does not extend a registered wrapper class, when it has no derivable type
|
|
218
|
-
* name
|
|
384
|
+
* name or the name is not a valid GType name, when an entry in
|
|
385
|
+
* `RegisterClassOptions.implements` is not a registered interface, when a
|
|
219
386
|
* listed interface has a prerequisite that neither the parent type nor another listed interface meets,
|
|
220
|
-
*
|
|
221
|
-
*
|
|
387
|
+
* when the list names `Gio.AsyncInitable` as an interface the parent type does not already
|
|
388
|
+
* implement and no method on the chain fills `vfuncInitAsync`, since the default `init_async`
|
|
389
|
+
* would run `vfuncInit` on a worker thread, when an entry in
|
|
390
|
+
* `RegisterClassOptions.properties` names its `GObject.ParamSpec` something other than the canonical
|
|
391
|
+
* spelling of the key it sits under, when an entry in
|
|
392
|
+
* `RegisterClassOptions.signals` carries an invalid name, a name the type already knows, a GType
|
|
393
|
+
* that cannot hold a value, or an accumulator the spec does not admit, and when
|
|
394
|
+
* `RegisterClassOptions.cssName` is
|
|
395
|
+
* given for a class that does not extend `Gtk.Widget`. An exception thrown by
|
|
396
|
+
* `RegisterClassOptions.classInit` also propagates, after the type has already been registered.
|
|
222
397
|
*
|
|
223
398
|
* A slot is filled from the `vfunc`-prefixed methods on the class's prototype chain, up to but not
|
|
224
399
|
* including the registered ancestor the class extends, so a method an intermediate base class
|
|
@@ -228,6 +403,19 @@ const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec
|
|
|
228
403
|
* Declare every slot as a method: a class field holding a function, such as `vfuncGetNItems = () => 1`,
|
|
229
404
|
* is assigned to each instance after registration and never reaches the vtable.
|
|
230
405
|
*
|
|
406
|
+
* A method named `on<SignalName>` — the signal's name in camelCase after the `on`, so `onClicked`
|
|
407
|
+
* for `clicked` and `onItemsChanged` for `items-changed` — becomes that signal's default handler
|
|
408
|
+
* when the type carries the signal, whether an ancestor type or an implemented interface brings it
|
|
409
|
+
* or `RegisterClassOptions.signals` declares it. The method is installed as a class-closure
|
|
410
|
+
* override, so it runs on every emission, on the instances a native caller creates included, in
|
|
411
|
+
* the stage the signal's flags name rather than alongside connected handlers. It receives the
|
|
412
|
+
* emission's arguments without the leading emitter, with `this` bound to the emitter, and what it
|
|
413
|
+
* returns becomes the emission's result when the signal declares one. The same discovery walks the
|
|
414
|
+
* prototype chain vfunc discovery walks, and a subclass registering its own `on<SignalName>`
|
|
415
|
+
* replaces the handler for its instances, where `super.on<SignalName>()` reaches the replaced one.
|
|
416
|
+
* An `on`-prefixed method naming no signal the type carries is left alone as the ordinary method
|
|
417
|
+
* it is.
|
|
418
|
+
*
|
|
231
419
|
* An override of `vfuncConstructed` runs from inside the base constructor, before JavaScript
|
|
232
420
|
* installs the subclass's field initializers and runs its constructor body, so a field still
|
|
233
421
|
* reads `undefined` there and reading a `#private` field throws. Declare state the override
|
|
@@ -243,7 +431,11 @@ const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec
|
|
|
243
431
|
function registerClass<
|
|
244
432
|
T extends AnyClass,
|
|
245
433
|
TProperties extends Record<string, PropertySpec> = Record<never, PropertySpec>,
|
|
246
|
-
|
|
434
|
+
TSignals extends Record<string, SignalSpec> = Record<never, SignalSpec>,
|
|
435
|
+
>(
|
|
436
|
+
klass: T,
|
|
437
|
+
options?: RegisterClassOptions<T["prototype"], TProperties, TSignals>,
|
|
438
|
+
): RegisteredClass<T, TProperties, TSignals>;
|
|
247
439
|
|
|
248
440
|
function registerClass(klass: AnyClass, options: AnyRegisterClassOptions = {}): AnyClass {
|
|
249
441
|
const parentType = resolveParentType(klass);
|
|
@@ -252,16 +444,14 @@ function registerClass(klass: AnyClass, options: AnyRegisterClassOptions = {}):
|
|
|
252
444
|
throw new TypeError(`registerClass: ${klass.name} must extend a registered wrapper class`);
|
|
253
445
|
}
|
|
254
446
|
|
|
255
|
-
const name = options
|
|
256
|
-
|
|
257
|
-
if (!name) {
|
|
258
|
-
throw new Error("registerClass: cannot derive a GType name (anonymous class with no typeName option)");
|
|
259
|
-
}
|
|
260
|
-
|
|
447
|
+
const name = resolveTypeName(klass, options);
|
|
261
448
|
const declaredTypes = resolveInterfaceTypes(klass, options.implements ?? []);
|
|
262
449
|
const adoptedTypes = declaredTypes.filter((gtype) => !typeIsA(parentType, gtype));
|
|
263
450
|
const properties = options.properties ?? {};
|
|
264
|
-
const
|
|
451
|
+
const signals = resolveDeclaredSignals(klass, options.signals ?? {});
|
|
452
|
+
const members = collectInstanceMembers(klass);
|
|
453
|
+
const { methods, inheritedNames } = members;
|
|
454
|
+
checkAsyncInitable(klass, adoptedTypes, methods);
|
|
265
455
|
const dispatch = buildPropertyDispatch({ klass, properties, adoptedTypes });
|
|
266
456
|
|
|
267
457
|
const classVfuncs = [
|
|
@@ -271,15 +461,54 @@ function registerClass(klass: AnyClass, options: AnyRegisterClassOptions = {}):
|
|
|
271
461
|
|
|
272
462
|
const claimedMethodNames = new Set(classVfuncs.map((vfunc) => vfunc.methodName));
|
|
273
463
|
const interfaceBindings = discoverInterfaceBindings(methods, parentType, declaredTypes, claimedMethodNames);
|
|
274
|
-
|
|
464
|
+
|
|
465
|
+
const nativeOptions = withNativeSignals(
|
|
466
|
+
toNativeOptions(classVfuncs, interfaceBindings, properties, options),
|
|
467
|
+
signals.native,
|
|
468
|
+
);
|
|
469
|
+
|
|
275
470
|
const newType: bigint = nativeRegisterClass(name, parentType, nativeOptions);
|
|
276
471
|
registerClassType(klass, newType);
|
|
277
472
|
markDerivedClass(klass);
|
|
473
|
+
installSignalOverrides(newType, methods);
|
|
278
474
|
applyInterfaceMixins(klass, adoptedTypes, inheritedNames);
|
|
475
|
+
installDeclaredSignalMethods(klass, signals.table, members.names);
|
|
476
|
+
invokeClassInit(options, newType);
|
|
279
477
|
|
|
280
478
|
return klass;
|
|
281
479
|
}
|
|
282
480
|
|
|
481
|
+
function invokeClassInit(options: AnyRegisterClassOptions, newType: bigint): void {
|
|
482
|
+
if (options.classInit === undefined) {
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
const structClass = getClassStructClass(newType);
|
|
487
|
+
|
|
488
|
+
if (structClass === undefined) {
|
|
489
|
+
throw new Error("registerClass: no ancestor of the new type registers a class struct wrapper");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
options.classInit(wrapHandle(getTypeClassHandle(newType), structClass));
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
function resolveTypeName(klass: AnyClass, options: AnyRegisterClassOptions): string {
|
|
496
|
+
const name = options.typeName ?? klass.name;
|
|
497
|
+
|
|
498
|
+
if (!name) {
|
|
499
|
+
throw new TypeError("registerClass: cannot derive a GType name (anonymous class with no typeName option)");
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (!TYPE_NAME_PATTERN.test(name)) {
|
|
503
|
+
throw new TypeError(
|
|
504
|
+
`registerClass: '${name}' is not a valid GType name (a letter or underscore, then at ` +
|
|
505
|
+
"least two more characters, all from A-Z, a-z, 0-9, '-', '_' and '+')",
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
return name;
|
|
510
|
+
}
|
|
511
|
+
|
|
283
512
|
function resolveInterfaceType(klass: AnyClass, entry: AnyClass): bigint {
|
|
284
513
|
const gtype = getClassType(entry);
|
|
285
514
|
|
|
@@ -298,6 +527,17 @@ function resolveInterfaceTypes(klass: AnyClass, entries: AnyClass[]): bigint[] {
|
|
|
298
527
|
return [...new Set(entries.map((entry) => resolveInterfaceType(klass, entry)))];
|
|
299
528
|
}
|
|
300
529
|
|
|
530
|
+
function checkAsyncInitable(klass: AnyClass, adoptedTypes: bigint[], methods: MethodTable): void {
|
|
531
|
+
const isAsyncInitable = adoptedTypes.some((gtype) => typeName(gtype) === ASYNC_INITABLE_TYPE_NAME);
|
|
532
|
+
|
|
533
|
+
if (isAsyncInitable && !methods.has(INIT_ASYNC_METHOD_NAME)) {
|
|
534
|
+
throw new TypeError(
|
|
535
|
+
`registerClass: ${klass.name} implements Gio.AsyncInitable without overriding 'vfuncInitAsync'; ` +
|
|
536
|
+
"the default 'init_async' would run 'vfuncInit' on a worker thread",
|
|
537
|
+
);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
301
541
|
function applyInterfaceMixins(klass: AnyClass, adoptedTypes: bigint[], inheritedNames: Set<string>): void {
|
|
302
542
|
for (const gtype of adoptedTypes) {
|
|
303
543
|
const mixin = getInterfaceMixin(gtype);
|
|
@@ -527,6 +767,159 @@ function buildPropertyVfunc(
|
|
|
527
767
|
};
|
|
528
768
|
}
|
|
529
769
|
|
|
770
|
+
const canonicalSignalName = (name: string): string => name.replaceAll("_", "-");
|
|
771
|
+
|
|
772
|
+
function installSignalOverrides(newType: bigint, methods: MethodTable): void {
|
|
773
|
+
for (const [methodName, fn] of methods) {
|
|
774
|
+
if (!SIGNAL_OVERRIDE_PATTERN.test(methodName)) {
|
|
775
|
+
continue;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const handler = fn as (...args: unknown[]) => unknown;
|
|
779
|
+
|
|
780
|
+
overrideSignalClassClosure(newType, kebabCase(methodName.slice(2)), (...args: unknown[]) =>
|
|
781
|
+
handler.apply(args[0], args.slice(1)),
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function resolveSignalGType(klass: AnyClass, signalName: string, role: string, entry: SignalGType): bigint {
|
|
787
|
+
if (typeof entry === "bigint") {
|
|
788
|
+
return entry;
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
const gtype = getClassType(entry);
|
|
792
|
+
|
|
793
|
+
if (gtype === TYPE_INVALID) {
|
|
794
|
+
throw new TypeError(
|
|
795
|
+
`registerClass: signal '${signalName}' of ${klass.name} names a class with no registered ` +
|
|
796
|
+
`GType as its ${role}`,
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return gtype;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
function resolveSignalReturnType(klass: AnyClass, name: string, spec: SignalSpec): bigint | undefined {
|
|
804
|
+
if (spec.returnType === undefined) {
|
|
805
|
+
return undefined;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
const returnType = resolveSignalGType(klass, name, "return type", spec.returnType);
|
|
809
|
+
|
|
810
|
+
return returnType === TYPE_NONE ? undefined : returnType;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
function resolveDeclaredSignal(
|
|
814
|
+
klass: AnyClass,
|
|
815
|
+
name: string,
|
|
816
|
+
spec: SignalSpec,
|
|
817
|
+
): { native: NativeRegisterClassSignal; declared: DeclaredSignalTypes } {
|
|
818
|
+
const paramTypes = (spec.paramTypes ?? []).map((entry, index) =>
|
|
819
|
+
resolveSignalGType(klass, name, `parameter ${String(index)}`, entry),
|
|
820
|
+
);
|
|
821
|
+
|
|
822
|
+
const returnType = resolveSignalReturnType(klass, name, spec);
|
|
823
|
+
const native: NativeRegisterClassSignal = { name, paramTypes };
|
|
824
|
+
const declared: DeclaredSignalTypes = { paramTypes };
|
|
825
|
+
|
|
826
|
+
if (spec.flags !== undefined) {
|
|
827
|
+
native.flags = spec.flags;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
if (spec.accumulator !== undefined) {
|
|
831
|
+
native.accumulator = spec.accumulator;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
if (returnType !== undefined) {
|
|
835
|
+
native.returnType = returnType;
|
|
836
|
+
declared.returnType = returnType;
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return { native, declared };
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
function resolveDeclaredSignals(klass: AnyClass, signals: Record<string, SignalSpec>): DeclaredSignals {
|
|
843
|
+
const native: NativeRegisterClassSignal[] = [];
|
|
844
|
+
const table: Map<string, DeclaredSignalTypes> = new Map();
|
|
845
|
+
|
|
846
|
+
for (const [name, spec] of Object.entries(signals)) {
|
|
847
|
+
const resolved = resolveDeclaredSignal(klass, name, spec);
|
|
848
|
+
native.push(resolved.native);
|
|
849
|
+
table.set(canonicalSignalName(name), resolved.declared);
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
return { native, table };
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function inheritedSignalMethod<T>(inherited: T | undefined, signal: string): T {
|
|
856
|
+
if (inherited === undefined) {
|
|
857
|
+
throw new Error(`Unknown signal '${signal}'`);
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
return inherited;
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function installDeclaredConnect(
|
|
864
|
+
proto: SignalMethodHost,
|
|
865
|
+
findDeclared: (signal: string) => DeclaredSignalTypes | undefined,
|
|
866
|
+
): void {
|
|
867
|
+
const inheritedConnect = proto.connect;
|
|
868
|
+
|
|
869
|
+
proto.connect = function connect(
|
|
870
|
+
this: object,
|
|
871
|
+
signal: string,
|
|
872
|
+
handler: SignalHandler,
|
|
873
|
+
isAfter?: boolean,
|
|
874
|
+
): number {
|
|
875
|
+
if (findDeclared(signal) === undefined) {
|
|
876
|
+
return inheritedSignalMethod(inheritedConnect, signal).call(this, signal, handler, isAfter);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
return connectClosureSignal(this, signal, handler, isAfter ?? false);
|
|
880
|
+
};
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function installDeclaredEmit(
|
|
884
|
+
proto: SignalMethodHost,
|
|
885
|
+
findDeclared: (signal: string) => DeclaredSignalTypes | undefined,
|
|
886
|
+
): void {
|
|
887
|
+
const inheritedEmit = proto.emit;
|
|
888
|
+
|
|
889
|
+
proto.emit = function emit(this: object, sigName: string, ...args: unknown[]): unknown {
|
|
890
|
+
const declared = findDeclared(sigName);
|
|
891
|
+
|
|
892
|
+
if (declared === undefined) {
|
|
893
|
+
return inheritedSignalMethod(inheritedEmit, sigName).call(this, sigName, ...args);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
return emitDeclaredSignal(this, sigName, declared, args);
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function installDeclaredSignalMethods(
|
|
901
|
+
klass: AnyClass,
|
|
902
|
+
table: Map<string, DeclaredSignalTypes>,
|
|
903
|
+
definedNames: Set<string>,
|
|
904
|
+
): void {
|
|
905
|
+
if (table.size === 0) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const proto = klass.prototype as SignalMethodHost;
|
|
910
|
+
|
|
911
|
+
const findDeclared = (signal: string): DeclaredSignalTypes | undefined =>
|
|
912
|
+
table.get(canonicalSignalName(getSignalBaseName(signal)));
|
|
913
|
+
|
|
914
|
+
if (!definedNames.has("connect")) {
|
|
915
|
+
installDeclaredConnect(proto, findDeclared);
|
|
916
|
+
}
|
|
917
|
+
|
|
918
|
+
if (!definedNames.has("emit")) {
|
|
919
|
+
installDeclaredEmit(proto, findDeclared);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
530
923
|
function toNativeInterface(binding: InterfaceVfuncBinding): NativeRegisterClassInterface {
|
|
531
924
|
const nativeInterface: NativeRegisterClassInterface = {
|
|
532
925
|
type: binding.gtype,
|
|
@@ -542,34 +935,49 @@ function toNativeInterface(binding: InterfaceVfuncBinding): NativeRegisterClassI
|
|
|
542
935
|
return nativeInterface;
|
|
543
936
|
}
|
|
544
937
|
|
|
938
|
+
function withNativeSignals(
|
|
939
|
+
options: NativeRegisterClassOptions | undefined,
|
|
940
|
+
signals: NativeRegisterClassSignal[],
|
|
941
|
+
): NativeRegisterClassOptions | undefined {
|
|
942
|
+
if (signals.length === 0) {
|
|
943
|
+
return options;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
return { ...options, signals };
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
function applyNativeTypeOptions(options: NativeRegisterClassOptions, source: AnyRegisterClassOptions): void {
|
|
950
|
+
if (source.abstract ?? false) {
|
|
951
|
+
options.abstract = true;
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
if (source.cssName !== undefined) {
|
|
955
|
+
options.cssName = source.cssName;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
|
|
545
959
|
function toNativeOptions(
|
|
546
960
|
classVfuncs: DiscoveredVfunc[],
|
|
547
961
|
interfaceBindings: InterfaceVfuncBinding[],
|
|
548
962
|
properties: Record<string, PropertySpec>,
|
|
963
|
+
source: AnyRegisterClassOptions,
|
|
549
964
|
): NativeRegisterClassOptions | undefined {
|
|
550
|
-
const hasInterfaces = interfaceBindings.length > 0;
|
|
551
|
-
const hasClassVfuncs = classVfuncs.length > 0;
|
|
552
|
-
const hasProperties = Object.keys(properties).length > 0;
|
|
553
|
-
|
|
554
|
-
if (!hasClassVfuncs && !hasInterfaces && !hasProperties) {
|
|
555
|
-
return undefined;
|
|
556
|
-
}
|
|
557
|
-
|
|
558
965
|
const options: NativeRegisterClassOptions = {};
|
|
966
|
+
applyNativeTypeOptions(options, source);
|
|
559
967
|
|
|
560
|
-
if (
|
|
968
|
+
if (Object.keys(properties).length > 0) {
|
|
561
969
|
options.properties = toNativeProperties(properties);
|
|
562
970
|
}
|
|
563
971
|
|
|
564
|
-
if (
|
|
972
|
+
if (classVfuncs.length > 0) {
|
|
565
973
|
options.vfuncs = [...classVfuncs];
|
|
566
974
|
}
|
|
567
975
|
|
|
568
|
-
if (
|
|
976
|
+
if (interfaceBindings.length > 0) {
|
|
569
977
|
options.interfaces = interfaceBindings.map((binding) => toNativeInterface(binding));
|
|
570
978
|
}
|
|
571
979
|
|
|
572
|
-
return options;
|
|
980
|
+
return Object.keys(options).length > 0 ? options : undefined;
|
|
573
981
|
}
|
|
574
982
|
|
|
575
|
-
export { type Interface, registerClass };
|
|
983
|
+
export { type Interface, registerClass, type SignalGType, type SignalSpec };
|