Add draft for GNOME Shell extension creation post
This commit is contained in:
		
							
								
								
									
										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. | ||||||
		Reference in New Issue
	
	Block a user