Skip to content

Advanced GObject

TIP

See GObject Basics for an introduction to GObject in GJS.

This guide covers advanced GObject features that are less commonly used.

GObject Construction

Failable Initialization

NOTE

It is not currently possible to implement Gio.AsyncInitable in GJS due to thread-safety issues.

In JavaScript object construction is usually non-blocking and non-failable. GObject classes can support failable and asynchronous construction by implementing the Gio.Initable or Gio.AsyncInitable interfaces.

js
try {
    const proc = Gio.Subprocess.new(['unknown-command'],
        Gio.SubprocessFlags.NONE);
} catch (e) {
    // GLib.SpawnError: Failed to execute child process “unknown-command” (No such file or directory)
    console.error(e);
}

Most classes will provide constructor methods that call Gio.Initable.init() or Gio.AsyncInitable.init_async(), but the interface methods may be called directly if necessary. Below is an example of calling Gio.Initable.init() to pass in a Gio.Cancellable.

js
try {
    const proc = new Gio.Subprocess({
        argv: ['ls'],
        flags: Gio.SubprocessFlags.NONE,
    });

    const cancellable = Gio.Cancellable.new();
    cancellable.cancel();
    proc.init(cancellable);
} catch (e) {
    // Gio.IOErrorEnum: Operation was cancelled
    console.error(e);
}

Gio.DBusObjectManagerClient is an example of a class that implements Gio.AsyncInitable and can be constructed using asynchronous JavaScript. If construction fails, the Promise will reject with the error.

js
try {
    const manager = await Gio.DBusObjectManagerClient.new_for_bus(
        Gio.BusType.SYSTEM,
        Gio.DBusObjectManagerClientFlags,
        'org.freedesktop.login1',
        '/org/freedesktop/login1',
        null,
        null);
} catch (e) {
    if (e instanceof Gio.DBusError)
        Gio.DBusError.strip_remote_error(e);

    // Gio.DBusError: Sender is not authorized to send message
    console.error(e);
}

Properties

Introspection

You can list the properties for a class with GObject.Object.list_properties() or lookup a property by name with GObject.Object.find_property().

js
for (const pspec of Gtk.Widget.list_properties())
    console.log(`Found property: ${pspec.name}`);

// Looking up a property by canonical name
const wrapParamSpec = Gtk.Label.find_property('wrap');

if (wrapParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${wrapParamSpec.name}`);

To lookup a property for an instance, use the constructor property:

js
// Looking up a property for an instance
const exampleBox = new Gtk.Box();

// Looking up a property for an instance
const spacingParamSpec = exampleBox.constructor.find_property('spacing');

if (spacingParamSpec instanceof GObject.ParamSpec)
    console.log(`Found property: ${spacingParamSpec.name}`);

API Documentation

Notification Freezing

When a property is expected to be set multiple times, it may result in unwanted emissions of GObject.Object::notify.

GObject.Object.freeze_notify() allows you to freeze change notifications until GObject.Object.thaw_notify() is called. Duplicate notifications are squashed so that at most one signal is emitted for each property.

js
const exampleLabel = new Gtk.Label({
    label: 'Initial Value',
});

exampleLabel.connect('notify::label', (object, _pspec) => {
    console.log(`New label is "${object.label}"`);
});

// Freeze notification during multiple changes
exampleLabel.freeze_notify();

exampleLabel.label = 'Example 1';
exampleLabel.label = 'Example 2';
exampleLabel.label = 'Example 3';

// Expected output: New label is "Example 3"
exampleLabel.thaw_notify();

API Documentation

Signals

Introspection

You can list the signal IDs for a class with GObject.signal_list_ids() or lookup a signal ID by name with GObject.signal_lookup().

js
for (const signalId of GObject.signal_list_ids(Gtk.Widget))
    console.log(`Found signal: ${signalId}`);

const notifyId = GObject.signal_lookup('notify', GObject.Object);

if (notifyId !== 0)
    console.log(`Found signal: ${notifyId}`);

To lookup a signal for an instance, use the constructor property:

js
const exampleBox = new Gtk.Box();
const destroyId = GObject.signal_lookup('destroy', exampleBox.constructor);

if (destroyId !== 0)
    console.log(`Found signal: ${destroyId}`);

Passing the signal ID to GObject.signal_query() will return the signal information as a GObject.SignalQuery object.

js
const destroyQuery = GObject.signal_query(destroyId);

if (destroyQuery !== null)
    console.log(`Found signal: ${destroyQuery.itype.name}::${destroyQuery.signal_name}`);

API Documentation

Signal Matches

WARNING

Function.prototype.bind() creates a new Function instance and a handler will only match to the connected instance.

GJS has overrides for a number of signal utilities that take an object of parameter for matching signals. There are three properties, but func must be specified for a successful match.

  • signalId (String) — A signal name. Note that this is not the signal ID as it is in the original GObject functions.
  • detail (String) — A signal detail, such as prop in notify::prop.
  • func (Function) — A signal callback function.
js
function notifyCallback(obj, pspec) {
    console.log(pspec.name);
}

const objectInstance = new GObject.Object();
const handlerId = objectInstance.connect('notify::property-name',
    notifyCallback);

const result = GObject.signal_handler_find(objectInstance, {
    detail: 'property-name',
    func: notifyCallback,
});

console.assert(result === handlerId);

Blocking Signals

A signal can blocked from being emitted for a period of time by calling GObject.signal_handlers_block_matched() followed by GObject.signal_handlers_unblock_matched() to resume normal behavior. This can prevent reentrancy issues when a signal handler may cause the same signal to be re-emitted.

js
function notifyLabelCallback(object, _pspec) {
    console.log('notify::label emitted');

    const blocked = GObject.signal_handlers_block_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Blocked ${blocked} handlers`);

    // The handler will not be run recursively, since it is currently blocked
    object.label = object.label.toUpperCase();

    GObject.signal_handlers_unblock_matched(object, {
        func: notifyLabelCallback,
        signalId: 'notify',
        detail: 'label',
    });
    console.log(`Unblocked ${blocked} handlers`);
}

const upperCaseLabel = new Gtk.Label();
const notifyLabelId = upperCaseLabel.connect('notify::label',
    notifyLabelCallback);
upperCaseLabel.label = 'example';

API Documentation

Stopping Signals

A signal emission can be stopped from inside a signal handler by calling GObject.signal_stop_emission_by_name(). This prevents any remaining signal handlers from being invoked for the current emission.

js
const linkLabel = new Gtk.Label({label: 'Example'});
linkLabel.connect('activate-current-link', (label, _uri) => {
    console.log('First handler that will be run');

    // No other handlers will run after the emission is stopped
    GObject.signal_stop_emission_by_name(label, 'activate-current-link');
});
linkLabel.connect('activate-current-link', (_label, _uri) => {
    console.log('Second handler that will not be run');
});
linkLabel.emit('activate-current-link');

API Documentation

MIT Licensed | GJS, A GNOME Project