20
More GNOME Shell Customization More GNOME Shell Customization Finnbarr P. Murphy ([email protected]) If you plan to customize the GNOME Shell in any meaningful way, you need to understand the technologies underlying the GNOME Shell and understand how to write a GNOME Shell extension to provide the customization that you require. In this post I delve deeper into the technologies behind the new GNOME Shell and provide sample code for a number of simple extensions which demonstrate how to customize and extend various components of the GNOME Shell user interface. Essentially, the GNOME Shell is an integrated window manager, compositor, and application launcher. It acts as a composting window manager for the desktop displaying both application windows and other objects in a Clutter scene graph. Most of the UI code is written in JavaScript which accesses Clutter and other underlying libraries via GObject Introspection. Here is a block diagram of the underlying technologies that support the GNOME Shell as of v3.0: This is a modified version of a diagram that exists on the GNOME website. The GNOME Shell uses OpenGL to render graphics. OpenGL uses a hardware accelerated pixel format by default but can support software rendering. However, hardware acceleration is required to run the GNOME Shell as it uses a number of 3D capabilities to accelerate the transforms. Most graphics cards less than 3 years old should support hardware acceleration. If hardware acceleration is unavailable, the GNOME Shell defaults back to a modified version of the GNOME 2 Panel. See Vincent Untz’s post for further information on this fallback mode. In addition, you can For personnal use only 05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/20

More Customizing the GNOME Shell

Embed Size (px)

DESCRIPTION

This document discusses how to customize the GNOME shell using simple extensions. This document is a living document and is updated frequently. See http://blog.fpmurphy.com/2011/05/more-gnome-shell-customization.html for the latest version.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: More Customizing the GNOME Shell

More GNOME Shell Customization

More GNOME Shell CustomizationFinnbarr P. Murphy

([email protected])

If you plan to customize the GNOME Shell in any meaningful way, you need to understand thetechnologies underlying the GNOME Shell and understand how to write a GNOME Shell extensionto provide the customization that you require. In this post I delve deeper into the technologiesbehind the new GNOME Shell and provide sample code for a number of simple extensions whichdemonstrate how to customize and extend various components of the GNOME Shell user interface.

Essentially, the GNOME Shell is an integrated window manager, compositor, and applicationlauncher. It acts as a composting window manager for the desktop displaying both applicationwindows and other objects in a Clutter scene graph. Most of the UI code is written in JavaScriptwhich accesses Clutter and other underlying libraries via GObject Introspection.

Here is a block diagram of the underlying technologies that support the GNOME Shell as of v3.0:

This is a modified version of a diagram that exists on the GNOME website.

The GNOME Shell uses OpenGL to render graphics. OpenGL uses a hardware accelerated pixelformat by default but can support software rendering. However, hardware acceleration is requiredto run the GNOME Shell as it uses a number of 3D capabilities to accelerate the transforms. Mostgraphics cards less than 3 years old should support hardware acceleration. If hardwareacceleration is unavailable, the GNOME Shell defaults back to a modified version of the GNOME 2Panel. See Vincent Untz’s post for further information on this fallback mode. In addition, you can

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 1/20

Page 2: More Customizing the GNOME Shell

More GNOME Shell Customization

force the GNOME Shell to use the fallback mode via a switch in the Settings, System Info panel.Access to OpenGL is via Cogl which is a graphics API that exposes the features of 3D graphicshardware using a more object oriented design than OpenGL.

The Clutter graphics library handles scene graphing. In Clutter, widgets are called actors, andwindows are called stages. A Clutter application contains at least one stage containing actors suchas rectangles, images, or text. A useful online resource for Clutter programming is Programmingin Clutter by Murray Cumming. By the way, the Clutter library is also used in Moblin which, alongwith Maemo, is now part of Meego. Meego uses MX widgets on top of Clutter (a useful tutorial canbe found here) whereas the GNOME Shell uses a Shell Toolkit (St) which implements manycustom actors, such as containers, bins, boxes, buttons and scrollbars that are useful inimplementing GNOME Shell UI features. The Shell Toolkit was derived from the Moblin UI Toolkit.See ../src/st in the gnome-shell GIT source code repository. The Shell Toolkit also implements CSSsupport (see ../src/st/st-theme*) which makes the GNOME Shell themeable. Generally if you seeany object whole name starts with St. you can assume you are dealing with the Shell Toolkit.Accessibility is handled by Cally (Clutter Accessibility Implementation Library). By the way, theGNOME Shell is implemented as a Mutter plugin.

