@gtkx/runtime 1.3.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -4
- package/dist/field.d.ts +42 -0
- package/dist/field.d.ts.map +1 -0
- package/dist/field.js +41 -0
- package/dist/field.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/param-spec.d.ts +2 -1
- package/dist/param-spec.d.ts.map +1 -1
- package/dist/param-spec.js +2 -1
- package/dist/param-spec.js.map +1 -1
- package/dist/properties.d.ts +21 -5
- package/dist/properties.d.ts.map +1 -1
- package/dist/properties.js +55 -15
- package/dist/properties.js.map +1 -1
- package/dist/register-class.d.ts +17 -10
- package/dist/register-class.d.ts.map +1 -1
- package/dist/register-class.js +21 -3
- package/dist/register-class.js.map +1 -1
- package/dist/signal.d.ts +5 -2
- package/dist/signal.d.ts.map +1 -1
- package/dist/signal.js +32 -10
- package/dist/signal.js.map +1 -1
- package/dist/t.d.ts +11 -0
- package/dist/t.d.ts.map +1 -1
- package/dist/t.js +3 -0
- package/dist/t.js.map +1 -1
- package/package.json +5 -5
- package/src/field.ts +76 -0
- package/src/index.ts +16 -2
- package/src/param-spec.ts +2 -0
- package/src/properties.ts +97 -28
- package/src/register-class.ts +47 -12
- package/src/signal.ts +47 -11
- package/src/t.ts +13 -0
package/dist/t.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"t.js","sourceRoot":"","sources":["../src/t.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"t.js","sourceRoot":"","sources":["../src/t.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,KAAK,OAAO,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5C,OAAO,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAkG7B;;;;GAIG;AACH,MAAM,CAAC,GAAM;IACT,IAAI;IACJ,IAAI,EAAE,OAAO,CAAC,KAAK;IACnB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,QAAQ,EAAE,OAAO,CAAC,SAAS;IAC3B,SAAS,EAAE,OAAO,CAAC,UAAU;IAC7B,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,OAAO,EAAE,OAAO,CAAC,QAAQ;IACzB,OAAO,EAAE,OAAO,CAAC,QAAQ;IACzB,OAAO,EAAE,OAAO,CAAC,QAAQ;IACzB,IAAI,EAAE,OAAO,CAAC,KAAK;IACnB,OAAO,EAAE,OAAO,CAAC,QAAQ;IACzB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,WAAW,EAAE,OAAO,CAAC,YAAY;IACjC,GAAG,EAAE,OAAO,CAAC,IAAI;IACjB,SAAS,EAAE,OAAO,CAAC,UAAU;IAC7B,IAAI,EAAE,OAAO,CAAC,KAAK;IACnB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,IAAI,EAAE,OAAO,CAAC,KAAK;IACnB,KAAK,EAAE,OAAO,CAAC,MAAM;IACrB,QAAQ,EAAE,OAAO,CAAC,SAAS;IAC3B,MAAM,EAAE,OAAO,CAAC,OAAO;IACvB,SAAS,EAAE,OAAO,CAAC,UAAU;IAC7B,UAAU,EAAE,OAAO,CAAC,WAAW;IAC/B,UAAU,EAAE,OAAO,CAAC,WAAW;IAC/B,WAAW,EAAE,OAAO,CAAC,YAAY;IACjC,QAAQ,EAAE,OAAO,CAAC,SAAS;IAC3B,EAAE;IACF,KAAK;IACL,OAAO;CACV,CAAC;AAEF,OAAO,EAAE,CAAC,EAAE,CAAC","sourcesContent":["import { bind } from \"./bind.js\";\nimport * as helpers from \"./descriptors.js\";\nimport { field, fieldAt } from \"./field.js\";\nimport { fn } from \"./fn.js\";\n\n/** The descriptor factories and function binders exposed as {@link t}. */\ntype T = {\n /** Binds a symbol in a shared library to a callable that marshals its arguments and return value. */\n bind: typeof bind;\n /** Descriptor for a `gint8`, marshalled as a number. */\n int8: typeof helpers.int8T;\n /** Descriptor for a `guint8`, marshalled as a number. */\n uint8: typeof helpers.uint8T;\n /** Descriptor for a `gint16`, marshalled as a number. */\n int16: typeof helpers.int16T;\n /** Descriptor for a `guint16`, marshalled as a number. */\n uint16: typeof helpers.uint16T;\n /** Descriptor for a `gint32`, marshalled as a number. */\n int32: typeof helpers.int32T;\n /** Descriptor for a `guint32`, marshalled as a number. */\n uint32: typeof helpers.uint32T;\n /** Descriptor for a `gint64`, marshalled as a number and rejected outside the 2^53 safe range. */\n int64: typeof helpers.int64T;\n /** Descriptor for a `guint64`, marshalled as a number and rejected outside the 2^53 safe range. */\n uint64: typeof helpers.uint64T;\n /** Descriptor for a `gint64`, marshalled as a bigint so the full 64-bit range survives. */\n bigint64: typeof helpers.bigint64T;\n /** Descriptor for a `guint64`, marshalled as a bigint so the full 64-bit range survives. */\n biguint64: typeof helpers.biguint64T;\n /** Descriptor for a `GType`, marshalled as a bigint and recognized as a GType by GValue conversion. */\n gtype: typeof helpers.gtypeT;\n /** Descriptor for a `gfloat`. */\n float32: typeof helpers.float32T;\n /** Descriptor for a `gdouble`. */\n float64: typeof helpers.float64T;\n /** Descriptor for a `gboolean`, marshalled as a JavaScript boolean. */\n boolean: typeof helpers.booleanT;\n /** Descriptor for the absence of a value, used as the return descriptor of a `void` function. */\n void: typeof helpers.voidT;\n /** Descriptor for a `gunichar`, marshalled as a single-character string or a codepoint number. */\n unichar: typeof helpers.unicharT;\n /** Descriptor for an opaque `gpointer` argument, taken from a typed array's memory or a numeric address. */\n buffer: typeof helpers.bufferT;\n /**\n * Builds a descriptor for a C string, whose optional length sizes the caller-allocated buffer\n * used when the string is passed by reference.\n */\n string: typeof helpers.stringT;\n /** Builds a descriptor for a `GObject`, wrapped in the class registered for its runtime GType. */\n object: typeof helpers.objectT;\n /** Builds a descriptor for a `GBoxed` value of the named type. */\n boxed: typeof helpers.boxedT;\n /** Builds a descriptor for a plain C struct. */\n struct: typeof helpers.structT;\n /** Builds a descriptor for a fundamental type whose lifetime is managed by named ref and unref functions. */\n fundamental: typeof helpers.fundamentalT;\n /** Wraps a descriptor in a pointer to it, for an output or inout argument. */\n ref: typeof helpers.refT;\n /** Builds a descriptor for a `GHashTable`, marshalled as an array of key/value pairs. */\n hashTable: typeof helpers.hashTableT;\n /** Builds a descriptor for an enumeration, resolving its GType from the named `get_type` function. */\n enum: typeof helpers.enumT;\n /** Builds a descriptor for a flags type, resolving its GType from the named `get_type` function. */\n flags: typeof helpers.flagsT;\n /** Builds a descriptor for an array of items in one of the supported container layouts. */\n array: typeof helpers.arrayT;\n /** Builds a descriptor for a `GList` of items. */\n list: typeof helpers.listT;\n /** Builds a descriptor for a `GSList` of items. */\n slist: typeof helpers.slistT;\n /** Builds a descriptor for a `GPtrArray` of items. */\n ptrArray: typeof helpers.ptrArrayT;\n /** Builds a descriptor for a `GArray` of items. */\n gArray: typeof helpers.gArrayT;\n /** Builds a descriptor for a `GByteArray`. */\n byteArray: typeof helpers.byteArrayT;\n /** Builds a descriptor for a C array whose length is carried by another argument. */\n sizedArray: typeof helpers.sizedArrayT;\n /** Builds a descriptor for a C array of a fixed length. */\n fixedArray: typeof helpers.fixedArrayT;\n /** Builds a descriptor for an out pointer into the buffer another argument supplied. */\n cursorArray: typeof helpers.cursorArrayT;\n /** Builds a descriptor for a function pointer, marshalling a JavaScript function into a native closure. */\n callback: typeof helpers.callbackT;\n /**\n * Binds a native function, wiring up argument directions, `GError` checking, and packing output\n * arguments into the result.\n */\n fn: typeof fn;\n /**\n * Binds a struct field at a fixed offset, compiling its descriptor once into an accessor that\n * reads and writes it.\n */\n field: typeof field;\n /**\n * Binds a struct field whose offset is supplied per access, for walking records stored at a\n * stride in a buffer.\n */\n fieldAt: typeof fieldAt;\n};\n\n/**\n * Type descriptor builder: a collection of factory helpers that describe C types for\n * FFI marshalling, plus `bind` to bind a native function and `fn` to describe a\n * function signature.\n */\nconst t: T = {\n bind,\n int8: helpers.int8T,\n uint8: helpers.uint8T,\n int16: helpers.int16T,\n uint16: helpers.uint16T,\n int32: helpers.int32T,\n uint32: helpers.uint32T,\n int64: helpers.int64T,\n uint64: helpers.uint64T,\n bigint64: helpers.bigint64T,\n biguint64: helpers.biguint64T,\n gtype: helpers.gtypeT,\n float32: helpers.float32T,\n float64: helpers.float64T,\n boolean: helpers.booleanT,\n void: helpers.voidT,\n unichar: helpers.unicharT,\n buffer: helpers.bufferT,\n string: helpers.stringT,\n object: helpers.objectT,\n boxed: helpers.boxedT,\n struct: helpers.structT,\n fundamental: helpers.fundamentalT,\n ref: helpers.refT,\n hashTable: helpers.hashTableT,\n enum: helpers.enumT,\n flags: helpers.flagsT,\n array: helpers.arrayT,\n list: helpers.listT,\n slist: helpers.slistT,\n ptrArray: helpers.ptrArrayT,\n gArray: helpers.gArrayT,\n byteArray: helpers.byteArrayT,\n sizedArray: helpers.sizedArrayT,\n fixedArray: helpers.fixedArrayT,\n cursorArray: helpers.cursorArrayT,\n callback: helpers.callbackT,\n fn,\n field,\n fieldAt,\n};\n\nexport { t };\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gtkx/runtime",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "FFI runtime the generated
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "FFI runtime for the generated GTK bindings: types, GValues, signals.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"gtkx",
|
|
7
7
|
"gtk",
|
|
@@ -55,11 +55,11 @@
|
|
|
55
55
|
"node": ">=24"
|
|
56
56
|
},
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@gtkx/
|
|
59
|
-
"@gtkx/
|
|
58
|
+
"@gtkx/native": "1.5.0",
|
|
59
|
+
"@gtkx/utils": "1.5.0"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@gtkx/vitest": "1.
|
|
62
|
+
"@gtkx/vitest": "1.5.0"
|
|
63
63
|
},
|
|
64
64
|
"scripts": {
|
|
65
65
|
"release": "tsx ../../scripts/release-package.ts"
|
package/src/field.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import {
|
|
2
|
+
bindField,
|
|
3
|
+
type Descriptor,
|
|
4
|
+
type ExternalObject,
|
|
5
|
+
type FieldDescriptor,
|
|
6
|
+
type Handle,
|
|
7
|
+
readField,
|
|
8
|
+
writeField,
|
|
9
|
+
} from "@gtkx/native";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* A field of a native struct at a fixed offset, bound once against the descriptor its bytes are
|
|
13
|
+
* marshalled through, so the descriptor is not parsed again on access.
|
|
14
|
+
*/
|
|
15
|
+
type Field = {
|
|
16
|
+
/** Reads and decodes the field out of `handle`'s memory. */
|
|
17
|
+
read: (handle: ExternalObject<Handle>) => unknown;
|
|
18
|
+
/** Encodes `value` and writes it into the field of `handle`'s memory. */
|
|
19
|
+
write: (handle: ExternalObject<Handle>, value: unknown) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A field of a native struct whose offset is supplied per access, bound once against the
|
|
24
|
+
* descriptor its bytes are marshalled through, for walking records stored at a stride.
|
|
25
|
+
*/
|
|
26
|
+
type StridedField = {
|
|
27
|
+
/** Reads and decodes the field `offset` bytes into `handle`'s memory. */
|
|
28
|
+
read: (handle: ExternalObject<Handle>, offset: number) => unknown;
|
|
29
|
+
/** Encodes `value` and writes it `offset` bytes into `handle`'s memory. */
|
|
30
|
+
write: (handle: ExternalObject<Handle>, offset: number, value: unknown) => void;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Binds a struct field whose offset is only known per access, compiling `descriptor` once into an
|
|
35
|
+
* accessor that reads and writes it wherever it is pointed. Use it to walk records stored one
|
|
36
|
+
* after another in a buffer; {@link field} is the one to reach for when the offset is fixed.
|
|
37
|
+
*
|
|
38
|
+
* @param descriptor Describes how the field's bytes are marshalled.
|
|
39
|
+
* @returns An accessor reading and writing that field at any offset of any handle it is given.
|
|
40
|
+
*/
|
|
41
|
+
const fieldAt = (descriptor: Descriptor): StridedField => {
|
|
42
|
+
const bound: ExternalObject<FieldDescriptor> = bindField(descriptor);
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
read: (handle, offset) => readField(bound, handle, offset),
|
|
46
|
+
write: (handle, offset, value) => {
|
|
47
|
+
writeField(bound, handle, offset, value);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Binds a struct field at a fixed offset, compiling `descriptor` once into an accessor that reads
|
|
54
|
+
* and writes it. The `read` and `write` functions compile the descriptor on every call instead, so
|
|
55
|
+
* they remain the ones to reach for when a descriptor is only known per access.
|
|
56
|
+
*
|
|
57
|
+
* @param descriptor Describes how the field's bytes are marshalled.
|
|
58
|
+
* @param offset Byte offset of the field within its owner's memory.
|
|
59
|
+
* @returns An accessor reading and writing that field of any handle it is given.
|
|
60
|
+
*/
|
|
61
|
+
const field = (descriptor: Descriptor, offset: number): Field => {
|
|
62
|
+
if (!Number.isSafeInteger(offset) || offset < 0) {
|
|
63
|
+
throw new RangeError(`A field offset must be a safe whole byte count, got ${String(offset)}`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const bound = fieldAt(descriptor);
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
read: (handle) => bound.read(handle, offset),
|
|
70
|
+
write: (handle, value) => {
|
|
71
|
+
bound.write(handle, offset, value);
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export { field, type Field, fieldAt, type StridedField };
|
package/src/index.ts
CHANGED
|
@@ -7,6 +7,8 @@ export { type ClosureCallback, ClosureMarshalError, toClosure, tryToClosure } fr
|
|
|
7
7
|
/** @public */
|
|
8
8
|
export { createErrorDomain, type ErrorDomain } from "./error.js";
|
|
9
9
|
/** @public */
|
|
10
|
+
export { type Field, type StridedField } from "./field.js";
|
|
11
|
+
/** @public */
|
|
10
12
|
export { onExit, quit, quitApplication, runApplication, type RunApplicationResult } from "./lifecycle.js";
|
|
11
13
|
/** @public */
|
|
12
14
|
export { offSignal, onceSignal, onSignal } from "./listeners.js";
|
|
@@ -28,7 +30,12 @@ export { getParamSpecFlags, getParamSpecOwnerType, getParamSpecValueType } from
|
|
|
28
30
|
/** @public */
|
|
29
31
|
export { promisify, trimFinish } from "./promisify.js";
|
|
30
32
|
/** @public */
|
|
31
|
-
export {
|
|
33
|
+
export {
|
|
34
|
+
coerceObjectProperty,
|
|
35
|
+
getDeclaredPropertyName,
|
|
36
|
+
isReadableProperty,
|
|
37
|
+
newParamSpecOverride,
|
|
38
|
+
} from "./properties.js";
|
|
32
39
|
/** @public */
|
|
33
40
|
export { matchAllRegex, matchRegex } from "./regex.js";
|
|
34
41
|
/** @public */
|
|
@@ -50,7 +57,14 @@ export {
|
|
|
50
57
|
type WrapperClassResolver,
|
|
51
58
|
} from "./registry.js";
|
|
52
59
|
/** @public */
|
|
53
|
-
export {
|
|
60
|
+
export {
|
|
61
|
+
connectSignal,
|
|
62
|
+
disconnectSignal,
|
|
63
|
+
emitSignal,
|
|
64
|
+
getSignalBaseName,
|
|
65
|
+
type SignalHandler,
|
|
66
|
+
signalForHandlerName,
|
|
67
|
+
} from "./signal.js";
|
|
54
68
|
/** @public */
|
|
55
69
|
export { t } from "./t.js";
|
|
56
70
|
/** @public */
|
package/src/param-spec.ts
CHANGED
|
@@ -154,6 +154,7 @@ const getParamSpecOwnerType = (spec: object): bigint => {
|
|
|
154
154
|
};
|
|
155
155
|
|
|
156
156
|
const isParamWritable = (flags: number): boolean => (flags & PARAM_WRITABLE) !== 0;
|
|
157
|
+
const isParamReadable = (flags: number): boolean => (flags & PARAM_READABLE) !== 0;
|
|
157
158
|
const isParamConstructOnly = (flags: number): boolean => (flags & PARAM_CONSTRUCT_ONLY) !== 0;
|
|
158
159
|
const isParamLaxlyValidated = (flags: number): boolean => (flags & PARAM_LAX_VALIDATION) !== 0;
|
|
159
160
|
const isParamExplicitlyNotified = (flags: number): boolean => (flags & PARAM_EXPLICIT_NOTIFY) !== 0;
|
|
@@ -308,6 +309,7 @@ export {
|
|
|
308
309
|
INT64_MAXIMUM,
|
|
309
310
|
INT64_MINIMUM,
|
|
310
311
|
isParamConstructOnly,
|
|
312
|
+
isParamReadable,
|
|
311
313
|
isStringArray,
|
|
312
314
|
isParamExplicitlyNotified,
|
|
313
315
|
isParamLaxlyValidated,
|
package/src/properties.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { ExternalObject, Handle, RegisterClassProperty } from "@gtkx/native";
|
|
2
2
|
import type { AnyClass } from "@gtkx/utils";
|
|
3
|
-
import { camelCase, kebabCase } from "@gtkx/utils";
|
|
3
|
+
import { camelCase, kebabCase, toCamelIdentifier } from "@gtkx/utils";
|
|
4
4
|
import { bind } from "./bind.js";
|
|
5
|
-
import { biguint64T, fundamentalT, stringT, structT, voidT } from "./descriptors.js";
|
|
5
|
+
import { biguint64T, fundamentalT, refT, sizedArrayT, stringT, structT, uint32T, voidT } from "./descriptors.js";
|
|
6
6
|
import { LIB, PARAM_T, VALUE_T } from "./library.js";
|
|
7
7
|
import {
|
|
8
8
|
getParamFlags,
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
isParamConstructOnly,
|
|
11
11
|
isParamExplicitlyNotified,
|
|
12
12
|
isParamLaxlyValidated,
|
|
13
|
+
isParamReadable,
|
|
13
14
|
isParamWritable,
|
|
14
15
|
type ValueGuard,
|
|
15
16
|
valueGuardFor,
|
|
@@ -47,14 +48,19 @@ type PropertyCheck = {
|
|
|
47
48
|
scratch?: ExternalObject<Handle>;
|
|
48
49
|
};
|
|
49
50
|
|
|
50
|
-
type
|
|
51
|
-
memberName: string;
|
|
51
|
+
type AccessorBase = PropertyCheck & {
|
|
52
52
|
storage: symbol;
|
|
53
|
-
hasGeneratedMember: boolean;
|
|
54
|
-
isInterfaceProperty?: boolean;
|
|
55
53
|
delegate?: InterfaceProperty;
|
|
56
54
|
};
|
|
57
55
|
|
|
56
|
+
type DeclaredAccessor = AccessorBase & {
|
|
57
|
+
isInterfaceProperty?: false;
|
|
58
|
+
memberName: string;
|
|
59
|
+
hasGeneratedMember: boolean;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type InterfaceAccessor = AccessorBase & { isInterfaceProperty: true };
|
|
63
|
+
type PropertyAccessor = DeclaredAccessor | InterfaceAccessor;
|
|
58
64
|
/** A `GObject.ParamSpec` wrapper describing one property's name, type, flags and default. */
|
|
59
65
|
type PropertySpec = object;
|
|
60
66
|
|
|
@@ -82,6 +88,8 @@ const SET_PROPERTY_VFUNC = "vfuncSetProperty";
|
|
|
82
88
|
const READ_ONLY_REASON = "the property is read-only";
|
|
83
89
|
const CONSTRUCT_ONLY_REASON = "the property can only be set when the object is constructed";
|
|
84
90
|
const CLASS_T = structT("borrowed");
|
|
91
|
+
const CANONICAL_NAME_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
92
|
+
const DECLARED_NAMES = Symbol("gtkx:declaredPropertyNames");
|
|
85
93
|
|
|
86
94
|
const OVERRIDE_PARAM_T = fundamentalT(LIB, "g_param_spec_ref_sink", "g_param_spec_unref", {
|
|
87
95
|
ownership: "borrowed",
|
|
@@ -95,6 +103,14 @@ const typeClassRef = bind(LIB, "g_type_class_ref", [biguint64T], CLASS_T);
|
|
|
95
103
|
const typeClassUnref = bind(LIB, "g_type_class_unref", [CLASS_T], voidT);
|
|
96
104
|
const coercionChecks: Map<bigint, Map<string, PropertyCheck | null>> = new Map();
|
|
97
105
|
const classFindProperty = bind(LIB, "g_object_class_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
106
|
+
|
|
107
|
+
const classListProperties = bind(
|
|
108
|
+
LIB,
|
|
109
|
+
"g_object_class_list_properties",
|
|
110
|
+
[CLASS_T, refT(uint32T)],
|
|
111
|
+
sizedArrayT(PARAM_T, 1, "full"),
|
|
112
|
+
);
|
|
113
|
+
|
|
98
114
|
const defaultInterfaceRef = bind(LIB, "g_type_default_interface_ref", [biguint64T], CLASS_T);
|
|
99
115
|
const defaultInterfaceUnref = bind(LIB, "g_type_default_interface_unref", [CLASS_T], voidT);
|
|
100
116
|
const interfaceFindProperty = bind(LIB, "g_object_interface_find_property", [CLASS_T, stringT("borrowed")], PARAM_T);
|
|
@@ -128,10 +144,22 @@ function checkFor(handle: ExternalObject<Handle>, name: string): PropertyCheck {
|
|
|
128
144
|
};
|
|
129
145
|
}
|
|
130
146
|
|
|
147
|
+
function accessorSpecs(klass: ExternalObject<Handle>): Map<string, ExternalObject<Handle>> {
|
|
148
|
+
const specs: Map<string, ExternalObject<Handle>> = new Map();
|
|
149
|
+
const countRef = { value: 0 };
|
|
150
|
+
|
|
151
|
+
for (const pspec of classListProperties(klass, countRef) as ExternalObject<Handle>[]) {
|
|
152
|
+
specs.set(toCamelIdentifier(paramSpecName(pspec) as string), pspec);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return specs;
|
|
156
|
+
}
|
|
157
|
+
|
|
131
158
|
function findPropertySpec(klass: ExternalObject<Handle>, name: string): ExternalObject<Handle> | null {
|
|
132
|
-
const found = classFindProperty(klass, name)
|
|
159
|
+
const found = (classFindProperty(klass, name) ??
|
|
160
|
+
classFindProperty(klass, canonicalCase(name))) as ExternalObject<Handle> | null;
|
|
133
161
|
|
|
134
|
-
return found ?? (
|
|
162
|
+
return found ?? accessorSpecs(klass).get(toCamelIdentifier(name)) ?? null;
|
|
135
163
|
}
|
|
136
164
|
|
|
137
165
|
const sourceLabel = (source: bigint | AnyClass, gtype: bigint): string =>
|
|
@@ -290,6 +318,22 @@ function constructValueFor(wrapper: object, check: PropertyCheck, value: unknown
|
|
|
290
318
|
return { name: check.propertyName, value: checkedValueFor(wrapper, check, value) };
|
|
291
319
|
}
|
|
292
320
|
|
|
321
|
+
/**
|
|
322
|
+
* Whether the type declares a readable GObject property under `propertyName`, asking GObject itself
|
|
323
|
+
* rather than looking for a generated accessor, which codegen omits when a method shares the name.
|
|
324
|
+
*/
|
|
325
|
+
function isReadableProperty(gtype: bigint, propertyName: string): boolean {
|
|
326
|
+
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
327
|
+
|
|
328
|
+
try {
|
|
329
|
+
const pspec = findPropertySpec(klass, propertyName);
|
|
330
|
+
|
|
331
|
+
return pspec !== null && isParamReadable(getParamFlags(pspec));
|
|
332
|
+
} finally {
|
|
333
|
+
typeClassUnref(klass);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
293
337
|
function lookupCoercionCheck(gtype: bigint, name: string): PropertyCheck | null {
|
|
294
338
|
const klass = typeClassRef(gtype) as ExternalObject<Handle>;
|
|
295
339
|
|
|
@@ -436,13 +480,13 @@ function storedGetter(accessor: PropertyAccessor): (this: object) => unknown {
|
|
|
436
480
|
};
|
|
437
481
|
}
|
|
438
482
|
|
|
439
|
-
function memberGetter(accessor:
|
|
483
|
+
function memberGetter(accessor: DeclaredAccessor): (this: object) => unknown {
|
|
440
484
|
return function get(this: object) {
|
|
441
485
|
return (this as Record<string, unknown>)[accessor.memberName];
|
|
442
486
|
};
|
|
443
487
|
}
|
|
444
488
|
|
|
445
|
-
function memberSetter(accessor:
|
|
489
|
+
function memberSetter(accessor: DeclaredAccessor): (this: object, value: unknown) => void {
|
|
446
490
|
return function set(this: object, value: unknown) {
|
|
447
491
|
(this as Record<string, unknown>)[accessor.memberName] = value;
|
|
448
492
|
};
|
|
@@ -454,7 +498,7 @@ function refusalFor(accessor: PropertyAccessor, reason: string): (this: object,
|
|
|
454
498
|
};
|
|
455
499
|
}
|
|
456
500
|
|
|
457
|
-
function checkedSetter(accessor:
|
|
501
|
+
function checkedSetter(accessor: DeclaredAccessor): (this: object, value: unknown) => void {
|
|
458
502
|
if (!isParamWritable(accessor.flags)) {
|
|
459
503
|
return refusalFor(accessor, READ_ONLY_REASON);
|
|
460
504
|
}
|
|
@@ -480,7 +524,7 @@ function defineAccessor(prototype: object, descriptor: PropertyDescriptor, alias
|
|
|
480
524
|
Object.defineProperty(prototype, alias, descriptor);
|
|
481
525
|
}
|
|
482
526
|
|
|
483
|
-
function installAccessors(klass: AnyClass, accessor:
|
|
527
|
+
function installAccessors(klass: AnyClass, accessor: DeclaredAccessor): void {
|
|
484
528
|
const prototype = (klass as { prototype: object }).prototype;
|
|
485
529
|
accessor.hasGeneratedMember = !hasOwnMember(prototype, accessor.memberName);
|
|
486
530
|
|
|
@@ -509,16 +553,14 @@ function storageFor(propertyName: string): symbol {
|
|
|
509
553
|
return created;
|
|
510
554
|
}
|
|
511
555
|
|
|
512
|
-
function interfaceAccessorFor(dispatch: PropertyDispatch, pspec: PropertySpec):
|
|
556
|
+
function interfaceAccessorFor(dispatch: PropertyDispatch, pspec: PropertySpec): InterfaceAccessor {
|
|
513
557
|
const handle = getHandle(pspec);
|
|
514
558
|
const propertyName = paramSpecName(handle) as string;
|
|
515
559
|
const delegate = dispatch.delegates.get(propertyName);
|
|
516
560
|
|
|
517
|
-
const accessor:
|
|
561
|
+
const accessor: InterfaceAccessor = {
|
|
518
562
|
...checkFor(handle, propertyName),
|
|
519
|
-
memberName: camelCase(propertyName),
|
|
520
563
|
storage: storageFor(propertyName),
|
|
521
|
-
hasGeneratedMember: false,
|
|
522
564
|
isInterfaceProperty: true,
|
|
523
565
|
};
|
|
524
566
|
|
|
@@ -556,7 +598,7 @@ function callMember(instance: object, member: string, args: unknown[]): unknown
|
|
|
556
598
|
return (fn as (...values: unknown[]) => unknown).apply(instance, args);
|
|
557
599
|
}
|
|
558
600
|
|
|
559
|
-
function backingMemberFor(instance: object, accessor:
|
|
601
|
+
function backingMemberFor(instance: object, accessor: DeclaredAccessor): string | undefined {
|
|
560
602
|
if (accessor.hasGeneratedMember && !Object.hasOwn(instance, accessor.memberName)) {
|
|
561
603
|
return undefined;
|
|
562
604
|
}
|
|
@@ -632,16 +674,17 @@ function makeSetProperty(dispatch: PropertyDispatch) {
|
|
|
632
674
|
};
|
|
633
675
|
}
|
|
634
676
|
|
|
635
|
-
|
|
636
|
-
|
|
677
|
+
const isCanonicalName = (name: string, propertyName: string): boolean =>
|
|
678
|
+
CANONICAL_NAME_PATTERN.test(propertyName) && toCamelIdentifier(propertyName) === toCamelIdentifier(name);
|
|
637
679
|
|
|
638
|
-
|
|
680
|
+
function assertCanonicalName(klass: AnyClass, name: string, propertyName: string): void {
|
|
681
|
+
if (isCanonicalName(name, propertyName)) {
|
|
639
682
|
return;
|
|
640
683
|
}
|
|
641
684
|
|
|
642
685
|
throw new TypeError(
|
|
643
686
|
`registerClass: ${klass.name} keys the property '${name}' to a GObject.ParamSpec named ` +
|
|
644
|
-
`'${propertyName}', which is the name GObject notifies under; name the ParamSpec '${
|
|
687
|
+
`'${propertyName}', which is the name GObject notifies under; name the ParamSpec '${canonicalCase(name)}'`,
|
|
645
688
|
);
|
|
646
689
|
}
|
|
647
690
|
|
|
@@ -651,8 +694,8 @@ function assertCanonicalNames(klass: AnyClass, properties: Record<string, Proper
|
|
|
651
694
|
}
|
|
652
695
|
}
|
|
653
696
|
|
|
654
|
-
function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec):
|
|
655
|
-
const accessor:
|
|
697
|
+
function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): DeclaredAccessor {
|
|
698
|
+
const accessor: DeclaredAccessor = {
|
|
656
699
|
...checkFor(getHandle(pspec), name),
|
|
657
700
|
memberName: camelCase(name),
|
|
658
701
|
storage: Symbol(`gtkx:property:${name}`),
|
|
@@ -664,7 +707,7 @@ function buildAccessor(klass: AnyClass, name: string, pspec: PropertySpec): Prop
|
|
|
664
707
|
return accessor;
|
|
665
708
|
}
|
|
666
709
|
|
|
667
|
-
function buildAccessors(source: PropertyDispatchSource):
|
|
710
|
+
function buildAccessors(source: PropertyDispatchSource): DeclaredAccessor[] {
|
|
668
711
|
const { klass, properties } = source;
|
|
669
712
|
assertCanonicalNames(klass, properties);
|
|
670
713
|
|
|
@@ -691,11 +734,35 @@ function interfaceDelegatesFor(adoptedTypes: bigint[]): Map<string, InterfacePro
|
|
|
691
734
|
return delegates;
|
|
692
735
|
}
|
|
693
736
|
|
|
737
|
+
function recordDeclaredNames(klass: AnyClass, accessors: DeclaredAccessor[]): void {
|
|
738
|
+
const proto = klass.prototype as Record<PropertyKey, unknown>;
|
|
739
|
+
const inherited = proto[DECLARED_NAMES] as Record<string, string> | undefined;
|
|
740
|
+
const own: Record<string, string> = {};
|
|
741
|
+
|
|
742
|
+
for (const accessor of accessors) {
|
|
743
|
+
own[accessor.memberName] = accessor.propertyName;
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
Object.defineProperty(proto, DECLARED_NAMES, { value: { ...inherited, ...own }, enumerable: false });
|
|
747
|
+
}
|
|
748
|
+
|
|
694
749
|
function buildPropertyDispatch(source: PropertyDispatchSource): PropertyDispatch {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
};
|
|
750
|
+
const declared = buildAccessors(source);
|
|
751
|
+
recordDeclaredNames(source.klass, declared);
|
|
752
|
+
|
|
753
|
+
return { accessors: [...declared], delegates: interfaceDelegatesFor(source.adoptedTypes) };
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
/**
|
|
757
|
+
* The GObject name a class registered with `registerClass` installed an accessor under, or
|
|
758
|
+
* `undefined` when the accessor names no property the class declared itself.
|
|
759
|
+
*/
|
|
760
|
+
function getDeclaredPropertyName(object: object, accessor: string): string | undefined {
|
|
761
|
+
const declared = (object as Record<PropertyKey, unknown>)[DECLARED_NAMES] as
|
|
762
|
+
| Record<string, string> |
|
|
763
|
+
undefined;
|
|
764
|
+
|
|
765
|
+
return declared?.[accessor];
|
|
699
766
|
}
|
|
700
767
|
|
|
701
768
|
function toNativeProperties(properties: Record<string, PropertySpec>): RegisterClassProperty[] {
|
|
@@ -708,6 +775,8 @@ function toNativeProperties(properties: Record<string, PropertySpec>): RegisterC
|
|
|
708
775
|
export {
|
|
709
776
|
buildPropertyDispatch,
|
|
710
777
|
coerceObjectProperty,
|
|
778
|
+
getDeclaredPropertyName,
|
|
779
|
+
isReadableProperty,
|
|
711
780
|
coercePropertyValue,
|
|
712
781
|
constructPropertyFor,
|
|
713
782
|
GET_PROPERTY_VFUNC,
|
package/src/register-class.ts
CHANGED
|
@@ -35,7 +35,9 @@ import {
|
|
|
35
35
|
emitDeclaredSignal,
|
|
36
36
|
getSignalBaseName,
|
|
37
37
|
overrideSignalClassClosure,
|
|
38
|
+
signalForHandlerName,
|
|
38
39
|
type SignalHandler,
|
|
40
|
+
signalIdFor,
|
|
39
41
|
} from "./signal.js";
|
|
40
42
|
import {
|
|
41
43
|
TYPE_INTERFACE,
|
|
@@ -251,10 +253,15 @@ type RegisterClassOptions<
|
|
|
251
253
|
* Properties to install on the new type, keyed by the name JavaScript addresses each one by and
|
|
252
254
|
* valued with the `GObject.ParamSpec` describing it. A key is read in camelCase however it is
|
|
253
255
|
* written, so `dewPoint`, `dew_point` and `dew-point` all name the same member, and the ParamSpec
|
|
254
|
-
* has to carry
|
|
255
|
-
* ParamSpec's name is the one GObject emits `notify` with, and a name the key does
|
|
256
|
-
* reaches nothing that listens for it.
|
|
257
|
-
*
|
|
256
|
+
* has to carry a name that reads back as that same member, such as `dew-point`, or registration
|
|
257
|
+
* throws: the ParamSpec's name is the one GObject emits `notify` with, and a name the key does
|
|
258
|
+
* not spell reaches nothing that listens for it. A word starting with a digit is a word of its
|
|
259
|
+
* own on either side of that reading, so `level2Depth` takes a ParamSpec named `level-2-depth`
|
|
260
|
+
* as readily as one named `level2-depth`, the way `WebKit.Settings` names
|
|
261
|
+
* `enable-2d-canvas-acceleration` for its `enable2dCanvasAcceleration` member. An uppercase
|
|
262
|
+
* letter in the ParamSpec's own name is refused, since GObject notifies under that spelling
|
|
263
|
+
* and nothing else reads it back. Every property gains prototype accessors, one for the key as
|
|
264
|
+
* written, one for it with dashes turned into underscores and one for it in camelCase, each
|
|
258
265
|
* unless the class already defines that name. They serve the value from storage of their own on
|
|
259
266
|
* the instance, which is also what the type's `get_property` and `set_property` slots read and
|
|
260
267
|
* write, so a value set from JavaScript, from `g_object_set_property` and at construction all
|
|
@@ -305,9 +312,11 @@ type RegisterClassOptions<
|
|
|
305
312
|
properties?: TProperties;
|
|
306
313
|
/**
|
|
307
314
|
* 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
|
|
309
|
-
* letters, digits, `-` and `_`, and be new to the type: one an ancestor type or a
|
|
310
|
-
* interface already carries throws
|
|
315
|
+
* {@link SignalSpec} describing each one. A name has to start with a lowercase letter, continue
|
|
316
|
+
* in lowercase letters, digits, `-` and `_`, and be new to the type: one an ancestor type or a
|
|
317
|
+
* listed interface already carries throws, and so does one carrying an uppercase letter, which
|
|
318
|
+
* GObject would carry under that exact spelling, out of reach of both its dashed spelling and
|
|
319
|
+
* its `on<SignalName>` default handler. The two word separators spell the same signal, so a
|
|
311
320
|
* signal declared as `data_changed` is connected to and emitted as `data-changed` too.
|
|
312
321
|
*
|
|
313
322
|
* Instances connect and emit by name through the same `connect`, `on`, `once`, `off` and
|
|
@@ -373,6 +382,7 @@ const PROPERTY_VFUNC_SPECS: PropertyVfuncSpec[] = [
|
|
|
373
382
|
|
|
374
383
|
const PROPERTY_VFUNC_NAMES: Set<string> = new Set(PROPERTY_VFUNC_SPECS.map((spec) => spec.methodName));
|
|
375
384
|
const TYPE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9\-_+]{2,}$/;
|
|
385
|
+
const UPPER_CASE_PATTERN = /[A-Z]/;
|
|
376
386
|
const SIGNAL_OVERRIDE_PATTERN = /^on[A-Z]/;
|
|
377
387
|
|
|
378
388
|
/**
|
|
@@ -387,9 +397,9 @@ const SIGNAL_OVERRIDE_PATTERN = /^on[A-Z]/;
|
|
|
387
397
|
* when the list names `Gio.AsyncInitable` as an interface the parent type does not already
|
|
388
398
|
* implement and no method on the chain fills `vfuncInitAsync`, since the default `init_async`
|
|
389
399
|
* would run `vfuncInit` on a worker thread, when an entry in
|
|
390
|
-
* `RegisterClassOptions.properties` names its `GObject.ParamSpec` something
|
|
391
|
-
*
|
|
392
|
-
*
|
|
400
|
+
* `RegisterClassOptions.properties` names its `GObject.ParamSpec` something the key it sits under
|
|
401
|
+
* does not spell, when an entry in `RegisterClassOptions.signals` carries an invalid name, a name
|
|
402
|
+
* spelled with an uppercase letter rather than dashed, a name the type already knows, a GType
|
|
393
403
|
* that cannot hold a value, or an accumulator the spec does not admit, and when
|
|
394
404
|
* `RegisterClassOptions.cssName` is
|
|
395
405
|
* given for a class that does not extend `Gtk.Widget`. An exception thrown by
|
|
@@ -769,15 +779,39 @@ function buildPropertyVfunc(
|
|
|
769
779
|
|
|
770
780
|
const canonicalSignalName = (name: string): string => name.replaceAll("_", "-");
|
|
771
781
|
|
|
782
|
+
function assertLowerCaseSignalName(klass: AnyClass, name: string): void {
|
|
783
|
+
if (!UPPER_CASE_PATTERN.test(name)) {
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
throw new TypeError(
|
|
788
|
+
`registerClass: ${klass.name} declares the signal '${name}'; GObject would carry it under that ` +
|
|
789
|
+
"exact spelling, out of reach of both its dashed spelling and its default handler; " +
|
|
790
|
+
`declare it as '${kebabCase(name)}'`,
|
|
791
|
+
);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function overriddenSignalId(type: bigint, methodName: string): number {
|
|
795
|
+
if (!SIGNAL_OVERRIDE_PATTERN.test(methodName)) {
|
|
796
|
+
return 0;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
const signal = signalForHandlerName(type, methodName);
|
|
800
|
+
|
|
801
|
+
return signal === undefined ? 0 : signalIdFor(type, signal);
|
|
802
|
+
}
|
|
803
|
+
|
|
772
804
|
function installSignalOverrides(newType: bigint, methods: MethodTable): void {
|
|
773
805
|
for (const [methodName, fn] of methods) {
|
|
774
|
-
|
|
806
|
+
const signalId = overriddenSignalId(newType, methodName);
|
|
807
|
+
|
|
808
|
+
if (signalId === 0) {
|
|
775
809
|
continue;
|
|
776
810
|
}
|
|
777
811
|
|
|
778
812
|
const handler = fn as (...args: unknown[]) => unknown;
|
|
779
813
|
|
|
780
|
-
overrideSignalClassClosure(newType,
|
|
814
|
+
overrideSignalClassClosure(newType, signalId, (...args: unknown[]) =>
|
|
781
815
|
handler.apply(args[0], args.slice(1)),
|
|
782
816
|
);
|
|
783
817
|
}
|
|
@@ -844,6 +878,7 @@ function resolveDeclaredSignals(klass: AnyClass, signals: Record<string, SignalS
|
|
|
844
878
|
const table: Map<string, DeclaredSignalTypes> = new Map();
|
|
845
879
|
|
|
846
880
|
for (const [name, spec] of Object.entries(signals)) {
|
|
881
|
+
assertLowerCaseSignalName(klass, name);
|
|
847
882
|
const resolved = resolveDeclaredSignal(klass, name, spec);
|
|
848
883
|
native.push(resolved.native);
|
|
849
884
|
table.set(canonicalSignalName(name), resolved.declared);
|