You are on page 1of 20

More GNOME Shell Customization

More GNOME Shell Customization


Finnbarr P. Murphy
(fpm@fpmurphy.com)

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.

ly
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

on
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:

se
a lu
o nn
rs
pe
r
Fo

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

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


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 graphics
hardware using a more object oriented design than OpenGL.

The Clutter graphics library handles scene graphing. In Clutter, widgets are called actors, and
windows are called stages. A Clutter application contains at least one stage containing actors such
as rectangles, images, or text. A useful online resource for Clutter programming is Programming
in Clutter by Murray Cumming. By the way, the Clutter library is also used in Moblin which, along
with Maemo, is now part of Meego. Meego uses MX widgets on top of Clutter (a useful tutorial can
be found here) whereas the GNOME Shell uses a Shell Toolkit (St) which implements many
custom actors, such as containers, bins, boxes, buttons and scrollbars that are useful in
implementing 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 CSS

ly
support (see ../src/st/st-theme*) which makes the GNOME Shell themeable. Generally if you see
any 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, the

on
GNOME Shell is implemented as a Mutter plugin.

Window management is handled by a modified version of Metacity called Mutter. Before the
introduction of Metacity in GNOME 2.2, GNOME used Enlightenment and then Sawfish as its

se
window manager. Metacity uses the GTK+ graphical widget toolkit to create its user interface
components, enabling it to be themeable and blend in with other GTK+ applications. Mutter is a
lu
newer compositing window manager based on Metacity and Clutter. Note that the GNOME Shell
fallback mode still uses Metacity and Gtk+, whereas the normal hardware accelerated mode uses
Mutter.
a
The GObject Introspection layer sits on top of Mutter and the Shell toolkit. One way to look at this
nn

layer 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 the
JavaScript language available using gjs is 1.8.5 as this JavaScript engine is based on Mozilla’s
o

Spidermonkey JavaScript engine.


rs

The goal of GObject Introspection is to describe the set of APIs in a uniform, machine readable
XML format called GIR. A typelib is a compiled version of a GIR which is designed to be fast,
pe

memory efficient and complete enough so that language bindings can be written on top of it
without other sources of information. You can examine the contents of a specific typelib file using
g-ir-generate. For example, here is what is stored in the Shell Toolkit typelib file for
st_texture_cache_load_uri_sync which I use in Example 7 below.
r
Fo

# 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>

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


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 are
going to be successful in customizing the GNOME Shell. Here are the various components of the
screen displayed just after you successfully log in.

ly
on
se
a lu
o nn
rs

Here is a description of the various components:


pe

● 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)
r

● Application menu – Shows the name of the currently-focused application. You can click on it to
Fo

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 mainly
implemented in ../js/ui/overview.js. It has the following UI components:

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


More GNOME Shell Customization

ly
on
se
lu
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)
a
● View Selector – Lets you pick between Windows and Applications. (../js/ui/viewSelector.js,
../js/ui/overview.js)
nn

● 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 of
search results)
o

● Workspace List – Vertical bar on the right, with thumbnails for the active workspaces. (
../js/ui/workspaceSwitcherPopup.js)
rs

In the following examples, I demonstrate how to customize various components of the GNOME
pe

Shell UI using extensions or by directly modifying the source code as in Example 7 if an extension
is not possible. I assume that you know JavaScript and the components that form a GNOME Shell
extension.
r

Example 1:
Fo

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

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

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


More GNOME Shell Customization

ly
on
se
With this simple extension:
a lu
const Panel = imports.ui.panel;
const StatusIconDispatcher = imports.ui.statusIconDispatcher;
nn

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
o

StatusIconDispatcher.STANDARD_TRAY_ICON_IMPLEMENTATIONS['gnote'] = 'gnote';
}
rs

you can get gnote to display on the Panel.


pe
r
Fo

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


More GNOME Shell Customization

ly
on
se
lu
Note how you import a module using the imports keyword. If you want to import a specific API
version of a module, you can do so by specifying the required version number, e.g.
a
const Gtk = imports.gi.versions.Gtk = '3.0';
nn

Example 2:
o

