Continue GNOME Shell extension article with settings
This commit is contained in:
parent
8eeb6f6351
commit
262ebd7616
@ -4,10 +4,11 @@ title: "Writing a GNOME Shell extension"
|
|||||||
---
|
---
|
||||||
|
|
||||||
I could not find a good tutorial on how to write a GNOME Shell
|
I could not find a good tutorial on how to write a GNOME Shell
|
||||||
extension. There is a so called step by step instruction list on how
|
extension. There is a so called step by step
|
||||||
to do it, but it has its flaws, including grammar and clearance. As I
|
[instruction list](https://wiki.gnome.org/Projects/GnomeShell/Extensions/StepByStepTutorial)
|
||||||
wanted to create an extension for my SWE GLib library to show the
|
on how to do it, but it has its flaws, including grammar and clearance.
|
||||||
current position of some planets, I dug into existing (and working)
|
As I wanted to create an extension for my SWE GLib library to display
|
||||||
|
the current position of some planets, I dug into existing (and working)
|
||||||
extensions’ source code and made up something. Comments welcome!
|
extensions’ source code and made up something. Comments welcome!
|
||||||
|
|
||||||
---
|
---
|
||||||
@ -31,25 +32,76 @@ either use a so called extension controller, or plain old JavaScript
|
|||||||
functions `enable()` and `disable()`; I will go on with the former
|
functions `enable()` and `disable()`; I will go on with the former
|
||||||
method for reasons discussed later.
|
method for reasons discussed later.
|
||||||
|
|
||||||
|
If you are fine with the `enable()`/`disable()` function version, you
|
||||||
|
can ease your job with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
gnome-shell-extension-tool --create-extension
|
||||||
|
```
|
||||||
|
|
||||||
|
This will ask you a few parameters and create the necessary files for
|
||||||
|
you. On what these parameters should look like, please come with me to
|
||||||
|
the next section.
|
||||||
|
|
||||||
|
## Placement and naming
|
||||||
|
|
||||||
|
Extensions reside under `$HOME/.local/share/gnome-shell/extensions`,
|
||||||
|
where each of them have its own directory. The directory name has to be
|
||||||
|
unique, of course; to achieve this, they are usually the same as the
|
||||||
|
UUID of the extension.
|
||||||
|
|
||||||
|
The UUID is a string of alphanumeric characters, with some extras added.
|
||||||
|
Generally, it should match this regular expression:
|
||||||
|
`^[-a-zA-Z0-9@._]+$`. The convention is to use the form
|
||||||
|
`extension-name@author-id`, e.g. `Planets@gergely.polonkai.eu`. Please
|
||||||
|
see
|
||||||
|
[this link](https://wiki.gnome.org/Projects/GnomeShell/Extensions/UUIDGuidelines)
|
||||||
|
for some more information about this.
|
||||||
|
|
||||||
## Anatomy of an extension
|
## Anatomy of an extension
|
||||||
|
|
||||||
The only thing you actually need is an `init()` function:
|
Extensions consist of two main parts, `metadata.json` and
|
||||||
|
`extension.js`.
|
||||||
|
|
||||||
|
The `metadata.json` file contains compatibility information and, well,
|
||||||
|
some meta data:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"shell-version": ["3.18"],
|
||||||
|
"uuid": "planets@gergely.polonkai.eu",
|
||||||
|
"name": "Planets",
|
||||||
|
"description": "Display current planet positions"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here, `shell-version` must contain all versions of GNOME Shell that is
|
||||||
|
known to load and display your extension correctly. You can insert minor
|
||||||
|
versions here, like I did, or exact version numbers, like `3.18.1`.
|
||||||
|
|
||||||
|
In the `extension.js` file, which contains the actual extension code,
|
||||||
|
the only thing you actually need is an `init()` function:
|
||||||
|
|
||||||
|
```javascript
|
||||||
function init(extensionMeta) {
|
function init(extensionMeta) {
|
||||||
// Do whatever it takes to initialize your extension,
|
// Do whatever it takes to initialize your extension, like
|
||||||
// like initializing the translations.
|
// initializing the translations. However, never do any widget
|
||||||
|
// magic here yet.
|
||||||
|
|
||||||
// Then return the controller object
|
// Then return the controller object
|
||||||
return new ExtensionController(extensionMeta);
|
return new ExtensionController(extensionMeta);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Extension controller
|
## Extension controller
|
||||||
|
|
||||||
So far so good, but what is this extension controller thing? It is an
|
So far so good, but what is this extension controller thing? It is an
|
||||||
object which is capable of managing your GNOME Shell extension. Whenever
|
object which is capable of managing your GNOME Shell extension. Whenever
|
||||||
the extension is loaded, its `enable()` method is called; when the
|
the extension is loaded, its `enable()` method is called; when the
|
||||||
extension is unloaded, the `disable()` method gets called.
|
extension is unloaded, you guessed it, the `disable()` method gets
|
||||||
|
called.
|
||||||
|
|
||||||
|
```javascript
|
||||||
function ExtensionController(extensionMeta) {
|
function ExtensionController(extensionMeta) {
|
||||||
return {
|
return {
|
||||||
extensionMeta: extensionMeta,
|
extensionMeta: extensionMeta,
|
||||||
@ -71,6 +123,7 @@ extension is unloaded, the `disable()` method gets called.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
This controller will create a new instance of the `PlanetsExtension`
|
This controller will create a new instance of the `PlanetsExtension`
|
||||||
class and add it to the panel’s right side when loaded. Upon
|
class and add it to the panel’s right side when loaded. Upon
|
||||||
@ -84,7 +137,7 @@ extension is set to `null`.
|
|||||||
The extension is a bit more tricky, as, for convenience reasons, it
|
The extension is a bit more tricky, as, for convenience reasons, it
|
||||||
should extend an existing panel widget type.
|
should extend an existing panel widget type.
|
||||||
|
|
||||||
```
|
```javascript
|
||||||
function PlanetsExtension(extensionMeta) {
|
function PlanetsExtension(extensionMeta) {
|
||||||
this._init(extensionMeta);
|
this._init(extensionMeta);
|
||||||
}
|
}
|
||||||
@ -119,26 +172,155 @@ The only parameter passed to the parent’s `_init()` function is
|
|||||||
menu arrow. (_Note: I cannot find any documentation on this, but it
|
menu arrow. (_Note: I cannot find any documentation on this, but it
|
||||||
seems that with the value `0.0`, a menu arrow is not added._)
|
seems that with the value `0.0`, a menu arrow is not added._)
|
||||||
|
|
||||||
|
The extension class in its current form is capable of creating the
|
||||||
|
actual panel button displaying the text “Loading” in its center.
|
||||||
|
|
||||||
## Loading up the extension
|
## Loading up the extension
|
||||||
|
|
||||||
Now with all the necessary import lines added:
|
Now with all the necessary import lines added:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// The PanelMenu module that contains Button
|
||||||
const PanelMenu = imports.ui.panelMenu;
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
// The St class that contains lots of UI functions
|
||||||
const St = imports.gi.St;
|
const St = imports.gi.St;
|
||||||
|
// Clutter, which is used for displaying everything
|
||||||
const Clutter = imports.gi.Clutter;
|
const Clutter = imports.gi.Clutter;
|
||||||
|
```
|
||||||
The only thing to create now is the `metadata.json` file, which
|
|
||||||
contains compatibility information and, well, some meta data.
|
|
||||||
|
|
||||||
{
|
|
||||||
"shell-version": ["3.18"],
|
|
||||||
"uuid": "planets@gergely.polonkai.eu",
|
|
||||||
"name": "Planets",
|
|
||||||
"description": "Display current planet positions"
|
|
||||||
}
|
|
||||||
|
|
||||||
As soon as this file is ready, you can restart your Shell (press
|
As soon as this file is ready, you can restart your Shell (press
|
||||||
Alt-F2 and enter the command `r`), and load the extension with
|
Alt-F2 and enter the command `r`), and load the extension with
|
||||||
e.g. the GNOME Tweak Tool. You will see the Planets button on the
|
e.g. the GNOME Tweak Tool. You will see the Planets button on the
|
||||||
right. This little label showing the static text “Planets”, however,
|
right. This little label showing the static text “Planets”, however,
|
||||||
is pretty boring, so let’s add some action.
|
is pretty boring, so let’s add some action.
|
||||||
|
|
||||||
|
## Adding some periodical change
|
||||||
|
|
||||||
|
Since the planets’ position continuously change, we should update our
|
||||||
|
widget every minute or so. Let’s patch our `_init()` a bit:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
this.last_update = 0;
|
||||||
|
|
||||||
|
MainLoop.timeout_add(1, Lang.bind(this, function() {
|
||||||
|
this.last_update++;
|
||||||
|
this.panelLabel.set_text("Update_count: " + this.last_update);
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
This, of course, needs a new import line for `MainLoop` to become available:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const MainLoop = imports.mainloop;
|
||||||
|
const Lang = imports.lang;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now if you restart your Shell, your brand new extension will increase
|
||||||
|
its counter every second. This, however, presents some problems.
|
||||||
|
|
||||||
|
SWE GLib queries can sometimes be expensive, both in CPU and disk
|
||||||
|
operations, so updating our widget every second may present problems.
|
||||||
|
Also, planets don’t go **that** fast. We may update our timeout value
|
||||||
|
from `1` to `60` or something, but why don’t just give our user a chance
|
||||||
|
to set it?
|
||||||
|
|
||||||
|
## Introducing settings
|
||||||
|
|
||||||
|
Getting settings from `GSettings` is barely straightforward, especially
|
||||||
|
for software installed in a non-GNOME directory (which includes
|
||||||
|
extensions). To make our lives easier, I copied over a
|
||||||
|
[convenience library](https://github.com/projecthamster/shell-extension/blob/master/convenience.js)
|
||||||
|
from the [Hamster project](https://projecthamster.wordpress.com/)’s
|
||||||
|
extension, originally written by Giovanni Campagna. The relevant
|
||||||
|
function here is `getSettings()`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
/**
|
||||||
|
* getSettings:
|
||||||
|
* @schema: (optional): the GSettings schema id
|
||||||
|
*
|
||||||
|
* Builds and return a GSettings schema for @schema, using schema files
|
||||||
|
* in extensionsdir/schemas. If @schema is not provided, it is taken from
|
||||||
|
* metadata['settings-schema'].
|
||||||
|
*/
|
||||||
|
function getSettings(schema) {
|
||||||
|
let extension = ExtensionUtils.getCurrentExtension();
|
||||||
|
|
||||||
|
schema = schema || extension.metadata['settings-schema'];
|
||||||
|
|
||||||
|
const GioSSS = Gio.SettingsSchemaSource;
|
||||||
|
|
||||||
|
// check if this extension was built with "make zip-file", and thus
|
||||||
|
// has the schema files in a subfolder
|
||||||
|
// otherwise assume that extension has been installed in the
|
||||||
|
// same prefix as gnome-shell (and therefore schemas are available
|
||||||
|
// in the standard folders)
|
||||||
|
let schemaDir = extension.dir.get_child('schemas');
|
||||||
|
let schemaSource;
|
||||||
|
if (schemaDir.query_exists(null))
|
||||||
|
schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
|
||||||
|
GioSSS.get_default(),
|
||||||
|
false);
|
||||||
|
else
|
||||||
|
schemaSource = GioSSS.get_default();
|
||||||
|
|
||||||
|
let schemaObj = schemaSource.lookup(schema, true);
|
||||||
|
if (!schemaObj)
|
||||||
|
throw new Error('Schema ' + schema + ' could not be found for extension '
|
||||||
|
+ extension.metadata.uuid + '. Please check your installation.');
|
||||||
|
|
||||||
|
return new Gio.Settings({ settings_schema: schemaObj });
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can either incorporate this function into your `extension.js` file,
|
||||||
|
or just use `convenience.js` file like I (and the Hamster applet) did
|
||||||
|
and import it:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ExtensionUtils = imports.misc.extensionUtils;
|
||||||
|
const Me = ExtensionUtils.getCurrentExtension;
|
||||||
|
const Convenience = Me.imports.convenience;
|
||||||
|
```
|
||||||
|
|
||||||
|
Now let’s create the settings definition. GSettings schema files are XML
|
||||||
|
files. We want to add only one settings for now, the refresh interval.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<schemalist>
|
||||||
|
<schema id="org.gnome.shell.extensions.planets" path="/org/gnome/shell/extensions/planets/">
|
||||||
|
<key name="refresh-interval" type="i">
|
||||||
|
<default>30</default>
|
||||||
|
<summary>Refresh interval of planet data</summary>
|
||||||
|
<description>Interval in seconds. Sets how often the planet positions are recalculated. Setting this too low (e.g. below 30) may raise performance issues.</description>
|
||||||
|
</key>
|
||||||
|
</schema>
|
||||||
|
</schemalist>
|
||||||
|
```
|
||||||
|
you need to compile these settings with
|
||||||
|
|
||||||
|
glib-compile-schemas --strict schemas/
|
||||||
|
|
||||||
|
Now let’s utilize this new setting. In the extension’s `_init()`
|
||||||
|
function, add the following line:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
this._settings = Convenience.getSettings();
|
||||||
|
```
|
||||||
|
|
||||||
|
And, for `getSettings()` to work correctly, we also need to extend our
|
||||||
|
`metadata.json` file:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"settings-schema": "planets"
|
||||||
|
```
|
||||||
|
|
||||||
|
After another restart (please, GNOME guys, add an option to reload
|
||||||
|
extensions!), your brand new widget will refresh every 30 seconds.
|
||||||
|
|
||||||
|
## Displaying the planet positions
|
||||||
|
|
||||||
|
## The settings panel
|
||||||
|
|
||||||
|
## Start an application
|
||||||
|
Loading…
Reference in New Issue
Block a user