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