@gtkx/gir 0.1.17 → 0.1.19

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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/parser.ts +30 -1
  3. package/src/types.ts +148 -41
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gtkx/gir",
3
- "version": "0.1.17",
3
+ "version": "0.1.19",
4
4
  "description": "GObject Introspection file parser for GTKX",
5
5
  "keywords": [
6
6
  "gtk",
package/src/parser.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { XMLParser } from "fast-xml-parser";
2
2
  import type {
3
+ GirCallback,
3
4
  GirClass,
4
5
  GirConstructor,
5
6
  GirEnumeration,
@@ -23,6 +24,7 @@ const ARRAY_ELEMENT_PATHS = new Set<string>([
23
24
  "namespace.enumeration",
24
25
  "namespace.bitfield",
25
26
  "namespace.record",
27
+ "namespace.callback",
26
28
  "namespace.class.method",
27
29
  "namespace.class.constructor",
28
30
  "namespace.class.function",
@@ -49,6 +51,7 @@ const ARRAY_ELEMENT_PATHS = new Set<string>([
49
51
  "namespace.record.method.parameters.parameter",
50
52
  "namespace.record.constructor.parameters.parameter",
51
53
  "namespace.record.function.parameters.parameter",
54
+ "namespace.callback.parameters.parameter",
52
55
  ]);
53
56
 
54
57
  const extractDoc = (node: Record<string, unknown>): string | undefined => {
@@ -105,15 +108,38 @@ export class GirParser {
105
108
  enumerations: this.parseEnumerations(namespace.enumeration ?? []),
106
109
  bitfields: this.parseEnumerations(namespace.bitfield ?? []),
107
110
  records: this.parseRecords(namespace.record ?? []),
111
+ callbacks: this.parseCallbacks(namespace.callback ?? []),
108
112
  };
109
113
  }
110
114
 
115
+ private parseCallbacks(callbacks: Record<string, unknown>[]): GirCallback[] {
116
+ if (!callbacks || !Array.isArray(callbacks)) {
117
+ return [];
118
+ }
119
+ return callbacks
120
+ .filter((cb) => cb["@_introspectable"] !== "0")
121
+ .map((cb) => ({
122
+ name: String(cb["@_name"] ?? ""),
123
+ cType: String(cb["@_c:type"] ?? ""),
124
+ returnType: this.parseReturnType(cb["return-value"] as Record<string, unknown> | undefined),
125
+ parameters: this.parseParameters(
126
+ (cb.parameters && typeof cb.parameters === "object" && cb.parameters !== null
127
+ ? cb.parameters
128
+ : {}) as Record<string, unknown>,
129
+ ),
130
+ doc: extractDoc(cb),
131
+ }));
132
+ }
133
+
111
134
  private parseClasses(classes: Record<string, unknown>[]): GirClass[] {
112
135
  return classes.map((cls) => ({
113
136
  name: String(cls["@_name"] ?? ""),
114
137
  cType: String(cls["@_c:type"] ?? cls["@_glib:type-name"] ?? ""),
115
138
  parent: String(cls["@_parent"] ?? ""),
116
139
  abstract: cls["@_abstract"] === "1",
140
+ glibTypeName: cls["@_glib:type-name"] ? String(cls["@_glib:type-name"]) : undefined,
141
+ glibGetType: cls["@_glib:get-type"] ? String(cls["@_glib:get-type"]) : undefined,
142
+ cSymbolPrefix: cls["@_c:symbol-prefix"] ? String(cls["@_c:symbol-prefix"]) : undefined,
117
143
  implements: this.parseImplements(
118
144
  cls.implements as Record<string, unknown>[] | Record<string, unknown> | undefined,
119
145
  ),
@@ -193,6 +219,7 @@ export class GirParser {
193
219
  ? ctor.parameters
194
220
  : {}) as Record<string, unknown>,
195
221
  ),
222
+ throws: ctor["@_throws"] === "1",
196
223
  doc: extractDoc(ctor),
197
224
  }));
198
225
  }
@@ -227,6 +254,7 @@ export class GirParser {
227
254
  return params.map((param: Record<string, unknown>) => {
228
255
  const scope = param["@_scope"] as string | undefined;
229
256
  const closure = param["@_closure"] as string | undefined;
257
+ const destroy = param["@_destroy"] as string | undefined;
230
258
  return {
231
259
  name: String(param["@_name"] ?? ""),
232
260
  type: this.parseType((param.type ?? param.array) as Record<string, unknown> | undefined),
@@ -235,6 +263,7 @@ export class GirParser {
235
263
  optional: param["@_allow-none"] === "1",
236
264
  scope: scope as "async" | "call" | "notified" | undefined,
237
265
  closure: closure !== undefined ? parseInt(closure, 10) : undefined,
266
+ destroy: destroy !== undefined ? parseInt(destroy, 10) : undefined,
238
267
  doc: extractDoc(param),
239
268
  };
240
269
  });
@@ -263,7 +292,7 @@ export class GirParser {
263
292
  const typeName = typeNode["@_name"] ? String(typeNode["@_name"]) : undefined;
264
293
 
265
294
  if (typeName === "GLib.List" || typeName === "GLib.SList") {
266
- const innerType = typeNode.type as Record<string, unknown> | undefined;
295
+ const innerType = (typeNode.type ?? typeNode.array) as Record<string, unknown> | undefined;
267
296
  return {
268
297
  name: "array",
269
298
  cType: typeNode["@_c:type"] ? String(typeNode["@_c:type"]) : undefined,
package/src/types.ts CHANGED
@@ -22,10 +22,28 @@ export interface GirNamespace {
22
22
  bitfields: GirEnumeration[];
23
23
  /** All records (structs) defined in this namespace. */
24
24
  records: GirRecord[];
25
+ /** All callback types defined in this namespace. */
26
+ callbacks: GirCallback[];
25
27
  /** Documentation for the namespace. */
26
28
  doc?: string;
27
29
  }
28
30
 
31
+ /**
32
+ * Represents a GIR callback type definition.
33
+ */
34
+ export interface GirCallback {
35
+ /** The callback name. */
36
+ name: string;
37
+ /** The C type name. */
38
+ cType: string;
39
+ /** The return type. */
40
+ returnType: GirType;
41
+ /** The callback parameters. */
42
+ parameters: GirParameter[];
43
+ /** Documentation for the callback. */
44
+ doc?: string;
45
+ }
46
+
29
47
  /**
30
48
  * Represents a GIR interface definition.
31
49
  */
@@ -56,6 +74,12 @@ export interface GirClass {
56
74
  parent?: string;
57
75
  /** Whether this is an abstract class. */
58
76
  abstract?: boolean;
77
+ /** The GLib type name. */
78
+ glibTypeName?: string;
79
+ /** The GLib get-type function. */
80
+ glibGetType?: string;
81
+ /** The C symbol prefix for this class. */
82
+ cSymbolPrefix?: string;
59
83
  /** List of interface names this class implements. */
60
84
  implements: string[];
61
85
  /** Methods defined on this class. */
@@ -148,6 +172,8 @@ export interface GirConstructor {
148
172
  returnType: GirType;
149
173
  /** The constructor parameters. */
150
174
  parameters: GirParameter[];
175
+ /** Whether this constructor can throw a GError. */
176
+ throws?: boolean;
151
177
  /** Documentation for the constructor. */
152
178
  doc?: string;
153
179
  }
@@ -188,6 +214,8 @@ export interface GirParameter {
188
214
  scope?: "async" | "call" | "notified";
189
215
  /** Index of the closure/user_data parameter for callbacks. */
190
216
  closure?: number;
217
+ /** Index of the destroy notifier parameter for callbacks. */
218
+ destroy?: number;
191
219
  /** Documentation for the parameter. */
192
220
  doc?: string;
193
221
  }
@@ -296,12 +324,14 @@ export interface FfiTypeDescriptor {
296
324
  itemType?: FfiTypeDescriptor;
297
325
  /** List type for arrays (glist, gslist) - indicates native GList/GSList iteration. */
298
326
  listType?: "glist" | "gslist";
299
- /** Trampoline type for callbacks (asyncReady, destroy, sourceFunc). Default is "closure". */
300
- trampoline?: "asyncReady" | "destroy" | "sourceFunc";
327
+ /** Trampoline type for callbacks (asyncReady, destroy, sourceFunc, drawFunc). Default is "closure". */
328
+ trampoline?: "asyncReady" | "destroy" | "sourceFunc" | "drawFunc";
301
329
  /** Source type for asyncReady callback (the GObject source). */
302
330
  sourceType?: FfiTypeDescriptor;
303
331
  /** Result type for asyncReady callback (the GAsyncResult). */
304
332
  resultType?: FfiTypeDescriptor;
333
+ /** Argument types for callbacks that need explicit type info (e.g., drawFunc). */
334
+ argTypes?: FfiTypeDescriptor[];
305
335
  }
306
336
 
307
337
  /**
@@ -350,7 +380,7 @@ export const registerEnumsFromNamespace = (typeMapper: TypeMapper, namespace: Gi
350
380
 
351
381
  type TypeMapping = { ts: string; ffi: FfiTypeDescriptor };
352
382
 
353
- export type TypeKind = "class" | "interface" | "enum" | "record";
383
+ export type TypeKind = "class" | "interface" | "enum" | "record" | "callback";
354
384
 
355
385
  export interface RegisteredType {
356
386
  kind: TypeKind;
@@ -414,6 +444,16 @@ export class TypeRegistry {
414
444
  });
415
445
  }
416
446
 
447
+ registerCallback(namespace: string, name: string): void {
448
+ const transformedName = toPascalCase(name);
449
+ this.types.set(`${namespace}.${name}`, {
450
+ kind: "callback",
451
+ name,
452
+ namespace,
453
+ transformedName,
454
+ });
455
+ }
456
+
417
457
  resolve(qualifiedName: string): RegisteredType | undefined {
418
458
  return this.types.get(qualifiedName);
419
459
  }
@@ -441,16 +481,13 @@ export class TypeRegistry {
441
481
  registry.registerEnum(ns.name, bitfield.name);
442
482
  }
443
483
  for (const record of ns.records) {
444
- if (
445
- record.glibTypeName &&
446
- !record.disguised &&
447
- !record.name.endsWith("Class") &&
448
- !record.name.endsWith("Private") &&
449
- !record.name.endsWith("Iface")
450
- ) {
484
+ if (record.glibTypeName && !record.disguised) {
451
485
  registry.registerRecord(ns.name, record.name, record.glibTypeName);
452
486
  }
453
487
  }
488
+ for (const callback of ns.callbacks) {
489
+ registry.registerCallback(ns.name, callback.name);
490
+ }
454
491
  }
455
492
  return registry;
456
493
  }
@@ -458,6 +495,65 @@ export class TypeRegistry {
458
495
 
459
496
  const STRING_TYPES = new Set(["utf8", "filename"]);
460
497
 
498
+ const POINTER_TYPE: TypeMapping = { ts: "number", ffi: { type: "int", size: 64, unsigned: true } };
499
+
500
+ const C_TYPE_MAP = new Map<string, TypeMapping>([
501
+ ["void", { ts: "void", ffi: { type: "undefined" } }],
502
+ ["gboolean", { ts: "boolean", ffi: { type: "boolean" } }],
503
+ ["gchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
504
+ ["guchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
505
+ ["gint", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
506
+ ["guint", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
507
+ ["gshort", { ts: "number", ffi: { type: "int", size: 16, unsigned: false } }],
508
+ ["gushort", { ts: "number", ffi: { type: "int", size: 16, unsigned: true } }],
509
+ ["glong", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
510
+ ["gulong", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
511
+ ["gint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
512
+ ["guint8", { ts: "number", ffi: { type: "int", size: 8, unsigned: true } }],
513
+ ["gint16", { ts: "number", ffi: { type: "int", size: 16, unsigned: false } }],
514
+ ["guint16", { ts: "number", ffi: { type: "int", size: 16, unsigned: true } }],
515
+ ["gint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
516
+ ["guint32", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
517
+ ["gint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
518
+ ["guint64", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
519
+ ["gfloat", { ts: "number", ffi: { type: "float", size: 32 } }],
520
+ ["gdouble", { ts: "number", ffi: { type: "float", size: 64 } }],
521
+ ["gsize", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
522
+ ["gssize", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
523
+ ["goffset", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
524
+ ["int", { ts: "number", ffi: { type: "int", size: 32, unsigned: false } }],
525
+ ["unsigned int", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
526
+ ["long", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
527
+ ["unsigned long", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
528
+ ["double", { ts: "number", ffi: { type: "float", size: 64 } }],
529
+ ["float", { ts: "number", ffi: { type: "float", size: 32 } }],
530
+ ["size_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
531
+ ["ssize_t", { ts: "number", ffi: { type: "int", size: 64, unsigned: false } }],
532
+ ["GType", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
533
+ ["GQuark", { ts: "number", ffi: { type: "int", size: 32, unsigned: true } }],
534
+ ]);
535
+
536
+ const mapCType = (cType: string | undefined): TypeMapping => {
537
+ if (!cType) {
538
+ return POINTER_TYPE;
539
+ }
540
+
541
+ if (cType.endsWith("*")) {
542
+ return POINTER_TYPE;
543
+ }
544
+
545
+ const mapped = C_TYPE_MAP.get(cType);
546
+ if (mapped) {
547
+ return mapped;
548
+ }
549
+
550
+ if (cType.startsWith("const ")) {
551
+ return mapCType(cType.slice(6));
552
+ }
553
+
554
+ return POINTER_TYPE;
555
+ };
556
+
461
557
  const BASIC_TYPE_MAP = new Map<string, TypeMapping>([
462
558
  ["gboolean", { ts: "boolean", ffi: { type: "boolean" } }],
463
559
  ["gchar", { ts: "number", ffi: { type: "int", size: 8, unsigned: false } }],
@@ -518,16 +614,6 @@ const BASIC_TYPE_MAP = new Map<string, TypeMapping>([
518
614
  ["FreeFunc", { ts: "number", ffi: { type: "int", size: 64, unsigned: true } }],
519
615
  ]);
520
616
 
521
- const LIBRARY_MAP: Record<string, string> = {
522
- Gtk: "libgtk-4.so.1",
523
- GObject: "libgobject-2.0.so.0",
524
- GLib: "libglib-2.0.so.0",
525
- Gio: "libgio-2.0.so.0",
526
- GdkPixbuf: "libgdk_pixbuf-2.0.so.0",
527
- Pango: "libpango-1.0.so.0",
528
- Cairo: "libcairo.so.2",
529
- };
530
-
531
617
  export interface ExternalTypeUsage {
532
618
  namespace: string;
533
619
  name: string;
@@ -763,6 +849,9 @@ export class TypeMapper {
763
849
  externalType: isExternal ? externalType : undefined,
764
850
  };
765
851
  }
852
+ if (registered.kind === "callback") {
853
+ return POINTER_TYPE;
854
+ }
766
855
  return {
767
856
  ts: qualifiedName,
768
857
  ffi: { type: "gobject", borrowed: isReturn },
@@ -771,10 +860,7 @@ export class TypeMapper {
771
860
  };
772
861
  }
773
862
  }
774
- return {
775
- ts: "unknown",
776
- ffi: { type: "gobject", borrowed: isReturn },
777
- };
863
+ return mapCType(girType.cType);
778
864
  }
779
865
 
780
866
  if (this.typeRegistry && this.currentNamespace) {
@@ -816,6 +902,9 @@ export class TypeMapper {
816
902
  kind: registered.kind,
817
903
  };
818
904
  }
905
+ if (registered.kind === "callback") {
906
+ return POINTER_TYPE;
907
+ }
819
908
  return {
820
909
  ts: qualifiedName,
821
910
  ffi: { type: "gobject", borrowed: isReturn },
@@ -825,10 +914,17 @@ export class TypeMapper {
825
914
  }
826
915
  }
827
916
 
828
- return {
829
- ts: "unknown",
830
- ffi: { type: "gobject", borrowed: isReturn },
831
- };
917
+ return mapCType(girType.cType);
918
+ }
919
+
920
+ isCallback(typeName: string): boolean {
921
+ if (this.typeRegistry) {
922
+ const resolved = this.currentNamespace
923
+ ? this.typeRegistry.resolveInNamespace(typeName, this.currentNamespace)
924
+ : this.typeRegistry.resolve(typeName);
925
+ return resolved?.kind === "callback";
926
+ }
927
+ return false;
832
928
  }
833
929
 
834
930
  /**
@@ -881,7 +977,23 @@ export class TypeMapper {
881
977
  };
882
978
  }
883
979
 
884
- if (param.type.name === "GLib.Closure" || param.type.name.endsWith("Func")) {
980
+ if (param.type.name === "Gtk.DrawingAreaDrawFunc" || param.type.name === "DrawingAreaDrawFunc") {
981
+ return {
982
+ ts: "(self: unknown, cr: unknown, width: number, height: number) => void",
983
+ ffi: {
984
+ type: "callback",
985
+ trampoline: "drawFunc",
986
+ argTypes: [
987
+ { type: "gobject", borrowed: true },
988
+ { type: "int", size: 64, unsigned: true }, // cairo_t* as raw pointer
989
+ { type: "int", size: 32, unsigned: false },
990
+ { type: "int", size: 32, unsigned: false },
991
+ ],
992
+ },
993
+ };
994
+ }
995
+
996
+ if (param.type.name === "GLib.Closure" || this.isCallback(param.type.name)) {
885
997
  return {
886
998
  ts: "(...args: unknown[]) => unknown",
887
999
  ffi: { type: "callback" },
@@ -892,13 +1004,17 @@ export class TypeMapper {
892
1004
  }
893
1005
 
894
1006
  /**
895
- * Checks if a parameter is a closure/user_data target for a GAsyncReadyCallback.
1007
+ * Checks if a parameter is a closure/user_data target or destroy notifier for a callback that uses trampolines.
1008
+ * These parameters are handled automatically by the trampoline mechanism.
896
1009
  * @param paramIndex - The index of the parameter to check
897
1010
  * @param allParams - All parameters in the method
898
- * @returns True if this parameter is user_data for a GAsyncReadyCallback
1011
+ * @returns True if this parameter is user_data or destroy for a trampoline callback
899
1012
  */
900
1013
  isClosureTarget(paramIndex: number, allParams: GirParameter[]): boolean {
901
- return allParams.some((p) => p.type.name === "Gio.AsyncReadyCallback" && p.closure === paramIndex);
1014
+ const trampolineCallbacks = ["Gio.AsyncReadyCallback", "Gtk.DrawingAreaDrawFunc", "DrawingAreaDrawFunc"];
1015
+ return allParams.some(
1016
+ (p) => trampolineCallbacks.includes(p.type.name) && (p.closure === paramIndex || p.destroy === paramIndex),
1017
+ );
902
1018
  }
903
1019
 
904
1020
  /**
@@ -909,13 +1025,4 @@ export class TypeMapper {
909
1025
  isNullable(param: GirParameter): boolean {
910
1026
  return param.nullable === true || param.optional === true;
911
1027
  }
912
-
913
- /**
914
- * Gets the shared library name for a namespace.
915
- * @param namespace - The namespace name
916
- * @returns The shared library file name
917
- */
918
- getLibraryName(namespace: string): string {
919
- return LIBRARY_MAP[namespace] ?? `lib${namespace.toLowerCase()}.so`;
920
- }
921
1028
  }