16
GNOME Shell Extensions GNOME Shell Extensions Finnbarr P. Murphy ([email protected]) The new GNOME Shell in GNOME 3 includes support for GNOME Shellextensions. What, you may ask, is a GNOME Shell extension? According to the GNOME web page on GNOME Shell extensions the GNOME Shell extension design is designed to give a high degree of power to the parts of the GNOME interface managed by the shell, such as window management and application launching. It simply loads arbitrary JavaScript and CSS. This gives developers a way to make many kinds of changes and share those changes with others, without having to patch the original source code and recompile it, and somehow distribute the patched code. In other ways, a GNOME Shell extension can be used to alter the existing functionality of the GNOME Shell or to provide additional functionality. This post assumes that you are familiar with the GNOME 3 Shell provided in Fedora 15 and have a working knowledge of JavaScript. By means of a number of examples, it will introduce you to some of the key concepts required to write you own extension. So how should you go about creating a GNOME Shell extension? Let us dive in an create a simple extension and explain the concepts and theory as we go along. We will use gnome-shell-extension-tool for our first example. This tool is available in Fedora 15 Alpha. I am not sure whether it is available on other GNU/Linux distributions. There is no manpage for this tool but it is simple to use. Just answer a couple of questions and all the necessary files are created for you. $ gnome-shell-extension-tool --help Usage: gnome-shell-extension-tool [options] Options: -h, --help show this help message and exit --create-extension Create a new GNOME Shell extension Example 1: Suppose I use this gnome-shell-extension-tool to create an extension named helloworld with a UUID of [email protected] and a description of My first GNOME 3 Shell extension . The tool, which is just a Python script, creates an appropriately named subdirectory (actually it is the uuid of the extension) under ~/.local/share/gnome-shell/extensions and populates that subdirectory with three files. Note that the UUID can be the classical 128 bit number, some other number or alphanumeric combination, or something more mundane like [email protected]. So long as it can be used to create a subdirectory, it will be regarded as a valid UUID. $ cd .local/share/gnome-shell/extensions $ find ./ For personnal use only 04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/16

GNOME Shell Extensions

Embed Size (px)

DESCRIPTION

Extensions are one important way of customizing and extending the GNOME Shell. This post shows you how you write your own extensions for the GNOME Shell.This document is a living document and is updated frequently. See http://blog.fpmurphy.com/2011/04/gnome-3-shell-extensions.html for the latest version. It is one of a series of documents on how to customize the GNOME Shell that I have written.

Citation preview

Page 1: GNOME Shell Extensions

GNOME Shell Extensions

GNOME Shell ExtensionsFinnbarr P. Murphy

([email protected])

The new GNOME Shell in GNOME 3 includes support for GNOME Shell extensions. What, youmay ask, is a GNOME Shell extension? According to the GNOME web page on GNOME Shellextensions

the GNOME Shell extension design is designed to give a high degree of power to theparts of the GNOME interface managed by the shell, such as window managementand application launching. It simply loads arbitrary JavaScript and CSS. This givesdevelopers a way to make many kinds of changes and share those changes withothers, without having to patch the original source code and recompile it, andsomehow distribute the patched code.

In other ways, a GNOME Shell extension can be used to alter the existing functionality of theGNOME Shell or to provide additional functionality.

This post assumes that you are familiar with the GNOME 3 Shell provided in Fedora 15 and have aworking knowledge of JavaScript. By means of a number of examples, it will introduce you to someof the key concepts required to write you own extension.

So how should you go about creating a GNOME Shell extension? Let us dive in an create a simpleextension and explain the concepts and theory as we go a long. We wi l l usegnome-shell-extension-tool for our first example. This tool is available in Fedora 15 Alpha. I am notsure whether it is available on other GNU/Linux distributions. There is no manpage for this toolbut it is simple to use. Just answer a couple of questions and all the necessary files are created foryou.

$ gnome-shell-extension-tool --helpUsage: gnome-shell-extension-tool [options]

Options: -h, --help show this help message and exit --create-extension Create a new GNOME Shell extension

Example 1:

