Tweaking a the GTK+ theme, using CSS

I got asked today:

How do I make the sidebar in evolution as narrow as the one in thunderbird, so I can see all my mail folders ? Doesn’t GTK+’s awesome CSS theming make that sort of thing very easy ?

Well, I was hopeful that it would, but I figured that I better try before saying yes. Here is what I came up with:

First, create a local directory to hold our theme modification:

mkdir ~/.local/share/themes/Adwaita/gtk-3.0

Next, copy the gtk.css file from the system theme:

cp /usr/share/themes/Adwaita/gtk-3.0/gtk.css ~/.local/share/themes/Adwaita/gtk-3.0

Well, that didn’t go as expected:

Oops

What went wrong ? It turns out that the Adwaita css file is pretty minimal:

@import url("resource:///org/gnome/adwaita/gtk-main.css");

All the secret sauce is wrapped up in a resource bundle in the same directory. GTK+ automatically looks for such a bundle in the same directory as the css file when loading themes. With this knowledge, making my local copy of Adwaita work is a simple as linking the resources into the right place:

ln -s /usr/share/themes/Adwaita/gtk-3.0/gtk.gresource ~/.local/share/themes/Adwaita/gtk-3.0

The last step is to add a little bit of css to gtk.css:

EMailSidebar.view {
  font-size: 5px;
}

@import url("resource:///org/gnome/adwaita/gtk-main.css");

And we reach the desired end result:

Small print

I know what you are going to ask:

How did I come up with the selector EMailSidebar.view that matches the evolution sidebar ?

The answer is, I used gtkparasite, which is a pretty useful debugging tool for this sort of problem.

A parasite

And because it is so useful, we are planning to include something very similar to it in GTK+ itself soon. You can follow this bug to track the progress.

Westcoast summit, day three

This being last day of the hackfest, people started to disappear in the afternoon. Before that, we had a planning session for Wayland in GNOME 3.14, and came up with a number of concrete tasks and goals. We decided to use bugzilla to track the tasks, so I’ve updated the existing tracker bug.  One of our goals for this cycle is to make Wayland sessions day-to-day usable and keep them that way. Therefore, we want to get Wayland testing into gnome-continuous and aim for having Wayland sessions working well in Fedora rawhide by the end of this month.

Westcoast Summit, day two

San Francisco
The second day of the hackfest in San Francisco was punctuated by hour-long presentation of the Endless Mobile team about their mission and product. Part of the presentation was done by video stream from Rio.

Around this highlight, the usual small group discussions and hacking continued all day.

I got a demonstration of california from its author. CaliforniaIt is still young, but looks very nice already. ‘Just enough calendar’, was my impression. Since it uses eds underneath, all my stuff shows up right away.

I also had a good exchange with Daniel from elementary about their widget and theming needs.

One of the bigger discussions in the afternoon was about application  development, how to make it easier and remove obstacles. To kick it off, Christian demonstrated his IDE prototype, which looks really nice. The discussion circulated around the expected topics: better documentation, better tools, distribution and revenue, etc. I don’t think there were any grand conclusions, but as far as GTK+ is concerned, I think we should do a few things:

  • Add more topic- or recipe-based documentation. I’ve made a start on this with my Building Applications talk. Some of the examples that were mentioned include: A preference dialog from scratch, Side-bar best practices.
  • Get serious about considering glade part of the (wider) GTK+ project. The challenge for 3.14: Have all widgets supported in glade at the day of the release.
  • Adopt gtkparasite. We’ve let gtkparasite languish in random forks on github for too long. This is really useful technology, and should be fully integrated into glade and other ides.

Throughout the day, I fixed several GTK+ bugs that were pointed out by Daniel and Jim. I also continued to port applications to  DBus-activatable.

Westcoast Summit, day one

The first day of the Westcoast summit is now over. We enjoyed the excellent hospitality of the Endless Mobile crew.

Before coming here, I set up a GNOME goal for converting applications to DBus activation, and with the help of Cosimo, I converted a few applications. This will make applications fit nicely into the coming session setup with systemd –user and kdbus.

One of the longer discussions we had today was around this topic. Lennart explained how things might work: The systemd user instance creates the user bus when a user logs in, and every application that is started via DBus activation is actually spawned by the systemd user instance, and thus gets its own cgroup, and D-Bus policy, which will be used to limit the access that sandboxed applications have on the bus.

Kay demonstrated systemd –user and kdbus launching a full GNOME session on his laptop. Most things just work already. Only a few things will need fixes:

  •  PolicyKit currently uses the session of the caller in determining which authentication agent to talk to, etc.  Applications that are activated on the user bus are not technically part of a session (as in: a descendent of the gnome-session process), so this won’t work anymore.
  • gdm keeps a process around for the lifetime of the session that provides the pam stack used e.g. when unlocking the session, and without a clear session association, this may not work in the same way anymore.

