How to use the system GTK theme via the gtk-common-themes snap

A common request is to allow applications to follow the GTK theme of the host system desktop. While we don’t yet have all the infrastructure in place for arbitrary themes, we do now have one that will automatically handle the default themes of common Linux distributions. This will be sufficient for applications based on GTK 3, and Qt 5 apps using the libqgtk3.so platform theme.

I’ve put together a short Snapcraft project that demonstrates how everything fits together:

Now for a breakdown of the important parts of this project:

Content interface plugs

The theme data is made available through a set of content interface plugs. This can be achieved by adding the following boilerplate to the plugs: stanza of the project:

plugs:
  gtk-3-themes:
    interface: content
    target: $SNAP/share/themes
    default-provider: gtk-common-themes
  icon-themes:
    interface: content
    target: $SNAP/share/icons
    default-provider: gtk-common-themes
  sound-themes:
    interface: content
    target: $SNAP/share/sounds
    default-provider: gtk-common-themes

This will cause various pieces of theme data to be mounted under $SNAP/share, and to source it from the gtk-common-themes snap by default. As with other content interfaces, snapd will use the default-provider line to install the gtk-common-themes snap if it isn’t present.

In addition to the plug definitions, I’ve also made sure one of the parts will create the directories used as content interface mount points and ensure they are primed.

In the future, we hope to remove the need for much of this boilerplate through the use of Snapcraft templates.

Allowing GTK to detect the selected system theme

The way GTK determines what the current theme is depends on the windowing system in use:

  • On an X11 session, the XSETTINGS specification is used. This only involves a set of X11 calls, so plugging the x11 interface is sufficient.
  • On a Wayland session, GTK instead uses the GSettings API to read the users preferences directly, bypassing the display server. So plugging the gsettings interface is also necessary.

Putting that all together, most apps should plug at least the following:

  - desktop
  - gsettings
  - wayland
  - x11

How do I know if everything is working?

Other than changing your desktop’s current theme, GTK lets you use the GTK_THEME environment variable to override the default theme. So using our example project, I can run the app using a theme not currently installed on the host system:

$ GTK_THEME=Arc-Dark gtk3-demo

You can also inspect the available themes within the confinement sandbox:

$ snap run --shell gtk3-demo

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

$ ls $SNAP/share/themes
Adwaita       Ambiance	Arc-Dark    Communitheme  EvoPop-Azure	Radiance
Adwaita-dark  Arc	Arc-Darker  EvoPop	  HighContrast

What about uncommon themes?

We don’t currently have a system in place to automatically install third party theme snaps yet, but that should come in future. The way we’ve used the content interface is intended to allow third party themes to supplement those provided by gtk-common-themes rather than replacing those themes. So a GTK theme that re-uses a common icon theme doesn’t need to repackage all the icons.

If a third party snap also provides a gtk-3-themes slot, it can be connected to applications simultaneously with gtk-common-themes. The gtk-common-themes snap can also be used as a template for your own third party theme snaps:

To test these additional snaps while we’re working on an automatic install solution, you will need to manually install the theme snap and then manually connect the interfaces to each application snap.

9 Likes

This looks like cool stuff. A question: you say “this can be achieved by adding the following boilerplate to the plugs: stanza of the project”, but surely this should be in there by default? If a developer wants their app to look inconsistent then that seems like something worth adding configuration for; an app should surely not have to opt in to looking normal? Possibly I’ve misunderstood this, though!

4 Likes