Suppose I use this gnome-shell-extension-tool to create an extension named helloworld with aUUID of [email protected] and a description of My first GNOME 3 Shell extension. Thetool, which is just a Python script, creates an appropriately named subdirectory (actually it is theuuid of the extension) under ~/.local/share/gnome-shell/extensions and populates thatsubdirectory with three files. Note that the UUID can be the classical 128 bit number, some othernumber or alphanumeric combination, or something more mundane like [email protected] long as it can be used to create a subdirectory, it will be regarded as a valid UUID.

$ cd .local/share/gnome-shell/extensions

$ find ./

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/16

Page 2: GNOME Shell Extensions

GNOME Shell Extensions

./[email protected]

./[email protected]/stylesheet.css

./[email protected]/extension.js

./[email protected]/metadata.json

$ cd [email protected]

$ ls -l-rw-rw-r--. 1 fpm fpm 718 Mar 31 00:24 extension.js-rw-rw-r--. 1 fpm fpm 137 Mar 31 00:23 metadata.json-rw-rw-r--. 1 fpm fpm 177 Mar 31 00:23 stylesheet.css

Here are the contents of these three files:

$ cat metadata.json{ "shell-version": ["2.91.92"], "uuid": "[email protected]", "name": "helloworld", "description": "My first GNOME 3 Shell extension"}$ cat extension.js//// Sample extension code, makes clicking on the panel show a message//const St = imports.gi.St;const Mainloop = imports.mainloop;const Main = imports.ui.main;function _showHello() { let text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" }); let monitor = global.get_primary_monitor(); global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); });}// Put your extension initialization code herefunction main() { Main.panel.actor.reactive = true; Main.panel.actor.connect('button-release-event', _showHello);}$ cat stylesheet.css/* Example stylesheet */.helloworld-label { font-size: 36px; font-weight: bold; color: #ffffff; background-color: rgba(10,10,10,0.7); border-radius: 5px;}

What is created is a very simple extension that display a message, Hello, world!, in the middle ofyour screen as shown below whenever you click the panel (the horizontal bar at the top of yourscreen in the GNOME 3 Shell) or a menu selection.

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 2/16

Page 3: GNOME Shell Extensions

GNOME Shell Extensions

This extension is created under ~/.local/share/gnome-shell/extensions which is the designatedlocation for per user extensions. Note that ~/.local is also used for other purposes, not just for peruser extensions.

$ find .local.local.local/share.local/share/gnome-shell.local/share/gnome-shell/extensions.local/share/gnome-shell/extensions/[email protected]/share/gnome-shell/extensions/[email protected]/stylesheet.css.local/share/gnome-shell/extensions/[email protected]/extension.js.local/share/gnome-shell/extensions/[email protected]/metadata.json.local/share/gnome-shell/application_state.local/share/icc.local/share/icc/edid-67c2e64687cb4fd59883902829614117.icc.local/share/gsettings-data-convert

Global (system-wide) extensions should be placed in either /usr/share/gnome-shell/extensions or/usr/local/share/gnome-shell/extensions.

By the way I really wish the GNOME developers would stop creating more and more hiddensubdirectories in a users home directory! It would be really nice if everything to do with GNOME 3was located under, say, .gnome3.

Notice that the actual code for the extension is written in JavaScript and contained in a file calledextension.js. This file is mandatory and is what gets loaded into GNOME Shell. At a minimum, itmust contain a main() function which is invoked immediately after the extension is loaded byGNOME shell.

The JavaScript language version is 1.8 (which is a Mozilla extension to ECMAscript 262.) This iswhy non-standard JavaScript keywords like let are supported in shell extensions. The actualJavaScript engine (called gjs) is based on the Mozilla SpiderMonkey JavaScript engine and theGObject introspection framework.

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 3/16

Page 4: GNOME Shell Extensions

GNOME Shell Extensions

Interestingly, a gjs shell is provided but unfortunately most of the shell functionality present inSpiderMonkey such as quit() does not appear to be supported in this particular JavaScript shell

$ gjs** (gjs:11363): DEBUG: Command line: gjs** (gjs:11363): DEBUG: Creating new context to eval console scriptgjs> help()ReferenceError: help is not definedgjs> quit()ReferenceError: quit is not definedgjs>

Persistent metadata for the extension is stored in the file metadate.json which uses the JSON fileformat. JSON was chosen because it is natively supported by JavaScript. Here is the current list ofdefined strings:

shell-version: A list of GNOME Shell versions compatible with the extension. For example●

["2.91.92", "3.0.0"].uuid: A unique UUID for the extension. This must be unique among any installed extensions as●

the UUID is used as the name of the subdirectory under which the files for the extension arelocated.name: The name of the extension. This is displayed by the Looking Glass debugger when the●

extensions panel is displayed.description: The description of the extension. This is also displayed by Looking Glass when the●

extensions panle is displayed.url: [OPTIONAL] A valid URL pointing the the source code for the extension. Looking Glass uses●

this URL, if provided, to display a button which when pressed opens the source code file.js-version: [OPTIONAL] This can be used to specify that a particular version of hjs is required by●

the extension.

There is nothing stopping you adding additional strings to this file. It some cases this may beuseful as the contents of metadata.json is passed as an argument to the main() function inextension.js.

The third file is stylesheet.css. This contains all the CSS (Cascading Style Sheet) information foryour extension. This file is mandatory even if it is empty because your extension does not requireit’s own presentation markup.

Example 2:

What if we want to display localized message strings (always a good idea!) in our helloworld shellextension. In this case we need to modify entension.js to support message catalogs and we need toprovide and install the relevant message catalogs in the appropriate directories.

The GNOME Shell uses the standard GNU/Linux gettext paradigm. I am going to assume that youare somewhat familiar with software localization and how to use gettext. A whole post could bedevoted to the use of gettext but that is not the purpose of this post.

Fortunately the heavy lifting has been done for us by others and a JavaScript binding to gettext isavailable to us. We import the necessary JavaScript gettext module into the helloworld shellextension using imports and modify the code to use Gettext.gettext(“message string”) to retrievethe localized version of the message string if provided in a message catalog.

Normally compiled gettext message catalogs (.mo files) are placed under /usr/share/locale onGNU/Linux distributions. However I do not think that this is a good location for extension messagecatalogs as I believe that extensions should be as self-contained as possible to aid in their easy

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 4/16

Page 5: GNOME Shell Extensions

GNOME Shell Extensions

installation and removal. It also avoids the possibility of message catalog namespace collisions.For this reason, we place the message catalogs in a subdirectory called locale under the topdirectory of the extension.

By way of example, here is a listing of the files for our helloworld extension after it has beenmodified to support message localization and message catalogs for en_US and fr_FR localesprovided.

./[email protected]

./[email protected]/stylesheet.css

./[email protected]/extension.js

./[email protected]/locale

./[email protected]/locale/fr_FR

./[email protected]/locale/fr_FR/LC_MESSAGES

./[email protected]/locale/fr_FR/LC_MESSAGES/helloworld.mo

./[email protected]/locale/fr_FR/LC_MESSAGES/helloworld.po

./[email protected]/locale/en_US

./[email protected]/locale/en_US/LC_MESSAGES

./[email protected]/locale/en_US/LC_MESSAGES/helloworld.mo

./[email protected]/locale/en_US/LC_MESSAGES/helloworld.po

./[email protected]/metadata.json

As you can see I have provided support for two locales, en_US for Americanese speakers andfr_FR for French speakers. The default message string Hello, world! will be displayed if neither ofthese two locales is set. Only the .mo files are necessary but I suggest that the corresponding .pofiles also reside there to make it easy to update a message catalog.

Here is our entension.js after it was modified to support message string localization:

const St = imports.gi.St;const Mainloop = imports.mainloop;const Main = imports.ui.main;const Gettext = imports.gettext;function _showHello() { let text = new St.Label({ style_class: 'helloworld-label', text: Gettext.gettext("Hello, world!") }); let monitor = global.get_primary_monitor(); global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); });}function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("helloworld", userExtensionLocalePath); Gettext.textdomain("helloworld"); Main.panel.actor.reactive = true; Main.panel.actor.connect('button-release-event', _showHello);}

Note that the main function now has one parameter extensionMeta. This is an object that containsall the information from the extension’s metadata.json file. This is the only parameter available tot h e m a i n f u n c t i o n i n a n e x t e n s i o n . S e e t h e l o a d E x t e n s i o n f u n c t i o n i n/usr/share/gnome-shell / js/ui/extensionSystem.js for further details.

