Proposal: allow snaps to specify their exact desktop file ID

As part of the effort by the Ubuntu desktop team to migrate another application to a strict confinement snap, we are running up against problems with the way snapd names desktop files for applications. In particular, it is a problem for handling desktop notifications in gnome-shell.

This isn’t the first time people have run into problems with the current naming scheme:

In most of these cases you could argue that the application developer should adapt to snapd’s way of doing things, but I don’t think that holds for the notifications case. I’ll describe my reasoning for that in a follow-up post so as not to bog down the proposal.

Proposed Change

The common use case here is for applications that want to use the same identifier for their desktop file and a bus name they acquire on the D-Bus session bus. This is also commonly used as an AppStream identifier, which snaps can currently provide via the common-id key.

So I propose that if a snap provides any desktop files in meta/gui that are named like common-id + ".desktop" for any of the snap’s apps, that they be installed without the $snapname_ prefix. Further more, that these desktop files be associated with the app in the same way a $snapname_$appname.desktop file would.

This obviously introduces the possibility for naming conflicts where they didn’t previously exist, so snapd should refuse to install/upgrade a snap if the common-id desktop file is provided by another snap, which can be done by checking the X-SnapInstanceName key in the existing file.

It is also incompatible with parallel installs of a snap, but many desktop applications will try to acquire a particular D-Bus name. So they are likely already incompatible with parallel installation.

To achieve this, I think the following changes would be necessary:

  1. As we’re actually making use of the AppStream ID, we should perform actual validation of the field beyond that it is unique within a given snap. The specification provides some guidance, but we might want to check the two AppStream libraries too. We should check the existing corpus of snaps against whatever validation rules we decide on.

  2. wrappers.AddSnapDesktopFiles should recognise desktop files whose name matches a common-id for the snap and install them without the snap name prefix. A check should be added that we aren’t overwriting a desktop file owned by a different snap.

  3. Rather than using a $snapname_*.desktop glob, wrappers.RemoveSnapDesktopFiles should remove all desktop files with a matching X-SnapInstanceName key.

  4. cmd.ClientAppInfosFromSnapAppInfos should check for the existence of a desktop file named for the common-id of the app, and include it in the client.AppInfo metadata.

2 Likes

Now for a rundown of the specific issue the current desktop file naming scheme causes for notifications.

GTK Notifications

GNOME 3 introduced a new desktop notifications API that I’ll refer to as “GTK Notifications” from here on since it uses the bus name org.gtk.Notifications.

Posting new notifications is done via an AddNotification method that takes the desktop ID of the application, a notification identifier local to the application, and the notification body.

When an action associated with the notification is fired, the shell uses the D-Bus activation feature of the Desktop Entry Specification to deliver action to the application via the Activate or ActivateAction methods. The main benefits of this over the older FDO Notifications spec is that shell can direct the action to relevant action rather than broadcasting a signal that wakes up every application, and that the application need not be running to receive the action.

For a desktop file to be D-Bus activatable, it’s name must be a valid D-Bus bus name. The desktop file names snapd assigns to applications include an underscore, which is invalid in bus names. So at present a snap app’s desktop file can never be D-Bus activatable.

And we can’t just pass a D-Bus name that the snap application is allowed to acquire, since gnome-shell explicitly checks to see if a file matching the passed in desktop ID actually exists:

https://gitlab.gnome.org/GNOME/gnome-shell/blob/master/js/ui/notificationDaemon.js#L627

Portal Notifications

The xdg-desktop-portal service does provide an API to let confined applications post notifications without being granted access to the FDO or GTK notification APIs:

Unfortunately, this does not help us. If we are running on a GNOME 3 system, the portal is effectively a simple proxy to the GTK notifications API that forces desktop ID to the one associated with the confined application (so it can’t spoof notifications for other apps), and the shell will then deliver actions to the app as normal. This means we run into exactly the same problems as if we granted the confined app access to org.gtk.Notifications directly.

So far common-id has been used only has a hint for searches. Also while its documentation refers to AppStream its name is for a generic concept on our side.

Anyway as you mentioned because of the possibility of clashes (so possibly disputes, priority issues) is not clear to me that we can do this without some store side blessing mechanism like we have for aliases.

I would be interested in @jdstrand thoughts on this.

The way we prepend $snapname_ has always been both practical and uncomfortable. I appreciate @jamesh looking into this (thanks).

While reading the proposal, like @pedronis, I was reminded of aliases which require a snap declaration for an auto-alias. As @jamesh mentioned, the desktop file name is related to the dbus name to bind, and I’ll remind others that in order to be able to bind to a dbus name, the dbus slot must specify the name to bind to, and that, like aliases, also needs a snap declaration for use in the store (more on this later). When we first implemented the desktop file prepending, we hadn’t yet had a lot of experience with snap declarations for dbus and aliases, but now we do.

I like @jamesh proposal overall and IME the question is what are the policies that snapd should adhere to (and when) and what can the store modify (and perhaps the user). OTOH, this very much feels like something in snap declaration territory where snapd could (this is not fully formed and just some thoughts):

  1. continue to support $snapname_ for various cases
  2. provide a means to strip the $snapname_ via snap declaration (and maybe user command analgous to ‘snap alias’? Probably too weird and unneeded)
  3. have a mechanism for providing an automatic stripped desktop file

I’ll now think through the proposal and these points and argue both sides of my points and hopefully that will help shake out other things to think about so we can decide how to move forward. :slight_smile:

AIUI, @jamesh is saying we continue to prepend (ie, ‘1’, above) when common-id is not used. We could consider falling back to prepending when there is a naming conflict (analogous to not applying an alias if one is already in place). However, since this is closely tied to the dbus name to bind to and we have no checks in snapd to prevent install/warn/whathaveyou when a second snap that declares the same dbus name is installed, it is perhaps correct to not install (though, for non-autostart, that would preclude two snaps that could otherwise be used at different times but both installed on the system from working.).

While use of common-id is convenient, it isn’t clear to me that this is the best field to use or if we should use something else (I am not an expert in this area, so, @jamesh, please correct me). AIUI, the common-id is used to map a particular snap command to an external appstream data source where the snap is expected to ship a desktop file in meta/gui/*.desktop for this command. To date, there isn’t a snap.yaml declaration that says ‘this command goes with this desktop file’. I can understand why you chose common-id and among the reasons you listed, it also happens clean up this ambiguity. For the proposal as it stands, there is no clear declared link between common-id, the desktop file and the dbus slot which is perhaps desirable. Also, it is unclear to me how to bring this into the fold of snap declarations/store to regulate things without breaking existing snaps (but perhaps it isn’t required).

In thinking about this, I kept coming back to the fact that the desktop filename is closely tied to notifications and DBus and it was nagging at me that perhaps this should somehow be tied to the dbus interface: the dbus interface declares the name, that name is used with notifications and that name must match the desktop file name. If we tied this into the dbus interface, then implementation for my points ‘2’ and ‘3’ becomes clear: if you were granted use of a dbus slot with name: org.foo.bar, then snapd could let you have org.foo.bar.desktop automatically. Snapd can check for conflicts at install time and do something sensible (TBD). This has some nice properties, but on its own is a bit magical with a need to be opinionated (eg, only for bus: session? if activatable?). We could be a bit more declarative and have something like:

name: foo
slots:
  myslot:
  - interface: dbus
  - bus: session
  - name: org.bar.foo
  - with-desktop: true
apps:
  foo:
    common-id: org.bar.foo
    slots:
    - myslot

the idea being that with-desktop: true triggers the new desktop filename behavior where the desktop filename is value of 'name' + .desktop (‘with-desktop’ isn’t a great attribute name, but at least gets the conversation started). It would also be nice if we didn’t have to list org.foo.bar twice, so perhaps have something like:

name: foo
slots:
  myslot:
  - interface: dbus
  - bus: session
  - name: org.bar.foo
  - with-desktop: true
apps:
  foo:
    common-id: $myslot.name    # ie, reference 'name' from 'myslot'
    slots:
    - myslot

(there are many variations; main point is that the dbus interface is used to trigger the desktop filename writing and tie in somehow with common-id)

In short, I think that @jamesh ideas overall are sound. If common-id is what we want, I think we need to work through how to regulate it if at all. Otherwise perhaps tie this in with the dbus interface.

something like this might be viable. I don’t like with-desktop for the attribute though. It’s really more defines-desktop-id: true or binds-desktop-id. At most one slot could have that set.

To manage clashes we would have to make creating a dbus slot with that attribute set superprivileged.

This came up in discussion with @pedronis today. Below is a short sketch of what was proposed:

  1. The desktop interface grows a new plug attribute that could be declared with something like the following (with attribute names undecided):

    plugs:
      desktop:
        desktop-id: org.example
    
  2. The base declaration for the the desktop interface denies installation of snaps if the desktop-id attribute is non-empty. The store can override this to allow a snap to claim the ID.

  3. snapd will prevent two snaps from being installed that claim the same interface using similar means to the D-Bus service conflict resolution in PR #8748. Although it may want to also check that no claimed desktop ID is a prefix of another claimed desktop ID too.

  4. If any of the desktop files found in $SNAP/meta/gui are prefixed by the claimed desktop ID, then install them named as is.

  5. If any icons theme icons found in $SNAP/meta/gui/icons are prefixed by the claimed desktop ID, then install them named as is.

I had a few thoughts about this after the discussion though:

  • If we are hanging this logic off a plug, what does it mean for the plug to be connected/disconnected? Would the snap have ownership of the prefix if the interface is disconnected? Would we have to go through and rename/delete desktop files and icons when the state changes?
  • There is still some duplication with dbus slots, if the app wants to use the prefix on the session bus too. If that’s common enough, maybe @jdstrand’s suggestion of using the dbus slot would fit better?

We discussed this today and for my part, I think the overall proposal sounds fine (we can work out specific details in the PR). From a security perspective, if we do ‘2’ and ‘3’ correctly, there are no concerns and this ties in well with existing store-side check patterns (eg, dbus well-known names).

As for the connected/disconnected question, if this is tied to the desktop interface, then you can use the PermanentPlugs concept.

As for the dbus question, we should probably consider the existing dbus interface implementation, the upcoming activation PRs and this so we can have a full view on how to proceed. In a lot of ways it does seem like all these things are related so it feels like a nice way to combine things (though it is a bit magical and perhaps inflexible). Arguing against myself, it also seems plausible there might be drift between a desktop id and the dbus well-known name (when GNOME went from org.gnome.thing to org.gnome.Thing for dbus names, did the desktop ids change accordingly?). What does it mean for a snap to provide two slots but only one has a desktop id? desktop-id also doesn’t seem to have any meaning for system services…

while there is some conventional expectation that slots are “providers” and plugs “consumers”, they are relatively symmetrical concepts and plugs can have permanent setup/state as well if applicable.

as @jdstrand said the two could diverge and it feels too magical to me. I think clarity here is more important than avoiding some repetition, is not like anyway the configuration of a desktop app (independently of snaps) doesn’t involve any repetition of the id.

1 Like

Is there any progress on this issue?