girgen 0.3.13

GIR Parser and type generator
Documentation

GirGen

GIR Parser and type generator.

Install

There are pre built binaries for linux-x64 and linux-arm64 published on NPM.

npm install girgen -D
./node_modules/.bin/girgen --help

Otherwise you can install from crates.io using cargo.

cargo install girgen --root .
./bin/girgen --help

TypeScript

Generate a standalone package that contains every namespace found in the given directories.

girgen typescript --help

[!TIP]

You can use the Gnome flatpak SDK to acquire GIR files on systems that don't have them in one place, e.g NixOS or when you are targeting Flatpak.

flatpak run --command=cp --filesystem=home org.gnome.Sdk -r /usr/share/gir-1.0 gir-1.0
girgen -d gir-1.0 typescript

By default it will generate the package to .types/gi which you can then source in tsconfig.json.

{
  "compilerOptions": {
    "lib": ["es2024"], // don't forget to specify a `lib` to avoid sourcing TypeScript's `dom` lib
    "skipLibCheck": true, // it's recommended to turn this on
    "typeRoots": [".types"]
  }
}

[!TIP]

Don't forget to gitignore generated files.

echo ".types/gi/" > .gitignore

Note that when using --alias flag to generate non version imports such as gi://Gtk make sure to ignore the version you don't need so that it does not end up as a union of the two versions.

girgen typescript -i Gtk-3.0 --alias

TypeScript Annotations

GObject has a few additional concepts about class methods and properties that cannot be expressed with TypeScript alone. For these girgen generates type only fields on classes and interfaces.

We have annotations for:

  • signals
  • readable properties
  • writable properties
  • construct-only properties

When implementing a GObject subclass you might want to annotate a subset of these or all of these depending on usecase.

Every class that inherits from GObject is going to include a namespace containing type declarations where each member is written in kebab-case:

namespace MyClass {
  export interface SignalSignatures extends GObject.Object.SignalSignatures {
    // simple signal
    "my-signal"(arg: number): void
    // detailed signals are annotated with the `::{}` suffix
    "my-detailed-signal::{}"(arg: number): void
  }

  // ReadableProperties is also used for notify signal annotations
  export interface ReadableProperties
    extends GObject.Object.ReadableProperties {
    // property which has a public getter
    "my-prop": number
  }

  export interface WritableProperties
    extends GObject.Object.WritableProperties {
    // property which has a public setter
    "my-prop": number
  }

  export interface ConstructOnlyProperties
    extends GObject.Object.ConstructOnlyProperties {
    // property which can only be set at construction
    "my-ctor-prop": number
  }
}

And the Class will refer to these using special $ prefixed fields:

[!IMPORTANT]

These fields don't exist at runtime, they are used by other APIs to introspect GObjects.

class MyClass extends GObject.Object {
  declare readonly $signals: MyClass.SignalSignatures
  declare readonly $readableProperties: MyClass.ReadableProperties
  declare readonly $writableProperties: MyClass.WritableProperties
  declare readonly $constructOnlyProperties: MyClass.ConstructOnlyProperties

  static {
    GObject.registerClass(
      {
        Signals: {
          "my-signal": {
            param_types: [GObject.TYPE_DOUBLE],
          },
          "my-detailed-signal": {
            param_types: [GObject.TYPE_DOUBLE],
            flags: GObject.SignalFlags.DETAILED,
          },
        },
        Properties: {
          "my-prop": GObject.ParamSpec.double(
            "my-prop",
            null,
            null,
            GObject.ParamFlags.READWRITE,
            -GObject.Double.MAX_VALUE,
            GObject.Double.MAX_VALUE,
          ),
          "my-ctor-prop": GObject.ParamSpec.double(
            "my-ctor-prop",
            null,
            null,
            GObject.ParamFlags.CONSTRUCT_ONLY,
            -GObject.Double.MAX_VALUE,
            GObject.Double.MAX_VALUE,
          ),
        },
      },
      MyClass,
    )
  }

  // GObject.ConstructorProps can be used to infer props from the annotations
  constructor(props: Partial<GObject.ConstructorProps<MyClass>>) {
    super(props)

    // note that properties will be annotated as camelCase
    console.log(props.myProp, props.myCtorProp)
  }
}

Methods such as connect(), emit(), notify() will infer from these annotations.

const instance = new MyClass()

instance.connect("my-signal", (source, arg) => {
  console.log(arg)
})

instance.connect("my-detailed-signal::detail", (source, arg) => {
  console.log(arg)
})

instance.connect("notify::my-prop", (_, pspec) => {
  console.log(pspec.name)
})

Due to how TypeScript this type works, you need to annotate this or use a typecast to correctly infer types within the class.

class MyClass {
  myFn(this: MyClass) {
    this.emit("my-signal", 0)
  }

  myFn() {
    const self = this as MyClass
    self.emit("my-signal", 0)
  }
}

Module Augmentation

If you are using Gio._promisify you can augment namespaces.

import Gio from "gi://Gio?version=2.0"
import GLib from "gi://GLib?version=2.0"

Gio._promisify(
  Gio.InputStream.prototype,
  "read_bytes_async",
  "read_bytes_finish",
)

declare module "gi://Gio?version=2.0" {
  namespace GI {
    namespace Gio {
      interface InputStream {
        read_bytes_async(
          count: number,
          io_priority: number,
          cancellable: Gio.Cancellable | null,
        ): GLib.Bytes
      }
    }
  }
}

declare const stream: Gio.InputStream
const bytes = await stream.read_bytes_async(4096, GLib.PRIORITY_DEFAULT, null)