How to autostart a snap of a desktop application?

Spent some time investigating this today Here are the notes I took in the process:

systemd unit

  • nice & clean idea
  • potential cross-distro integration issues
  • need to access XAUTHORITY, DISPLAY
    • usual way is to do is call systemctl --user import-environment XAUTHORITY DISPLAY
    • automatically done by /etc/X11/xinit/xinitrc.d/50-systemd-user.sh
    • helper is shipped by Arch, Fedora, OpenSUSE
    • not shipped by Ubuntu and Debian, see https://salsa.debian.org/systemd-team/systemd/blob/master/debian/rules#L201 (anyone has a clue as to why?)
    • went through gdm source code, AFAIU the xinitrc.d files are run early in the startup process and the imports will happen before the DE is running
  • I can’t figure out when the ‘user’ units start wrt. applications started by the session (eg. gdm-x-session/gnome-session-binary), my guess is ‘before’, but it would be great if someone knowledgeable could confirm that
  • I had some initial hopes for graphical-session.target, but it does not seem to be activated/used at all
  • prototyped a helper that starts as a user unit for all users, seems to work (16.04 as well)

userd as snap-userd.desktop

  • should work fine, with the exception
  • disable startup notification
  • exit once applications are started
  • there are some graphical tools to control what is started and what is not, those will not work anymore
  • still need to consider the contents of snap desktop files

*.desktop files

  • need to whitelist DE specific entries: eg. X-GNOME-*, X-KDE-*
  • should we interpret specific settings? eg. X-GNOME-Autostart-Delay, X-GNOME-Autostart-Phase=Initialieation (this one is peculiar)
  • interpret OnlyShowIn & friends
  • feels a bit complicated

some alternatives to consider

  • if we could ensure that ‘user’ units run early in the process, we could rewrite the desktop files before the DE starts
  • drop a script that calls userd in xinitrc.d that call userd to populate desktop entries (what about wayland?)
1 Like

Small update. When working on this feature I stumbled upon some problems matching the autostart files with respective snap apps.

Original desktop file shipped by snap (/var/lib/snapd/snap/discord/current/meta/gui/discord.desktop):

[Desktop Entry]
Name=Discord
StartupWMClass=discord
Comment=All-in-one voice and text chat for gamers that's free, secure, and works on both your desktop and phone.
GenericName=Internet Messenger
Exec=discord %U
Icon=${SNAP}/usr/share/discord/discord.png
Type=Application
Categories=Network;InstantMessaging;

Generated by snapd during installation (/var/lib/snapd/desktop/applications/discord_discord.desktop):

[Desktop Entry]
Name=Discord
StartupWMClass=discord
Comment=All-in-one voice and text chat for gamers that's free, secure, and works on both your desktop and phone.
GenericName=Internet Messenger
Exec=env BAMF_DESKTOP_FILE_HINT=/var/lib/snapd/desktop/applications/discord_discord.desktop /var/lib/snapd/snap/bin/discord %U
Icon=/var/lib/snapd/snap/discord/52/usr/share/discord/discord.png
Type=Application
Categories=Network;InstantMessaging;

Dropped by discord when I enabled autostart, the file is ~/snap/discord/current/.config/autostart/discord-stable.desktop (NOTE the file name is not even named discord.desktop)

[Desktop Entry]
Type=Application
Exec=/snap/discord/52/usr/share/discord/Discord
Hidden=false
NoDisplay=false
Name=Discord
Icon=/snap/discord/52/usr/share/discord/discord.png
Comment=Text and voice chat for gamers.
X-GNOME-Autostart-enabled=true

The Exec=/snap/discord/52/usr/share/discord/Discord stanza is not usable for autostart and looks more like a dump of whatever /proc/self/exe pointed to.

For now I have attempted to explore the idea of matching the application with a snap using its desktop file name. It goes like this:

  1. application foo drops a file named foo.desktop into ~/snap/my-snap/current/.config/autostart, hence we assume snap is my-snap, app is foo
  2. upon seeing the file we determine the snap name and try to locate a file /var/lib/snapd/snap/desktop/applications/my-snap_foo.desktop
    • the file was already patched when the snap was getting installed, so it contains a proper Exec line that references the wrapper /snap/bin/foo
  3. grab the Exec line from autogenerated desktop file, run it
    • special Exec flags such as %U, %c are filtered out
    • no attempt to look at other flags is done, (eg. X-GNOME-Autorstart-enable is ignored)

Clearly this approach of matching snap/app with whatever was dumped into autostart directory involves some guesswork. I have opened a RFC pull request https://github.com/snapcore/snapd/pull/4759

Let’s discuss other options.

My suggestion would be to add a per-app autostart (or perhaps user-autostart? userd/autostart? Something more involved?) to the yaml, and when the user’s session starts we look to see if that file exists, and if so, we start the associated app.

As an example and/or strawman:

apps:
  foo:
    command: foo
    userd:
      autostart-if-exists: .config/autostart/foo.desktop

(of course my personal preference would be to try to go with something less nested at first, but I’m just exploring things here).

I like it because there’s no parsing of the file involved, no guessing, no matching heuristics, nothing. It’d be purely declarative.

Note this supports this use case, but also simpler cases with no desktop files involved at all.