There is an existing extension in the gnome-shell-extensions source code repository called


alternative status menu which replaces the GNOME Shell status menu in its entirety just so that
rs

the Power Off, Suspend and Hibernate menu options can be displayed instead of just the Suspend
menu option.
pe
r
Fo

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


More GNOME Shell Customization

ly
on
se
lu
Instead of the brute force approach used by this extension, the code shown below simply locates
the suspend menu option and replaces it with the three required menu options and the auxiliary
support functions.
a
nn

const St = imports.gi.St;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
o

const Shell = imports.gi.Shell;


const Lang = imports.lang;
rs

const Gettext = imports.gettext.domain('gnome-shell');


const _ = Gettext.gettext;
function updateSuspend(object, pspec, item) {
pe

item.actor.visible = object.get_can_suspend();
}
function updateHibernate(object, pspec, item) {
item.actor.visible = object.get_can_hibernate();
}
r

function onSuspendActivate(item) {
Fo

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);

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


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..."));

ly
item.connect('activate', Lang.bind(this, function() {
this._session.ShutdownRemote();
}));

on
this.menu.addMenuItem(item);
}
function main(metadata) {
// Post 3.0 let statusMenu = Main.panel._userMenu;
let statusMenu = Main.panel._statusmenu;

se
changeUserMenu.call(statusMenu);
}
lu
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.
a
Note that I have commented out a line of code in the main function. The commented out line is
what you should use in post 3.0 versions of the GNOME Shell. How you access Panel objects is
nn

changing. See GNOME Bugzilla 646915 for full details. Essentially, a number of Panel objects
have been renamed and a _statusArea object now points to status area PanelButton objects. The
idea is that you will be able to address each Panel object consistently as follows:
o
rs

Main.panel._activities
Main.panel._appMenu
pe

Main.panel._dateMenu
Main.panel._statusArea.a11y
Main.panel._statusArea.volume
Main.panel._userMenu
r

Example 3:
Fo

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

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


More GNOME Shell Customization

ly
on
se
lu
Here is the source code for the extension used to create that menu:
a
const St = imports.gi.St;
const Main = imports.ui.main;
nn

const PopupMenu = imports.ui.popupMenu;


const PanelMenu = imports.ui.panelMenu;
const Gettext = imports.gettext;
const _ = Gettext.gettext;
o

function PlacesButton() {
this._init();
rs

}
PlacesButton.prototype = {
__proto__: PanelMenu.Button.prototype,
pe

_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 });
r

let placeid;
this.placeItems = [];
Fo

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[pl
aceid].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++, placei
d++) {
this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.bookmarks[bookma
rkid].name));

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


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) {

ly
actor.place.launch();
});
}

on
Main.panel._centerBox.add(this.actor, { y_fill: true });
Main.panel._menus.addMenu(this.menu);
}
};
function main(extensionMeta) {

se
let userExtensionLocalePath = extensionMeta.path + '/locale';
Gettext.bindtextdomain("places_button", userExtensionLocalePath);
Gettext.textdomain("places_button");
new PlacesButton();
}
lu
Notice how you can retrieve details of all places, bookmarks and mounts from
a
Main.placesManager:
nn

places = Main.placesManager.getDefaultPlaces();
bookmarks = Main.placesManager.getBookmarks();
o

mounts = Main.placesManager.getMounts();
rs

Example 4:
pe

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

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


More GNOME Shell Customization

ly
on
se
Here is the modified source code:
a lu
const St = imports.gi.St;
const Main = imports.ui.main;
nn

const PopupMenu = imports.ui.popupMenu;


const PanelMenu = imports.ui.panelMenu;
const Gettext = imports.gettext;
const _ = Gettext.gettext;
o

const MYPLACES_ICON_SIZE = 22;


function PlacesButton() {
rs

this._init();
}
PlacesButton.prototype = {
pe

__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);
r

Main.panel._centerBox.add(this.actor, { y_fill: true });


let placeid;
Fo

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[pl
aceid].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

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


More GNOME Shell Customization

