Вы находитесь на странице: 1из 13

Updating GNOME Shell Extensions To Work With GNOME 3.

Updating GNOME Shell Extensions To Work With GNOME 3.2


Finnbarr P. Murphy
(fpm@fpmurphy.com) This particular post will contain the knowledge that I accumulate of the next few weeks about updating GNOME 3.0 Shell extensions so that they work with the GNOME 3.2 Shell. It will be a living document for a period of about 2 months so check back frequently for updates. When the GNOME 3.4 Shell is released, it will be covered in a separate post. As you are probably aware, GNOME Shell comes with an interactive GNOME Shell extension creator called gnome-shell-extension-tool. The utility is actually a simple Python script. It generates a simple Hello World extension using:

gnome-shell-extension-tool --create-extension

After asking you 3 questions, the utility generates three files which are placed in $HOME/.local/share/gnome-shell/extensions//.

-rw-rw-r--. 1 fpm fpm 1488 Oct 25 21:46 extension.js -rw-rw-r--. 1 fpm fpm 152 Oct 26 12:30 metadata.json -rw-rw-r--. 1 fpm fpm 172 Oct 25 21:46 stylesheet.css

Here is what is generated by this utility for extension.js on Fedora 15 which uses GNOME 3.0:

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 here function main() { Main.panel.actor.reactive = true; Main.panel.actor.connect('button-release-event', _showHello); }

Fo r

Thanks to the seemingly tireless Jasper St. Pierre, here is a slightly more advanced version of a GNOME 3.0 HelloWorld Shell extension which was backported from the code generated by the same utility on Fedora 16 beta.

const St = imports.gi.St; const Main = imports.ui.main;

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se
1/13

on

ly