This parameter is used to build the path to the shell extension locale subdirectory. We then tellgettext that we want to use message catalogs from this subdirectory using bindtextdomain andspecify the relevant message catalog, helloworld.mo, using textdomain.

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 5/16

Page 6: GNOME Shell Extensions

GNOME Shell Extensions

Here is what is displayed when the locale is set to en_US:

and here is what is displayed when the locale set to fr_FR

If no suitable message catalog is found, the message string Hello, world! will be displayed.

Example 3:

This example shows you how modify our helloworld example extension to add a menu item to theStatus Menu (the menu at the top right hand corner) of your primary display and output the Hello,world! message.

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/16

Page 7: GNOME Shell Extensions

GNOME Shell Extensions

Here is the modified extensions.js:

const Main = imports.ui.main;const Shell = imports.gi.Shell;const Lang = imports.lang;const PopupMenu = imports.ui.popupMenu;const Gettext = imports.gettext;const _ = Gettext.gettext;function _showHello() { let text = new St.Label({ style_class: 'helloworld-label', text: _("Hello, world!") }); let monitor = global.get_primary_monitor(); global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); });}function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("helloworld", userExtensionLocalePath); Gettext.textdomain("helloworld"); let statusMenu = Main.panel._statusmenu; let item = new PopupMenu.PopupSeparatorMenuItem(); statusMenu.menu.addMenuItem(item); item = new PopupMenu.PopupMenuItem(_("Hello")); item.connect('activate', Lang.bind(this, this._showHello)); statusMenu.menu.addMenuItem(item);}

Note the use of const _ to make message strings note legible in the source code.

Notice how we increased the size of the message box. No code changes were required; we simplyedited the relevant styling markup. Here is the new version of stylesheet.css

.helloworld-label { font-size: 36px;

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 7/16

Page 8: GNOME Shell Extensions

GNOME Shell Extensions

font-weight: bold; color: #ffffff; background-color: rgba(10,10,10,0.7); border-radius: 15px; margin: 50px; padding: 50px;

Example 4:

This example modifies the previous example to display a message in the GNOME Shell messagetray.

Here is the modified extensions.js:

const St = imports.gi.St;const Mainloop = imports.mainloop;const Main = imports.ui.main;const Shell = imports.gi.Shell;const Lang = imports.lang;const PopupMenu = imports.ui.popupMenu;const Gettext = imports.gettext;const MessageTray = imports.ui.messageTray;const _ = Gettext.gettext;function _myNotify(text){ global.log("_myNotify called: " + text); let source = new MessageTray.SystemNotificationSource(); Main.messageTray.add(source); let notification = new MessageTray.Notification(source, text, null); notification.setTransient(true); source.notify(notification);}function _showHello() { _myNotify(_("Hello, world!")) let text = new St.Label({ style_class: 'helloworld-label', text: _("Hello, world!") }); let monitor = global.get_primary_monitor();

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 8/16

Page 9: GNOME Shell Extensions

GNOME Shell Extensions

global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); });}function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("helloworld", userExtensionLocalePath); Gettext.textdomain("helloworld"); let statusMenu = Main.panel._statusmenu; let item = new PopupMenu.PopupSeparatorMenuItem(); statusMenu.menu.addMenuItem(item); item = new PopupMenu.PopupMenuItem(_("Hello, world!")); item.connect('activate', Lang.bind(this, this._showHello)); statusMenu.menu.addMenuItem(item);}

Note the use of global.log to log a message to the error log. This log can be viewed in LookingGlass. This is useful when debugging an extension.

Example 5:

This example demonstrates how to modify our helloworld extension to add a button to the panelwhich when pressed displays a single option menu which when selected displays our Hello, world!message.

Here is the modified extensions.js:

const St = imports.gi.St;const Mainloop = imports.mainloop;const Main = imports.ui.main;const Shell = imports.gi.Shell;const Lang = imports.lang;const PopupMenu = imports.ui.popupMenu;const PanelMenu = imports.ui.panelMenu;const Gettext = imports.gettext;const MessageTray = imports.ui.messageTray;

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 9/16

Page 10: GNOME Shell Extensions

GNOME Shell Extensions

