Add draft for GNOME Shell extension creation post
This commit is contained in:
parent
9cfd8a19a7
commit
2b14481253
132
_drafts/writing-a-gnome-shell-extension.md
Normal file
132
_drafts/writing-a-gnome-shell-extension.md
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
---
|
||||||
|
layout: post
|
||||||
|
title: "Writing a GNOME Shell extension"
|
||||||
|
---
|
||||||
|
|
||||||
|
I could not find a tutorial on how to write a GNOME Shell extension,
|
||||||
|
but I wanted to create one for my SWE GLib library to show the current
|
||||||
|
positions of the planets. So I dug into existing (and working)
|
||||||
|
extensions’ source code and made up something. Comments welcome!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
GNOME Shell extensions are written in JavaScript and are interpreted
|
||||||
|
by [GJS](https://wiki.gnome.org/action/show/Projects/Gjs). Using
|
||||||
|
introspected libraries from JavaScript is not a problem for me (see
|
||||||
|
SWE GLib’s
|
||||||
|
[Javascript example](https://github.com/gergelypolonkai/swe-glib/blob/master/examples/basic.js);
|
||||||
|
it’s not beautiful, but it’s working), but wrapping your head around
|
||||||
|
the Shell’s concept can take some time.
|
||||||
|
|
||||||
|
The Shell is a Clutter stage, and all the buttons (including the
|
||||||
|
top-right “Activities” button) are actors on this stage. You can add
|
||||||
|
practically anything to the Shell panel that you can add to a Clutter
|
||||||
|
stage.
|
||||||
|
|
||||||
|
The other thing to remember is the lifecycle of a Shell extension. There
|
||||||
|
are two methods here: either you use an extension controller, or plain
|
||||||
|
old Javascript functions `enable()` and `disable()`; I will go on with
|
||||||
|
the former method.
|
||||||
|
|
||||||
|
## Anatomy of an extension
|
||||||
|
|
||||||
|
The only thing you actually need is an `init()` function:
|
||||||
|
|
||||||
|
function init(extensionMeta) {
|
||||||
|
// Do whatever it takes to initialize your extension,
|
||||||
|
// like initializing the translations.
|
||||||
|
return new ExtensionController(extensionMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
## Extension controller
|
||||||
|
|
||||||
|
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
|
||||||
|
the extension is loaded, its `enable()` method is called; when the
|
||||||
|
extension is unloaded, the `disable()` method gets called.
|
||||||
|
|
||||||
|
function ExtensionController(extensionMeta) {
|
||||||
|
return {
|
||||||
|
extensionMeta: extensionMeta,
|
||||||
|
extension: null,
|
||||||
|
|
||||||
|
enable: function() {
|
||||||
|
this.extension = new PlanetsExtension(this.extensionMeta);
|
||||||
|
|
||||||
|
Main.panel.addToStatusArea("planets", this.extension, 0, "right");
|
||||||
|
},
|
||||||
|
|
||||||
|
disable: function() {
|
||||||
|
this.extension.actor.destroy();
|
||||||
|
this.extension.destroy();
|
||||||
|
|
||||||
|
this.extension = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
This controller will create a new instance of the `PlanetsExtension`
|
||||||
|
class and add it to the panel’s right side when loaded. Upon unloading,
|
||||||
|
the extension’s actor gets destroyed, together with the extension
|
||||||
|
itself. Also, for safety measures, the extension is set to `null`.
|
||||||
|
|
||||||
|
As you will see soon, `extension.actor` is not created by us, but behind
|
||||||
|
the scenes by the Shell.
|
||||||
|
|
||||||
|
## The extension
|
||||||
|
|
||||||
|
The extension is a bit more tricky, as, for convenience reasons, it
|
||||||
|
should extend an existing panel widget type.
|
||||||
|
|
||||||
|
```
|
||||||
|
function PlanetsExtension(extensionMeta) {
|
||||||
|
this._init(extensionMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlanetsExtension.prototype = {
|
||||||
|
__proto__ = PanelMenu.Button.prototype,
|
||||||
|
|
||||||
|
_init: function(extensionMeta) {
|
||||||
|
PanelMenu.Button.prototype._init.call(this, 0.0);
|
||||||
|
|
||||||
|
this.extensionMeta = extensionMeta;
|
||||||
|
|
||||||
|
this.panelContainer = new St.BoxLayout({style_class: 'panel-box'});
|
||||||
|
this.actor.add_actor(this.panelContainer);
|
||||||
|
this.actor.add_style_class_name('panel-status-button');
|
||||||
|
|
||||||
|
this.panelLabel = new St.Label({text: 'Loading', y_align: Clutter.ActorAlign.CENTER});
|
||||||
|
|
||||||
|
this.panelContainer.add(this.panelLabel);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
|
The only parameter passed to the parent’s `_init` function is
|
||||||
|
`menuAlignment`, with the value `0.0`, which is used to position the
|
||||||
|
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._)
|
||||||
|
|
||||||
|
## Loading up the extension
|
||||||
|
|
||||||
|
Now with the correct import lines added:
|
||||||
|
|
||||||
|
const PanelMenu = imports.ui.panelMenu;
|
||||||
|
const St = imports.gi.St;
|
||||||
|
const Clutter = imports.gi.Clutter;
|
||||||
|
|
||||||
|
The only thing to create now is the `metadata.json` file. It 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 (Alt-F2, enter
|
||||||
|
the command `r`), and load the extension with e.g. the GNOME Tweak Tool.
|
||||||
|
|
||||||
|
This little label showing the static text “Planets” is pretty boring, so
|
||||||
|
let’s add some content.
|
Loading…
Reference in New Issue
Block a user