Updating GNOME Shell Extensions To Work With GNOME 3.2 const Tweener = imports.ui.tweener; let text, button; function _hideHello() { Main.uiGroup.remove_actor(text); text = null; } function _showHello() { if (!text) { text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" }); Main.uiGroup.add_actor(text); } text.opacity = 255; let monitor = Main.layout.primaryMonitor; text.set_position(Math.floor(monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Tweener.addTween(text, { opacity: 0, time: 2, transition: 'easeOutQuad', onComplete: _hideHello }); } function main() { button = new St.Bin({ style_class: 'panel-button', reactive: true, can_focus: true, x_fill: true, y_fill: false, track_hover: true }); let icon = new St.Icon({ icon_name: 'system-run', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); button.set_child(icon); button.connect('button-press-event', _showHello); Main.panel._rightBox.insert_actor(button, 0); }

Fo r

11-11-2011

pe rs o

This extension simply adds a button to the right hand side of the (top) panel and displays a Hello World message for a number of seconds. I am going to assume that you are familiar with JavaScript and GNOME Shell extensions in general if you are reading this post so I am not going to explain the code.

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

2/13

Updating GNOME Shell Extensions To Work With GNOME 3.2

Fo r

Uuid [example__@ultra.xfpmurphy.com]: helloworld@example.com Created extension in '/home/fpm/.local/share/gnome-shell/extensions/helloworld@example.com ' Traceback (most recent call last): File "/usr/bin/gnome-shell-extension-tool", line 151, in subprocess.Popen(['gnome-open', extensionjs_path]) NameError: name 'extensionjs_path' is not defined

See this bug report for more information. The fix is simple add the line denoted by the +.

print "Created extension in %r" % (extension_path, ) extensionjs_path = os.path.join(extension_path, 'extension.js') subprocess.Popen(['xdg-open', extensionjs_path]) sys.exit(0)

You may notice that I also replaced gnome-open with xdg-open in subprocess.Popen. That is because gnome-open is deprecated. Here is what is generated by the gnome-shell-extension-tool on Fedora 16 Beta:

const St = imports.gi.St;

11-11-2011

pe rs o

To gain an understanding of what changes may be required in a 3.0 Shell extension in order for the extension to work in GNOME 3.2, we examine the code generated by this utility for a 3.2 Shell extension. By the way, with Fedora 16 Beta you may get the following error when you run this utility:

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

In GNOME 3.0, all Shell extensions are enabled by default unless the Shell extension is in a blacklist, i.e. org.gnome.shell.disabled-extensions. Per-user and systemwide Shell extensions can be disabled with this GSettings key. Thus, this Shell extension is enabled by default and loaded when you restart the Shell using Alt+F2 R, gnome-shell replace or some other means.

lu

se
3/13

on

ly

Updating GNOME Shell Extensions To Work With GNOME 3.2 const Main = imports.ui.main; const Tweener = imports.ui.tweener; let text, button; function _hideHello() { Main.uiGroup.remove_actor(text); text = null; } function _showHello() { if (!text) { text = new St.Label({ style_class: 'helloworld-label', text: "Hello, world!" }); Main.uiGroup.add_actor(text); } text.opacity = 255; let monitor = Main.layoutManager.primaryMonitor; text.set_position(Math.floor(monitor.width / 2 - text.width / 2), Math.floor(monitor.height / 2 - text.height / 2)); Tweener.addTween(text, { opacity: 0, time: 2, transition: 'easeOutQuad', onComplete: _hideHello }); } function init() { button = new St.Bin({ style_class: 'panel-button', reactive: true, can_focus: true, x_fill: true, y_fill: false, track_hover: true }); let icon = new St.Icon({ icon_name: 'system-run', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); button.set_child(icon); button.connect('button-press-event', _showHello); } function enable() { Main.panel._rightBox.insert_actor(button, 0); } function disable() { Main.panel._rightBox.remove_actor(button); }

Fo r

As you can see it is quite different than the backported GNOME 3.0 HelloWorld Shell extension. The basic functionality of the Shell extension is the same. There is no longer a main function; it has been replaced by init. Also two functions or methods named enable and disable are mandatory. More about these later. In GNOME 3.2, a Shell extension is not available to you by default when the Shell is restarted because, in GNOME 3.2, Shell extensions are no longer enabled by default. The Shell extension is loaded but not enabled. Looking Glass will show the Shell extension in the Extensions pane but it will display as disabled. Any errors in the Shell extension code will be displayed in the Errors pane.

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

4/13

Updating GNOME Shell Extensions To Work With GNOME 3.2

Suppose a Shell extension is named example32@example.com. One way to enable the Shell extension is to use the gsettings utility:

% gsettings get org.gnome.shell enabled-extensions[] % gsettings set org.gnome.shell enabled-extensions "['example32@example.com']" % gsettings get org.gnome.shell enabled-extensions ['example@example.com']

Another way to enable or disable the Shell extension is to use John Stowers excellent gnome-tweet-tool or the dconf-editor utility.

Fo r

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

To become enabled, a Shell extension must be specifically added to a whitelist, i.e. the GSettings org.gnome.shell.enabled-extensions key. Then the enable method or function in the Shell extension must be called. Typically this automatically happens more about this later.

lu

se
5/13

on

ly

Updating GNOME Shell Extensions To Work With GNOME 3.2

Another way is to use a future version of gnome-shell-extension-tool, not yet released as of the date of this blog, which has options to enable or disable extensions.

$ ./gnome-shell-extension-tool --help Usage: gnome-shell-extension-tool [options] Options: -h, --help show this help message and exit -d DISABLE, --disable-extension=DISABLE Disable a GNOME Shell extension -e ENABLE, --enable-extension=ENABLE Enable a GNOME Shell extension -c, --create-extension Create a new GNOME Shell extension $ gnome-shell-extension-tool -e example32@example.com 'example32@example.com' is now enabled. $ gnome-shell-extension-tool -d example32@example.com 'example32@example.com' is now disabled.

Fo r

A copy of this new version of gnome-shell-extension-tool is available here. Note that it does not check the validity of extension names! By the way, while it is not specifically stated in the GNOME 3.2 Release Notes, from looking at the GNOME 3.2 Shell source code, it would appear that the org.gnome.shell.disabled-extensions key is no longer used to blacklist Shell extensions. If you compare the 3.0 and 3.2 versions of the Shell extension code, the main function in 3.0 is replaced by three functions in 3.2, i.e. init, enable and disable. The init function is mandatory just like main was in 3.0. It does everything the main function usually does, except it cannot change
11-11-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/13

pe rs o

nn a

lu

se

on

ly

Updating GNOME Shell Extensions To Work With GNOME 3.2

any Shell state. Its purpose is to setup whatever needs to be setup, such as labels, text, icons or actors, prior to enabling the extension. It is only called once per shell session. The enable and disable functions are mandatory and are intended to be used to make a Shell extensions UI visible (enable) or invisible (disable) to the user. Note that while enable and disable can be empty functions and init can contain the code to enable the UI, it goes against what the GNOME Shell developers are trying to achieve, i.e. provide a way to enable or disable a Shell extension without having to reload the GNOME Shell. Remember, in GNOME Shell 3.0, every time you wished to do something with an extension you had to reload the GNOME Shell. This design is not enforced; it cannot be. All the Shell loadExtension function, which was extensively modified in 3.2 (see extensionSystem.js), can do is to check for the existence of init, enable and disable. This is all the Shell can do to support the intent of the new design without extensive code being added to the Shell. It is up to writers of Shell extensions to comply with the spirit and intent of the design. When the proposed extensions.gnome.org materializes, a tool could probably be written to check that enable and disable actually enable or disable the Shell extension UI as part of the acceptable criteria for repository.

Fo r

if (!extensionModule.init) { logExtensionError(uuid, 'missing \'init\' function'); return; } try { extensionState = extensionModule.init(meta); } catch (e) { if (stylesheetPath != null) theme.unload_stylesheet(stylesheetPath); logExtensionError(uuid, 'Failed to evaluate init function:' + e); return; } if (!extensionState) extensionState = extensionModule; extensionStateObjs[uuid] = extensionState; if (!extensionState.enable) { logExtensionError(uuid, 'missing \'enable\' function'); return; } if (!extensionState.disable) { logExtensionError(uuid, 'missing \'disable\' function'); return; }

The Shell listens for a GSettings changed signal that the org.gnome.shell.enabled_extensions key has changed and invokes onEnabledExtensionsChanged which gets the list of enabled Shell extensions, compares it to the current in-memory list and enables or disables Shell extensions accordingly by calling init followed by enable for any new Shell extension added to the whitelist, enable for Shell extensions previously initialized but disabled, and disable for Shell extensions that are no longer on the whitelist.

function disableExtension(uuid) { let meta = extensionMeta[uuid]; if (!meta) return; if (meta.state != ExtensionState.ENABLED) return; let extensionState = extensionStateObjs[uuid]; // "Rebase" the extension order by disabling and then enabling extensions

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on

ly

7/13

Updating GNOME Shell Extensions To Work With GNOME 3.2 // in order to help prevent conflicts. // Example: // order = [A, B, C, D, E] // user disables C // this should: disable E, disable D, disable C, enable D, enable E let orderIdx = extensionOrder.indexOf(uuid); let order = extensionOrder.slice(orderIdx + 1); let orderReversed = order.slice().reverse(); for (let i = 0; i < orderReversed.length; i++) { let uuid = orderReversed[i]; try { extensionStateObjs[uuid].disable(); } catch(e) { logExtensionError(uuid, e.toString()); } } try { extensionState.disable(); } catch(e) { logExtensionError(uuid, e.toString()); return; } for (let i = 0; i < order.length; i++) { let uuid = order[i]; try { extensionStateObjs[uuid].enable(); } catch(e) { logExtensionError(uuid, e.toString()); } } extensionOrder.splice(orderIdx, 1); meta.state = ExtensionState.DISABLED; _signals.emit('extension-state-changed', meta);

} function enableExtension(uuid) { let meta = extensionMeta[uuid]; if (!meta) return; if (meta.state == ExtensionState.INITIALIZED) { loadExtension(meta.dir, meta.type, true); return; } if (meta.state != ExtensionState.DISABLED) return; let extensionState = extensionStateObjs[uuid]; extensionOrder.push(uuid); try { extensionState.enable(); } catch(e) { logExtensionError(uuid, e.toString()); return; } meta.state = ExtensionState.ENABLED; _signals.emit('extension-state-changed', meta); } function onEnabledExtensionsChanged() { let newEnabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY); // Find and enable all the newly enabled extensions: UUIDs found in the // new setting, but not in the old one. newEnabledExtensions.filter(function(uuid) { return enabledExtensions.indexOf(uuid) == -1; }).forEach(function(uuid) { enableExtension(uuid); }); // Find and disable all the newly disabled extensions: UUIDs found in the // old setting, but not in the new one. enabledExtensions.filter(function(item) { return newEnabledExtensions.indexOf(item) == -1;

Fo r

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

on
8/13

ly

Updating GNOME Shell Extensions To Work With GNOME 3.2 }).forEach(function(uuid) { disableExtension(uuid); }); enabledExtensions = newEnabledExtensions; }

