Translate

Archives

Patching a GNOME Shell theme

Recently I wished to modify how Looking Glass, the GNOME Shell quasi-debugger and code inspector, was styled (or to use the GNOME Shell vernacular – themed.) I did not particularly like the green phosphorus foreground color and wished to change it to a more pleasing (at least to my eyes) white.

I could have simply edited the default theme file, i.e. /usr/share/gnome-shell/theme/gnome-shell.css, but I would have lost such edits if the file was updated when an updated gnome-shell package was installed. Instead, I decided to see if I could write a GNOME Shell extension to achieve what I wanted to do.

Here is what Looking Glass looks like by default:

GNOME3 Shell screenshot

and here is what I would like it to look like:

GNOME3 Shell screenshot

By the way, the wallpaper is called five minutes of day.

Initially I thought I could simply add a custom stylesheet containing the modified LookingGlass selectors in such a way that the modified selectors had the highest priority. Recall from your CSS studies that the stylesheet with the highest priority controls the content presentation. This turned out not to be possible because of design decisions within the St (Shell Toolkit.)

It turns out that the GNOME Shell theming code in supports four types of CSS stylesheets.

  • application. There can be only one application stylesheet at any one time.
  • theme. There can be only one theme stylesheet at any one time.
  • default. There can be only one default stylesheet at any one time.
  • custom. There can be one or more custom stylesheets at any time.

The GNOME Shell UI code (/usr/share/gnome-shell/js/ui) uses the application stylesheet for theming itself and custom stylesheets for theming extensions. It does not appear to use the theme or default stylesheets.

Here is the source code for st_theme_new. This code is basically some wrapper code around the actual object constructor code (g_object_new for St.Theme.

/**
 * st_theme_new:
 * @application_stylesheet: The highest priority stylesheet, representing application-specific
 *   styling; this is associated with the CSS "author" stylesheet, may be %NULL
 * @theme_stylesheet: The second priority stylesheet, representing theme-specific styling ;
 *   this is associated with the CSS "user" stylesheet, may be %NULL
 * @default_stylesheet: The lowest priority stylesheet, representing global default styling;
 *   this is associated with the CSS "user agent" stylesheet, may be %NULL
 *
 * Return value: the newly created theme object
 **/
StTheme *
st_theme_new (const char       *application_stylesheet,
              const char       *theme_stylesheet,
              const char       *default_stylesheet)
{
  StTheme *theme = g_object_new (ST_TYPE_THEME,
                                    "application-stylesheet", application_stylesheet,
                                    "theme-stylesheet", theme_stylesheet,
                                    "default-stylesheet", default_stylesheet,
                                    NULL);

  return theme;
}


Note the description about the relative priority of the three stylesheet types that can be passed to it. As you can see, it can be passed between one and three stylesheets. I use this new knowledge later on when I develop the new extension.

The next item that I looked at for a clue to a possible solution was the source code for the load_stylesheet method in st-theme.c as shown here:

gboolean
st_theme_load_stylesheet (StTheme    *theme,
                          const char *path,
                          GError    **error)
{
  CRStyleSheet *stylesheet;

  stylesheet = parse_stylesheet (path, error);
  if (!stylesheet)
    return FALSE;

  insert_stylesheet (theme, path, stylesheet);
  cr_stylesheet_ref (stylesheet);
  theme->custom_stylesheets = g_slist_prepend (theme->custom_stylesheets, stylesheet);

  return TRUE;
}


As you can see this method only deals with custom stylesheets. These only apply to extensions and thus cannot be used to change the GNOME shell theming as far as I can see.

Next, this is the function used to resolve URLs within stylesheets:

char *
_st_theme_resolve_url (StTheme      *theme,
                       CRStyleSheet *base_stylesheet,
                       const char   *url)
{
  const char *base_filename = NULL;
  char *dirname;
  char *filename;

  /* Handle absolute file:/ URLs */
  if (g_str_has_prefix (url, "file:") ||
      g_str_has_prefix (url, "File:") ||
      g_str_has_prefix (url, "FILE:"))
    {
      GError *error = NULL;
      char *filename;

      filename = g_filename_from_uri (url, NULL, &error);
      if (filename == NULL)
        {
          g_warning ("%s", error->message);
          g_error_free (error);
        }

      return NULL;
    }

  /* Guard against http:// URLs  */
  if (g_str_has_prefix (url, "http:") ||
      g_str_has_prefix (url, "Http:") ||
      g_str_has_prefix (url, "HTTP:"))
    {
      g_warning ("Http URL '%s' in theme stylesheet is not supported", url);
      return NULL;
    }

  /* Assume anything else is a relative URL, and "resolve" it
   */
  if (url[0] == '/')
    return g_strdup (url);

  base_filename = g_hash_table_lookup (theme->filenames_by_stylesheet, base_stylesheet);

  if (base_filename == NULL)
    {
      g_warning ("Can't get base to resolve url '%s'", url);
      return NULL;
    }

  dirname = g_path_get_dirname (base_filename);
  filename = g_build_filename (dirname, url, NULL);
  g_free (dirname);

  return filename;
}


As you can see from the above code there are some limitations to the types of URLs that are supported. Looking at the above code, I decided that I should provide absolute pathnames for images used in the stylesheet which was going to be used to change the look of Looking Glass. These URLs are simply an absolute pathname to the original image files as there was no need to modify the images for what I wished to do.

Armed with this new knowledge, here is extension.js:

//
//  Copyright (c) 2011  Finnbarr P. Murphy   
//

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


function main(extensionMeta) {

    let defaultStylesheet = Main._defaultCssStylesheet;
    let patchStylesheet = extensionMeta.path + '/lookingglass.css';

    let themeContext = St.ThemeContext.get_for_stage(global.stage);
    let theme = new St.Theme ({ application_stylesheet: patchStylesheet,
                                theme_stylesheet: defaultStylesheet });
    try {
        themeContext.set_theme(theme);
    } catch (e) {
        global.logError('Stylesheet parse error: ' + e);
    }

}


You may wonder why I assigned patchStylesheet to application_stylesheet and defaultStylesheet to theme_stylesheet and not visa versa given the comments above the st_theme_new. I did do that but found out that it did not work as I expected as only new attributes in selectors were picked up.

Here is the new stylesheet for the various elements in Looking Glass. Note that I choose to call it lookingglass.css and not stylesheet.css. I did that to check out if there was some impediment in the Shell Toolkit or elsewhere that would force me to use the filename stylesheet.css. It turns out that there in no such impediment for theme stylesheets but extension stylesheets must be named stylesheet.css

/* Modified LookingGlass theme */

#LookingGlassDialog
{
    background-color: rgba(0,0,0,0.7);
    spacing: 4px;
    padding: 4px;
    border: 2px solid grey;
    border-radius: 4px;
    color: white;
}

#LookingGlassDialog > #Toolbar
{
    border: 1px solid grey;
    border-radius: 4px;
}

#LookingGlassDialog .labels {
    spacing: 4px;
}

