girgen 0.3.9

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)
    }
}