Changes to the Shell UI by a Shell extension are controlled by whatever code is in the Shell extensions enable and disable methods or functions. Typically, the Shell will call init, and then enable if the Shell extension is enabled, i.e. is in the Shell extension whitelist when the Shell starts up. The Shell may call disable and enable again during the same session. However, the Shell does not call enable or disable if a Shell extension is disabled at startup. Currently, the Shell calls init for all Shell extension including disabled Shell extensions at startup. You can code your Shell extension so that init returns a JavaScript object with two methods called enable and disable. The object type returned does not matter. The Shell does not touch that object except to invoke either the enable or disable methods. If init does not return anything or returns a falsey (something which evaluates to FALSE) object, the Shell then assumes that enable and disable are functions defined within extension.js and acts accordingly.

const Lang = imports.lang; const St = imports.gi.St; const Main = imports.ui.main; const Tweener = imports.ui.tweener; function HelloWorldExtension() { this._init(); } HelloWorldExtension.prototype = { _init: function() { this.button = new St.Bin({ style_class: 'panel-button', reactive: true, can_focus: true, x_fill: true, y_fill: false, track_hover: true }); this.text = null; let icon = new St.Icon({ icon_name: 'system-run', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); this.button.set_child(icon); this.button.connect('button-press-event', Lang.bind(this, this._showHello)); }, _hideHello: function() { Main.uiGroup.remove_actor(this.text); this.text = null; }, _showHello: function() { if (!this.text) { this.text = new St.Label({ style_class: 'helloworld-label', text: "Hello, worl d!" }); Main.uiGroup.add_actor(this.text); } this.text.opacity = 255; let monitor = Main.layout.primaryMonitor; this.text.set_position(Math.floor(monitor.width / 2 - this.text.width / 2), Math.floor(monitor.height / 2 - this.text.height / 2)); Tweener.addTween(this.text, { opacity: 0, time: 2, transition: 'easeOutQuad',

Fo r

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

se

Here is a modified version of the extension.js generated by the 3.2 version of gnome-shell-extension-tool in which init returns a simple object with enable and disable methods.

on

ly

9/13

Updating GNOME Shell Extensions To Work With GNOME 3.2 onComplete: Lang.bind(this, this._hideHello) }); }, enable: function() { Main.panel._rightBox.insert_actor(this.button, 0); }, disable: function() { Main.panel._rightBox.remove_actor(this.button); } }; function init() { return new HelloWorldExtension(); }

From the above, it should be obvious that you can modify your 3.0 Shell extension in one of three ways to enable it to work with GNOME Shell 3.2.
q

For example, here is the GNOME 3.0 version of extension.js for my noa11y Shell extension. The purpose of that extension is to remove the accessibility (AKA a11y) button from the right-hand side of the panel.

const Panel = imports.ui.panel; function main() { let index = Panel.STANDARD_TRAY_ICON_ORDER.indexOf('a11y'); if (index >= 0) { Panel.STANDARD_TRAY_ICON_ORDER.splice(index, 1); } delete Panel.STANDARD_TRAY_ICON_SHELL_IMPLEMENTATION['a11y']; }

Fo r

As you can see it was a very simple extension. It simply blew away the code associated with a11y. Here is the code for GNOME 3.2 version of the same Shell extension:

const Main = imports.ui.main; function NoA11y() { this._init() } NoA11y.prototype = { _init: function() {

11-11-2011

pe rs o

In theory, if you want your Shell extension to work with both GNOME 3.2 and GNOME 3.0, you could add a simple main function that calls init followed by enable. In practice, I have not found this to be workable unless the Shell extension is extremely simple because of the large number of differences in the names of internal variables and the workings of functions between GNOME 3.0 and GNOME 3.2.

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

BAD You can rename the main function to init, and add empty enable and disable functions. The UI is displayed when the extension is loaded and cannot be enabled or disabled. GOOD You can rename the main function to init, and move any code that adds actors, or other UI components, to the Shell into a new enable function and add a new disable function that removes such UI components. BETTER You can rename the main function to init and modify the code in init to return an object that has enable and disable functions.

se

on

ly

The key line is return new HelloWorldExtension. This is the initialized object that is passed back to the Shell. It contains the enable method which inserts the button on the panel and the disable method which removes the button from the panel.

10/13

Updating GNOME Shell Extensions To Work With GNOME 3.2 this._removed = null; }, enable: function() { if (this._removed == true) return; let _a11y = Main.panel._statusArea['a11y']; let children = Main.panel._rightBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i]._delegate == _a11y) { children[i].destroy(); this._removed = true; Main.panel._statusArea['a11y'] = null; break; } } }, disable: function() { if (this._removed == false) return; let _index = 0; let _volume = Main.panel._statusArea['volume']; let children = Main.panel._rightBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i]._delegate == _volume) { _index = i; break; } } if (_index > 0) _index--; let indicator = new Main.panel._status_area_shell_implementation['a11y']; Main.panel.addToStatusArea('a11y', indicator, _index); this._removed = false; }

