Skip to content

GObject

GObject is the base library Gtk is built upon. GObject implements object oriented concepts in C which GJS will call into using GObject Introspection.

GObject Construction

The most common way to create a GObject instance is using the new operator. When constructing a GObject this way, you can pass a dictionary of properties:

ts
const labelWidget = new Gtk.Label({
  label: "<b>Text</b>",
  useMarkup: true,
})

Many classes also have static constructor methods you can use directly:

ts
const labelWidget = Gtk.Label.new("Text")

TIP

In rare cases, like the Gio.File interface, objects cannot be constructed with the new operator and a constructor method must always be used.

Signals

GObjects support a signaling system, similar to events and EventListeners in the JavaScript Web API. Here we will cover the basics of connecting and disconnecting signals, as well as using callbacks.

Connecting Signals

Signals are connected by calling GObject.Object.prototype.connect(), which returns a handler ID. Signals are disconnected by passing that ID to GObject.Object.prototype.disconnect():

ts
const button = new Gtk.Button({ label: "Lorem Ipsum" })

// Connecting a signal
const handlerId = button.connect("clicked", () => {
  console.log("Button clicked!")
})

// Disconnecting a signal
button.disconnect(handlerId)

Callback Arguments

Signals often have multiple callback arguments, but the first is always the emitting object:

ts
const selectLabel = Gtk.Label.new("")

selectLabel.connect("move-cursor", (label, step, count, extendSelection) => {
  if (label === selectLabel) {
    console.log("selectLabel emitted the signal!")
  }

  if (step === Gtk.MovementStep.WORDS) {
    console.log(`The cursor was moved ${count} word(s)`)
  }

  if (extendSelection) {
    console.log("The selection was extended")
  }
})

Callback Return Values

Some signals expect a return value, usually a boolean. The type and behavior of the return value will be described in the documentation for the signal.

ts
const linkLabel = new Gtk.Label({
  label: '<a href="https://www.gnome.org">GNOME</a>',
  use_markup: true,
})

linkLabel.connect("activate-link", (label, uri) => {
  if (uri.startsWith("file://")) {
    console.log(`Ignoring ${uri}`)
    return true
  }

  return false
})

WARNING

A callback with no return value will implicitly return undefined, while an async function will implicitly return a Promise.

Using an async function as a signal handler will return an implicit Promise, which will be coerced to a truthy value. If necessary, use a traditional Promise chain and return the expected value type explicitly.

ts
linkLabel.connect("activate-link", (label, uri) => {
  // Do something asynchronous with the signal arguments
  Promise.resolve(uri).catch(console.error)

  return true
})

Properties

GObject supports a property system that is slightly more powerful than native JavaScript properties.

Accessing Properties

GObject properties may be retrieved and set using native property style access or GObject get and set methods.

ts
const invisibleLabel = new Gtk.Label({
  visible: false,
})

let visible

visible = invisibleLabel.visible
visible = invisibleLabel.get_visible()

invisibleLabel.visible = false
invisibleLabel.set_visible(false)

GObject property names have a canonical form that is kebab-cased, however they are accessed differently depending on the situation:

ts
const markupLabel = new Gtk.Label({
  label: "<i>Italics</i>",
  useMarkup: true,
})

let useMarkup

// If using native accessors, you can use `underscore_case` or `camelCase`
useMarkup = markupLabel.use_markup
useMarkup = markupLabel.useMarkup

// Anywhere the property name is a string, you must use `kebab-case`
markupLabel["use-markup"] = true
markupLabel.connect("notify::use-markup", () => {})

// Getter and setter functions are always case sensitive
useMarkup = markupLabel.get_use_markup()
markupLabel.set_use_markup(true)

IMPORTANT

Gnim, by convention only uses camelCase where appropriate so type annotations might be intentionally missing. For example while markupLabel["use-markup"] works at runtime, types are not generated for it.

Property Change Notification

Most GObject properties will emit GObject.Object::notify signal when the value is changed. You can connect to this signal in the form of notify::property-name to invoke a callback when it changes:

ts
const changingLabel = Gtk.Label.new("Original Label")

const labelId = changingLabel.connect("notify::label", (object, _pspec) => {
  console.log(`New label is "${object.label}"`)
})

Subclassing GObject

Implementing a GObject in a Gnim app is usually not necessary since you will mostly be using function components. However, Gnim provides an abstraction over GJS's API using decorators.

ts
import GObject from "gi://GObject?version=2.0"
import { register, property, signal } from "gnim/gobject"

@register
class MyObj extends GObject.Object {
  @property myProp: string = ""

  @signal
  mySignal(a: string, b: number): void {
    // default handler
  }
}