diff --git a/_drafts/writing-a-gnome-shell-extension.md b/_drafts/writing-a-gnome-shell-extension.md new file mode 100644 index 0000000..0a4115a --- /dev/null +++ b/_drafts/writing-a-gnome-shell-extension.md @@ -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.