Wednesday, October 25, 2017

Using RetroArch via Snappy

I've been working with some folks on trying to get a snap package up and running for RetroArch to go with our FlatPak and AppImage universal linux packages, and it's turned out to be more complicated than I expected to navigate the particulars of the packaging format combined with the restrictions of the security sandboxing.

We announced the package a couple of weeks ago but quickly got reports that users couldn't load files, they were confused as to why the package took a long time to load (only on the first launch, but they didn't know that), and more. Since updating my laptop to Ubuntu 17.10, I decided to "dogfood" the snap package, since that would be the only way I could get in front of the reports and ensure a good experience.

Since RetroArch requires a lot of stuff to look nice and function properly, we use a wrapper script that checks for the existence of that stuff and, if it's not where we expect it, it copies the stuff into the snap's user directory. Since that copying can take a long time, I decided to use notify-send to include some admittedly uninformative notices just to let the user know that nothing is frozen/broken and that we're just copying stuff in the background. The catch here is that you can't use the system's notify-send, you have to include it in the snap as a runtime dependency, under the "stage-packages" in the snapcraft.yaml recipe, like this. I tried adding an icon to make the notifications prettier and more obviously RetroArch-related, but I could never get it to actually see the icon, no matter where I stored it, so I gave up on that.

Ok, so the notifications were a nice little improvement, but we still couldn't actually get to any files to launch them, which makes the program pretty useless. For that, we needed to add the "home" plug to the recipe, like this. This plug is supposed to be auto-connected, so you shouldn't need to do anything to make it accessible to your application once it's in the recipe. However, RetroArch's non-native file-browsing meant that it tried to start in /, which is inaccessible (and in fact, totally invisible) to the snap (and if your snap starts you in an inaccessible directory, you can't ever get out of it), so I added a line to my wrapper that pre-populates the user's retroarch.cfg config file with a line telling it to start in the home directory, where we should have access. I tried using $HOME and ~/, both of which just sent me to the snap's home directory instead of the true home directory with all the files -_-. The solution I found--which is pretty crummy but whatever--is to use a relative path that points to one level above the snap package. That is, ~/../../../

Similarly, I couldn't reach my network shares, which I mount in /media (despite adding any plug that seemed even vaguely related to such a thing to the recipe), so I had to move my mount points into my true home directory and use those same relative paths to everything, e.g. ~/../../../smbshare/Emulation/BIOS for my 'system' directory. Once the mount point is in my true home directory, I could probably put symlinks into the snap package, as well, to avoid the silly relative paths.

The last major issue I ran into was that the *.desktop launcher that shows up when you search for programs in the sidebar kept complaining about not having an "exec" line and then failing to launch because of it. This one was super-confusing because our snap has a *.desktop file (it lives in $SNAP/meta/gui), and that file definitely has an exec line. It turns out that, during installation, snapd generates the *.desktop file that the OS actually looks for and stores it in /var/lib/snapd/desktop/applications. This file is based on the *.desktop included with your program, but if the exec line isn't just like it expects, it will strip it out entirely and not give you any indication of why. Initially, our *.desktop file pointed the exec line to the retroarch.wrapper script that does so much work for us, but snapd didn't like this and rejected it until we switched it to just "Exec=retroarch", which isn't the name of the actual executable but rather the name of the snap itself. It still launched the wrapper script, since that's what our recipe points to, so we're all set.

Since we need to use our script when we launch from a command line, as well, we made sure to end it with $*, but this has a couple of problems that experienced scriptors will spot immediately. First, it's not escaped, so any spaces in file names will break it. Second, it will only accept a single argument, which isn't going to work for us. So, I changed it to "$@" and all is well.

Now, the only issues left that I know of are: 1.) the wrapper script has our nice invader icon, which shows in the sidebar while the script is running, but once it dies off, the icon goes with it and the actual program just has an ugly question-mark/no-icon-found icon in the sidebar and 2.) the snap can't load any dynamic libraries that live outside of its domain, so I can't conveniently compile a test core and then launch from command line to test it with the -L switch. #1 isn't a big deal and #2 probably isn't possible to fix, so it is what it is.

No comments:

Analytics Tracking Footer