Skip to content

Instantly share code, notes, and snippets.

@andyholmes
Created May 25, 2023 19:57
Show Gist options
  • Save andyholmes/a2f41c7dc3330dddcde1ef610b2126f6 to your computer and use it in GitHub Desktop.
Save andyholmes/a2f41c7dc3330dddcde1ef610b2126f6 to your computer and use it in GitHub Desktop.

Defining Interfaces

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 the Properties class 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 calling GObject.Object.prototype.emit() on an instance.

  • Requirements

    Interfaces can define prerequisites for implementation, using the Requires class definition property. Usually this is simply GObject.Object, but interfaces may require an implementation to implement more than one interface.

A Simple 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');

A Complex Interface

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.Object by inheriting from Gio.ListStore, a GObject.Object-based class
  • Gio.ListModel by inheriting from Gio.ListStore, which implements Gio.ListModel
  • SimpleInterface by 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');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment