Custom interfaces can be defined in GJS, although this is rarely done
Interfaces are defined in GJS by inheriting from GObject.Interface and providing the class definition property Requires. The general rules for interfaces defined in GJS are:
-
Methods
Methods defined on an interface must be implemented, if the method throws the special error
GObject.NotImplementedError(). Methods that do not throw this error are optional to implement.Note that unlike GObject Interfaces defined by a C library, methods are overridden directly rather than by virtual function.
-
Properties
Properties defined on an interface must be implemented, using
GObject.ParamSpec.override()in thePropertiesclass definition property. -
Signals
Signals defined on an interface do not need to be implemented. Typically interface definitions will provide emitter methods, such as
Gio.ListModel.items_changed(), otherwise they can be emitted by callingGObject.Object.prototype.emit()on an instance. -
Requirements
Interfaces can define prerequisites for implementation, using the
Requiresclass definition property. Usually this is simplyGObject.Object, but interfaces may require an implementation to implement more than one interface.
Below is a simple example of defining an interface that only requires GObject.Object:
const GObject = imports.gi.GObject;
const SimpleInterface = GObject.registerClass({
GTypeName: 'SimpleInterface',
Requires: [GObject.Object],
Properties: {
'simple-property': GObject.ParamSpec.boolean(
'simple-property',
'Simple property',
'A property that must be implemented',
GObject.ParamFlags.READABLE,
true
),
},
Signals: {
'simple-signal': {},
},
}, class SimpleInterface extends GObject.Interface {
/**
* By convention interfaces provide methods for emitting their signals, but
* you can still call `emit()` on the instance of an implementation.
*/
emitSimple() {
this.emit('simple-signal');
}
/**
* Interfaces can define methods that MAY be implemented, by providing a
* default implementation.
*/
optionalMethod() {
return true;
}
/**
* Interfaces can define methods that MUST be implemented, by throwing the
* special error `GObject.NotImplementedError()`.
*/
requiredMethod() {
throw new GObject.NotImplementedError();
}
});Note that unlike with interfaces defined by C libraries, we override methods like requiredMethod() directly, not vfunc_requiredMethod(). Below is a minimal implementation of SimpleInterface:
const GObject = imports.gi.GObject;
const SimpleImplementation = GObject.registerClass({
Implements: [SimpleInterface],
Properties: {
'simple-property': GObject.ParamSpec.override('simple-property',
SimpleInterface),
},
}, class SimpleImplementation extends GObject.Object {
get simple_property() {
return true;
}
requiredMethod() {
log('requiredMethod() implemented');
}
});Instances of the implementation can then be constructed like any class. instanceof can be used to confirm it is both a GObject and implements a given interface:
let simpleInstance = new SimpleImplementation();
if (simpleInstance instanceof GObject.Object)
log('An instance of a GObject');
if (simpleInstance instanceof SimpleInterface)
log('An instance implementing SimpleInterface');More complex interfaces can also be defined that depend on other interfaces. ComplexInterface depends on Gio.ListModel and SimpleInterface, while adding a property and a method.
const {Gio, GObject} = imports.gi;
const ComplexInterface = GObject.registerClass({
GTypeName: 'ComplexInterface',
Requires: [Gio.ListModel, SimpleInterface],
Properties: {
'complex-property': GObject.ParamSpec.boolean(
'complex-property',
'Complex property',
'A property that must be implemented',
GObject.ParamFlags.READABLE,
true
),
},
}, class ComplexInterface extends GObject.Interface {
complexMethod() {
throw new GObject.NotImplementedError();
}
});An implementation of this interface must then meet the requirements of Gio.ListModel and SimpleInterface, which both require GObject.Object. The following implementation of ComplexInterface will meet the requirements of:
GObject.Objectby inheriting fromGio.ListStore, aGObject.Object-based classGio.ListModelby inheriting fromGio.ListStore, which implementsGio.ListModelSimpleInterfaceby implementing its methods and properties
const {Gio, GObject} = imports.gi;
const ComplexImplementation = GObject.registerClass({
Implements: [Gio.ListModel, SimpleInterface, ComplexInterface],
Properties: {
'complex-property': GObject.ParamSpec.override('complex-property',
ComplexInterface),
'simple-property': GObject.ParamSpec.override('simple-property',
SimpleInterface),
},
}, class ComplexImplementation extends Gio.ListStore {
get complex_property() {
return false;
}
get simple_property() {
return true;
}
complexMethod() {
log('complexMethod() implemented');
}
requiredMethod() {
log('requiredMethod() implemented');
}
});By using instanceof, we can confirm the inheritance and interface support of the implementation:
let complexInstance = new ComplexImplementation();
if (complexInstance instanceof GObject.Object &&
complexInstance instanceof Gio.ListStore)
log('An instance with chained inheritance');
if (complexInstance instanceof Gio.ListModel &&
complexInstance instanceof SimpleInterface &&
complexInstance instanceof ComplexInterface)
log('An instance implementing three interfaces');