const _ = Gettext.gettext;function _myButton() { this._init();}_myButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); this._label = new St.Label({ style_class: 'panel-label', text: _("HelloWorld Button") }); this.actor.set_child(this._label); Main.panel._centerBox.add(this.actor, { y_fill: true }); this._myMenu = new PopupMenu.PopupMenuItem(_('HelloWorld MenuItem')); this.menu.addMenuItem(this._myMenu); this._myMenu.connect('activate', Lang.bind(this, _showHello)); }, _onDestroy: function() {}};function _showHello() { let text = new St.Label({ style_class: 'helloworld-label', text: _("Hello, world!") }); let monitor = global.get_primary_monitor(); global.stage.add_actor(text); text.set_position(Math.floor (monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Mainloop.timeout_add(3000, function () { text.destroy(); });}function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("helloworld", userExtensionLocalePath); Gettext.textdomain("helloworld"); let _myPanelButton = new _myButton();}

Here is the modified stylesheet.css

.panel-label { padding: .4em 1.75em; font-size: 10.5pt; color: #cccccc; font-weight: bold;}.helloworld-label { font-size: 36px; font-weight: bold; color: #ffffff; background-color: rgba(10,10,10,0.7); border-radius: 15px; margin: 50px; padding: 50px;

Turning now to the question of how to determine which GNOME Shell extensions are loaded andwhat information is available about the state of such extensions. Currently no tool is provided in adistribution to list information about extensions.

Here is a small Python utility which lists the details of all your GNOME Shell extensions on thecommand line:

#!/usr/bin/python## Copyright (c) 2011 Finnbarr P. Murphy

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 10/16

Page 11: GNOME Shell Extensions

GNOME Shell Extensions

## This utility is free software. You can redistribute it and/or# modify it under the terms of the GNU General Public License as# published by the Free Software Foundation, either version 2 of# the License, or (at your option) any later version.## This utility is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.## See <http://www.gnu.org/licenses/> for full text of the license.#import os.pathimport jsonfrom gi.repository import Giofrom gi.repository import GLibstate = { 1:"enabled", 2:"disabled", 3:"error", 4:"out of date"}type = { 1:"system", 2:"per user"}class GnomeShell: def __init__(self): d = Gio.bus_get_sync(Gio.BusType.SESSION, None) self._proxy = Gio.DBusProxy.new_sync( d, 0, None, 'org.gnome.Shell', '/org/gnome/Shell', 'org.gnome.Shell', None) def execute_javascript(self, js): result, output = self._proxy.Eval('(s)', js) if not result: raise Exception(output) return output def list_extensions(self): out = self.execute_javascript('const ExtensionSystem = imports.ui.extensionSystem; ExtensionSystem.extensionMeta') return json.loads(out) def get_shell_version(self): out = self.execute_javascript('const Config = imports.misc.config; version = Config.PACKAGE_VERSION') return out def get_gjs_version(self): out = self.execute_javascript('const Config = imports.misc.config; version = Config.GJS_VERSION') return outif __name__ == "__main__": s = GnomeShell() print print "Shell Version:", s.get_shell_version() print " GJS Version:", s.get_gjs_version() print l = s.list_extensions() for k, v in l.iteritems(): print 'Extension: %s' % k print "-" * (len(k) + 11) for k1, v1 in v.iteritems(): if k1 == 'state': print '%15s: %s (%s)' % (k1, v1, state[v1]) elif k1 == 'type': print '%15s: %s (%s)' % (k1, v1, type[v1]) elif k1 == 'shell-version': print '%15s:' % k1, print ", ".join(v1) else: print '%15s: %s' % (k1, v1) print

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 11/16

Page 12: GNOME Shell Extensions

GNOME Shell Extensions

Here is what is outputted for our HelloWorld example extension:

Shell Version: "3.0.0" GJS Version: "0.7.13"

Extension: [email protected] description: My first GNOME 3 Shell extension shell-version: 2.91.91, 2.91.92, 2.91.93 name: helloworld url: http://example.com state: 1 (enabled) path: /home/fpm/.local/share/gnome-shell/extensions/[email protected] type: 2 (per user) uuid: [email protected]

You can also see which extensions are loaded using the Looking Glass debugger which isaccessible via Alt-F2 lg. Unfortunately, the current version of Looking Glass displays very littleinformation about extensions other than the fact that they are loaded, but that is easily remediedby adding a few lines of JavaScript to /usr/share/gnome-shell/js/ui/lookingGlass.js.

$ diff lookingGlass.js.org lookingGlass.js621a622,631> _typeToString: function(extensionType) {> switch (extensionType) {> case ExtensionSystem.ExtensionType.SYSTEM:> return _("System");> case ExtensionSystem.ExtensionType.PER_USER:> return _("Per User");> }> return 'Unknown';> },>637a648> let line1Box = new St.BoxLayout();640d650< box.add(name, { expand: true });642,643c652,655< text: meta.description });< box.add(description, { expand: true });---> text: " " + meta.description });> line1Box.add(name, { expand: false });> line1Box.add(description, { expand: false });> box.add(line1Box);645,647d656< let metaBox = new St.BoxLayout();< box.add(metaBox);< let stateString = this._stateToString(meta.state);649c658,667< text: this._stateToString(meta.state) });---> text: " State: " + this._stateToString(meta.state)+", " });> let type = new St.Label({ style_class: 'lg-extension-state',> text: "Type: " + this._typeToString(meta.type)+", " });> let uuid = new St.Label({ style_class: 'lg-extension-state',> text: "UUID: " + meta.uuid });>> let metaDataBox = new St.BoxLayout();> metaDataBox.add(state, { expand: false });> metaDataBox.add(type, { expand: false });

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 12/16

