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.Initableopen in new window or Gio.AsyncInitableopen in new window interfaces.

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.

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.DBusObjectManagerClientopen in new window 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.

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().

// Listing properties
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:

const exampleBox = new Gtk.Box();
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::notifyopen in new window.

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

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()open in new window or lookup a signal ID by name with GObject.signal_lookup()open in new window.

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

// Looking up a signal by canonical name
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:

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()open in new window will return the signal information as a GObject.SignalQueryopen in new window object.

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()open in new window 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.
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()open in new window followed by GObject.signal_handlers_unblock_matched()open in new window to resume normal behavior. This can prevent reentrancy issues when a signal handler may cause the same signal to be re-emitted.

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()open in new window. This prevents any remaining signal handlers from being invoked for the current emission.

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

Last Updated: 11/27/2023, 3:41:17 PM
Contributors: Andy Holmes