Automatic theme snap installation notes

Yesterday, we held a meeting discussing how we can automatically install snaps that provide themes not found in gtk-common-themes. Below is a dump of some of what was discussed, together with some of my own further thoughts on how we can handle this.

Finding Theme snaps

If a snap provides themes in a way compatible with gtk-common-themes, we can determine what themes it provides by inspecting the slots. For example:

  1. Check for the presence of a content interface slot with ID gtk-3-themes.
  2. Collect each path found in the source.read array attribute on the slot.
  3. Take the base name of each path to create the collection of GTK 3 themes provided by the snap.

The store doesn’t currently index this information, so at least initially we can’t depend on this metadata solely to discover the appropriate snap.

So we will initially encode the theme name into the snap name. The theme name will be case folded, reserved characters removed, and prefixed with gtk-theme-, icon-theme-, or sound-theme- to form the snap name. This means we can simply query the store for a particular snap name to determine whether the theme exists.

This is not ideal, since it is common to have collections of themes that share resources (e.g. Yaru-dark, and Yaru-light simply symlink to resources in the Yaru theme) and this would force them to be packaged separately. It is probably a good place to start though.

Tracking the the current theme

To track what theme the user(s) on a system has selected, we need some code running within the desktop session. This is probably best done with libgtk's GtkSettings API:

GtkSettings *settings;
char *gtk_theme, *icon_theme, *sound_theme;

settings = gtk_settings_get_default();
g_object_get(settings,
             "gtk-theme-name", &gtk_theme,
             "gtk-icon-theme-name", &icon_theme,
             "gtk-sound-theme-name", &sound_theme,
             NULL);

On an X11 desktop, this will make use of the XSETTINGS protocol to retrieve the theme names. On a Wayland desktop, it will read the value directly via GSettings/dconf. We don’t want to bypass GtkSettings and use GSettings directly, since some non-GNOME desktops store their theme configuration elsewhere but still support XSETTINGS.

We can follow theme changes real time by monitoring property change notifications on the GtkSettings object. This does mean we need a long running background service to observe the changes though.

While we have a few snap related desktop session components (userd and the session agent), it isn’t clear that either one is appropriate here:

  1. both of those services are implemented as subcommands of /usr/bin/snap, and this new functionality requires GTK. We don’t want to pull GTK and all its dependencies into the core or snapd snaps.
  2. in order to function correctly, this probably needs to be using the host system’s glib/GTK rather than something in a snap sandbox. For Wayland in particular, it needs to be able to see the correct GSettings defaults from the host.
  3. it will need to run for the duration of the desktop session, while those other services launch on demand and exit on idle (or at least they should exit on idle).

Given that we may also want some form of UI on this service, I think it would make sense to write this component in C or C++. If it was written in Go it would consist mostly of cgo calls and manual memory management, which could easily be less maintainable.

At the meeting, there was some concern about putting this code in the snapd repository due to the additional dependencies. Since it will probably be using snapd-glib, @jdstrand suggested bundling the service with that package. I discussed that option with @robert.ancell this morning, and he is not too keen on that. So I suggest we start out as a separate project for now. Robert suggested snap-desktop-support as a name.

Theme snap installation

Installation of a theme snap should be protected by polkit authorisation. Further more, the message in the polkit prompt should indicate that it is a theme installation. That prompt message is controlled by snapd, so we will want a new API endpoint for this feature rather than the existing generic “install a snap” endpoint.

This also has the benefit that we can put the smarts for “which snap provides theme X?” and “is a snap for theme X already installed?” into snapd itself. If the store gains a new way to query snaps based on slot metadata, then we only need to update snapd to take advantage of it.

I think we want two particular calls:

  1. an unprivileged “do we need to install anything to support this theme?” call.
  2. a polkit protected “install the snaps needed to support this theme” call.

This could be implemented as GET and POST calls on the same URI. Ideally it would be a single call covering all of {gtk,icon,sound} themes in one go so we only have a single polkit prompt.

On session start up and when themes change, we would first check to see if any new snaps need to be installed. If not, then we don’t need to bother the user. If so, we can offer to proceed with the theme installation. It’s not clear we want to do this without user input though, since polkit prompts that are not directly tied to user action are a bad idea both from a user experience and security point of view.

One option would be to post a desktop notification that the user can act on. For the exact details, we should consult with @mpt, who has put together some prototype designs.

What about users who haven’t installed any desktop snaps?

At the meeting, @pedronis raised the concern that we probably don’t want to trigger the download of theme snaps if the user hasn’t installed any snaps that can make use of that theme data. For example, if they’ve only installed CLI or daemon snaps on their system.

If we’re adding a new API specific to theme installation, then it would be pretty easy for the “do we need to install anything to support this theme?” API to always return false if there are no snaps on the system with the relevant theme content interface plugs.

But what happens once such a snap gets installed on the system? One possibility would be for snapd to signal the session agents of all running desktop sessions. That could in turn ask the theme checker service to repeat its checks via a D-Bus method call.

Requirements for new automated review rules

If we are going to be prompting the user to install new snaps provided by arbitrary third parties, we want to make sure that their installation is benign. There are two main requirements:

  1. the data the snap exports via the content interface should be valid. It will end up being read and interpreted by applications running in many different security domains, and we don’t want it to cause them to malfunction or open a security hole.
  2. the snap shall not contain any apps, daemons, or hooks. Installing a theme snap should not alter the behaviour of the system beyond making theme data available. No one expects to find a bitcoin miner daemon on their system simply because they agreed to install a theme.

These new review checks should only apply to snaps that define at least one content interface slot with an ID of gtk-3-themes, gtk-2-themes, icon-themes, or sound-themes. Further more, we would want to ensure snaps named gtk-theme-*, icon-theme-*, or sound-theme-* provide the appropriate slot (which in turn would trigger all the above rules).

This should have no effect on the vast majority of snaps in the store (it might only trigger for gtk-common-themes), so there should be low or no risk of us rejecting new uploads for existing registered snaps.

Autoconnection

In order for a theme snap to connected to the installed application snaps, the store will need to provide an assertion allowing auto-connection. This means we will have some oversight over what themes are made available through this system.

At the meeting, I asked whether there is any way we can check whether a snap will auto-connect before downloading it, as an additional protection. It isn’t clear that’s possible at present, so we won’t consider it for the initial implementation.

Even with the earlier concerns, packaging this component as a snap with user services and communicating with snapd through snapctl or directly via the snapd-snap.socket feels quite compelling.

If I understand this concern correctly, in a Wayland session, a GTK application running inside the sandbox would not be able to see the theme change anyway. Do you know how this is handled today?

With a sufficiently new xdg-desktop-portal outside the sandbox and a sufficiently new GTK inside the sandbox, we should be able to see theme changes within the snap environment on Wayland: the GtkSettings API will instead talk to the portal service rather than accessing GSettings directly.

So in theory we could run this in a snap sandbox. It would make the daemon somewhat heavier weight though, which we might want to avoid for something that will so rarely actually do something.