TypeScript
GObject has a few additional concepts about class methods and properties that cannot be expressed with TypeScript alone. For these we have a few special type only fields on classes.
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 the use case.
Generating types
Generating types can be done via the Gnim CLI.
gnim types --helpType annotations
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 signal 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
// 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)
}
@signal
mySignal(arg: number): void {}
@signal({ flags: GObject.SignalFlags.DETAILED })
myDetailedSignal(arg: number): void {}
@property
myProp: number = 0
}Methods such as connect(), emit(), notify() and functions such as bind() and connectSignal() 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)
})
connectSignal(instance, "my-signal", (arg) => {
console.log(arg)
})
const myProp = ref(instance, "my-prop")Due to how TypeScript's this type works, you need to annotate this or use a type cast to correctly infer types within the class.
class MyClass {
myFn(this: MyClass) {
this.notify("my-prop")
}
myFn() {
const self = this as MyClass
self.notify("my-prop")
}
}Annotating GI imports
You can define non-versioned GI imports.
declare module "gi://Gtk" {
import Gtk from "gi://Gtk?version=4.0"
export default Gtk
}You can also use the --alias flag when generating types to do this automatically, which will generate an alias for each namespace.
TIP
To target specific versions you can ignore other versions.
gnim types --alias -i Gtk-3.0 -i Gdk-3.0Escape hatches
To avoid using @ts-expect-error or as any assertions when the signal name is a string, you can use non-typed versions of signal-related functions:
GObject.signal_connect(object, "signal-name", (emitter, ...args) => {
console.log(emitter, ...args)
})
GObject.signal_emit_by_name(object, "signal-name", arg1, arg2)