Page 13: GNOME Shell Extensions

GNOME Shell Extensions

> metaDataBox.add(uuid, { expand: false });650a669,670> let metaBox = new St.BoxLayout();> box.add(metaBox);666a687,690> actionsBox.add(metaDataBox);

Here is what is displayed by the modified Looking Glass for our HelloWorld extension:

How do you disable or enable installed extensions? By default, all extensions are enabled providedthey match the current GNOME Shell version number (and the gjs version number if one isprovided in metadata.json.) You can disable extensions from the command line using the gsettingsutility. You should also be able to disable extensions using dconf-editor but this utility is broken asof the date of this post (and seems to be always broken for some reason or other) so I cannot testit. At present there is no specific GUI-based utility to disable or enable extensions.

$ gsettings list-recursively org.gnome.shellorg.gnome.shell command-history @as []org.gnome.shell development-tools trueorg.gnome.shell disabled-extensions @as []org.gnome.shell disabled-open-search-providers @as []org.gnome.shell enable-app-monitoring trueorg.gnome.shell favorite-apps ['mozilla-firefox.desktop', 'evolution.desktop', 'empathy.desktop', 'rhythmbox.desktop', 'shotwell.desktop', 'openoffice.org-writer.desktop', 'nautilus.desktop']org.gnome.shell looking-glass-history @as []org.gnome.shell.calendar show-weekdate falseorg.gnome.shell.clock show-date falseorg.gnome.shell.clock show-seconds falseorg.gnome.shell.recorder file-extension 'webm'org.gnome.shell.recorder framerate 15org.gnome.shell.recorder pipeline ''[root@ultra noarch]#

The requisite key is disabled-extensions

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 13/16

Page 14: GNOME Shell Extensions

GNOME Shell Extensions

$ gsettings get org.gnome.shell disabled-extensions@as []

The @as [] syntax is outputted whenever there are no disabled GNOME Shell extensions. Thisindicates a serialized GVariant. A GVariant is a variant datatype; it stores a value along with thetype of that value.

To disable an extension, simply add the UUID of the extension to the disabled-extensions key,logout and log back in, or reload the GNOME Shell using Alt-F2 r. Note that disabling anextension does not stop it operating once it has been loaded into the GNOME Shell; it merelystops it being loaded in the first place.

$ gsettings set org.gnome.shell disabled-extensions "['[email protected]']"

By the way, gsettings does not handle incrementally adding an extensions’ UUID to thedisabled-extensions key nor does it overwrite or remove existing values. You first have to reset thekey and then set the key with the new values.

$ gsettngs reset org.gnome.shell disabled-extensions$ gsettings set org.gnome.shell disabled-extensions "['[email protected]']"

Recently an official repository for GNOME Shell extensions was created by Giovanni Campagna.As of the date of this post it includes the following extensions:

alternate-tab: provides the classic GNOME Alt+Tab functionality.●