Here is how the Linux Mint developers implemented their noa11y Shell extension:

Fo r

const Main = imports.ui.main; const Panel = imports.ui.panel; let indicator; let idx = null; function init(extensionMeta) { indicator = new Panel.STANDARD_STATUS_AREA_SHELL_IMPLEMENTATION["a11y"]; } function enable() { // Remove A11Y menu for (let i = 0; i < Main.panel._rightBox.get_children().length; i++) { if (Main.panel._statusArea['a11y'] == Main.panel._rightBox.get_children()[i]._dele gate) { Main.panel._rightBox.get_children()[i].destroy(); break; } } // addToStatusArea would throw an error on disable if we don't set this to null Main.panel._statusArea['a11y'] = null; } function disable() {

11-11-2011

pe rs o

Most of the new code is in support of the enable and disable extension UI functionality. As you can see, supporting such functionality significantly increases the number of lines of code and complexity of the extension. I could easily make a valid argument that you need even more detailed knowledge of the internals of the 3.2 Shell than you needed for the 3.0 Shell to write Shell extensions that support enable and disable functionality.

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

}; function init() { return new NoA11y(); }

lu

se

on

ly
11/13

Updating GNOME Shell Extensions To Work With GNOME 3.2 Main.panel.addToStatusArea("a11y", indicator, idx); }