d++) {
this.placeItems[placeid] = new PopupMenu.PopupMenuItem(_(this.bookmarks[bookma
rkid].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].

ly
name));
let icon = this.mounts[mountid].iconFactory(MYPLACES_ICON_SIZE);
this.placeItems[placeid].addActor(icon, { align: St.Align.END});

on
this.placeItems[placeid].place = this.mounts[mountid];
this.menu.addMenuItem(this.placeItems[placeid]);
this.placeItems[placeid].connect('activate', function(actor,event) {
actor.place.launch();
});

se
}
Main.panel._centerBox.add(this.actor, { y_fill: true });
Main.panel._menus.addMenu(this.menu);
}
};
lu
function main(extensionMeta) {
let userExtensionLocalePath = extensionMeta.path + '/locale';
Gettext.bindtextdomain("places_button", userExtensionLocalePath);
a
Gettext.textdomain("places_button");
new PlacesButton();
nn

The heavy lifting in creating icons is done by iconFactory which is a JavaScript callback that
o

creates an icon texture given a size parameter. It is implemented in ../js/ui/placeDisplay.js


rs

iconFactory: function(size) {
pe

let icon = this._mount.get_icon();


return St.TextureCache.get_default().load_gicon(null, icon, size);
},
r

Example 5:
Fo

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

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


More GNOME Shell Customization

ly
on
se
Here is the relevant source code:
a lu
const St = imports.gi.St;
const Main = imports.ui.main;
nn

const PopupMenu = imports.ui.popupMenu;


const PanelMenu = imports.ui.panelMenu;
const Gettext = imports.gettext;
const _ = Gettext.gettext;
o

const MYPLACES_ICON_SIZE = 22;


function MyPopupMenuItem() {
rs

this._init.apply(this, arguments);
}
MyPopupMenuItem.prototype = {
pe

__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 });
r

this.addActor(this.label);
}
Fo

};
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();

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


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[plac
eid].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++, placei
d++) {

ly
let icon = this.bookmarks[bookmarkid].iconFactory(MYPLACES_ICON_SIZE);
this.placeItems[placeid] = new MyPopupMenuItem(icon, _(this.bookmarks[bookmark
id].name));

on
this.menu.addMenuItem(this.placeItems[placeid]);
this.placeItems[placeid].connect('activate', function(actor,event) {
actor.place.launch();
});
}

se
if (this.mounts.length > 0) {
this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
}
// Display default mounts
lu
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].na
me) );
a
this.placeItems[placeid].place = this.mounts[mountid];
this.menu.addMenuItem(this.placeItems[placeid]);
nn

this.placeItems[placeid].connect('activate', function(actor,event) {
actor.place.launch();
});
}
o

Main.panel._centerBox.add(this.actor, { y_fill: true });


Main.panel._menus.addMenu(this.menu);
rs

}
};
function main(extensionMeta) {
pe

let userExtensionLocalePath = extensionMeta.path + '/locale';


Gettext.bindtextdomain("places_button", userExtensionLocalePath);
Gettext.textdomain("places_button");
new PlacesButton();
}
r
Fo

I had to implement my own version of PopupMenuItem called MyPopupMenuItem in order to


display an icon in front on the menuitem label. This is basically just a wrapper around
PopupBaseMenuItem.

Example 6:

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

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


More GNOME Shell Customization

ly
on
se
Here is the source code for extension.js:
a lu
const St = imports.gi.St;
const Main = imports.ui.main;
nn

const PopupMenu = imports.ui.popupMenu;


const PanelMenu = imports.ui.panelMenu;
const Shell = imports.gi.Shell;
const Lang = imports.lang;
o

const Gettext = imports.gettext;


const _ = Gettext.gettext;
rs

const APPMENU_ICON_SIZE = 22;