#LookingGlassDialog .notebook-tab {
    padding: 2px;
}

#LookingGlassDialog .notebook-tab:hover {
    background-image: url("/usr/share/gnome-shell/theme/panel-button-highlight-wide.svg");
    color: white;
    text-shadow: black 0px 2px 2px;
}

#LookingGlassDialog .notebook-tab:selected {
    border-image: url("/usr/share/gnome-shell/theme/panel-button-border.svg") 10 10 0 2;
    background-image: url("/usr/share/gnome-shell/theme/panel-button-highlight-wide.svg");
    color: white;
    text-shadow: black 0px 2px 2px;
}

#LookingGlassDialog .lg-inspector-title {
    font-weight: bold;
    padding-bottom: 8px;
}

.lg-dialog StLabel
{
    color: white;
    font-weight: bold;
}

.lg-dialog StEntry
{
    color: white;
}

.lg-obj-inspector-title
{
    spacing: 4px;
}
.lg-obj-inspector-button
{
    border: 1px solid white;
    padding: 4px;
    border-radius: 4px;
}

.lg-obj-inspector-button:hover
{
    border: 1.5px solid white;
}

.lg-dialog .shell-link
{
    color: white;
    text-decoration: none;
}

.lg-dialog .shell-link:hover
{
    color: white;
    border-image: url("/usr/share/gnome-shell/theme/panel-button-border.svg") 10 10 0 2;
    background-image: url("/usr/share/gnome-shell/theme/panel-button-highlight-wide.svg");
    text-shadow: black 0px 2px 2px;
}

#LookingGlassDialog StBoxLayout#EvalBox
{
    padding: 4px;
    spacing: 4px;
}

#LookingGlassDialog StBoxLayout#ResultsArea
{
    spacing: 4px;
}

#lookingGlassExtensions {
    padding: 4px;
}

.lg-extension-list {
    padding: 4px;
    spacing: 6px;
}

