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.
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.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.
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()
.
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:
// 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
GObject.Object.list_properties()
GObject.Object.find_property()
GObject.ParamSpec
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.
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()
.
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:
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.
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.
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.
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
GObject.signal_handler_block()
GObject.signal_handler_unblock()
GObject.signal_handlers_block_matched()
GObject.signal_handlers_unblock_matched()
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.
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