Also note the app doesn’t need to be the exact same one that ships the desktop file, so you could have different options in there (but why would you do that :slight_smile: ).

That’s a good idea. The non-trivial heuristics will cause pain forever otherwise, while we try to accommodate magic matching to the real world.

We might be able to go with something simpler as you suggest, and have sane defaults. Perhaps:

apps:
  foo:
    command: foo
    autostart: foo.desktop

The semantics might be:

  1. When the user session starts, look into ~/snap/*/current/.config/autostart/

  2. If filename matches the value of the field in one of the snap commands, then process the file

  3. The Exec line in the file is parsed out, and the snap command is run

  4. If the Exec value is “some/command -a -b”, the equivalent snap command would be “snap run theapp -a -b”.

What do you think? Anything else we need to cover?

Note that this method opens the door to having autostart: true in the future, or autostart: maybe :slight_smile: which would allow having the same functionality without the requirement of having the desktop files.

Not sure which definition of ‘usable’ you meant but for clarity, if it could be made runnable it would not be viable for snapd since this isn’t using ‘snap run’ or ‘/snap/bin/discord’ and therefore would run outside of confinement.

How is th autostarted application started under confinement if there is no parsing and updating of the Exec line?

What is looking in ‘1’, userd?

This approach seems reasonable since the application itself shouldn’t need to be modified-- the snap publisher simply says ‘my snap sets up autostart using this desktop file’, then the application can write to it however it wants. userd now knows where to look for it and it will make sure that it is launched under confinement.

We need to be careful with parsing the Exec line; though, we should be able to reuse our current rewriting code. Likewise for verifying the validity of the other fields of the desktop file (ie, we want to be sure that other things in the desktop file don’t influence userd to break out).

Answering myself-- Gustavo outlined a plan that connected the dots, which I’ve responded to.

@niemeyer I’m not sure what the advantage is of reading the file and parsing its Exec line, could you elaborate on that?

We don’t control how the file is generated. The same desktop file may be written out in various ways depending on how the application (or more likely the user) decided for it to be auto-started.

The parsing code ought to be the same indeed. The rest of the file isn’t so relevant here, though, because it’s userd itself that will be running the command.

That’s what I was trying to get at-- userd is unconfined so we want to make sure that other fields aren’t influencing userd in unexpected ways. If userd only looks at the Exec line, then yes, nothing else to worry about. If it starts to look at other fields, just need to be careful of what the untrusted input from the desktop file is providing.

1 Like

RFC right here: https://github.com/snapcore/snapd/pull/4768 If the idea is fine, I’ll clean it up and add tests.

I’m testing with a customized discord snap. The changes I’ve made:

  • renamed command-discord.wrapper -> discord
  • patched snap.yaml:
    diff -upr discord.orig/meta/snap.yaml discord/meta/snap.yaml
    --- discord.orig/meta/snap.yaml 2018-01-09 12:11:20.000000000 +0100
    +++ discord/meta/snap.yaml      2018-03-01 08:03:30.931442231 +0100
    @@ -11,7 +11,8 @@ confinement: strict
     grade: stable
     apps:
       discord:
    -    command: command-discord.wrapper
    +    command: discord
    +    autostart: discord-stable.desktop
         environment:
           TMPDIR: $XDG_RUNTIME_DIR
         plugs:
    
  • patched discord-stable.desktop file:
    --- discord-stable.desktop.orig 2018-03-01 10:00:18.947229990 +0100
    +++ discord-stable.desktop      2018-03-01 10:00:03.166175163 +0100
    @@ -1,6 +1,6 @@
     [Desktop Entry]
     Type=Application
    -Exec=/snap/discord/x1/usr/share/discord/Discord
    +Exec=discord
     Hidden=false
     NoDisplay=false
     Name=Discord
    

Can you clarify if in some/command -a -b, the some part is an arbitrary prefix and as long as the whole line ends with command .. we count it as a match?

This is available with snapd 2.32.4 or later which is available in the candidate channel now.

There was a new feature made in a bugfix release?! Or was this meant to be a feature in 2.32.0 but a bug meant it wasn’t working properly?

@Ads20000 A bit late, but for the record the .N at the end stands for very low risk in snapd, rather than strictly a bug fix. In the case at hand, for example, this was a new feature that did not touch existing semantics elsewhere, so the risk of regression was very low. In some cases, we may also see slightly larger features landing in such releases if the earlier releases were blocked in testing channels (< stable) for too long, which in practice means the minor was not really released publicly yet.

To stick to SemVer would it be worth calling 2.34, when it comes, ‘2.34-rc.1’ to stick to SemVer whilst it’s still in non-stable channels? Then you could bump to 2.34-rc.2 etc and then go to 2.34 when it’s in stable? Also a possibility for the feature-releases-pretending-to-be-bugfix-releases would be releasing the next version (2.35) without all the features originally planned (pushing them back to 2.36) and then getting the release managers to promote that faster?

Just thoughts, I’m not a dev, just think that SemVer is very neat. Perhaps it is OK to do some very minor releases as bugfix releases as long as that’s carefully monitored and not allowed to allow bigger and bigger features as ‘bugfixes’…

Do we have any docs for how this works?

It’s described under app properties right here:

I see now that the description a bit brief, I’ll extend it a little.

2 Likes