alternative-status-menu: replaces the status menu with one featuring separate Suspend and●

Power Off menu options.auto-move-windows: assigns a specific workspace to each application as soon as it creates a●

window, in a configurable manner.dock: displays a docked task switcher on the right side of your screen.●

example: a minimal example illustrating how to write extensions.●

gajim: provides integration with Gajim, a Jabber/XMPP instant messaging client.●

user-theme: loads a user specified shell theme.●

windowsNavigator: enables keyboard selection of windows and workspaces in overlay mode.●

xrandr-indicator: enable you to rotate your screen and access display preferences quickly.●

Currently these extensions are not available as RPMs but I am sure that it will not take longbefore somebody does the necessary work to make this happen. If you want to build you ownRPMs, here is a .spec file which packages the example shell extension. It assumes that the sourcefiles exist in gnome-shell-extensions.tar.gz in the build source directory. You can easily modify thefile to use git archive –format=tar to pull the files directly from the extensions git repository andto build a package for other extensions.

Name: gnome-shell-extensionsVersion: 2.91.6Release: 1License: GPLv2+Group: User Interface/DesktopsSummary: A collection of extensions for the GNOME 3 ShellUrl: http://live.gnome.org/GnomeShell/ExtensionsSource: %{name}.tar.gz

BuildRequires: gnome-common

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 14/16

Page 15: GNOME Shell Extensions

GNOME Shell Extensions

BuildRequires: pkgconfig(gnome-desktop-3.0)Requires: gnome-shellBuildRoot: %{_tmppath}/%{name}-%{version}-buildBuildArch: noarch

%descriptionGNOME Shell Extensions is a collection of extensions providingadditional optional functionality for the GNOME 3 Shell.

%prep%setup -q -n %{name}

%build# Needed because we build from a git checkout[ -x autogen.sh ] && NOCONFIGURE=1 ./autogen.sh%configure --enable-extensions="example" --prefix=$HOME/.local

%configuremake %{?_smp_mflags}

%installrm -rf $RPM_BUILD_ROOT%make_install%find_lang %{name}

%clean%{?buildroot:%__rm -rf %{buildroot}}

%post

%postun

%files -f %{name}.lang%defattr(-,root,root)%doc README%dir %{_datadir}/gnome-shell%{_datadir}/gnome-shell/extensions/%{_datadir}/glib-2.0/schemas/

%changelog*Tue Mar 29 2011 1-- Initial Build

I am going to assume that you know how to build RPMs. Since the GNOME Shell and, by extension(bad pun?), extensions are still a moving target at present, you will probably have to make minorchanges to the above .spec file to meet your specific requirements and to conform to changes inthe extensions repository. A word of caution however – if you build and install all the shellextensions that are in the repository at the one time, you will most certainly break GNOME Shell.

What are my thoughts on an architecture and framework for extensions? Based on my experiencewith developing extensions for Mozilla Firefox, WordPress and suchlike, I believe that:

Each extension should be packaged separately to allow for individual installation, updating or●

removal.Extensions do not need to be packaged as RPMs but should be compressed into some kind of●

tarball or zip file. An extension installation tool i(possibly part of the GNOME Shell, shouldunpack the extension tarball, do some sanity checking, and then install the extension.All files, including message catalogs, images, and icons, for an extension should live under the●

extension top directory.There needs to be a GUI for listing, installing, disabling, enabling and removing extensions.●

There needs to be a better version checking mechanism in extensionSystem.js. The current code●

is too restrictive and does not support range checking, i.e. minimum and maximum supportedversions.

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 15/16

Page 16: GNOME Shell Extensions

GNOME Shell Extensions

There needs to be a basic validation/testsuite for extensions.●

An ecosystem needs to be put in place around extensions. Something like addons.mozilla.org.●

Whether an extension accepted or not should not depend on the whims of a single gatekeeper●

but solely depend on whether the extension met certain published guidelines.

The GNOME Shell is still under active development. It will probably be GNOME 3.2 before itstabilizes to the extent that serious effort will be expended developing an extensions ecosystem. Alot more documentation, tools and infrastructure need to be place before a formal reliableecosystem for GNOME Shell extensions emerges.

As always, experiment and enjoy the experience!

For p

erson

nal u

se on

ly

04-09-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 16/16