@gtkx/runtime 1.0.0-rc.1 → 1.0.0-rc.2
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/dist/arg.d.ts +6 -5
- package/dist/arg.d.ts.map +1 -1
- package/dist/arg.js +5 -4
- package/dist/arg.js.map +1 -1
- package/dist/bind.d.ts +3 -2
- package/dist/bind.d.ts.map +1 -1
- package/dist/bind.js +5 -3
- package/dist/bind.js.map +1 -1
- package/dist/callback.d.ts +3 -3
- package/dist/callback.d.ts.map +1 -1
- package/dist/callback.js +51 -33
- package/dist/callback.js.map +1 -1
- package/dist/descriptors.d.ts +67 -64
- package/dist/descriptors.d.ts.map +1 -1
- package/dist/descriptors.js +92 -60
- package/dist/descriptors.js.map +1 -1
- package/dist/error.d.ts +5 -6
- package/dist/error.d.ts.map +1 -1
- package/dist/error.js +10 -6
- package/dist/error.js.map +1 -1
- package/dist/exit-hook.d.ts +2 -0
- package/dist/exit-hook.d.ts.map +1 -0
- package/dist/exit-hook.js +3 -0
- package/dist/exit-hook.js.map +1 -0
- package/dist/fn.d.ts +2 -2
- package/dist/fn.d.ts.map +1 -1
- package/dist/fn.js +26 -15
- package/dist/fn.js.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/library.d.ts +6 -5
- package/dist/library.d.ts.map +1 -1
- package/dist/library.js +6 -5
- package/dist/library.js.map +1 -1
- package/dist/lifecycle.d.ts +10 -9
- package/dist/lifecycle.d.ts.map +1 -1
- package/dist/lifecycle.js +38 -23
- package/dist/lifecycle.js.map +1 -1
- package/dist/listeners.d.ts +8 -8
- package/dist/listeners.d.ts.map +1 -1
- package/dist/listeners.js +28 -15
- package/dist/listeners.js.map +1 -1
- package/dist/mixin.d.ts +5 -11
- package/dist/mixin.d.ts.map +1 -1
- package/dist/mixin.js +24 -14
- package/dist/mixin.js.map +1 -1
- package/dist/native-value.d.ts +3 -2
- package/dist/native-value.d.ts.map +1 -1
- package/dist/native-value.js +75 -42
- package/dist/native-value.js.map +1 -1
- package/dist/object.d.ts +4 -3
- package/dist/object.d.ts.map +1 -1
- package/dist/object.js +8 -5
- package/dist/object.js.map +1 -1
- package/dist/promisify.d.ts +9 -1
- package/dist/promisify.d.ts.map +1 -1
- package/dist/promisify.js +18 -15
- package/dist/promisify.js.map +1 -1
- package/dist/properties.d.ts +14 -0
- package/dist/properties.d.ts.map +1 -0
- package/dist/properties.js +79 -0
- package/dist/properties.js.map +1 -0
- package/dist/register-class.d.ts +10 -2
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +83 -29
- package/dist/register-class.js.map +1 -1
- package/dist/registry.d.ts +23 -21
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +54 -35
- package/dist/registry.js.map +1 -1
- package/dist/signal.d.ts +7 -6
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +19 -12
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +2 -2
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js +2 -1
- package/dist/t.js.map +1 -1
- package/dist/tuple.d.ts +3 -2
- package/dist/tuple.d.ts.map +1 -1
- package/dist/tuple.js +7 -4
- package/dist/tuple.js.map +1 -1
- package/dist/type.d.ts +48 -41
- package/dist/type.d.ts.map +1 -1
- package/dist/type.js +107 -87
- package/dist/type.js.map +1 -1
- package/dist/value.d.ts +19 -10
- package/dist/value.d.ts.map +1 -1
- package/dist/value.js +164 -99
- package/dist/value.js.map +1 -1
- package/package.json +4 -6
- package/src/arg.ts +7 -5
- package/src/bind.ts +13 -4
- package/src/callback.ts +97 -35
- package/src/descriptors.ts +227 -85
- package/src/error.ts +18 -9
- package/src/exit-hook.ts +3 -0
- package/src/fn.ts +53 -22
- package/src/index.ts +4 -2
- package/src/library.ts +7 -5
- package/src/lifecycle.ts +54 -26
- package/src/listeners.ts +46 -18
- package/src/mixin.ts +39 -14
- package/src/native-value.ts +103 -46
- package/src/object.ts +19 -7
- package/src/promisify.ts +42 -15
- package/src/properties.ts +115 -0
- package/src/register-class.ts +157 -30
- package/src/registry.ts +144 -54
- package/src/signal.ts +53 -13
- package/src/t.ts +3 -1
- package/src/tuple.ts +16 -4
- package/src/type.ts +183 -93
- package/src/value.ts +231 -108
package/src/object.ts
CHANGED
|
@@ -23,6 +23,7 @@ const gObjectGetProperty = bind(
|
|
|
23
23
|
[objectT("borrowed"), stringT("borrowed"), VALUE_T],
|
|
24
24
|
voidT,
|
|
25
25
|
);
|
|
26
|
+
|
|
26
27
|
const gObjectSetProperty = bind(
|
|
27
28
|
LIB,
|
|
28
29
|
"g_object_set_property",
|
|
@@ -39,15 +40,23 @@ const gObjectSetProperty = bind(
|
|
|
39
40
|
* @param props Property names mapped to `[descriptor, value]` pairs.
|
|
40
41
|
* @returns The handle of the newly created object.
|
|
41
42
|
*/
|
|
42
|
-
|
|
43
|
+
function newObjectWithProperties(gtype: bigint, props: Record<string, unknown>): ExternalObject<Handle> {
|
|
43
44
|
const names: string[] = [];
|
|
44
45
|
const values: ExternalObject<Handle>[] = [];
|
|
45
46
|
|
|
46
47
|
for (const name in props) {
|
|
47
|
-
const entry = props[name];
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const entry: unknown = props[name];
|
|
49
|
+
|
|
50
|
+
if (!Array.isArray(entry)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const [descriptor, value] = entry as [Descriptor, unknown];
|
|
55
|
+
|
|
56
|
+
if (value === undefined) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
51
60
|
names.push(name);
|
|
52
61
|
values.push(toValue(descriptor, value));
|
|
53
62
|
}
|
|
@@ -63,9 +72,10 @@ export function newObjectWithProperties(gtype: bigint, props: Record<string, unk
|
|
|
63
72
|
* @param propertyName The property name.
|
|
64
73
|
* @param descriptor Describes the property's type.
|
|
65
74
|
*/
|
|
66
|
-
|
|
75
|
+
function getObjectProperty(obj: object, propertyName: string, descriptor: Descriptor): unknown {
|
|
67
76
|
const value = newValueForDescriptor(descriptor);
|
|
68
77
|
gObjectGetProperty(getHandle(obj), propertyName, value);
|
|
78
|
+
|
|
69
79
|
return fromValue(value);
|
|
70
80
|
}
|
|
71
81
|
|
|
@@ -78,6 +88,8 @@ export function getObjectProperty(obj: object, propertyName: string, descriptor:
|
|
|
78
88
|
* @param descriptor Describes the property's type.
|
|
79
89
|
* @param jsValue The value to set.
|
|
80
90
|
*/
|
|
81
|
-
|
|
91
|
+
function setObjectProperty(obj: object, propertyName: string, descriptor: Descriptor, jsValue: unknown): void {
|
|
82
92
|
gObjectSetProperty(getHandle(obj), propertyName, toValue(descriptor, jsValue));
|
|
83
93
|
}
|
|
94
|
+
|
|
95
|
+
export { newObjectWithProperties, getObjectProperty, setObjectProperty };
|
package/src/promisify.ts
CHANGED
|
@@ -1,11 +1,43 @@
|
|
|
1
1
|
import { tryGetHandle } from "./registry.js";
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Extracts the finished value from the async result a GIO-style operation completes with.
|
|
5
|
+
*
|
|
6
|
+
* @template R - The async result type passed to the completion callback.
|
|
7
|
+
* @template T - The finished value type.
|
|
8
|
+
*/
|
|
9
|
+
type FinishResult<R extends object, T> = (result: R) => T;
|
|
10
|
+
|
|
11
|
+
type Settlement<R extends object, T> = {
|
|
12
|
+
finish: FinishResult<R, T>;
|
|
13
|
+
creationStack: Error | undefined;
|
|
14
|
+
resolve: (value: T) => void;
|
|
15
|
+
reject: (reason: Error) => void;
|
|
16
|
+
};
|
|
17
|
+
|
|
3
18
|
const attachCreationStack = (error: unknown, creationStack: Error | undefined): void => {
|
|
4
|
-
if (creationStack === undefined || !(error instanceof Error))
|
|
5
|
-
|
|
19
|
+
if (creationStack === undefined || !(error instanceof Error)) {
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (error.cause !== undefined || !Object.isExtensible(error)) {
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
6
27
|
error.cause = creationStack;
|
|
7
28
|
};
|
|
8
29
|
|
|
30
|
+
const settle = <R extends object, T>(settlement: Settlement<R, T>, asyncResult: object): void => {
|
|
31
|
+
const { finish, creationStack, resolve, reject } = settlement;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
resolve(finish(asyncResult as R));
|
|
35
|
+
} catch (error) {
|
|
36
|
+
attachCreationStack(error, creationStack);
|
|
37
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
9
41
|
/**
|
|
10
42
|
* Wraps a GIO-style asynchronous function that takes a completion callback into a
|
|
11
43
|
* promise, invoking the finish function to extract the result. Outside production,
|
|
@@ -17,24 +49,19 @@ const attachCreationStack = (error: unknown, creationStack: Error | undefined):
|
|
|
17
49
|
* @param leading Arguments passed to `asyncFn` before the cancellable and callback.
|
|
18
50
|
* @returns A promise resolving to the finished result, or rejecting if `finish` throws.
|
|
19
51
|
*/
|
|
20
|
-
|
|
52
|
+
const promisify = <R extends object, T>(
|
|
21
53
|
asyncFn: (...args: unknown[]) => void,
|
|
22
|
-
finish:
|
|
54
|
+
finish: FinishResult<R, T>,
|
|
23
55
|
cancellable: object | null | undefined,
|
|
24
56
|
...leading: unknown[]
|
|
25
57
|
): Promise<T> =>
|
|
26
58
|
new Promise<T>((resolve, reject) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Error.captureStackTrace(creationStack, promisify);
|
|
31
|
-
}
|
|
59
|
+
const creationStack =
|
|
60
|
+
process.env.NODE_ENV === "production" ? undefined : new Error("gtkx async operation started here");
|
|
61
|
+
|
|
32
62
|
asyncFn(...leading, tryGetHandle(cancellable), (_source: object | null, asyncResult: object) => {
|
|
33
|
-
|
|
34
|
-
resolve(finish(asyncResult as R));
|
|
35
|
-
} catch (error) {
|
|
36
|
-
attachCreationStack(error, creationStack);
|
|
37
|
-
reject(error);
|
|
38
|
-
}
|
|
63
|
+
settle({ finish, creationStack, resolve, reject }, asyncResult);
|
|
39
64
|
});
|
|
40
65
|
});
|
|
66
|
+
|
|
67
|
+
export { promisify, type FinishResult };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
|
|
2
|
+
import type { AnyClass } from "@gtkx/utils";
|
|
3
|
+
import { camelCase } from "@gtkx/utils";
|
|
4
|
+
import { bind } from "./bind.js";
|
|
5
|
+
import { LIB, PARAM_T, VALUE_T } from "./library.js";
|
|
6
|
+
import { getHandle } from "./registry.js";
|
|
7
|
+
import { fromValue, intoValue } from "./value.js";
|
|
8
|
+
|
|
9
|
+
type PropertyAccessor = {
|
|
10
|
+
name: string;
|
|
11
|
+
storage: symbol;
|
|
12
|
+
pspec: object;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type PropertySpec = object;
|
|
16
|
+
type NotifyingObject = { notify?: (propertyName: string) => void };
|
|
17
|
+
|
|
18
|
+
const FIRST_PROPERTY_ID = 1;
|
|
19
|
+
const paramSpecDefaultValue = bind(LIB, "g_param_spec_get_default_value", [PARAM_T], VALUE_T);
|
|
20
|
+
|
|
21
|
+
const underscoreCase = (name: string): string => name.replaceAll("-", "_");
|
|
22
|
+
const accessorNames = (name: string): string[] => [...new Set([name, underscoreCase(name), camelCase(name)])];
|
|
23
|
+
|
|
24
|
+
function readStored(instance: Record<symbol, unknown>, accessor: PropertyAccessor): unknown {
|
|
25
|
+
if (!Object.hasOwn(instance, accessor.storage)) {
|
|
26
|
+
const defaults = paramSpecDefaultValue(getHandle(accessor.pspec)) as ExternalObject<Handle>;
|
|
27
|
+
instance[accessor.storage] = fromValue(defaults);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return instance[accessor.storage];
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function defineAccessor(prototype: object, accessor: PropertyAccessor, alias: string): void {
|
|
34
|
+
if (Object.getOwnPropertyDescriptor(prototype, alias) !== undefined) {
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
Object.defineProperty(prototype, alias, {
|
|
39
|
+
configurable: true,
|
|
40
|
+
enumerable: true,
|
|
41
|
+
get(this: Record<symbol, unknown>) {
|
|
42
|
+
return readStored(this, accessor);
|
|
43
|
+
},
|
|
44
|
+
set(this: Record<symbol, unknown>, value: unknown) {
|
|
45
|
+
if (readStored(this, accessor) === value) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this[accessor.storage] = value;
|
|
50
|
+
(this as NotifyingObject).notify?.(accessor.name);
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function installAccessors(klass: AnyClass, accessor: PropertyAccessor): void {
|
|
56
|
+
const prototype = (klass as { prototype: object }).prototype;
|
|
57
|
+
|
|
58
|
+
for (const alias of accessorNames(accessor.name)) {
|
|
59
|
+
defineAccessor(prototype, accessor, alias);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolveAccessor(accessors: PropertyAccessor[], propertyId: number): PropertyAccessor {
|
|
64
|
+
const accessor = accessors[propertyId - FIRST_PROPERTY_ID];
|
|
65
|
+
|
|
66
|
+
if (accessor === undefined) {
|
|
67
|
+
throw new RangeError(`registerClass: no property registered for id ${String(propertyId)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return accessor;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function makeGetProperty(accessors: PropertyAccessor[]) {
|
|
74
|
+
return function getProperty(this: object, propertyId: number, value: object): void {
|
|
75
|
+
const accessor = resolveAccessor(accessors, propertyId);
|
|
76
|
+
const current = (this as Record<string, unknown>)[camelCase(accessor.name)];
|
|
77
|
+
intoValue(getHandle(value), current);
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function makeSetProperty(accessors: PropertyAccessor[]) {
|
|
82
|
+
return function setProperty(this: object, propertyId: number, value: object): void {
|
|
83
|
+
const accessor = resolveAccessor(accessors, propertyId);
|
|
84
|
+
const jsValue = fromValue(getHandle(value));
|
|
85
|
+
(this as Record<string, unknown>)[camelCase(accessor.name)] = jsValue;
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function buildAccessors(klass: AnyClass, properties: Record<string, PropertySpec>): PropertyAccessor[] {
|
|
90
|
+
const accessors: PropertyAccessor[] = [];
|
|
91
|
+
|
|
92
|
+
for (const [name, pspec] of Object.entries(properties)) {
|
|
93
|
+
const accessor: PropertyAccessor = { name, storage: Symbol(`gtkx:property:${name}`), pspec };
|
|
94
|
+
installAccessors(klass, accessor);
|
|
95
|
+
accessors.push(accessor);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return accessors;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toNativeProperties(properties: Record<string, PropertySpec>): RegisterClassProperty[] {
|
|
102
|
+
return Object.values(properties).map((pspec, index) => ({
|
|
103
|
+
id: index + FIRST_PROPERTY_ID,
|
|
104
|
+
pspec: getHandle(pspec),
|
|
105
|
+
}));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export {
|
|
109
|
+
buildAccessors,
|
|
110
|
+
makeGetProperty,
|
|
111
|
+
makeSetProperty,
|
|
112
|
+
toNativeProperties,
|
|
113
|
+
type PropertyAccessor,
|
|
114
|
+
type PropertySpec,
|
|
115
|
+
};
|
package/src/register-class.ts
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type { Descriptor } from "@gtkx/native";
|
|
1
2
|
import {
|
|
3
|
+
registerClass as nativeRegisterClass,
|
|
2
4
|
type RegisterClassOptions as NativeRegisterClassOptions,
|
|
3
5
|
type RegisterClassVfunc as NativeRegisterClassVfunc,
|
|
4
|
-
registerClass as nativeRegisterClass,
|
|
5
6
|
} from "@gtkx/native";
|
|
6
7
|
import { type AnyClass, getParentClass, walkClassChain } from "@gtkx/utils";
|
|
7
8
|
import { wrapCallback } from "./callback.js";
|
|
9
|
+
import {
|
|
10
|
+
buildAccessors,
|
|
11
|
+
makeGetProperty,
|
|
12
|
+
makeSetProperty,
|
|
13
|
+
type PropertyAccessor,
|
|
14
|
+
type PropertySpec,
|
|
15
|
+
toNativeProperties,
|
|
16
|
+
} from "./properties.js";
|
|
8
17
|
import {
|
|
9
18
|
getClassType,
|
|
10
19
|
getInterfaceVfuncRegistry,
|
|
@@ -14,12 +23,25 @@ import {
|
|
|
14
23
|
} from "./registry.js";
|
|
15
24
|
import { TYPE_INVALID, typeInterfaces } from "./type.js";
|
|
16
25
|
|
|
17
|
-
type RegisterClassOptions = {
|
|
26
|
+
type RegisterClassOptions = {
|
|
27
|
+
typeName?: string;
|
|
28
|
+
/**
|
|
29
|
+
* Properties to install on the new type, keyed by canonical property name. Each value is the
|
|
30
|
+
* `GObject.ParamSpec` describing it, for example `GObject.paramSpecString(...)`. Accessors are
|
|
31
|
+
* generated on the prototype for the dashed, underscored and camelCased spellings of the name
|
|
32
|
+
* unless the class already defines one, and writing through them emits `notify`.
|
|
33
|
+
*/
|
|
34
|
+
properties?: Record<string, PropertySpec>;
|
|
35
|
+
};
|
|
36
|
+
|
|
18
37
|
type VfuncFn = NativeRegisterClassVfunc["fn"];
|
|
19
38
|
type DiscoveredVfunc<K extends "class" | "interface"> = VfuncDescriptor<K> & { methodName: string; fn: VfuncFn };
|
|
20
39
|
type DiscoveredClassVfunc = DiscoveredVfunc<"class">;
|
|
21
40
|
type DiscoveredInterfaceVfunc = DiscoveredVfunc<"interface">;
|
|
22
|
-
type InterfaceVfuncBinding = { gtype: bigint; vfuncs: DiscoveredInterfaceVfunc[] };
|
|
41
|
+
type InterfaceVfuncBinding = { gtype: bigint; vtableSize: number; vfuncs: DiscoveredInterfaceVfunc[] };
|
|
42
|
+
|
|
43
|
+
const UNSUPPORTED_CONSTRUCT_VFUNCS: Set<string> = new Set(["constructed", "setProperty", "getProperty"]);
|
|
44
|
+
const VALUE_ARG_INDEX = 2;
|
|
23
45
|
|
|
24
46
|
/**
|
|
25
47
|
* Registers a subclass of a wrapper class as a new GType, wiring up any class and
|
|
@@ -31,8 +53,9 @@ type InterfaceVfuncBinding = { gtype: bigint; vfuncs: DiscoveredInterfaceVfunc[]
|
|
|
31
53
|
* @param options Registration options, such as an explicit type name.
|
|
32
54
|
* @returns The same class, now registered.
|
|
33
55
|
*/
|
|
34
|
-
|
|
56
|
+
function registerClass<T extends AnyClass>(klass: T, options: RegisterClassOptions = {}): T {
|
|
35
57
|
const parentType = resolveParentType(klass);
|
|
58
|
+
|
|
36
59
|
if (parentType === TYPE_INVALID) {
|
|
37
60
|
throw new TypeError(`registerClass: ${klass.name} must extend a registered wrapper class`);
|
|
38
61
|
}
|
|
@@ -43,11 +66,12 @@ export function registerClass<T extends AnyClass>(klass: T, options: RegisterCla
|
|
|
43
66
|
throw new Error("registerClass: cannot derive a GType name (anonymous class with no typeName option)");
|
|
44
67
|
}
|
|
45
68
|
|
|
46
|
-
const
|
|
69
|
+
const properties = options.properties ?? {};
|
|
70
|
+
const accessors = buildAccessors(klass, properties);
|
|
71
|
+
const classVfuncs = [...discoverClassVfuncs(klass), ...propertyVfuncs(klass, accessors)];
|
|
47
72
|
const claimedMethodNames = new Set(classVfuncs.map((vfunc) => vfunc.methodName));
|
|
48
73
|
const interfaceBindings = discoverInheritedInterfaceVfuncs(klass, parentType, claimedMethodNames);
|
|
49
|
-
|
|
50
|
-
const nativeOptions = toNativeOptions(classVfuncs, interfaceBindings);
|
|
74
|
+
const nativeOptions = toNativeOptions(classVfuncs, interfaceBindings, properties);
|
|
51
75
|
const newType: bigint = nativeRegisterClass(name, parentType, nativeOptions);
|
|
52
76
|
registerClassType(klass, newType);
|
|
53
77
|
|
|
@@ -58,20 +82,57 @@ function resolveParentType(klass: AnyClass): bigint {
|
|
|
58
82
|
return (
|
|
59
83
|
walkClassChain(getParentClass(klass), (cls) => {
|
|
60
84
|
const gtype = getClassType(cls);
|
|
61
|
-
|
|
85
|
+
|
|
86
|
+
return gtype === TYPE_INVALID ? undefined : gtype;
|
|
62
87
|
}) ?? TYPE_INVALID
|
|
63
88
|
);
|
|
64
89
|
}
|
|
65
90
|
|
|
66
91
|
function ownInstanceMethodNames(klass: AnyClass): string[] {
|
|
67
92
|
const proto = (klass as { prototype?: object }).prototype;
|
|
68
|
-
|
|
93
|
+
|
|
94
|
+
if (!proto) {
|
|
95
|
+
return [];
|
|
96
|
+
}
|
|
97
|
+
|
|
69
98
|
return Object.getOwnPropertyNames(proto).filter((name) => {
|
|
70
|
-
if (name === "constructor")
|
|
71
|
-
|
|
99
|
+
if (name === "constructor") {
|
|
100
|
+
return false;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return typeof Object.getOwnPropertyDescriptor(proto, name)?.value === "function";
|
|
72
104
|
});
|
|
73
105
|
}
|
|
74
106
|
|
|
107
|
+
function buildDiscoveredVfunc<K extends "class" | "interface">(
|
|
108
|
+
proto: Record<string, VfuncFn>,
|
|
109
|
+
methodName: string,
|
|
110
|
+
resolveDescriptor: (methodName: string) => VfuncDescriptor<K> | undefined,
|
|
111
|
+
skip: Set<string> | undefined,
|
|
112
|
+
): DiscoveredVfunc<K> | undefined {
|
|
113
|
+
if (skip?.has(methodName)) {
|
|
114
|
+
return undefined;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const descriptor = resolveDescriptor(methodName);
|
|
118
|
+
|
|
119
|
+
if (!descriptor) {
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const fn = proto[methodName];
|
|
124
|
+
|
|
125
|
+
if (!fn) {
|
|
126
|
+
return undefined;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
...descriptor,
|
|
131
|
+
methodName,
|
|
132
|
+
fn: wrapVfunc(fn, descriptor.argDescriptors, descriptor.returnDescriptor),
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
75
136
|
function collectDiscoveredVfuncs<K extends "class" | "interface">(
|
|
76
137
|
klass: AnyClass,
|
|
77
138
|
resolveDescriptor: (methodName: string) => VfuncDescriptor<K> | undefined,
|
|
@@ -79,31 +140,30 @@ function collectDiscoveredVfuncs<K extends "class" | "interface">(
|
|
|
79
140
|
): DiscoveredVfunc<K>[] {
|
|
80
141
|
const proto = (klass as { prototype: Record<string, VfuncFn> }).prototype;
|
|
81
142
|
const result: DiscoveredVfunc<K>[] = [];
|
|
143
|
+
|
|
82
144
|
for (const methodName of ownInstanceMethodNames(klass)) {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
result.push({
|
|
89
|
-
...descriptor,
|
|
90
|
-
methodName,
|
|
91
|
-
fn: wrapVfunc(fn, descriptor.argDescriptors, descriptor.returnDescriptor),
|
|
92
|
-
});
|
|
145
|
+
const discovered = buildDiscoveredVfunc(proto, methodName, resolveDescriptor, skip);
|
|
146
|
+
|
|
147
|
+
if (discovered) {
|
|
148
|
+
result.push(discovered);
|
|
149
|
+
}
|
|
93
150
|
}
|
|
151
|
+
|
|
94
152
|
return result;
|
|
95
153
|
}
|
|
96
154
|
|
|
97
|
-
const UNSUPPORTED_CONSTRUCT_VFUNCS: Set<string> = new Set(["constructed", "setProperty", "getProperty"]);
|
|
98
|
-
|
|
99
155
|
function discoverClassVfuncs(klass: AnyClass): DiscoveredClassVfunc[] {
|
|
100
156
|
return collectDiscoveredVfuncs(klass, (methodName) => {
|
|
101
157
|
const descriptor = findClassVfuncDescriptor(klass, methodName);
|
|
158
|
+
|
|
102
159
|
if (descriptor && UNSUPPORTED_CONSTRUCT_VFUNCS.has(methodName)) {
|
|
103
160
|
throw new Error(
|
|
104
|
-
`registerClass: overriding the GObject construct-time vtable slot '${methodName}' is not
|
|
161
|
+
`registerClass: overriding the GObject construct-time vtable slot '${methodName}' is not ` +
|
|
162
|
+
"supported; run construct-time initialization in the subclass constructor, after " +
|
|
163
|
+
"super(...), instead",
|
|
105
164
|
);
|
|
106
165
|
}
|
|
166
|
+
|
|
107
167
|
return descriptor ?? undefined;
|
|
108
168
|
});
|
|
109
169
|
}
|
|
@@ -122,12 +182,15 @@ function discoverInheritedInterfaceVfuncs(
|
|
|
122
182
|
claimedMethodNames: Set<string>,
|
|
123
183
|
): InterfaceVfuncBinding[] {
|
|
124
184
|
const bindings: InterfaceVfuncBinding[] = [];
|
|
185
|
+
|
|
125
186
|
for (const interfaceGtype of typeInterfaces(parentGtype)) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
187
|
+
const [first, ...rest] = discoverInterfaceVfuncs(klass, interfaceGtype, claimedMethodNames);
|
|
188
|
+
|
|
189
|
+
if (first !== undefined) {
|
|
190
|
+
bindings.push({ gtype: interfaceGtype, vtableSize: first.vtableSize, vfuncs: [first, ...rest] });
|
|
129
191
|
}
|
|
130
192
|
}
|
|
193
|
+
|
|
131
194
|
return bindings;
|
|
132
195
|
}
|
|
133
196
|
|
|
@@ -137,11 +200,16 @@ function discoverInterfaceVfuncs(
|
|
|
137
200
|
claimedMethodNames: Set<string>,
|
|
138
201
|
): DiscoveredInterfaceVfunc[] {
|
|
139
202
|
const vfuncRegistry = getInterfaceVfuncRegistry(interfaceGtype);
|
|
140
|
-
|
|
203
|
+
|
|
204
|
+
if (!vfuncRegistry) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
|
|
141
208
|
return collectDiscoveredVfuncs(
|
|
142
209
|
klass,
|
|
143
210
|
(methodName) => {
|
|
144
211
|
const entry = vfuncRegistry[methodName];
|
|
212
|
+
|
|
145
213
|
return entry?.kind === "interface" ? entry : undefined;
|
|
146
214
|
},
|
|
147
215
|
claimedMethodNames,
|
|
@@ -152,27 +220,86 @@ function findClassVfuncDescriptor(klass: AnyClass, methodName: string): VfuncDes
|
|
|
152
220
|
return (
|
|
153
221
|
walkClassChain(getParentClass(klass), (cls) => {
|
|
154
222
|
const entry = getVfuncRegistry(cls)?.[methodName];
|
|
223
|
+
|
|
155
224
|
return entry?.kind === "class" ? entry : undefined;
|
|
156
225
|
}) ?? null
|
|
157
226
|
);
|
|
158
227
|
}
|
|
159
228
|
|
|
229
|
+
function propertyVfuncs(klass: AnyClass, accessors: PropertyAccessor[]): DiscoveredClassVfunc[] {
|
|
230
|
+
if (accessors.length === 0) {
|
|
231
|
+
return [];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return [
|
|
235
|
+
buildPropertyVfunc(klass, "getProperty", makeGetProperty(accessors), true),
|
|
236
|
+
buildPropertyVfunc(klass, "setProperty", makeSetProperty(accessors), false),
|
|
237
|
+
];
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// `get_property` fills a GValue the caller owns, so the marshalled copy handed to JavaScript has to
|
|
241
|
+
// be written back into it; `set_property` only reads its GValue and needs no copy back.
|
|
242
|
+
function markValueCallerAllocated(argDescriptors: Descriptor[]): Descriptor[] {
|
|
243
|
+
return argDescriptors.map((arg, index) =>
|
|
244
|
+
index === VALUE_ARG_INDEX ? { ...arg, callerAllocated: true } : arg);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function buildPropertyVfunc(
|
|
248
|
+
klass: AnyClass,
|
|
249
|
+
methodName: string,
|
|
250
|
+
fn: VfuncFn,
|
|
251
|
+
isValueOut: boolean,
|
|
252
|
+
): DiscoveredClassVfunc {
|
|
253
|
+
const descriptor = findClassVfuncDescriptor(klass, methodName);
|
|
254
|
+
|
|
255
|
+
if (!descriptor) {
|
|
256
|
+
throw new Error(`registerClass: the parent class exposes no '${methodName}' vtable slot`);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const argDescriptors = isValueOut
|
|
260
|
+
? markValueCallerAllocated(descriptor.argDescriptors)
|
|
261
|
+
: descriptor.argDescriptors;
|
|
262
|
+
|
|
263
|
+
return {
|
|
264
|
+
...descriptor,
|
|
265
|
+
methodName,
|
|
266
|
+
argDescriptors,
|
|
267
|
+
fn: wrapVfunc(fn, argDescriptors, descriptor.returnDescriptor),
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
160
271
|
function toNativeOptions(
|
|
161
272
|
classVfuncs: DiscoveredClassVfunc[],
|
|
162
273
|
interfaceBindings: InterfaceVfuncBinding[],
|
|
274
|
+
properties: Record<string, PropertySpec>,
|
|
163
275
|
): NativeRegisterClassOptions | undefined {
|
|
164
276
|
const hasInterfaces = interfaceBindings.length > 0;
|
|
165
277
|
const hasClassVfuncs = classVfuncs.length > 0;
|
|
166
|
-
|
|
278
|
+
const hasProperties = Object.keys(properties).length > 0;
|
|
279
|
+
|
|
280
|
+
if (!hasClassVfuncs && !hasInterfaces && !hasProperties) {
|
|
167
281
|
return undefined;
|
|
168
282
|
}
|
|
283
|
+
|
|
169
284
|
const options: NativeRegisterClassOptions = {};
|
|
170
|
-
|
|
285
|
+
|
|
286
|
+
if (hasProperties) {
|
|
287
|
+
options.properties = toNativeProperties(properties);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
if (hasClassVfuncs) {
|
|
291
|
+
options.vfuncs = [...classVfuncs];
|
|
292
|
+
}
|
|
293
|
+
|
|
171
294
|
if (hasInterfaces) {
|
|
172
295
|
options.interfaces = interfaceBindings.map((binding) => ({
|
|
173
296
|
type: binding.gtype,
|
|
297
|
+
vtableSize: binding.vtableSize,
|
|
174
298
|
vfuncs: [...binding.vfuncs],
|
|
175
299
|
}));
|
|
176
300
|
}
|
|
301
|
+
|
|
177
302
|
return options;
|
|
178
303
|
}
|
|
304
|
+
|
|
305
|
+
export { registerClass };
|