@gtkx/runtime 1.0.0 → 1.2.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 +1 -1
- package/dist/application-class.d.ts +5 -0
- package/dist/application-class.d.ts.map +1 -1
- package/dist/application-class.js +11 -1
- package/dist/application-class.js.map +1 -1
- package/dist/callback.d.ts +2 -2
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +51 -54
- package/dist/callback.js.map +1 -1
- package/dist/closure.js +1 -1
- package/dist/closure.js.map +1 -1
- 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 +34 -9
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +42 -17
- package/dist/descriptors.js.map +1 -1
- package/dist/exit-hook.js +13 -0
- package/dist/exit-hook.js.map +1 -1
- package/dist/fn.d.ts +5 -0
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +11 -13
- 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/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 +16 -1
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +44 -14
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +16 -4
- package/dist/listeners.js.map +1 -1
- package/dist/native-value.js +2 -2
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +7 -2
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +7 -2
- package/dist/object.js.map +1 -1
- package/dist/param-spec.d.ts.map +1 -1
- package/dist/param-spec.js +18 -20
- package/dist/param-spec.js.map +1 -1
- package/dist/properties.d.ts +2 -1
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +13 -9
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +33 -3
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +21 -7
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +14 -4
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +24 -15
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +2 -2
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +22 -18
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +3 -1
- 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/value.d.ts +3 -1
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +64 -18
- package/dist/value.js.map +1 -1
- package/dist/vfunc-call.d.ts.map +1 -1
- package/dist/vfunc-call.js +5 -1
- package/dist/vfunc-call.js.map +1 -1
- package/dist/vfunc.d.ts.map +1 -1
- package/dist/vfunc.js +9 -3
- package/dist/vfunc.js.map +1 -1
- package/package.json +14 -4
- package/src/application-class.ts +12 -2
- package/src/callback.ts +70 -66
- package/src/closure.ts +1 -1
- package/src/default-application.ts +21 -0
- package/src/descriptors.ts +98 -29
- package/src/exit-hook.ts +18 -0
- package/src/fn.ts +23 -13
- package/src/folded-lengths.ts +64 -0
- package/src/internal.ts +1 -0
- package/src/library.ts +2 -1
- package/src/lifecycle.ts +60 -14
- package/src/listeners.ts +22 -5
- package/src/native-value.ts +2 -2
- package/src/object.ts +7 -2
- package/src/param-spec.ts +21 -22
- package/src/properties.ts +23 -9
- package/src/register-class.ts +66 -11
- package/src/registry.ts +43 -20
- package/src/signal.ts +32 -28
- package/src/t.ts +4 -1
- package/src/tuple.ts +18 -12
- package/src/value.ts +94 -19
- package/src/vfunc-call.ts +5 -1
- package/src/vfunc.ts +10 -3
package/src/internal.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export { checkError, createErrorDomain } from "./error.js";
|
|
2
|
+
export { type ApplicationInstance, getApplicationInstance } from "./lifecycle.js";
|
|
2
3
|
export { registerClassType, resolveWrapperClass, wrapHandle } from "./registry.js";
|
|
3
4
|
export { hasSignalListener } from "./signal.js";
|
|
4
5
|
export { resolveType } from "./type.js";
|
package/src/library.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type BoxedDescriptor, boxedT, type FundamentalDescriptor, fundamentalT } from "./descriptors.js";
|
|
2
2
|
|
|
3
3
|
const LIB = "libgobject-2.0.so.0,libglib-2.0.so.0";
|
|
4
|
+
const GIO_LIB = "libgio-2.0.so.0";
|
|
4
5
|
const VALUE_SIZE = 24;
|
|
5
6
|
const CLOSURE_SIZE = 32;
|
|
6
7
|
const VALUE_T: BoxedDescriptor = boxedT("GValue", { sharedLibrary: LIB, getTypeFnName: "g_value_get_type" });
|
|
@@ -15,4 +16,4 @@ const VARIANT_T: FundamentalDescriptor = fundamentalT(LIB, "g_variant_ref", "g_v
|
|
|
15
16
|
typeName: "GVariant",
|
|
16
17
|
});
|
|
17
18
|
|
|
18
|
-
export { CLOSURE_SIZE, LIB, VALUE_SIZE, VALUE_T, PARAM_T, VARIANT_T };
|
|
19
|
+
export { CLOSURE_SIZE, GIO_LIB, LIB, VALUE_SIZE, VALUE_T, PARAM_T, VARIANT_T };
|
package/src/lifecycle.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { keepAlive, quit as nativeQuit } from "@gtkx/native";
|
|
2
2
|
import { isDerivedApplication, type LocalCommandLineApplication, shutDownThroughRun } from "./application-class.js";
|
|
3
|
+
import { claimDefaultApplication, releaseDefaultApplication } from "./default-application.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* The GIO and GTK application surface {@link runApplication} and {@link quitApplication} drive, so any
|
|
@@ -30,6 +31,8 @@ type ApplicationLike = {
|
|
|
30
31
|
emit(signal: "shutdown"): unknown;
|
|
31
32
|
};
|
|
32
33
|
|
|
34
|
+
type ApplicationInstance = "primary" | "remote" | "shutDown" | "unregistered";
|
|
35
|
+
|
|
33
36
|
/** What {@link runApplication} reports about the process it just started. */
|
|
34
37
|
type RunApplicationResult = {
|
|
35
38
|
/** Whether this process owns the application ID and may build a user interface. */
|
|
@@ -40,6 +43,7 @@ type RunApplicationResult = {
|
|
|
40
43
|
|
|
41
44
|
const shutdownCallbacks: (() => void)[] = [];
|
|
42
45
|
const startedApplications: WeakSet<object> = new WeakSet();
|
|
46
|
+
const registeredApplications: WeakSet<object> = new WeakSet();
|
|
43
47
|
const shutDownApplications: WeakSet<object> = new WeakSet();
|
|
44
48
|
/**
|
|
45
49
|
* Runs every registered exit callback and shuts down the native runtime. Safe to
|
|
@@ -101,6 +105,14 @@ const restartApplication = (application: ApplicationLike): number => {
|
|
|
101
105
|
return 0;
|
|
102
106
|
};
|
|
103
107
|
|
|
108
|
+
const getApplicationInstance = (application: ApplicationLike): ApplicationInstance => {
|
|
109
|
+
if (!application.getIsRegistered()) {
|
|
110
|
+
return registeredApplications.has(application) ? "shutDown" : "unregistered";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return application.getIsRemote?.() === true ? "remote" : "primary";
|
|
114
|
+
};
|
|
115
|
+
|
|
104
116
|
/**
|
|
105
117
|
* Hands `argv` to GLib's own command line handling, which parses the application's options, prints
|
|
106
118
|
* `--help`, runs `handle-local-options`, registers the application, and either activates it or
|
|
@@ -119,6 +131,13 @@ const restartApplication = (application: ApplicationLike): number => {
|
|
|
119
131
|
* user interface may be built here, because a remote application has no `GtkApplicationImpl` and
|
|
120
132
|
* attaching a window to it crashes.
|
|
121
133
|
*
|
|
134
|
+
* The application also claims the process-wide default `Gio.Application.getDefault()` returns, before
|
|
135
|
+
* its command line is parsed, so an `activate` handler and anything the host reads afterwards see the
|
|
136
|
+
* application being started rather than an earlier one. That holds whether the start ends registered,
|
|
137
|
+
* remote or unregistered: a refused command line still has to be reportable through the default, which
|
|
138
|
+
* is how `gtkx dev` learns which application its entry mounted. {@link quitApplication} gives the
|
|
139
|
+
* default back up, since GLib drops it only at finalize.
|
|
140
|
+
*
|
|
122
141
|
* @param application The application to start.
|
|
123
142
|
* @param argv The command line, whose first entry names the program as `--help` should print it.
|
|
124
143
|
* @returns Whether this process may build a user interface, and the status to exit with.
|
|
@@ -132,11 +151,19 @@ const runApplication = (application: ApplicationLike, argv: string[]): RunApplic
|
|
|
132
151
|
);
|
|
133
152
|
}
|
|
134
153
|
|
|
154
|
+
claimDefaultApplication(application);
|
|
155
|
+
|
|
135
156
|
const exitStatus = startedApplications.has(application)
|
|
136
157
|
? restartApplication(application)
|
|
137
158
|
: startApplication(application, argv);
|
|
138
159
|
|
|
139
|
-
const
|
|
160
|
+
const instance = getApplicationInstance(application);
|
|
161
|
+
|
|
162
|
+
if (instance !== "unregistered") {
|
|
163
|
+
registeredApplications.add(application);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const isPrimary = instance === "primary";
|
|
140
167
|
|
|
141
168
|
if (isPrimary) {
|
|
142
169
|
keepAlive(true);
|
|
@@ -145,18 +172,7 @@ const runApplication = (application: ApplicationLike, argv: string[]): RunApplic
|
|
|
145
172
|
return { isPrimary, exitStatus };
|
|
146
173
|
};
|
|
147
174
|
|
|
148
|
-
|
|
149
|
-
* Detaches every window from the application and runs GLib's own shutdown, which emits `shutdown`,
|
|
150
|
-
* destroys the application implementation and releases the D-Bus registration. Does nothing for an
|
|
151
|
-
* application that is not registered, so a repeated call is a no-op.
|
|
152
|
-
*
|
|
153
|
-
* GLib's own shutdown is reachable once per application: reaching it marks the application as
|
|
154
|
-
* quitting, which GLib never undoes. An application that has already quit falls back to emitting
|
|
155
|
-
* `shutdown`, which releases the runtime and leaves the registration for GLib to drop at finalize.
|
|
156
|
-
*
|
|
157
|
-
* @param application The application to shut down.
|
|
158
|
-
*/
|
|
159
|
-
const quitApplication = (application: ApplicationLike): void => {
|
|
175
|
+
const tearDownApplication = (application: ApplicationLike): void => {
|
|
160
176
|
if (!application.getIsRegistered() || shutDownApplications.has(application)) {
|
|
161
177
|
return;
|
|
162
178
|
}
|
|
@@ -175,4 +191,34 @@ const quitApplication = (application: ApplicationLike): void => {
|
|
|
175
191
|
}
|
|
176
192
|
};
|
|
177
193
|
|
|
178
|
-
|
|
194
|
+
/**
|
|
195
|
+
* Detaches every window from the application and runs GLib's own shutdown, which emits `shutdown`,
|
|
196
|
+
* destroys the application implementation and releases the D-Bus registration. Does nothing for an
|
|
197
|
+
* application that is not registered, so a repeated call is a no-op.
|
|
198
|
+
*
|
|
199
|
+
* GLib's own shutdown is reachable once per application: reaching it marks the application as
|
|
200
|
+
* quitting, which GLib never undoes. An application that has already quit falls back to emitting
|
|
201
|
+
* `shutdown`, which releases the runtime and leaves the registration for GLib to drop at finalize.
|
|
202
|
+
*
|
|
203
|
+
* The process-wide default {@link runApplication} claimed is given up whichever of those paths the
|
|
204
|
+
* application took, including the ones that have no teardown left to do, so
|
|
205
|
+
* `Gio.Application.getDefault()` never hands back an application that has been torn down or that
|
|
206
|
+
* never registered in the first place. GLib clears that default only at finalize, which a
|
|
207
|
+
* garbage-collected wrapper reaches arbitrarily late or never.
|
|
208
|
+
*
|
|
209
|
+
* @param application The application to shut down.
|
|
210
|
+
*/
|
|
211
|
+
const quitApplication = (application: ApplicationLike): void => {
|
|
212
|
+
tearDownApplication(application);
|
|
213
|
+
releaseDefaultApplication(application);
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
export {
|
|
217
|
+
getApplicationInstance,
|
|
218
|
+
onExit,
|
|
219
|
+
quit,
|
|
220
|
+
runApplication,
|
|
221
|
+
quitApplication,
|
|
222
|
+
type ApplicationInstance,
|
|
223
|
+
type RunApplicationResult,
|
|
224
|
+
};
|
package/src/listeners.ts
CHANGED
|
@@ -11,10 +11,10 @@ type SignalConnectable = {
|
|
|
11
11
|
disconnect(handlerId: number): void;
|
|
12
12
|
};
|
|
13
13
|
|
|
14
|
-
const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number>>> = new WeakMap();
|
|
14
|
+
const listenerTable: WeakMap<object, Map<string, Map<SignalHandler, number[]>>> = new WeakMap();
|
|
15
15
|
|
|
16
16
|
const findListenerHandlerId = (instance: object, signal: string, handler: SignalHandler): number | undefined =>
|
|
17
|
-
listenerTable.get(instance)?.get(signal)?.get(handler);
|
|
17
|
+
listenerTable.get(instance)?.get(signal)?.get(handler)?.at(-1);
|
|
18
18
|
|
|
19
19
|
const trackListener = (instance: object, signal: string, handler: SignalHandler, handlerId: number): void => {
|
|
20
20
|
let bySignal = listenerTable.get(instance);
|
|
@@ -31,7 +31,24 @@ const trackListener = (instance: object, signal: string, handler: SignalHandler,
|
|
|
31
31
|
bySignal.set(signal, byHandler);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
byHandler.
|
|
34
|
+
let handlerIds = byHandler.get(handler);
|
|
35
|
+
|
|
36
|
+
if (!handlerIds) {
|
|
37
|
+
handlerIds = [];
|
|
38
|
+
byHandler.set(handler, handlerIds);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
handlerIds.push(handlerId);
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const removeTrackedHandlerId = (handlerIds: number[], handlerId: number): number => {
|
|
45
|
+
const index = handlerIds.lastIndexOf(handlerId);
|
|
46
|
+
|
|
47
|
+
if (index !== -1) {
|
|
48
|
+
handlerIds.splice(index, 1);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return handlerIds.length;
|
|
35
52
|
};
|
|
36
53
|
|
|
37
54
|
const untrackHandlerId = (instance: object, signal: string, handlerId: number): void => {
|
|
@@ -42,8 +59,8 @@ const untrackHandlerId = (instance: object, signal: string, handlerId: number):
|
|
|
42
59
|
return;
|
|
43
60
|
}
|
|
44
61
|
|
|
45
|
-
for (const [handler,
|
|
46
|
-
if (
|
|
62
|
+
for (const [handler, handlerIds] of byHandler) {
|
|
63
|
+
if (removeTrackedHandlerId(handlerIds, handlerId) === 0) {
|
|
47
64
|
byHandler.delete(handler);
|
|
48
65
|
}
|
|
49
66
|
}
|
package/src/native-value.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Descriptor, ExternalObject, Handle } from "@gtkx/native";
|
|
2
2
|
import type { ArrayDescriptor, FundamentalDescriptor, HashTableDescriptor, StructDescriptor } from "./descriptors.js";
|
|
3
|
-
import { getHandle, getWrapperClass, wrapHandle, wrapObject } from "./registry.js";
|
|
3
|
+
import { getHandle, getWrapperClass, wrapCallScopedObject, wrapHandle, wrapObject } from "./registry.js";
|
|
4
4
|
import { resolveDescriptorType } from "./type.js";
|
|
5
5
|
|
|
6
6
|
type MarshalledKind = "object" | "struct" | "boxed" | "fundamental" | "array" | "hashtable";
|
|
@@ -76,7 +76,7 @@ function fromNative(descriptor: Descriptor, value: unknown): unknown {
|
|
|
76
76
|
|
|
77
77
|
switch (descriptor.kind) {
|
|
78
78
|
case "object": {
|
|
79
|
-
return wrapObject(value);
|
|
79
|
+
return descriptor.isCallScoped === true ? wrapCallScopedObject(value) : wrapObject(value);
|
|
80
80
|
}
|
|
81
81
|
case "struct": {
|
|
82
82
|
return wrapHandle(value as ExternalObject<Handle> | null, (descriptor as StructDescriptor).wrapperClass);
|
package/src/object.ts
CHANGED
|
@@ -121,7 +121,9 @@ function constructPropertyForEntry(
|
|
|
121
121
|
* that name, dashed or camelCased, and is skipped when the type installs none.
|
|
122
122
|
* A value that ParamSpec would refuse throws before GObject sees it: a `TypeError`
|
|
123
123
|
* for a read-only property and for a value of a type the property cannot hold, and
|
|
124
|
-
* a `RangeError` for a value the ParamSpec rejects.
|
|
124
|
+
* a `RangeError` for a value the ParamSpec rejects. A value marshalled through a
|
|
125
|
+
* declared descriptor is converted rather than checked, so a `null` handed to a
|
|
126
|
+
* numeric one of those lands 0 rather than being refused.
|
|
125
127
|
* Properties whose value is `undefined` are skipped. A type registered with
|
|
126
128
|
* `registerClass` binds the wrapper before its `constructed` slot runs, so an
|
|
127
129
|
* override of that slot already sees a usable instance.
|
|
@@ -168,7 +170,10 @@ function getObjectProperty(obj: object, propertyName: string, descriptor: Descri
|
|
|
168
170
|
|
|
169
171
|
/**
|
|
170
172
|
* Writes a JavaScript value to a GObject property, converting it to native form
|
|
171
|
-
* using the descriptor.
|
|
173
|
+
* using the descriptor. The descriptor converts what it is given rather than checking it against
|
|
174
|
+
* the property's `GObject.ParamSpec`, so `null` and `undefined` written to a numeric or enum
|
|
175
|
+
* property land 0, where the same write to a property installed through `registerClass` is
|
|
176
|
+
* refused with a `TypeError`.
|
|
172
177
|
*
|
|
173
178
|
* @param obj The object to write to.
|
|
174
179
|
* @param propertyName The property name.
|
package/src/param-spec.ts
CHANGED
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
TYPE_ENUM,
|
|
14
14
|
TYPE_FLAGS,
|
|
15
15
|
TYPE_FLOAT,
|
|
16
|
+
TYPE_GTYPE,
|
|
16
17
|
TYPE_INT,
|
|
17
18
|
TYPE_INT64,
|
|
18
19
|
TYPE_INTERFACE,
|
|
@@ -51,7 +52,6 @@ const UINT32_MAXIMUM = 4_294_967_295;
|
|
|
51
52
|
const INT64_MINIMUM = -(2n ** 63n);
|
|
52
53
|
const INT64_MAXIMUM = 2n ** 63n - 1n;
|
|
53
54
|
const UINT64_MAXIMUM = 2n ** 64n - 1n;
|
|
54
|
-
const FLOAT_MAXIMUM = 3.4028234663852886e38;
|
|
55
55
|
|
|
56
56
|
const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
|
|
57
57
|
TYPE_BOXED,
|
|
@@ -61,6 +61,8 @@ const WRAPPED_FUNDAMENTALS: Set<bigint> = new Set([
|
|
|
61
61
|
TYPE_VARIANT,
|
|
62
62
|
]);
|
|
63
63
|
|
|
64
|
+
const isWideUnsignedValue: ValueGuard = wideIntegerGuardFor(0n, UINT64_MAXIMUM);
|
|
65
|
+
|
|
64
66
|
const SCALAR_GUARDS: Map<bigint, ValueGuard> = new Map([
|
|
65
67
|
[TYPE_BOOLEAN, isBooleanValue],
|
|
66
68
|
[TYPE_STRING, isStringValue],
|
|
@@ -71,11 +73,11 @@ const SCALAR_GUARDS: Map<bigint, ValueGuard> = new Map([
|
|
|
71
73
|
[TYPE_ENUM, integerGuardFor(INT32_MINIMUM, INT32_MAXIMUM)],
|
|
72
74
|
[TYPE_FLAGS, integerGuardFor(0, UINT32_MAXIMUM)],
|
|
73
75
|
[TYPE_LONG, wideIntegerGuardFor(INT64_MINIMUM, INT64_MAXIMUM)],
|
|
74
|
-
[TYPE_ULONG,
|
|
76
|
+
[TYPE_ULONG, isWideUnsignedValue],
|
|
75
77
|
[TYPE_INT64, wideIntegerGuardFor(INT64_MINIMUM, INT64_MAXIMUM)],
|
|
76
|
-
[TYPE_UINT64,
|
|
77
|
-
[TYPE_FLOAT,
|
|
78
|
-
[TYPE_DOUBLE,
|
|
78
|
+
[TYPE_UINT64, isWideUnsignedValue],
|
|
79
|
+
[TYPE_FLOAT, isNumberValue],
|
|
80
|
+
[TYPE_DOUBLE, isNumberValue],
|
|
79
81
|
[TYPE_POINTER, isNullValue],
|
|
80
82
|
]);
|
|
81
83
|
|
|
@@ -115,12 +117,8 @@ function isStrvValue(value: unknown): boolean {
|
|
|
115
117
|
return value == null || (Array.isArray(value) && value.every((item) => typeof item === "string"));
|
|
116
118
|
}
|
|
117
119
|
|
|
118
|
-
function
|
|
119
|
-
return
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function isFloatValue(value: unknown): boolean {
|
|
123
|
-
return isDoubleValue(value) && (value == null || Math.abs(value as number) <= FLOAT_MAXIMUM);
|
|
120
|
+
function isNumberValue(value: unknown): boolean {
|
|
121
|
+
return typeof value === "number";
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
function isAnyValue(): boolean {
|
|
@@ -131,12 +129,8 @@ function isNullValue(value: unknown): boolean {
|
|
|
131
129
|
return value == null;
|
|
132
130
|
}
|
|
133
131
|
|
|
134
|
-
function isIntegerWithin(value: unknown, minimum: number, maximum: number): boolean {
|
|
135
|
-
return Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
132
|
function integerGuardFor(minimum: number, maximum: number): ValueGuard {
|
|
139
|
-
return (value) => value
|
|
133
|
+
return (value) => Number.isSafeInteger(value) && (value as number) >= minimum && (value as number) <= maximum;
|
|
140
134
|
}
|
|
141
135
|
|
|
142
136
|
function toWideInteger(value: unknown): bigint | undefined {
|
|
@@ -149,10 +143,6 @@ function toWideInteger(value: unknown): bigint | undefined {
|
|
|
149
143
|
|
|
150
144
|
function wideIntegerGuardFor(minimum: bigint, maximum: bigint): ValueGuard {
|
|
151
145
|
return (value) => {
|
|
152
|
-
if (value == null) {
|
|
153
|
-
return true;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
146
|
const wide = toWideInteger(value);
|
|
157
147
|
|
|
158
148
|
return wide !== undefined && wide >= minimum && wide <= maximum;
|
|
@@ -173,11 +163,20 @@ function wrappedGuardFor(valueType: bigint): ValueGuard {
|
|
|
173
163
|
return (value) => value == null || typeIsA(resolveGtype(value), valueType);
|
|
174
164
|
}
|
|
175
165
|
|
|
166
|
+
function exactGuardFor(valueType: bigint): ValueGuard | undefined {
|
|
167
|
+
if (valueType === TYPE_GTYPE) {
|
|
168
|
+
return isWideUnsignedValue;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return valueType === getStrvType() ? isStrvValue : undefined;
|
|
172
|
+
}
|
|
173
|
+
|
|
176
174
|
function valueGuardFor(valueType: bigint): ValueGuard {
|
|
177
175
|
assertParamLayout();
|
|
176
|
+
const exact = exactGuardFor(valueType);
|
|
178
177
|
|
|
179
|
-
if (
|
|
180
|
-
return
|
|
178
|
+
if (exact !== undefined) {
|
|
179
|
+
return exact;
|
|
181
180
|
}
|
|
182
181
|
|
|
183
182
|
const fundamental = typeFundamental(valueType);
|
package/src/properties.ts
CHANGED
|
@@ -16,7 +16,14 @@ import {
|
|
|
16
16
|
} from "./param-spec.js";
|
|
17
17
|
import { getHandle, getInterfaceProperties, instanceClassName, type InterfaceProperty } from "./registry.js";
|
|
18
18
|
import { typeName } from "./type.js";
|
|
19
|
-
import {
|
|
19
|
+
import {
|
|
20
|
+
fromValue,
|
|
21
|
+
newValueForType,
|
|
22
|
+
type ValueNarrower,
|
|
23
|
+
valueNarrowerFor,
|
|
24
|
+
type ValueWriter,
|
|
25
|
+
valueWriterFor,
|
|
26
|
+
} from "./value.js";
|
|
20
27
|
|
|
21
28
|
type PropertyCheck = {
|
|
22
29
|
name: string;
|
|
@@ -25,6 +32,7 @@ type PropertyCheck = {
|
|
|
25
32
|
flags: number;
|
|
26
33
|
valueType: bigint;
|
|
27
34
|
canHoldValue: ValueGuard;
|
|
35
|
+
narrowValue: ValueNarrower;
|
|
28
36
|
write?: ValueWriter;
|
|
29
37
|
scratch?: ExternalObject<Handle>;
|
|
30
38
|
};
|
|
@@ -80,6 +88,8 @@ const typeLabel = (type: bigint): string => typeName(type) ?? String(type);
|
|
|
80
88
|
const defaultValueFor = (handle: ExternalObject<Handle>): ExternalObject<Handle> =>
|
|
81
89
|
paramSpecDefaultValue(handle) as ExternalObject<Handle>;
|
|
82
90
|
|
|
91
|
+
const heldValue = (value: unknown): unknown => value ?? null;
|
|
92
|
+
|
|
83
93
|
const holdsReason = (check: PropertyCheck): string =>
|
|
84
94
|
`the property holds values of type '${typeLabel(check.valueType)}'`;
|
|
85
95
|
|
|
@@ -93,6 +103,7 @@ function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
|
|
|
93
103
|
flags: getParamFlags(handle),
|
|
94
104
|
valueType,
|
|
95
105
|
canHoldValue: valueGuardFor(valueType),
|
|
106
|
+
narrowValue: valueNarrowerFor(valueType),
|
|
96
107
|
};
|
|
97
108
|
}
|
|
98
109
|
|
|
@@ -168,6 +179,11 @@ function assertWritable(instance: object, check: PropertyCheck, value: unknown):
|
|
|
168
179
|
throw new TypeError(propertyMessage(instance, check, refusalTail(check, value, READ_ONLY_REASON)));
|
|
169
180
|
}
|
|
170
181
|
|
|
182
|
+
function writeHeld(check: PropertyCheck, gValue: ExternalObject<Handle>, value: unknown): void {
|
|
183
|
+
check.write ??= valueWriterFor(check.valueType);
|
|
184
|
+
check.write(gValue, check.narrowValue(value));
|
|
185
|
+
}
|
|
186
|
+
|
|
171
187
|
function fillCheckedValue(
|
|
172
188
|
instance: object,
|
|
173
189
|
check: PropertyCheck,
|
|
@@ -175,8 +191,7 @@ function fillCheckedValue(
|
|
|
175
191
|
value: unknown,
|
|
176
192
|
): void {
|
|
177
193
|
assertValueFits(instance, check, value);
|
|
178
|
-
check
|
|
179
|
-
check.write(gValue, value);
|
|
194
|
+
writeHeld(check, gValue, value);
|
|
180
195
|
assertValueValidates(instance, check, value, gValue);
|
|
181
196
|
}
|
|
182
197
|
|
|
@@ -224,11 +239,11 @@ function readStored(instance: Record<symbol, unknown>, accessor: PropertyAccesso
|
|
|
224
239
|
}
|
|
225
240
|
|
|
226
241
|
function storeValue(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
227
|
-
instance[accessor.storage] = value;
|
|
242
|
+
instance[accessor.storage] = heldValue(value);
|
|
228
243
|
}
|
|
229
244
|
|
|
230
245
|
function writeStored(instance: Record<symbol, unknown>, accessor: PropertyAccessor, value: unknown): void {
|
|
231
|
-
if (readStored(instance, accessor) === value) {
|
|
246
|
+
if (readStored(instance, accessor) === heldValue(value)) {
|
|
232
247
|
return;
|
|
233
248
|
}
|
|
234
249
|
|
|
@@ -239,13 +254,12 @@ function writeStored(instance: Record<symbol, unknown>, accessor: PropertyAccess
|
|
|
239
254
|
function writeProperty(instance: object, accessor: PropertyAccessor, value: unknown): void {
|
|
240
255
|
const stored = instance as Record<symbol, unknown>;
|
|
241
256
|
|
|
242
|
-
if (readStored(stored, accessor) === value) {
|
|
257
|
+
if (readStored(stored, accessor) === heldValue(value)) {
|
|
243
258
|
return;
|
|
244
259
|
}
|
|
245
260
|
|
|
246
261
|
assertValueAccepted(instance, accessor, value);
|
|
247
|
-
|
|
248
|
-
(instance as NotifyingObject).notify?.(accessor.propertyName);
|
|
262
|
+
writeStored(stored, accessor, accessor.narrowValue(value));
|
|
249
263
|
}
|
|
250
264
|
|
|
251
265
|
function storedGetter(accessor: PropertyAccessor): (this: object) => unknown {
|
|
@@ -431,7 +445,7 @@ function makeGetProperty(dispatch: PropertyDispatch) {
|
|
|
431
445
|
const accessor = resolveAccessor(dispatch, propertyId, pspec);
|
|
432
446
|
const current = readCurrent(this, accessor);
|
|
433
447
|
assertValueServes(this, accessor, current);
|
|
434
|
-
|
|
448
|
+
writeHeld(accessor, getHandle(value), current);
|
|
435
449
|
};
|
|
436
450
|
}
|
|
437
451
|
|
package/src/register-class.ts
CHANGED
|
@@ -61,7 +61,7 @@ type Camelized<TName extends string> = TName extends `${infer THead}-${infer TTa
|
|
|
61
61
|
|
|
62
62
|
/**
|
|
63
63
|
* The names a registered class carries in its property map: every key of
|
|
64
|
-
* `RegisterClassOptions.properties` in camelCase, whichever
|
|
64
|
+
* `RegisterClassOptions.properties` in camelCase, whichever spelling it was written in.
|
|
65
65
|
* A `properties` object given a type of its own rather than left to inference has `string` for its key
|
|
66
66
|
* type and names nothing, because a name only known as `string` addresses no member in particular.
|
|
67
67
|
*/
|
|
@@ -129,13 +129,43 @@ type RegisterClassOptions<TInstance extends object, TProperties extends Record<s
|
|
|
129
129
|
* A write the ParamSpec would refuse throws rather than reaching GObject, which reports such a
|
|
130
130
|
* write as a GLib critical and drops it: a `TypeError` for a read-only or construct-only
|
|
131
131
|
* property and for a value of a type the property cannot hold, and a `RangeError` for a value
|
|
132
|
-
* the ParamSpec rejects. The same
|
|
132
|
+
* the ParamSpec rejects. The same checks run over a value handed to the constructor, where
|
|
133
133
|
* a construct-only property is the one that is writable. An accepted write emits one `notify`,
|
|
134
134
|
* which a `freeze_notify` batch collects; a write of the value the property already holds is
|
|
135
135
|
* dropped and emits none.
|
|
136
136
|
*
|
|
137
|
+
* `null` and `undefined` both mean NULL, and mean it only where the ParamSpec's own type holds
|
|
138
|
+
* NULL, which is a string, string-array, boxed, object, interface, param, variant or pointer
|
|
139
|
+
* property. Such a property holds the `null` either spelling wrote, so the member, the type's
|
|
140
|
+
* `get_property` slot and `g_object_get_property` serve the same thing, and writing the other
|
|
141
|
+
* spelling over it emits no `notify`. Every other property, so every integer, floating-point,
|
|
142
|
+
* boolean, enum, flags and GType one, refuses both with the same `TypeError` it refuses a
|
|
143
|
+
* string with, and keeps the value it already holds. It refuses them for what it holds rather
|
|
144
|
+
* than for its range, whatever that range is: the type is checked before the range, so a
|
|
145
|
+
* `gint` whose range excludes 0 answers a nullish with that same `TypeError` and never with
|
|
146
|
+
* the `RangeError` that names the value GObject would put in its place. The one place
|
|
147
|
+
* `undefined` means something else is the constructor, which reads it as the property not
|
|
148
|
+
* being given at all and leaves it at the ParamSpec's default, so a property is never handed a
|
|
149
|
+
* value it cannot serve back.
|
|
150
|
+
*
|
|
151
|
+
* A floating-point property takes every JavaScript number, `NaN` and both infinities
|
|
152
|
+
* included, and its ParamSpec alone rules on which of them the range admits: a `gdouble`
|
|
153
|
+
* bounded by `-Infinity` and `Infinity` holds either infinity, and a magnitude a bounded
|
|
154
|
+
* one excludes, like any `NaN`, comes back as the `RangeError` that names what GObject
|
|
155
|
+
* would put in its place rather than as the `TypeError` a type the property cannot hold
|
|
156
|
+
* earns. A `gfloat` property holds what GObject narrows the double to, so it serves `0.1`
|
|
157
|
+
* back as `0.10000000149011612` and a finite magnitude no `gfloat` reaches as an infinity,
|
|
158
|
+
* which the range then rules on in turn. That narrowing belongs to the property alone: the
|
|
159
|
+
* same magnitude written to a `gfloat` through a generated binding, a signal argument or a
|
|
160
|
+
* closure return is refused outright rather than narrowed.
|
|
161
|
+
*
|
|
162
|
+
* A generated property of a wrapped type answers a nullish differently, and the two halves of
|
|
163
|
+
* the API disagree here: that property marshals what it is written through its descriptor
|
|
164
|
+
* rather than through the checks above, so `new Gtk.Label({ widthRequest: null })` and a later
|
|
165
|
+
* write of `null` to that member both land 0, where a `gint` installed here refuses both.
|
|
166
|
+
*
|
|
137
167
|
* A class that defines the camelCase member itself owns the property: its own accessor decides
|
|
138
|
-
* what a write means, the other
|
|
168
|
+
* what a write means, the other spellings forward to it, and the type's property slots read
|
|
139
169
|
* and write it rather than the generated storage.
|
|
140
170
|
*/
|
|
141
171
|
properties?: TProperties;
|
|
@@ -166,7 +196,11 @@ type PropertyVfuncSpec = {
|
|
|
166
196
|
makeDispatch: (dispatch: PropertyDispatch) => VfuncFn;
|
|
167
197
|
};
|
|
168
198
|
|
|
199
|
+
type ArgPatch = { isCallerAllocated: true } | { isCallScoped: true };
|
|
200
|
+
|
|
201
|
+
const INSTANCE_ARG_INDEX = 0;
|
|
169
202
|
const VALUE_ARG_INDEX = 2;
|
|
203
|
+
const TEARDOWN_VFUNC_NAMES: Set<string> = new Set(["dispose", "finalize"]);
|
|
170
204
|
|
|
171
205
|
const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
|
|
172
206
|
{ methodName: GET_PROPERTY_VFUNC, isValueOut: true, makeDispatch: makeGetProperty },
|
|
@@ -345,10 +379,13 @@ function buildDiscoveredVfunc(
|
|
|
345
379
|
return undefined;
|
|
346
380
|
}
|
|
347
381
|
|
|
382
|
+
const argDescriptors = slotArgDescriptors(descriptor);
|
|
383
|
+
|
|
348
384
|
return {
|
|
349
385
|
...descriptor,
|
|
350
386
|
methodName,
|
|
351
|
-
|
|
387
|
+
argDescriptors,
|
|
388
|
+
fn: wrapVfunc(fn, argDescriptors, descriptor),
|
|
352
389
|
};
|
|
353
390
|
}
|
|
354
391
|
|
|
@@ -381,9 +418,13 @@ function discoverClassVfuncs(klass: AnyClass, methods: MethodTable): DiscoveredV
|
|
|
381
418
|
function wrapVfunc(
|
|
382
419
|
fn: VfuncFn,
|
|
383
420
|
argDescriptors: NativeRegisterClassVfunc["argDescriptors"],
|
|
384
|
-
|
|
421
|
+
descriptor: VfuncDescriptor,
|
|
385
422
|
): VfuncFn {
|
|
386
|
-
return wrapCallback(
|
|
423
|
+
return wrapCallback(
|
|
424
|
+
fn as (...args: unknown[]) => unknown,
|
|
425
|
+
{ argDescriptors, returnDescriptor: descriptor.returnDescriptor },
|
|
426
|
+
"vfunc",
|
|
427
|
+
);
|
|
387
428
|
}
|
|
388
429
|
|
|
389
430
|
function discoverInterfaceBindings(
|
|
@@ -443,9 +484,23 @@ function propertyVfuncs(source: PropertyVfuncSource): DiscoveredVfunc[] {
|
|
|
443
484
|
);
|
|
444
485
|
}
|
|
445
486
|
|
|
446
|
-
function
|
|
447
|
-
return argDescriptors.map((arg,
|
|
448
|
-
|
|
487
|
+
function markArg(argDescriptors: Descriptor[], index: number, patch: ArgPatch): Descriptor[] {
|
|
488
|
+
return argDescriptors.map((arg, at) => (at === index ? { ...arg, ...patch } : arg));
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
function isTeardownSlot(descriptor: VfuncDescriptor): boolean {
|
|
492
|
+
return (
|
|
493
|
+
TEARDOWN_VFUNC_NAMES.has(descriptor.vfuncName) &&
|
|
494
|
+
descriptor.argDescriptors[INSTANCE_ARG_INDEX]?.kind === "object"
|
|
495
|
+
);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function slotArgDescriptors(descriptor: VfuncDescriptor): Descriptor[] {
|
|
499
|
+
if (!isTeardownSlot(descriptor)) {
|
|
500
|
+
return descriptor.argDescriptors;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return markArg(descriptor.argDescriptors, INSTANCE_ARG_INDEX, { isCallScoped: true });
|
|
449
504
|
}
|
|
450
505
|
|
|
451
506
|
function buildPropertyVfunc(
|
|
@@ -461,14 +516,14 @@ function buildPropertyVfunc(
|
|
|
461
516
|
}
|
|
462
517
|
|
|
463
518
|
const argDescriptors = isValueOut
|
|
464
|
-
?
|
|
519
|
+
? markArg(descriptor.argDescriptors, VALUE_ARG_INDEX, { isCallerAllocated: true })
|
|
465
520
|
: descriptor.argDescriptors;
|
|
466
521
|
|
|
467
522
|
return {
|
|
468
523
|
...descriptor,
|
|
469
524
|
methodName,
|
|
470
525
|
argDescriptors,
|
|
471
|
-
fn: wrapVfunc(fn, argDescriptors, descriptor
|
|
526
|
+
fn: wrapVfunc(fn, argDescriptors, descriptor),
|
|
472
527
|
};
|
|
473
528
|
}
|
|
474
529
|
|