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.

2 Likes

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.

CC’ing @jdstrand and @mpt for their thoughts. It might also be of interest to @gantonayde judging by this thread.

I’ve started working on some code to help with automatic theme installation for snaps, which I’ve uploaded here for now:

At present there is no UI, and it won’t actually install any snaps. It does include the following though:

  1. code to watch changes in the currently selected theme. This makes use of the GtkSettings class, which gives us theme names on both X11 and Wayland desktop sessions. I’m watching the GTK, icon, cursor, and sound themes via this API. The code checks the initial theme selection, and sends a signal whenever the theme is changed (with a 1 second delay in case multiple themes are changed at once).

  2. code to determine whether a set of themes are available to snaps. First it enumerates all content interface slots to determine whether an installed snap provides the theme. Next, it checks whether there are snaps published on the store named after the theme (e.g. gtk-theme-foo for a theme called Foo).

Putting the two together, is a command line program that will check whether your current theme is available to snaps, and repeat this check if the theme is changed. At present it is just logging a warning if there is no snap available for a selected theme, but it would be pretty easy to return that info too.

This is all the work I’d expect such a tool to perform prior to showing any kind of UI to the user. I know @mpt has done some work on what the UI should look like, but there are a few open questions:

  1. The current lookup method of deriving a snap name from the theme name means that there could be four snaps to install (at worst). How much information about each do we want to expose?

  2. As we’re reacting to the theme change after it has happened, is popping up a dialog appropriate? Might a desktop notification fit better?

5 Likes

A lot of the custom themes have some flavour attached to the name, i.e. foo-light or foo-compact-light, etc. If that’s the case, will the program check for a snap called gtk-theme-foo or gtk-theme-foo-bar (where bar is the flavour)?

2 Likes

That sounds like a good 95% solution that should avoid the need to split most themes. I’ll look at adding that to the prototype.

It doesn’t cover cases like Ambiance/Radiance, but that seems to be a bit of an outlier for theme naming and is already in gtk-common-themes.

1 Like

i talked to someone in the #ubuntu IRC channel on the weekend that had all his theme variables pointing to “default” in the desktop … he was desparately trying to get a proper cursor in chromium and it only worked if he picked an actual theme name, could we take that case into account ? (IIRC “default” is typically a symlink (or a few of them) in the hosts theme dir somewhere)

also … it would be really nice if we could fall back to something saner than the builtin XCursor in case we do not find a matching theme. By my own experience the XCursor never scales on hidpi while any other one we could pick will do …

i guess less people would complain about the wrong cursor theme if the default wouldnt look like a 4x4px thingie on hidpi screens :slight_smile:

2 Likes

Note that we can’t easily tell confined applications to use a different theme to the host system. For X11 based desktops, the theme to use is communicated via XSETTINGS, which we can’t intercept. We probably could for Wayland, but I’m not sure it is worth it.

I haven’t seen any (working) systems where the GTK theme name is set to the string “default”. I’d be kind of interested to know how it got into that state.

It does look like the Xcursor library will look for an icon theme called “default” as a fallback though, so there having that provided by gtk-common-themes would probably solve most of the hidpi complaints. We’ve got no idea what “default” points to on the host, but can make sure it points to something supporting large cursors.

1 Like

In my experience, Flatpak apps default to the Adwaita theme when the current theme is not supported. It might be interesting to look at how they achieve that.