29 releases

Uses new Rust 2024

new 0.3.17 May 15, 2026
0.3.16 May 8, 2026
0.3.14 Apr 14, 2026
0.3.7 Mar 29, 2026
0.1.0 Feb 8, 2026

#298 in Unix APIs

MIT license

390KB
6.5K SLoC

Rust 4.5K SLoC // 0.0% comments TypeScript 2K SLoC // 0.6% comments Jinja2 366 SLoC

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)

Dependencies

~7–11MB
~196K SLoC