.lg-extension {
    border: 1px solid white;
    border-radius: 4px;
    padding: 4px;
}

.lg-extension-name {
    font-weight: bold;
}

.lg-extension-actions {
    spacing: 6px;
}

#LookingGlassPropertyInspector {
    background: rgba(0, 0, 0, 0.9);
    border: 2px solid grey;
    border-radius: 4px;
    padding: 6px;
    color: white;
}


The extension works as expected. See the second screenshot above.

Well, as you can see, it is possible to patch the GNOME Shell theme stylesheet even though the way we have to do it is a bit unconventional. It might be better if proper support were build into the St. Technically I cannot see a particular reason to restrict application_stylesheet or theme_stylesheet to a single stylesheet.

On a side note, I got prompted by a frequent correspondent, Charles (Chuck) Bowman, to look at why an error message about not being able to create a directory which is always the first error listed on the Looking Glass errors panel. Turns out to be due to a piece of less than stellar coding in extensionSystem.js.

function init() {
    let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
    userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
    try {
        userExtensionsDir.make_directory_with_parents(null);
    } catch (e) {
        global.logError('' + e);
    }

    disabledExtensions = global.settings.get_strv('disabled-extensions', -1);
}


One possible fix is to test whether the extensions directory exists before trying to make it. This is the approach that I took as shown below:

function init() {
    let userExtensionsPath = GLib.build_filenamev([global.userdatadir, 'extensions']);
    userExtensionsDir = Gio.file_new_for_path(userExtensionsPath);
    if (!userExtensionsDir.query_exists(null)) {
        try {
            userExtensionsDir.make_directory_with_parents(null);
        } catch (e) {
            global.logError('' + e);
        }
    }
    disabledExtensions = global.settings.get_strv('disabled-extensions', -1);
}


Another solution would be to handle the different possible errors within different catch statements.

Enjoy and keep experimenting!

P.S. I have placed a copy of the above extension, lookingglasscss-1.0.tar,gz in the GNOME Shell Extensions area of my website.

6 comments to Patching a GNOME Shell theme

  • Charles Bowman

    Thanks for the quick response Finn. Since I’m running on top of Ubuntu I was mostly concerned that it might be a major error, somewhat relieved to know that it was a glitch in the extensionSystem.js. Thank you for the quick fix, I already applied it to get rid of the error message.

    So, you going to mention your statusbuttoncss-1.0.tar.gz hiding in the extensions area? You know I already had to give it a try and I really like the result. :)

    The lookingglass.css looks nice too but for some reason it colors my borders but doesn’t change text. Do you have any suggestions on where I can look?

  • Peter

    Hi there,

    I am really loving your work, I really hope they speed up the extensions website development because yeah at the moment Gnome 3 (on Fedora 15 at any rate), is like Google Chrome.

    Great ideas, great product etc but even better with extensions. However I feel that there is some basic functionality that should be included in the base code and not relying on extensions to achieve what should be possible out of the box.

    While extensions are small and don’t use so many resources, they can cause instability and if you have to run 20 extensions to get your final customised interface i am sure it will degrade performance to some degree.

    I also have a question regarding customising one of your extensions to include a new toggle button. Can I ask you here or is there a particular forum you could direct me to because i’m not having much luck finding knowledgeable people re gnome 3 extensions…

    Again brilliant work man and hope you can keep it coming ;)

  • Frank

    How to remove icons from windows list in bottom panel, to just have titles text but without browser and other applications icons?

  • John

    Hello,

    great job, I love Gnome3 and your posts are so good.

    I have a question about this post. You have write a patched css and change application_stylesheet with it and theme_stylesheet with defaultStylesheet. What about other stuff not looking glass? In the patched css you only write about it.

    Sorry for my english and my possibly stupid question.

    Thank you.

  • eb

    Hello,
    Thanks for great posts about gnome shell. I want to have “Recent Items” tab next to “Applications” tab, can you give me an any guide or sample?

  • Agnelo

    Hi,
    Do you know by chance how I could reload the default theme (after editing /usr/share/gnome-shell/theme/gnome-shell.css) on the command line? I need a command that I could put in a Bash script. A small js script would do it to. Typing Main.loadTheme(); in Looking Glass works. So, how can I execute this function outside of the Looking Glass. I tried the following but got errors (I have no idea how to do it actually):

    gjs -I /usr/share/gnome-shell/js -c “const Main = imports.ui.main; Main.loadTheme();”

    * Thanks a lot for the great infos you’re sharing here! Your blog is amazing.