This doesn’t seem to be working with Mattermost. However, when I run snap run --shell mattermost-desktop, I see the themes are mounted correctly in share/*.

Just to clarify; does this also work with GTK2 applications or only GTK3 applications?

I think, but am ready to hold my hand up to the fact that it’s a guesstimate, that the sound and icon themes will work cross-toolkit but the themes will only work with gtk3 because gtk2 needs some library support in addition to styling rules. GTK3 doesn’t have the library requirement for it’s themes, so they’re easier to move between builds.

1 Like

@jamesh

Can you turn your post into a wiki? I’m experimenting with getting GTK2 theming working and I’d like to add it to your post.

Also, why do you use $SNAP/share instead of $SNAP/usr/share? A lot more stuff magically works when you use $SNAP/usr/share, I’m still trying to find why but I think it’s because that’s where the desktop helpers expect the themes to be.

1 Like

I got it working for the Communitheme GTK2 theme. It should work for most other GTK2 themes too.

How to use gtk-common-themes with GTK2 app?

It’s quite simple if you’re already using the desktop-helpers.

  1. Add the GTK2 theme engine to stage-packages. In the case of communitheme, this is gtk2-engines-pixbuf.
  2. Add the plugs with target $SNAP/usr/share instead of $SNAP/share.

For a full working example of an electron app with GTK2 using desktop helpers, see the Mattermost snapcraft.yaml file.

2 Likes

Note that there is still an issue somewhere. The first time users refresh to a snap using gtk-common-themes, they get the following error message:

error: cannot perform the following tasks:
- Download snap "gtk-common-themes" (319) from channel "stable" (rename /var/lib/snapd/snaps/gtk-common-themes_319.snap.partial /var/lib/snapd/snaps/gtk-common-themes_319.snap: no such file or directory)
- Download snap "gtk-common-themes" (319) from channel "stable" (rename /var/lib/snapd/snaps/gtk-common-themes_319.snap.partial /var/lib/snapd/snaps/gtk-common-themes_319.snap: no such file or directory)

Despite this error message, the refresh seems to succeed. However, some users are reporting that gtk-common-themes is still not installed: Auto-connect doesn't seem to work on refresh and the original issue: https://github.com/ubuntu/gtk-communitheme/issues/354#issuecomment-401633857

Actually the desktop helpers are looking for them in $SNAP/data-dir

This is because the mount fails when the directory already exists. For an example see:

https://bazaar.launchpad.net/~ubuntu-desktop/quadrapassel/snap/view/head:/snapcraft.yaml#L26

Ultimately we don’t want everyone to put this into their snaps, we want developers to use the upcoming template feature in snapcraft to prevent lots of boiler plate yaml that ends up being hard to maintain.

2 Likes

This doesn’t seem to be the case when I test it. When I use snap run --shell mattermost-desktop to look into those directories, I see the mounted ones, not the original ones. It seems to work just like a normal mount, hiding the original directory.

That is where we want to get to eventually, but don’t have the infrastructure in place yet. I mentioned the upcoming Snapcraft Templates feature a little further down in that section as a solution, which will allow the required interface definitions to be inserted automatically.

While that feature isn’t quite ready yet, I thought there was value in sharing details of how people can test our what we’ve got working right now.

At the moment this is just GTK 3. GTK 2 is more complicated due to the use of native code theme engines that need to match the GTK 2 build the application is using.

While you managed to get Communitheme working by packaging the pixbuf engine with your app, that wouldn’t be sufficient for e.g. Ubuntu’s Ambiance/Radiance themes that use the murrine engine.

We will probably look at dealing with GTK 2 themes in future (together with packaging the binary engines), but having third parties packaging their themes will likely require more review due to the fact that the theme engines can effectively do anything.

For Electron apps, it is worth considering moving forward to an Electron build that uses GTK 3, since that will be required for other upcoming desktop related features anyway.

1 Like

Ah, gotcha; that makes sense, and so this will just work later on. That will be good! Nice work.

Snapcraft Templates seems like a good way to go. In the meantime, can we add this support to the desktop helpers? It already has some support for themes, but using gtk-common-themes would make that more robust, support more themes and use less space.

Communitheme is gaining a lot of traction but isn’t supported by default by desktop-helpers, so many snaps look ugly and some are even broken. Including gtk-common-themes support in desktop-helpers will probably be the easiest way to fix as many snaps as soon as possible.

@kenvandine @didrocks, what do you think?

That’s not something we can do directly in the desktop-helpers. Each snap would need to be updated adding the yaml to connect the content interfaces. I’ve updated all the snaps maintained by the desktop team and they all work fine with communitheme. I just don’t have a way to update other people’s snaps and I don’t want to encourage too many people to add this boilerplate code in their yaml that will be harder to maintain in the future. It’s fine if they want to, but I don’t want to push people to do that.

1 Like

I think @galgalesh is suggesting that snapcraft-desktop-helpers pull in Communitheme manually so it gets included into the application snap.

I’d personally prefer not to do that: the whole point of the theme snap work was to recognise that no matter how many themes we bundle with an application snap, it will never be enough. And each bundled theme adds to the package size for every app the user installs. In contrast, with theme snaps we only transfer the theme data once, and can update the themes without requiring every application developer to rebuild their snaps.

2 Likes

I thought it would be possible to add the plugs to the desktop-helpers.

Since this isn’t possible, I still think desktop-helpers should pull the theme manually because communitheme will be included in Ubuntu 17.10. I’d like to do this as soon as possible so that most snaps support Communitheme when 17.10 lands.

To add something “noobish” to this conversation full of knowledge:
If gtk2 theming turns out to be really, absolutely impossible for snaps, there is one last thing that could be done as a backup plan b:
If it’s a Qt snap using the gtk2 theme via QGtkStyle then better use the native Qt skin then no skin. An example for this worst case is simplescreenrecorder which has no theme at all and ends up with no theme at all (grey).

Note that what I’ve documented in this thread is the primary system we want to use for making themes available to snap applications going forward: it isn’t a fallback for those not using Ubuntu.

Updating desktop-helpers would also require all application developers to rebuild their snaps, so isn’t obviously better. And with themes like Communitheme that are still under active development, it wouldn’t just be a single rebuild (compare with the content interface solution, where pushing out a new gtk-common-themes release updates all applications).

I agree that the currently required boilerplate is undesirable, which is why the Snapcraft developers are working on the new templates feature.

I don’t think handling GTK 2 themes would be impossible: they just have vastly different security considerations compared to GTK 3 themes (e.g. how do you know that a GTK 2 theme engine doesn’t embed a Bitcoin miner?).

As for Qt applications, any Qt 5 app should be able to use GTK 3 themes via the qgtk3 platform theme plugin. However, the simplescreenrecorder snap appears to have been built with Qt 4. Looking upstream it seems the application supports Qt 5, so rebuilding the snap would probably be the best option.

This shouldn’t be necessary now as $SNAP/share should be already prepended in XDG_DATA_DIRS since https://github.com/ubuntu/snapcraft-desktop-helpers/pull/121

It looks like there are some problems with picking the correct theme under Wayland.

Under Wayland, GTK selects its theme via the GSettings API. While the gsettings interface can give a snapped application access to the user’s configuration database (~/.config/dconf/user), it will be using a different set of GSettings schemas which can have different default values.

So on my laptop running Cosmic, I see the following:

$ gsettings get org.gnome.desktop.interface gtk-theme
'Yaru'
$ snap run --shell gnome-calculator
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

$ $SNAP/bin/desktop-launch gsettings get org.gnome.desktop.interface gtk-theme
'Ambiance'

… and sure enough, gnome-calculator displays using the Ambiance theme while everything else is using Yaru. This is not something that can be solved from within the snap, so we’ll probably need some extra support snapd side.

If you don’t mind I would like to drop this section as the environment is already handled by the desktop-launch script as I previously mentioned.