We can work on these issues while we are waiting for kdbus to find its way in the kernel.

After discussing this, and many other topics, we ended the day in a fantastic Chinese restaurant, followed by beer.

Keeping gnome-shell approachable

One aspect that I always found very appealing about gnome-shell is that you could just go in /usr/share/gnome-shell/js, make a few changes, hit Alt-F2 r and try them out. This was a very low barrier to entry – no development environment needed, no days of jhbuilding dependencies. This is at least part of the explanation why shell extensions exist in large numbers. Sure, you still have to make yourself familiar with the internal and external APIs that are used in gnome-shell, and if you are unlucky, then Alt-F2 r will show you not your cool hack, but the fail whale.

I was a bit sad to see that we’ve lost a bit of this newcomer friendliness in 3.12, when all the JavaScript and css files were wrapped up in resources and included in the gnome-shell binary (to be exact, they are located in /usr/lib64/gnome-shell/libgnome-shell.so, not in /usr/bin/gnome-shell itself). Why was this done ? I guess having everything in one file and not spread across the file system makes gnome-shell start up a tiny bit faster (although I’m not sure if anybody has measured this).

But how do I now try gnome-shell changes quickly ? Let see…

For some background, gnome-shell is using the GResource mechanism for embedding the js files in the binary. Under the covers, this puts the files in a separate ELF section and makes their content available in a filesystem-like structure. The application itself can get at the resources e.g.  by constructing GFiles from resource:// URIs, like this:

file = g_file_new_for_uri ("resource:///org/gnome/software/gtk-style.css");

To access the embedded resources from the outside, you can use the gresource utility that is shipped with GLib. It can list the resources and also extract their content. Sadly, there is currently no easy way to replace existing resources with newer versions, since that requires recreating the ELF section and relinking the application.

gnome-shell has quite a few resources; the list looks like this:

gresource list /usr/lib64/gnome-shell/libgnome-shell.so
/org/gnome/shell/extensionPrefs/main.js
/org/gnome/shell/gdm/authPrompt.js
/org/gnome/shell/gdm/batch.js
/org/gnome/shell/gdm/fingerprint.js
/org/gnome/shell/gdm/loginDialog.js
/org/gnome/shell/gdm/oVirt.js
/org/gnome/shell/gdm/realmd.js
/org/gnome/shell/gdm/util.js
...

Here is how I used the gresource tool to get back to gnome-shell tweakability. Since the gresource commandline is not very versatile, I wrote this little script:

#! /bin/sh

gs=/usr/lib64/gnome-shell/libgnome-shell.so

cd $HOME/gnome-shell-js

mkdir -p ui/components ui/status misc perf extensionPrefs gdm

