Preferences

Our preferences dialog will be written in Gtkopen in new window, which gives us a lot of options for how we present settings to the user. You may consider looking through the GNOME Human Interface Guidelines for ideas or guidance.

GSettings

GSettingsopen in new window provides a simple, extremely fast API for storing application settings, that can also be used by GNOME Shell extensions.

Creating the schema

Schema files describe the types and default values of a particular group of settings, using the same type format as GVariantopen in new window. The first thing to do is create a subdirectory for your settings schema and open the schema file in your editor:

$ mkdir schemas/
$ gedit schemas/org.gnome.shell.extensions.example.gschema.xml
1
2

Then using your edit, create a schema describing the settings for your extension:

<?xml version="1.0" encoding="UTF-8"?>
<schemalist>
  <schema id="org.gnome.shell.extensions.example" path="/org/gnome/shell/extensions/example/">
    <!-- See also: https://docs.gtk.org/glib/gvariant-format-strings.html -->
    <key name="show-indicator" type="b">
      <default>true</default>
    </key>
  </schema>
</schemalist>
1
2
3
4
5
6
7
8
9

In the case of GSchema IDs, it is convention to use the above id and path form so that all GSettings for extensions can be found in a common place.

Compiling the schema

Once you are done defining you schema, you must compile it before it can be used.

If you use gnome-extension pack to package your extension, the schemas in the schemas directory will automatically be compiled.

To manually compile your schema:

$ glib-compile-schemas schemas/
$ ls schemas
example.gschema.xml  gschemas.compiled
1
2
3

Integrating GSettings

Now that our GSettings schema is compiled and ready to be used, we'll integrate it into our extension:

const Gio = imports.gi.Gio;
const St = imports.gi.St;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;


class Extension {
    constructor() {
        this._indicator = null;
    }
    
    enable() {
        log(`enabling ${Me.metadata.name}`);

        this.settings = ExtensionUtils.getSettings(
            'org.gnome.shell.extensions.example');

        let indicatorName = `${Me.metadata.name} Indicator`;
        
        // Create a panel button
        this._indicator = new PanelMenu.Button(0.0, indicatorName, false);
        
        // Add an icon
        let icon = new St.Icon({
            gicon: new Gio.ThemedIcon({name: 'face-laugh-symbolic'}),
            style_class: 'system-status-icon'
        });
        this._indicator.add_child(icon);

        // Bind our indicator visibility to the GSettings value
        //
        // NOTE: Binding properties only works with GProperties (properties
        // registered on a GObject class), not native JavaScript properties
        this.settings.bind(
            'show-indicator',
            this._indicator,
            'visible',
            Gio.SettingsBindFlags.DEFAULT
        );

        Main.panel.addToStatusArea(indicatorName, this._indicator);
    }
    
    disable() {
        log(`disabling ${Me.metadata.name}`);

        this._indicator.destroy();
        this._indicator = null;
    }
}


function init() {
    log(`initializing ${Me.metadata.name}`);
    
    return new Extension();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

Now save extension.js and restart GNOME Shell to load the changes to your extension.

Preferences Window

Now that we have GSettings for our extension, we will give the use some control by creating a simple preference dialog. Start by creating the prefs.js file and opening it in your text editor:

$ gedit prefs.js
1

Then we'll create a simple preferences row with a switch to control our settings:

'use strict';

const { Adw, Gio, Gtk } = imports.gi;

const ExtensionUtils = imports.misc.extensionUtils;
const Me = ExtensionUtils.getCurrentExtension();


function init() {
}

function fillPreferencesWindow(window) {
    // Use the same GSettings schema as in `extension.js`
    const settings = ExtensionUtils.getSettings(
        'org.gnome.shell.extensions.example');
    
    // Create a preferences page and group
    const page = new Adw.PreferencesPage();
    const group = new Adw.PreferencesGroup();
    page.add(group);

    // Create a new preferences row
    const row = new Adw.ActionRow({ title: 'Show Extension Indicator' });
    group.add(row);

    // Create the switch and bind its value to the `show-indicator` key
    const toggle = new Gtk.Switch({
        active: settings.get_boolean ('show-indicator'),
        valign: Gtk.Align.CENTER,
    });
    settings.bind(
        'show-indicator',
        toggle,
        'active',
        Gio.SettingsBindFlags.DEFAULT
    );

    // Add the switch to the row
    row.add_suffix(toggle);
    row.activatable_widget = toggle;

    // Add our page to the window
    window.add(page);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

To test the new preferences dialog, you can launch it directly from the command line:

$ gnome-extensions prefs example@shell.gnome.org
1

The extension should be kept up to date with any changes that happen, because of the binding in extension.js watching for changes.

Debugging

Because preferences are not run within gnome-shell but in a separate process, the logs will appear in the gjs process:

journalctl -f -o cat /usr/bin/gjs
1
Last Updated: 3/16/2022, 1:40:16 AM
Contributors: Andy Holmes, Javad Rahmatzadeh, Lorenzo Paderi, Romain