Window management is handled by a modified version of Metacity called Mutter. Before theintroduction of Metacity in GNOME 2.2, GNOME used Enlightenment and then Sawfish as itswindow manager. Metacity uses the GTK+ graphical widget toolkit to create its user interfacecomponents, enabling it to be themeable and blend in with other GTK+ applications. Mutter is anewer compositing window manager based on Metacity and Clutter. Note that the GNOME Shellfallback mode still uses Metacity and Gtk+, whereas the normal hardware accelerated mode usesMutter.

The GObject Introspection layer sits on top of Mutter and the Shell toolkit. One way to look at thislayer is to consider it a glue layer between the Mutter and Shell Tookit libraries and JavaScript.GObject Introspection is used to automatically generate the GObject bindings for JavaScript (gjs)which is the language used to implement the GNOME Shell UI. The actual version of theJavaScript language available using gjs is 1.8.5 as this JavaScript engine is based on Mozilla’sSpidermonkey JavaScript engine.

The goal of GObject Introspection is to describe the set of APIs in a uniform, machine readableXML format called GIR. A typelib is a compiled version of a GIR which is designed to be fast,memory efficient and complete enough so that language bindings can be written on top of itwithout other sources of information. You can examine the contents of a specific typelib file usingg-ir-generate. For example, here is what is stored in the Shell Toolkit typelib file forst_texture_cache_load_uri_sync which I use in Example 7 below.

# g-ir-generate /usr/lib64/gnome-shell/St-1.0.typelib.... <method name="load_uri_sync" c:identifier="st_texture_cache_load_uri_sync" throws="1"> <return-value transfer-ownership="none"> <type name="Clutter.Actor"/> </return-value> <parameters> <parameter name="policy" transfer-ownership="none"> <type name="TextureCachePolicy"/> </parameter> <parameter name="uri" transfer-ownership="none"> <type name="utf8"/> </parameter> <parameter name="available_width" transfer-ownership="none"> <type name="gint32"/> </parameter>

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 2/20

Page 3: More Customizing the GNOME Shell

More GNOME Shell Customization

<parameter name="available_height" transfer-ownership="none"> <type name="gint32"/> </parameter> </parameters> </method>....

You also need to understand the various components that make up the GNOME Shell UI if you aregoing to be successful in customizing the GNOME Shell. Here are the various components of thescreen displayed just after you successfully log in.

Here is a description of the various components:

Top Bar (Also called Panel) – Horizontal bar at the top of the scrren. This is main access point to●

the shell. (../js/ui/panel.js)Activities button (Also called Hotspot) – Button and Hot Corner that brings up the Overview (see●

below). (../js/ui/panel.js)Application menu – Shows the name of the currently-focused application. You can click on it to●

get a menu of relevant actions. (../js/ui/panel.js)Clock – Also contains the Calendar (../js/ui/dateMenu.js)●