for r in `gresource list $gs`; do
  gresource extract $gs $r > ${r/#\/org\/gnome\/shell/.}
done

After running this script, all the js files that make up the gnome-shell UI can be found in $HOME/gnome-shell. Now I can point gnome-shell at these files with the (undocumented) GNOME_SHELL_JS variable:

GNOME_SHELL_JS=$HOME/gnome-shell-js gnome-shell

And – voila! – gnome-shell is as hackable as it always was.

Dialogs in GTK+ 3.12

Dialogs are getting a face-lift in GTK+ 3.12.

Most of the work on this was done by Jon McCann, I’ve only helped out here and there. The main visible change is the switch to client-side decorations and headerbars.

Here are some examples of GTK+’s built-in complex dialogs with their new look:

File Chooser

Color Chooser

The application chooser has had a bit more work done – we have a search button in the header bar, which makes a search bar appear when clicked.

Application Chooser

The most common dialogs in applications are preference dialogs. gedit shows how these can look with client-side decorations.

Preferences

And then there are simple prompts.  GTK+ has the GtkMessageDialog class for these.  Their new look is maybe the boldest part of this refresh.

Prompt

Of course, GTK+ is used in many places, and client-side decorations may look foreign in some of them. Together with these changes, we introduced a dialogs-use-header setting. Built-in dialogs will fall back to a more traditional appearance if it is not set:

Traditional File Chooser

Traditional Color Chooser

Note that some of the details in my screenshots, such as the blue color for suggested actions, depend on the theme. What you see here is how dialogs will appear with the Adwaita theme in GNOME 3.12.

The GNOME HIG contains a lot of helpful advice on how to make best use of dialogs in your application.

New in GTK+ 3.12: popovers

In the third part of my recap of the GNOME 3.12 development cycle, I’ll talk about some of the changes in GTK+ that I have been involved in.

Popovers have already been discussed quite a bit. Most of the popover implementation has been done by Carlos Garnacho, generalizing his earlier work on touch selection popups that has been in GTK+ since 3.8.

One of the nice things about popovers is that they are just normal containers – you can put any widget into them, and keyboard navigation and input works like everywhere else. This is a marked contrast to menus, which are very specialized. Attempts to put entries, sliders or buttons into menus usually end badly.

I recently acquired a laptop with a touchscreen, so I can say with confidence that popovers are also much easier to use with touch than menus.

Here are some examples of popovers in gedit:

PopoverPopover

My own contribution to popovers has been to convert GtkVolumeButton to use a popover:

Popover

I also made it possible to populate popovers from a GMenuModel, giving you instant popover menus:

Popover

Popovers are still very new, so their adoption in GNOME 3.12 will be somewhat limited. But we are in the lucky position that we already have quite good design guidance for popovers, so this will probably change soon.

App folder configuration

Continuing my 3.12 recap, this post is about gnome-software. I’ve done much less work on it this cycle than the previous one. All the heavy lifting has been done by Richard. The one feature that I did add to gnome-software this cycle is app folder configuration.

GNOME SoftwareGNOME has been moving away from hierarchical menus for applications. It is problematic for many reasons. One problem is the need for a global, hierarchical classification (‘categories’) – the world is just not that simple, and applications don’t always fit into these predefined categories. Another problem is that menus don’t really scale beyond a single level of submenus or beyond more than 10-15 items per menu.   Not to mention that menus are hard to use on touch devices.

The transition from menus and categories to a scrollable grid for applications was pretty much complete in 3.10. But there is still some need for grouping of related applications, and this is where app folders come in. In 3.10, we provided predefined folders for ‘Utilities’ and ‘Sundry’.

In 3.12,  we are adding an easy way for users to create  their own folders.  We chose to add this feature in the application that always shows you a list of all installed applications anyway, gnome -software.

Installed appsThe alternative would be do implement this directly in the shell overview, but that would be pretty complicated, requiring either a selection mode or complex drag-and-drop, so we decided not to do this (at least for now).

Once you’ve selected the apps you want to group, you can select an existing folder in the ‘Add to Folder’ dialog:

Add to FolderOr you can click on the ‘+’ button to create a new folder:

New FolderOnce you have done this, the new dialog will show up in the GNOME shell overview:

OverviewAnd that’s all there is to this feature!

If you are not using gnome-software, the app folder configuration is also available via gsettings.  It is using relocatable schemas, so the required gsettings command-line looks a little different from the usual, and may be worth showing. First,

$ gsettings get org.gnome.desktop.app-folders folder-children
['Utilities', 'Sundry', 'Feet']

will show you the list of defined app folders. Then,

$ gsettings get org.gnome.desktop.app-folders.folder:/org/gnome/desktop/app-folders/folders/Feet/ apps
['dconf-editor.desktop', 'd-feet.desktop', 'devhelp.desktop']

will list the apps that are in the folder named ‘Feet’. The folder schema has a few more settings that you can explore or change with similar commands.

The new gnome-initial-setup

As the development cycle for 3.12 is winding down, I want to take the time to look back at some of the things I’ve worked on this cycle.

First, gnome-initial-setup has received a design overhaul that I’ve implemented together with Jasper. The pages now look a lot more uniform and polished. We use headerbars and we are consistently using list boxes for selections.

The first few pages are about language, region and input.

Language

Region

Input

The network page is skipped if a we have a connection.

Wifi

The timezone map is now properly sized.

Timezone

Online accounts have been moved earlier.

Online Accounts

This lets us pick up avatar and name for the account page from a configured online account, which is something we’ve wanted to do all along:

Account

The on-screen keyboard works during initial setup now:

On-Screen Keyboard

Setting a password has been separated from the account creation:

Password

And thats all!

Summary

Getting the details right

I’ve recently explained how GTK+ does quite a few things for you out of the box.

  • Theming ? You got it.
  • Accessibility ? You’re covered.
  • Keynav ? Sure.

But as it turns out, default implementations can’t always provide the optimum. To go from an application that works ok to one that is gets the details just right, some fine-tuning may be required.

Today, I want to take a look at a few examples of such fine-tuning for keyboard navigation, in particular around lists. I hope this also shows how you can learn tricks and borrow well-working code from other applications. If you ask yourself

How did they do this, and why does my app not do this ?’

look at the source! We all do it, and don’t feel bad about it.

Why lists ? They come in all sizes and shapes, from straightforward and simple to interactive and complex. It is no wonder that GtkTreeView with its supporting classes has around 40000 lines of code.

Connected lists

My first example is about segmented lists of controls. These have become more common in gnome-control-center panels. Here is the accessibility panel:

Accessibility panelWhen you use the arrow keys to navigate among the buttons, the default behavior of GTK+ is to stop when you come to the edge of the container. But in the situation above, we all would expect the focus to jump from the first list to the second.

Thankfully, GTK+ emits a ::keynav-failed signal when you use the arrow keys to go beyond the end of a container, and we can use this to our advantage:

static gboolean
keynav_failed (GtkWidget        *list,
               GtkDirectionType  direction,
               CcUaPanel        *self)
{
  CcUaPanelPrivate *priv = self->priv;
  GList *item, *sections;
  gdouble value, lower, upper, page;

  /* Find the list in the list of GtkListBoxes */
  if (direction == GTK_DIR_DOWN)
    sections = priv->sections;
  else
    sections = priv->sections_reverse;

  item = g_list_find (sections, list);
  g_assert (item);
  if (item->next)
    {
      gtk_widget_child_focus (GTK_WIDGET (item->next->data), direction);
      return TRUE;
    }
...
}

We use this signal handler on every list:

g_signal_connect (list, "keynav-failed",
                  G_CALLBACK (keynav_failed),
                  self);

And thats all! Here is a quick video of this in action (I’m repeatedly using the Down arrow key):

If you watch closely, you’ll notice another fine point of this example – we scroll the panel to keep the focus location visible. This functionality is built into GTK+’s container widgets, and we activate it by setting a focus adjustment on the box that contains all the lists:

adjustment = gtk_scrolled_window_get_vadjustment (GTK_SCROLLED_WINDOW (panel));
gtk_container_set_focus_vadjustment (GTK_CONTAINER (content), adjustment);

These code examples were taken from cc-ua-panel.c in gnome-control-center.

The same trick is also used in the gnome-control-center overview to allow arrow keys to move between several icon views.

Tabbing out

GTK+ uses the Tab key to connect all active UI elements into a focus chain.  The default behavior of GtkListBox is to put all rows into the focus chain – that makes a lot of sense for the previous example where each row contains controls such as buttons, or brings up a dialog when activated.

Sometimes, it is more natural to treat a list as a single item in the focus chain, so that the next Tab key press takes you out of the list. The list content will still be keyboard-accessible with the arrow keys.

A sidebar like in gnome-logs is an example where this makes sense:

A sidebar listTo achieve this behavior, we can override the focus vfunc of our GtkListBox subclass:

widget_class->focus = gl_category_list_focus;

with a function that special-cases Tab key presses:

static gboolean
gl_category_list_focus (GtkWidget *listbox, 
                        GtkDirectionType direction)
{
  switch (direction)
    {
    case GTK_DIR_TAB_BACKWARD:
    case GTK_DIR_TAB_FORWARD:
      if (gtk_container_get_focus_child (GTK_CONTAINER (listbox)))
        {
          /* Force tab events which jump to
           * another child to jump out of the
           * category list.
           */
          return FALSE;
        }
...
}

This code example was adapted from gl-categorylist.c

A back button

The last example does not involve lists, but a simple Back button. For example, gnome-software has one:

A back buttonYou will probably add a mnemonic to the button label, so it can be activated using the Alt-B shortcut. But your users will also expect the Back key on their keyboard to work, and many will probably try Alt-Left as well, since that is what they use in their web browser.

Key events in GTK+ bubble up from the focus widget, and until they are definitively handled by one of the intermediate containers, they eventually reach the toplevel GtkWindow. Therefore, to make the Back key work regardless where the focus currently is, we can override the key_press vfunc of the window:

static gboolean
window_key_press_event (GtkWidget *win,
                        GdkEventKey *event,
                        GsShell *shell)
{
...
  state = event->state & 
       gtk_accelerator_get_default_mod_mask ();
  is_rtl = gtk_widget_get_direction (button) == GTK_TEXT_DIR_RTL;

  if ((!is_rtl && state == GDK_MOD1_MASK &&
        event->keyval == GDK_KEY_Left) ||
      (is_rtl && state == GDK_MOD1_MASK && 
        event->keyval == GDK_KEY_Right) ||
      event->keyval == GDK_KEY_Back)
    {
      gtk_widget_activate (button);
      return GDK_EVENT_STOP;
    }

  return GDK_EVENT_PROPAGATE;
}

If you pay attention to detail, you’ll notice that we use Alt-Left or Alt-Right, depending on the text direction — your Hebrew-speaking users will appreciate.

This code example was taken from gs-shell.c