This post contains a more-than-usual amount of links to documentation and code. I’ve tried to link to the versions that are current at the time of writing, and to the exact relevant location in the pages.

Following are some unexpected and some very unexpected issues I came across while making a simple GNOME search provider for my notes.

  1. According to the docs,

    In order to register the search provider with GNOME Shell, you must provide a key/value file in $(datadir)/gnome-shell/search-providers for your provider.

    where $(datadir) is the usual $XDG_DATA_DIRS, /usr/share, $HOME/.local/share, etc. Except not quite. The code that loads the search provider files:

    for (const {file} of FileUtils.collectFromDatadirs('search-providers', false))
        loadRemoteSearchProvider(file);
    

    calls FileUtils.collectFromDatadirs(...) with the second argument set to false:

    export function* collectFromDatadirs(subdir, includeUserDir) {
        const dataDirs = GLib.get_system_data_dirs();
        if (includeUserDir)
            dataDirs.unshift(GLib.get_user_data_dir());
        ...
    }
    

    You can probably see the issue here. This is neither mentioned in the developer docs, nor makes much sense on its own. A user may want to have search providers on top of the system-provided ones, separately from another user who may want their own extra providers. No idea why the shell explicitly excludes the user data directories. Should I have had to look into the shell’s source to find this out? Probably not. I’m not sure which among the code and the documentation is wrong, just that they’re inconsistent, so I refrained from creating an issue for it.

  2. Each search provider has to provide a corresponding .desktop entry to enable GNOME to display available providers (and let the user enable/disable them) in its settings. Because my “app” is just a search provider and nothing else, it doesn’t make sense to add an entry for it in the global app menu. The XDG desktop entry spec recognizes this scenario and provides a NoDisplay key:

    NoDisplay means “this application exists, but don’t display it in the menus”. This can be useful to […] so that it gets launched from a file manager (or other apps), without having a menu entry for it (there are tons of good reasons for this […]).

    Reasons such as this use case. A search provider doesn’t have a sensible Exec action, so I can just set NoDisplay and mention it in the provider’s ini, right? Well.. in that same function,

    appInfo = GioUnix.DesktopAppInfo.new(desktopId);
    if (!appInfo.should_show())
        return;
    

    the shell explicitly excludes hidden (NoDisplay == true) entries. Why? Again, not sure. Another undocumented and unintuitive behaviour, considering this is a legitimate (even if a little unusual) use case of NoDisplay. Should I have had to look into the shell’s source to discover this behaviour? Probably not.

  3. All good apps need icons, so let’s make one, using the most popular vector graphics tool for Linux, Inkscape. If you’re an Inkscape user you’re probably not surprised to see it on this list, but more on it later. First, the icon size.

    I’ve always found XDG’s icon theme spec, to be a little suboptimal. At the time of writing, SVG support is optional, which is unfortunate but understandable. The primary format to provide icons is (still) PNG. Luckily, most popular desktops include support for SVGs, including GNOME. This point, I suppose, is not really GNOME’s fault but a.. gap in the spec.

    Once done with the icon, I exported it as a 512x512 SVG (we’ll come to what this “size” means), which is a reasonable size for a modern icon, and placed it at $HOME/.local/share/icons/hicolor/scalable/apps. And everything worked as expected. But for the sake of consistency, I decided to finalize its location in /usr/share/.... I place it there, re-log in (we’ll come back to this too), and.. no icon. Search results don’t display it, the overview doesn’t display it, settings doesn’t display it, nothing. What’s happening? Why does the user data directory work but the system directory does not?

    An SVG has no size. There’s the viewbox which sets the canvas origin and bounds, width and height attributes that make sense in the context of HTML, and for standalone files too as an image viewer can use those as the initial display size, but ultimately, an SVG has no size. The icon theme spec, however, treats SVGs pretty much the same way as PNGs, as it was written with primarily PNGs in mind (the latest spec is dated 2013, and the version before at 2006!). The icon I made (modded) was exported as a 512x512 SVG, meaning the width and height attributes said 512. This, the spec (and/or GLib), weirdly considers as the size of the SVG. This is very counter-intuitive, primarily because SVGs all go, by convention, in a directory named “scalable” in the icon theme base directory. But here’s the thing: the directory structure is only a convention, the theme as a whole is described in a theme description file index.theme in the theme base directory. It’s this directory that describes all the subdirectories, individually, for each icon size, scale and context. The fact that the SVG directory is called “scalable” is just a choice. It might’ve just been called “new-gen-icons”, and it wouldn’t matter. The part of the theme description file that describes this directory (more specifically scalable/apps) is this:

    [scalable/apps]
    MinSize=1
    Size=128
    MaxSize=256
    Context=Applications
    Type=Scalable
    

    Notice how Type says Scalable and there are MinSize and MaxSize keys present anyway? If the latter two aren’t specified, they default to the value given to Size. This makes sense for PNGs, not SVGs. Remember, an SVG has no size. When the shell needs the icon, for say the overview menu, it asks GTK, which asks GLib, which, having read this description, looks in .../scalable/apps and finds the right file, but because the file has an artificial size of 512x512, it is ignored. “Resizing” the SVG to 256x256 fixes this and everything works as intended. Seems a little.. hacky.

    You may be wondering why it is, then, that the 512x512 icon did work when placed in the user data directory. Well, that’s because-

  4. Alright, so based on where the icon file resides, there is an upper limit on the artificial size that is attached to the SVG icon. But there shouldn’t be a lower limit, right? You can probably see what’s coming. This is where I’d have linked to where in the shell source a lower limit is enforced, but I just couldn’t go to the source to debug an issue, again. After finding out about the aforementioned upper limit, I experimented with some standard icon sizes and found out that the shell didn’t accept 128x128 icons for the entry (._.). This is once again, undocumented (or worse, contradictorily documented?). I eventually settled for an artificial size of 256x256. Once again, this is something I hadn’t expected to come across as an issue.

  5. But why should any of this matter if trial and error is a completely normal part of learning how a new-to-you system works? Because the system wasn’t new to me, just this one aspect, so I didn’t anticipate this much friction. Two, the huge feedback time. GTK caches these icons and neither it, nor the GNOME shell, force a consistent cache update when the directory contents change, as the spec suggests. I say consistent because it does happen some times and not the others. It could be an optimization, could be a bug, could be a shell issue or could be a GTK issue. Nevertheless, I don’t think it’s too big of a deal, except when you’re actively updating icons and want to see the changes in real time. I expected to run into such “rate limit issues”, but they turned out to be much more inconsistent than you’d imagine. I settled with having a reproducible work setup and re-logging into the shell repeatedly for a while.

  6. Hello there, Inkscape. Call me old-fashioned, but I prefer my styles as attributes over CSS. While CSS in SVGs is supported almost everywhere lately, I don’t like messy clumps of CSS in my icons. Inkscape doesn’t have an option to prefer attributes, neither at design time nor at export time. On top of that, it doesn’t bother removing unnecessary CSS as you go through the UI designing your icon. Try adding a line and then setting the stroke paint to undefined. All the CSS for “stroke style” stays untouched in the file, despite being of no use anymore. If the icon is small enough (all my icons are), I just manually remove the CSS by editing the XML. If you search the internet for solutions, you’ll see dated answers mentioning an “Optimized SVG” export option, which is not (no longer?) present. You’ll also find answers telling you that you don’t have to export everything in the SVG. You can, for example, set a layer as invisible and Inkscape won’t export it. Others would say you can just select what you want and Inkscape will export just that. Maybe I’m just too dumb, but I tried everything and the farthest I got was (1) selecting a layer, (2) selecting everything (e.g. Ctrl-A), (3) using “Export Document” (or “Export Page”), while (4) checking the box that says “Exported Selected Only”. .__. Even “Export Selection” doesn’t work as you’d expect. I resorted to doing none of it and using SVGO instead, with “development” layers hidden from within Inkscape.

  7. GNOME seach providers are queried via D-Bus. My provider was reasonably fast and it was a no-brainer to turn it into a D-Bus [Activatable] Service. This requires a service description file, whose docs you can find here. Not satisfied? Here’s the rest of it from the mentioned link. Still not satisfied? Well, unfortunate. Here’s my favourite line from the spec:

    [FIXME the file format should be much better specified than “similar to .desktop entries” esp. since desktop entries are already badly-specified. ;-)]

    :D

    > “So I was wondering if there was a way to provide environment variables to my app? Without, you know, experimenting? Or looking into the sources? Maybe a line in the spec?”

    > “[FIXME the file format should be much better specified than “similar to .desktop entries” esp. since desktop entries are already badly-specified. ;-)]”

    > “Well I don’t see any example files out in the wild that say anything about it. Do I have to use a systemd unit? Well let me guess, my app will probably inherit the environment from it’s parent, which will most likely be dbus-broker, which starts as a systemd service, which means it’ll probably not have the environment variables set in my .bash_profile, and is far before graphical-session.target is reached.. so.. I guess not? Is that correct?”

    > “;-)”

  8. Alright, I’ll make it a systemd unit. Now I don’t think systemd unit docs are badly written. On the contrary, I think for their size, they’re very well structured and organized. But again, because of the sheer size, I still find myself writing my unit descriptions files off of a template or an example, never being sure that I’ve gotten everything relevant in it. I know from experience that D-Bus services have special recognition in the unit description files. You may read the full description by searching for “dbus” in the man page, but specifically:

    Services with Type=dbus set automatically acquire dependencies of type Requires= and After= on dbus.socket.

    That’s good, dependencies are automatically handled for a D-Bus-service-providing daemon.

    For services that acquire a name on the DBus system bus, use Type=dbus and set BusName= accordingly. […] systemd will consider the service to be initialized once the name has been acquired on the system bus.

    Okay, that makes sense.

    Notice the “system bus” though. The man page doesn’t even mention session bus anywhere. But it does mention a system bus explicitly. Does this mean that BusName only makes sense for a system bus service? What about Type=dbus? Is that just for system bus too? Seems like it:

    dbus.socket - A special unit for the D-Bus system bus socket. All units with Type=dbus automatically gain a dependency on this unit.

    And that’s where the docs end. Once again, counter-intuitively, despite them explicitly mentioning system bus, and not mentioning the session bus anywhere, both Type=dbus and BusName work for both the buses. You’re welcome to guess how I found that out.

    (If you feel like the terminology around system and session buses, systemd and D-Bus services, etc. is confusing, read this, it’ll confuse you even more :). Not bashing the answerer here, that’s just unfortunately how the jargon is.)