System Status Icons – Icons for accessibility options, Bluetooth, keyboard layout, etc. (●

../js/ui/status/*)Status Menu – Also contains the user menu. Lets you change your IM status, personal●

preferences, and exit the session. (../js/ui/statusMenu.js) Notifications Area – The part of the ephemeral horizontal bar at the bottom of screen where●

notifications are displayed. (../js/ui/notificationDaemon.js) Message tray – The part of the ephemeral horizontal bar at the bottom of screen where pending●

notifications and other messages display. (../js/ui/messageTray.js)

All the JavaScript code referenced above and in the next section is under /usr/share/gnome-shell.

The Overview screen is what you see when you click on the Activities button. It is mainlyimplemented in ../js/ui/overview.js. It has the following UI components:

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 3/20

Page 4: More Customizing the GNOME Shell

More GNOME Shell Customization

Here is a description of the various components in this particular screen:

Dash – Vertical bar on the left, that shows your favourite applications. (../js/ui/dash.js)●

View Selector – Lets you pick between Windows and Applications. (../js/ui/viewSelector.js,●

../js/ui/overview.js)Search Entry – When you enter a string, various things (application names, documents, etc.) get●

searched. (../js/ui/viewSelector.js for the entry, and ../js/ui/searchDisplay.js for the display ofsearch results)Workspace List – Vertical bar on the right, with thumbnails for the active workspaces. (●

../js/ui/workspaceSwitcherPopup.js)

In the following examples, I demonstrate how to customize various components of the GNOMEShell UI using extensions or by directly modifying the source code as in Example 7 if an extensionis not possible. I assume that you know JavaScript and the components that form a GNOME Shellextension.

Example 1:

The GNOME Shell developers are pushing hard to eliminate the traditional notification area on thetop bar of GNOME desktops. However for the moment, tray icons are still displayed on the Panelto the left of the System Status area.

For example gnote normally displays in the GNOME Shell message area as shown below:

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 4/20

Page 5: More Customizing the GNOME Shell

More GNOME Shell Customization

With this simple extension:

const Panel = imports.ui.panel;const StatusIconDispatcher = imports.ui.statusIconDispatcher;function main() { // add the notification(s) you want display on the top bar // - one per line. Use the english text string displayed when // hovering your mouse over the bottom right notification area StatusIconDispatcher.STANDARD_TRAY_ICON_IMPLEMENTATIONS['gnote'] = 'gnote';}

you can get gnote to display on the Panel.

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 5/20

Page 6: More Customizing the GNOME Shell

More GNOME Shell Customization

Note how you import a module using the imports keyword. If you want to import a specific APIversion of a module, you can do so by specifying the required version number, e.g.

const Gtk = imports.gi.versions.Gtk = '3.0';

Example 2:

There is an existing extension in the gnome-shell-extensions source code repository calledalternative status menu which replaces the GNOME Shell status menu in its entirety just so thatthe Power Off, Suspend and Hibernate menu options can be displayed instead of just the Suspendmenu option.

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 6/20

Page 7: More Customizing the GNOME Shell

More GNOME Shell Customization

Instead of the brute force approach used by this extension, the code shown below simply locatesthe suspend menu option and replaces it with the three required menu options and the auxiliarysupport functions.

const St = imports.gi.St;const Main = imports.ui.main;const PopupMenu = imports.ui.popupMenu;const Shell = imports.gi.Shell;const Lang = imports.lang;const Gettext = imports.gettext.domain('gnome-shell');const _ = Gettext.gettext;function updateSuspend(object, pspec, item) { item.actor.visible = object.get_can_suspend();}function updateHibernate(object, pspec, item) { item.actor.visible = object.get_can_hibernate();}function onSuspendActivate(item) { Main.overview.hide(); this._screenSaverProxy.LockRemote(Lang.bind(this, function() { this._upClient.suspend_sync(null); }));}function onHibernateActivate(item) { Main.overview.hide(); this._screenSaverProxy.LockRemote(Lang.bind(this, function() { this._upClient.hibernate_sync(null); }));}function changeUserMenu(){ let children = this.menu._getMenuItems(); for (let i = 0; i < children.length; i++) { let item = children[i]; if (item.label) { let _label = item.label.get_text(); // global.log("menu label: " + _label);

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 7/20

Page 8: More Customizing the GNOME Shell

More GNOME Shell Customization

if (_label == _("Suspend")) item.destroy(); } } let item = new PopupMenu.PopupMenuItem(_("Suspend")); item.connect('activate', Lang.bind(this, onSuspendActivate)); this._upClient.connect('notify::can-suspend', Lang.bind(this, updateSuspend, item)); updateSuspend(this._upClient, null, item); this.menu.addMenuItem(item); item = new PopupMenu.PopupMenuItem(_("Hibernate")); item.connect('activate', Lang.bind(this, onHibernateActivate)); this._upClient.connect('notify::can-hibernate', Lang.bind(this, updateHibernate, item)); updateHibernate(this._upClient, null, item); this.menu.addMenuItem(item); item = new PopupMenu.PopupMenuItem(_("Power Off...")); item.connect('activate', Lang.bind(this, function() { this._session.ShutdownRemote(); })); this.menu.addMenuItem(item);}function main(metadata) { // Post 3.0 let statusMenu = Main.panel._userMenu; let statusMenu = Main.panel._statusmenu; changeUserMenu.call(statusMenu);}

There are different types of power hibernation but the above example just uses the default method.Some people might find it useful to have a sleep menu option also.

Note that I have commented out a line of code in the main function. The commented out line iswhat you should use in post 3.0 versions of the GNOME Shell. How you access Panel objects ischanging. See GNOME Bugzilla 646915 for full details. Essentially, a number of Panel objectshave been renamed and a _statusArea object now points to status area PanelButton objects. Theidea is that you will be able to address each Panel object consistently as follows:

Main.panel._activities Main.panel._appMenu Main.panel._dateMenu Main.panel._statusArea.a11y Main.panel._statusArea.volume Main.panel._userMenu

Example 3:

In this example. I show you how to add a menu to the middle of the Panel as shown below:For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 8/20

Page 9: More Customizing the GNOME Shell

More GNOME Shell Customization

Here is the source code for the extension used to create that menu:

const St = imports.gi.St;const Main = imports.ui.main;const PopupMenu = imports.ui.popupMenu;const PanelMenu = imports.ui.panelMenu;const Gettext = imports.gettext;const _ = Gettext.gettext;function PlacesButton() { this._init();}PlacesButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); this._label = new St.Label({ text: _("MyPlaces") }); this.actor.set_child(this._label); Main.panel._centerBox.add(this.actor, { y_fill: true }); let placeid; this.placeItems = []; this.defaultPlaces = Main.placesManager.getDefaultPlaces(); this.bookmarks = Main.placesManager.getBookmarks(); this.mounts = Main.placesManager.getMounts(); // Display default places for ( placeid = 0; placeid < this.defaultPlaces.length; placeid++) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.defaultPlaces[placeid].name)); this.placeItems[placeid].place = this.defaultPlaces[placeid]; this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Display default bookmarks for ( let bookmarkid = 0; bookmarkid < this.bookmarks.length; bookmarkid++, placeid++) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.bookmarks[bookmarkid].name));

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 9/20

Page 10: More Customizing the GNOME Shell

More GNOME Shell Customization

this.placeItems[placeid].place = this.bookmarks[bookmarkid]; this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } if (this.mounts.length > 0) { this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); } // Display default mounts for ( let mountid = 0; mountid < this.mounts.length; placeid++, mountid++ ) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.mounts[mountid].name)); this.placeItems[placeid].place = this.mounts[mountid]; this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } Main.panel._centerBox.add(this.actor, { y_fill: true }); Main.panel._menus.addMenu(this.menu); }};function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("places_button", userExtensionLocalePath); Gettext.textdomain("places_button"); new PlacesButton();}

Notice how you can retrieve details of all places, bookmarks and mounts fromMain.placesManager :

places = Main.placesManager.getDefaultPlaces();bookmarks = Main.placesManager.getBookmarks();mounts = Main.placesManager.getMounts();

Example 4:

In this example, I show you how to extend the previous example to display icons on each menuoption as shown below:

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 10/20

Page 11: More Customizing the GNOME Shell

More GNOME Shell Customization

Here is the modified source code:

const St = imports.gi.St;const Main = imports.ui.main;const PopupMenu = imports.ui.popupMenu;const PanelMenu = imports.ui.panelMenu;const Gettext = imports.gettext;const _ = Gettext.gettext;const MYPLACES_ICON_SIZE = 22;function PlacesButton() { this._init();}PlacesButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); this._label = new St.Label({ text: _("MyPlaces") }); this.actor.set_child(this._label); Main.panel._centerBox.add(this.actor, { y_fill: true }); let placeid; this.placeItems = []; this.defaultPlaces = Main.placesManager.getDefaultPlaces(); this.bookmarks = Main.placesManager.getBookmarks(); this.mounts = Main.placesManager.getMounts(); // Display default places for ( placeid = 0; placeid < this.defaultPlaces.length; placeid++) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.defaultPlaces[placeid].name)); let icon = this.defaultPlaces[placeid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid].addActor(icon, { align: St.Align.END}); this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Display default bookmarks for ( let bookmarkid = 0; bookmarkid < this.bookmarks.length; bookmarkid++, placei

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 11/20

Page 12: More Customizing the GNOME Shell

More GNOME Shell Customization

d++) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.bookmarks[bookmarkid].name)); let icon = this.bookmarks[bookmarkid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid].addActor(icon, { align: St.Align.END}); this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } if (this.mounts.length > 0) { this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); } // Display default mounts for ( let mountid = 0; mountid < this.mounts.length; placeid++, mountid++ ) { this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.mounts[mountid].name)); let icon = this.mounts[mountid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid].addActor(icon, { align: St.Align.END}); this.placeItems[placeid].place = this.mounts[mountid]; this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } Main.panel._centerBox.add(this.actor, { y_fill: true }); Main.panel._menus.addMenu(this.menu); }};function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("places_button", userExtensionLocalePath); Gettext.textdomain("places_button"); new PlacesButton();}

The heavy lifting in creating icons is done by iconFactory which is a JavaScript callback thatcreates an icon texture given a size parameter. It is implemented in ../js/ui/placeDisplay.js

iconFactory: function(size) { let icon = this._mount.get_icon(); return St.TextureCache.get_default().load_gicon(null, icon, size);},

Example 5:

In this example, I show you how to modify the previous example to display icons followed by labelson each menu option as shown below:Fo

r pers

onna

l use

only

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 12/20

Page 13: More Customizing the GNOME Shell

More GNOME Shell Customization

Here is the relevant source code:

const St = imports.gi.St;const Main = imports.ui.main;const PopupMenu = imports.ui.popupMenu;const PanelMenu = imports.ui.panelMenu;const Gettext = imports.gettext;const _ = Gettext.gettext;const MYPLACES_ICON_SIZE = 22;function MyPopupMenuItem() { this._init.apply(this, arguments);}MyPopupMenuItem.prototype = { __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function(icon, text, params) { PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params); this.addActor(icon); this.label = new St.Label({ text: text }); this.addActor(this.label); }};function PlacesButton() { this._init();}function PlacesButton() { this._init();}PlacesButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function() { PanelMenu.Button.prototype._init.call(this, 0.0); this._icon = new St.Icon({ icon_name: 'start-here', icon_type: St.IconType.SYMBOLIC, style_class: 'system-status-icon' }); this.actor.set_child(this._icon); Main.panel._centerBox.add(this.actor, { y_fill: true }); let placeid; this.placeItems = []; this.defaultPlaces = Main.placesManager.getDefaultPlaces();

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 13/20

Page 14: More Customizing the GNOME Shell

More GNOME Shell Customization

this.bookmarks = Main.placesManager.getBookmarks(); this.mounts = Main.placesManager.getMounts(); // Display default places for ( placeid = 0; placeid < this.defaultPlaces.length; placeid++) { let icon = this.defaultPlaces[placeid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid] = new MyPopupMenuItem(icon, _(this.defaultPlaces[placeid].name)); this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); // Display default bookmarks for ( let bookmarkid = 0; bookmarkid < this.bookmarks.length; bookmarkid++, placeid++) { let icon = this.bookmarks[bookmarkid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid] = new MyPopupMenuItem(icon, _(this.bookmarks[bookmarkid].name)); this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } if (this.mounts.length > 0) { this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem()); } // Display default mounts for ( let mountid = 0; mountid < this.mounts.length; placeid++, mountid++ ) { let icon = this.mounts[mountid].iconFactory(MYPLACES_ICON_SIZE); this.placeItems[placeid] = new MyPopupMenuItem(icon, _(this.mounts[mountid].name) ); this.placeItems[placeid].place = this.mounts[mountid]; this.menu.addMenuItem(this.placeItems[placeid]); this.placeItems[placeid].connect('activate', function(actor,event) { actor.place.launch(); }); } Main.panel._centerBox.add(this.actor, { y_fill: true }); Main.panel._menus.addMenu(this.menu); }};function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("places_button", userExtensionLocalePath); Gettext.textdomain("places_button"); new PlacesButton();}

I had to implement my own version of PopupMenuItem called MyPopupMenuItem in order todisplay an icon in front on the menuitem label. This is basically just a wrapper aroundPopupBaseMenuItem.

Example 6:

In this example, I show you how to add an Applications menu next to the Activities button.

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 14/20

Page 15: More Customizing the GNOME Shell

More GNOME Shell Customization

Here is the source code for extension.js:

const St = imports.gi.St;const Main = imports.ui.main;const PopupMenu = imports.ui.popupMenu;const PanelMenu = imports.ui.panelMenu;const Shell = imports.gi.Shell;const Lang = imports.lang;const Gettext = imports.gettext;const _ = Gettext.gettext;const APPMENU_ICON_SIZE = 22;function MyPopupMenuItem() { this._init.apply(this, arguments);}MyPopupMenuItem.prototype = { __proto__: PopupMenu.PopupBaseMenuItem.prototype, _init: function(icon, text, menu_icon_first, params) { PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params); this.label = new St.Label({ text: text }); if (menu_icon_first) { this.box = new St.BoxLayout({ style_class: 'applications-menu-box'}); this.box.add(icon); this.box.add(this.label); this.addActor(this.box); } else { this.addActor(this.label); this.addActor(icon); } }};function ApplicationsButton() { this._init.apply(this, arguments);}ApplicationsButton.prototype = { __proto__: PanelMenu.Button.prototype, _init: function(mode) { PanelMenu.Button.prototype._init.call(this, 0.0); this._icon = new St.Icon({ icon_name: 'fedora-logo-icon', icon_type: St.IconType.FULLCOLOR,

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 15/20

Page 16: More Customizing the GNOME Shell

More GNOME Shell Customization

icon_size: Main.panel.button.height }); this.actor.set_child(this._icon); this._appSys = Shell.AppSystem.get_default(); this._categories = this._appSys.get_sections(); this._menuIconFirst = mode; this._display(); this._appSys.connect('installed-changed', Lang.bind(this, function() { Main.queueDeferredWork(this._reDisplay); })); // add immediately after hotspot Main.panel._leftBox.insert_actor(this.actor, 1); Main.panel._menus.addMenu(this.menu); }, _display: function() { this.appItems = []; for (let id = 0; id < this._categories.length; id++) { this.appItems[this._categories[id]] = new PopupMenu.PopupSubMenuMenuItem(this._categories[id]); this.menu.addMenuItem(this.appItems[this._categories[id]]); } let appInfos = this._appSys.get_flattened_apps().filter(function(app) { return !app.get_is_nodisplay(); }); for (let appid = appInfos.length-1; appid >= 0; appid--) { let appInfo = appInfos[appid]; let icon = appInfo.create_icon_texture(APPMENU_ICON_SIZE); let appName = new MyPopupMenuItem(icon, appInfo.get_name(), this._menuIconFirst); // let appName = new PopupMenu.PopupMenuItem(appInfo.get_name()); appName._appInfo = appInfo; this.appItems[appInfo.get_section()].menu.addMenuItem(appName); appName.connect('activate', function(actor,event) { let id = actor._appInfo.get_id(); Shell.AppSystem.get_default().get_app(id).activate(-1); }); } }, _redisplay: function() { for (let id = 0; id < this._categories.length; id++) { this.appItems[this._categories[id]].menu.destroy(); } this._display(); }};function main(extensionMeta) { let userExtensionLocalePath = extensionMeta.path + '/locale'; Gettext.bindtextdomain("applications_button", userExtensionLocalePath); Gettext.textdomain("applications_button"); new ApplicationsButton(false);}

Again I implemented my own version of PopupMenuItem called MyPopupMenuItem in order tohandle displaying an icon in front on the menuitem label or visa versa as shown below. If false ispassed to ApplicationsButton when creating the new object, menu options are displayed labelfollowed by icon, otherwise they are displayed icon followed by label.

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 16/20

Page 17: More Customizing the GNOME Shell

More GNOME Shell Customization

Example 7:

Not all components of the GNOME Shell can be easily modified or customized. For example,suppose I would like to display a search provider’s logo as an icon on their search provider button.The icon is already available as a base64 string in a search provider’s OpenSearch xml file but isnot currently used by the GNOME Shell.

Here is how the search provider buttons look like at present:

Looking at the current source code for the GNOME Shell, we can see that in search.js the icon

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 17/20

Page 18: More Customizing the GNOME Shell

More GNOME Shell Customization

string is read by parse_search_provider and stored in the _providers array.

_addProvider: function(fileName) { let path = global.datadir + '/search_providers/' + fileName; let source = Shell.get_file_contents_utf8_sync(path); let [success, name, url, langs, icon_uri] = global.parse_search_provider(source);

let provider ={ name: name, url: url, id: this._providers.length, icon_uri: icon_uri, langs: langs }; if (this._checkSupportedProviderLanguage(provider)) { this._providers.push(provider); this.emit('changed'); } },

However it is not passed to where the search provider button is created in searchDisplay.js due toa limitation in the following piece of code in search.js:

getProviders: function() { let res = []; for (let i = 0; i _createOpenSearchProviderButton: function(provider) { let button = new St.Button({ style_class: 'dash-search-button', reactive: true, x_fill: true, y_align: St.Align.MIDDLE }); button.connect('clicked', Lang.bind(this, function() { this._openSearchSystem.activateResult(provider.id); })); let title = new St.Label({ text: provider.name, style_class: 'dash-search-button-label' }); let textureCache = St.TextureCache.get_default(); let searchIcon = textureCache.load_uri_sync(ST_TEXTURE_CACHE_POLICY_FOREVER, provider.icon_uri, -1, -1); let iconBin = new St.Bin({ style_class: 'dash-search-button-icon', child: searchIcon }); let box = new St.BoxLayout(); box.add(iconBin, {expand: true, x_fill: false, x_align: St.Align.END }); box.add(title, {expand: true, x_fill: false, x_align: St.Align.START }); button.set_child(box); provider.actor = button; this._searchProvidersBox.add(button); },

With this modified code, here is how the search provider buttons now look like:Fo

r pers

onna

l use

only

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 18/20

Page 19: More Customizing the GNOME Shell

More GNOME Shell Customization

A GNOME Shell extension could probably be written to monkey patch the modified versions of thetwo functions into the GNOME Shell. It is something I will try to do when I get some free time.

Well, this post is getting too big and so it is time to conclude it. But before I do, I want to mentionsomething about the GNOME Shell that has been on my mind recently as I experiment with itsinternals. For some reason the GNOME developers seem to think that the GNOME Shell should bean integral part of the OS and that is obvious in some of the design decisions. Recently ColinWalters stated that

Where we want to get to is that there are really just three things:

* Apps* Extensions* The OS

This is just plain wrong as far as I am concerned. A user should always have a choice of desktopmanagers and shells. Sounds like the vision of the GNOME developers, at least as articulated byColin Walters in his post, is that the OS and the GNOME Shell should be one and the same as inMicrosoft Windows. If this is the goal, then I fear that many existing users will abandon theGNOME desktop.

Sometimes I get the impression that the GNOME Shell was designed and put together by a groupof arrogant young software engineers who were more interested in adhering to the tenants ofso-called usability and design theory studies than in what their end users really needed in amodern graphical shell. Frankly, I fully agree with what Bruce Byfield recently wrote inDatamation about usability studies hurting the free desktop.

Don’t get me wrong – I like and use the GNOME Shell and really do not want to go back toGNOME 2 or another desktop manager, but I am often frustrated by it’s design constraints whichget in the way of me doing what I want to do quickly and efficiently. Worse, when you look underthe hood of the GNOME Shell, you quickly become aware of serious shortcomings in much of the

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 19/20

Page 20: More Customizing the GNOME Shell

More GNOME Shell Customization

underlying codebase. For example, why is the GNOME shell dependant on two distinct JavaScriptengines, i.e. gjs and seed (webkit)? Pick one of these two JavaScript engines and remove thedependencies on the other. .

Adieu, and keep experimenting!

P.S. All of these extensions will be downloadable here.

For p

erson

nal u

se on

ly

05-15-2011 Copyright 2004-2011 Finnbarr P. Murphy. All rights reserved. 20/20