By the way, their MGSE (Mint GNOME Shell Extensions) repository is well worth checking out. Monkey patching does not work the same in the 3.2 Shell as it did in the 3.0 Shell. Consider the differences between the 3.0 version of my Shell extension that replaces the SYMBOLIC icons on the panel right-hand side with FULLCOLOR icons (much nicer!) Here is the contents of the 3.0 extension.js:

and here is what the 3.2 extension.js contains:

const Main = imports.ui.main; const St = imports.gi.St; function ColorStatusButtonsExtension() { this._init() } ColorStatusButtonsExtension.prototype = { _init: function() { this._fullcolor = null; }, disable: function() { let children = Main.panel._rightBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i] &amp;&amp; children[i]._delegate._iconActor) { children[i]._delegate._iconActor.icon_type = St.IconType.SYMBOLIC; children[i]._delegate._iconActor.style_class = 'system-status-icon'; } } }, enable: function() { let children = Main.panel._rightBox.get_children(); for (let i = 0; i < children.length; i++) { if (children[i] &amp;&amp; children[i]._delegate._iconActor) { children[i]._delegate._iconActor.icon_type = St.IconType.FULLCOLOR; children[i]._delegate._iconActor.style_class = 'color-status-button'; } } } }; function init() { return new ColorStatusButtonsExtension(); }

Fo r

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

const St = imports.gi.St; const Shell = imports.gi.Shell; const Main = imports.ui.main; const PanelMenu = imports.ui.panelMenu; function main() { // monkey patch PanelMenu.SystemStatusButton.prototype._init = function(iconName, tooltipText) { PanelMenu.Button.prototype._init.call(this, 0.0); this._iconActor = new St.Icon({ icon_name: iconName, icon_type: St.IconType.FULLCOLOR, style_class: 'system-status-icon' }); this.actor.set_child(this._iconActor); this.actor.set_style_class_name('panel-status-button'); this.setTooltip(tooltipText); }; }

se

on

ly
12/13

Updating GNOME Shell Extensions To Work With GNOME 3.2

Not all of the work that you need to do to enable your Shell Extension to work with GNOME 3.2 is in extension.js. You also need to modify metadata.json to match the required version of the GNOME Shell (shell-version) and possibly also the GJS version (js-version). See the versionCheck function in extensionSystem.js. To find out the version of GNOME Shell that you are running, enter gnome-shell version, or cat /usr/share/gnome-shell/js/misc/config.js. Currently, I set the shell-version to 3.2 in metadata.json. By the way, if you run into trouble getting your extension to work, try the following command to invoke the GNOME Shell in debug mode:

Fo r

11-11-2011

pe rs o

Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved.

nn a

lu

It is nice to see that the Shell extensions got a modicum of real love in GNOME 3.2. Remember that it is not so long ago that some people were adamant that Shell extensions and themes were an evil aberration, were detracting from the purity of the one single cross-distribution GNOME Shell experience, and should be totally unsupported! With the release of GNOME 3.2, this is no longer the case. The user base won the battle. Hundreds of extensions, some useful, some quirky, had been developed since the release of GNOME 3.0 earlier this year.

se

on

Also remember that Looking Glass is your friend. Get familiar with Looking Glass and you can use it to solve many a coding error. Note however that only the properties and methods of introspected objects that have been used before LG is invoked will show up.

ly

env GJS_DEBUG_OUTPUT=stderr

gnome-shell --replace

13/13

Вам также может понравиться