More GNOME Shell Customization

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 Clutterscene 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 force the GNOME Shell to use the fallback mode via a switch in the Settings, System Info panel. Whether a graphics card supports hardware acceleration or not is determined by a helper application /usr/libexec/gnome-session-is-accelerated which returns either 0 or 1. Another limitation in the 3.0 version of the GNOME shell is that the total horizontal or vertical pixel count of all your monitors must be less than 2048.

As you can see, the use of gotos is alive and well! If you are certain that your graphics card supports hardware acceleration but for some reason GNOME Shell only works in fallback mode, try replacing this application with a script that simply returns true. There is also a black list of graphic cards. According to Emmanuele Bassi:

the hard limit is on Intel 915/945 integrated graghics (which are sadly what netbooks generally use). The limit also used to exist on 965 but it was a soft limit that was lifted by fixing the driver in Mesa

You can also force the fallback mode using a gsettings key:

$ gsettings set org.gnome.desktop.session session-name gnome-fallback

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

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 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 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. By the way, the GNOME Shell is actually implemented as a Mutter plugin. You can obtain copious amounts of debug information from Mutter using imports.gi.Meta.set_verbose(true) or you can control the volume of debug information using the add_verbose_topic method, e.g. to view only window state use Meta = imports.gi.Meta; Meta.add_verbose_topic(Meta.DebugTopic.WINDOW_STATE).

The GObject Introspection layer sits on top of Mutter and the Shell toolkit. One way to look at this 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 Spidermonkey JavaScript engine.

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, 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.

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.

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)

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)

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 GNOME 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.

Example 1:

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:

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.

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.

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

Example 2:

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 the Power Off, Suspend and Hibernate menu options can be displayed instead of just the Suspend menu option.

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.

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 is what you should use in post 3.0 versions of the GNOME Shell. How you access Panel objects is 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:

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.

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 is not 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 string is read by parse_search_provider and stored in the _providers array.

The next problem to overcome is how to decode the icon_uri base64 string and convert it into an actor.to displaying a search provider’s icon on their button. To do this you need to modify _createOpenSearchProviderButton in searchDisplay.js to display the icon as well as the name of the search provider.

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

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.

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

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 desktop 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.

Fortunately, the decision to use JavaScript for the GNOME Shell UI may prove to be the salvation of the GNOME Shell as it allows ordinary users to quickly experiment with modifying parts of the UI. From what I see on the Internet, there are quite a few people experimenting with customizing the GNOME Shell either by directly modifying the source code or by developing extensions. While GNOME developers such as Lennart Poettering are advocating stricter vertical integration of the platform, I think that the decision to use JavaScript will be key to enabling users of GNOME Shell to easily bypass the diktats of the anointed few.

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 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. And why am I forced to use Evolution for calendaring? What happened to choice?

Adieu, and keep experimenting!

P.S. All of the extensions in this post will be downloadable from here.

May 12th, 2011

88 comments to More GNOME Shell Customization

I tried to modify the ‘autohidetopbar’ extension, because I only want to see the top panel in the Overview screen. I think I only have to edit the lines ‘Main.panel.actor.connect(‘leave-event’, Lang.bind(Main.panel, Main.panel._hidePanel));’ and ‘Main.panel.actor.connect(‘enter-event’, Lang.bind(Main.panel, Main.panel._showPanel));’. The problem is that I don’t know where to connect functions to.

I tried it with things like ‘Main.overview.connect(“show”, Lang.bind(Main.panel, Main.panel._showPanel));’, but that didn’t work.

Hi,
nice tutorials. I have a question too:
Do you know how to get subfolders or files of a folder (e.g. home). I don’t find anything in lg and dunno where to find an API or something like that.
As programmer it’s easy to follow your examples, but hard to write something new, cause there is no docs or is there?

Thanks for this: I was able to modify it to add my favourite apps’ systray icons to the panel.

Some comments:

1. You declare const Panel, but don’t use it; why?

2. The standard format for extensions now seems to be to have init, enable and disable methods. I tried renaming your main method to enable, providing a blank init method, but it doesn’t work; also, I tried to add a disable method which set the various elements to null instead of a string, but that didn’t work either. Any hints on making this extension better-behaved (so it can e.g. be turned on and off in gnome-tweak-tool?).

3. The comment says “Use the english text string displayed when hovering your mouse over the bottom right notification area”, but this seems to be wrong: for example, for caffeine (the screensaver controller), the text string is caffeine-cup-empty (even when it’s full!) but the string to use is “caffeine”. Similarly, the string displayed for GMPC is “GNOME Music Player” or something, but the string to use is “gmpc”. Hence, the correct string would seem to be the application name; at least, that’s more likely to work.

4. Could you please put a mailto: link on your contact page? Using web forms is a pain as I don’t get to keep a record of what I wrote. (The way to avoid spam is to use a spam filter, not to hide your email address!)

I tried sending this as an email, but I got an error “Illegal characters in POST. Possible email injection attempt.”

First and foremost your Gnome 3 extensions are brilliant and much appreciated! Thanks for the tweaks and tutorial.

I switched to Gnome 3 on Ubuntu 11.10 and hiding the top bar is the feature I desire Most! I installed your “autohidetopbar” extension and it didn’t work at first. Using looking glass I got errors for missing init, enable, and disable methods (is that new on the Gnome 3 moving target, or just an Ubuntu distro issue…). “As a test” I modified the extension to add empty “enable” and “disable” methods, then created “init” to call “main”. I also updated the shell version in .json to 3.2.0.

That made the Top bar hide on double click, but then I couldn’t get it to reveal again. So I was curious, do you have a version of the autohidetopbar extension that addresses the issue I’m encountering?

I’m sure you’re sick of requests for this by now, but could you please update the autohide top bar extension for Gnome 3.2 in Ubuntu 11.10? I can confirm that updating the version in .json doesn’t help. I even tried moving the autohide extension to /usr/share/gnome-shell/extensions with all the other gnome-shell extensions. It shows up in the gnome-tweak shell extension section, but it has an exclamation mark next to it and the on/off switch is grayed out.

Love your site and all your hard work on Gnome-shell, otherwise. You single handedly made Gnome Shell usable for me.

I tried to modify the ‘autohidetopbar’ extension, because I only want to see the top panel in the Overview screen. I think I only have to edit the lines ‘Main.panel.actor.connect(‘leave-event’, Lang.bind(Main.panel, Main.panel._hidePanel));’ and ‘Main.panel.actor.connect(‘enter-event’, Lang.bind(Main.panel, Main.panel._showPanel));’. The problem is that I don’t know where to connect functions to.

I tried it with things like ‘Main.overview.connect(“show”, Lang.bind(Main.panel, Main.panel._showPanel));’, but that didn’t work.

1. Line 104, change “main” to “init” –> function init() {
2. add the following at the end of file:

function enable() {

}

function disable() {

}

Then modify Line 2 of “metadata.json” to:
“shell-version”: [“3.2”]

Well, it’s kind of working although the top panel is not really hideaway that only the background is transparent and the rest is in very light gray. All windows can be maximized to the top of screen and you still can see topbar in Overview screen.

If you move the mouse cursor all the way to the top of screen (without going into overview screen) you still can use any item on the topbar like checking calender, battery status, changing volume, and etc.

It is not perfect yet but for a person like me who don’t know a thing about java script, this modified extension is now working great.