function MyPopupMenuItem() {
this._init.apply(this, arguments);
pe

}
MyPopupMenuItem.prototype = {
__proto__: PopupMenu.PopupBaseMenuItem.prototype,
_init: function(icon, text, menu_icon_first, params) {
PopupMenu.PopupBaseMenuItem.prototype._init.call(this, params);
r

this.label = new St.Label({ text: text });


if (menu_icon_first) {
Fo

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,

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


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++) {

ly
this.appItems[this._categories[id]] = new PopupMenu.PopupSubMenuMenuItem(this.
_categories[id]);
this.menu.addMenuItem(this.appItems[this._categories[id]]);

on
}
let appInfos = this._appSys.get_flattened_apps().filter(function(app) {
return !app.get_is_nodisplay();
});
for (let appid = appInfos.length-1; appid >= 0; appid--) {

se
let appInfo = appInfos[appid];
let icon = appInfo.create_icon_texture(APPMENU_ICON_SIZE);
let appName = new MyPopupMenuItem(icon, appInfo.get_name(), this._menuIconFirs
t);
lu
// 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) {
a
let id = actor._appInfo.get_id();
Shell.AppSystem.get_default().get_app(id).activate(-1);
nn

});
}
},
_redisplay: function() {
o

for (let id = 0; id < this._categories.length; id++) {


this.appItems[this._categories[id]].menu.destroy();
rs

}
this._display();
}
pe

};
function main(extensionMeta) {
let userExtensionLocalePath = extensionMeta.path + '/locale';
Gettext.bindtextdomain("applications_button", userExtensionLocalePath);
Gettext.textdomain("applications_button");
r

new ApplicationsButton(false);
Fo

Again I implemented my own version of PopupMenuItem called MyPopupMenuItem in order to


handle displaying an icon in front on the menuitem label or visa versa as shown below. If false is
passed to ApplicationsButton when creating the new object, menu options are displayed label
followed by icon, otherwise they are displayed icon followed by label.

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


More GNOME Shell Customization

ly
on
se
Example 7:
lu
Not all components of the GNOME Shell can be easily modified or customized. For example,
a
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 is
nn

not currently used by the GNOME Shell.

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


o
rs
pe
r
Fo

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

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


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');

ly
}
},

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

se
getProviders: function() {
let res = [];
for (let i = 0; i
lu
_createOpenSearchProviderButton: function(provider) {
let button = new St.Button({ style_class: 'dash-search-button',
reactive: true,
a
x_fill: true,
y_align: St.Align.MIDDLE });
nn

button.connect('clicked', Lang.bind(this, function() {


this._openSearchSystem.activateResult(provider.id);
}));
let title = new St.Label({ text: provider.name,
o

style_class: 'dash-search-button-label' });


let textureCache = St.TextureCache.get_default();
rs

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',
pe

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);
r

provider.actor = button;
this._searchProvidersBox.add(button);
Fo

},

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

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


More GNOME Shell Customization

ly
on
se
lu
A GNOME Shell extension could probably be written to monkey patch the modified versions of the
two functions into the GNOME Shell. It is something I will try to do when I get some free time.
a
Well, this post is getting too big and so it is time to conclude it. But before I do, I want to mention
something about the GNOME Shell that has been on my mind recently as I experiment with its
nn

internals. For some reason the GNOME developers seem to think that the GNOME Shell should be
an integral part of the OS and that is obvious in some of the design decisions. Recently Colin
Walters stated that
o
rs

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

* Apps
pe

* Extensions
* The OS
r

This is just plain wrong as far as I am concerned. A user should always have a choice of desktop
Fo

managers and shells. Sounds like the vision of the GNOME developers, at least as articulated by
Colin Walters in his post, is that the OS and the GNOME Shell should be one and the same as in
Microsoft Windows. If this is the goal, then I fear that many existing users will abandon the
GNOME desktop.

Sometimes I get the impression that the GNOME Shell was designed and put together by a group
of arrogant young software engineers who were more interested in adhering to the tenants of
so-called usability and design theory studies than in what their end users really needed in a
modern graphical shell. Frankly, I fully agree with what Bruce Byfield recently wrote in
Datamation 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 to
GNOME 2 or another desktop manager, but I am often frustrated by it’s design constraints which
get in the way of me doing what I want to do quickly and efficiently. Worse, when you look under
the hood of the GNOME Shell, you quickly become aware of serious shortcomings in much of the

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


More GNOME Shell Customization

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

Adieu, and keep experimenting!

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

ly
on
se
a lu
o nn
rs
pe
r
Fo

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