Using com.canonical.Unity.LauncherEntry DBus interface

I am trying to enable the com.canonical.Unity.LauncherEntry interface in a snapped app. The app communicates loading progress via this interface’s update signal, so that e.g. the dock can visualize the progress. This already works when not snapped, but now I want to extend the snap to also allow access to this interface.

To enable DBus access I already set up slots in my snapcraft.yml like this:

apps:
  picard:
    # ...
    slots:
      - dbus-session-interface
      - dbus-canonical-launcher
    
slots:
  dbus-session-interface:
    interface: dbus
    name: org.musicbrainz.Picard
    bus: session
  dbus-canonical-launcher:
    interface: dbus
    name: com.canonical.Unity.LauncherEntry
    bus: session

I also made sure that the app ID (which is supposed to match the .desktop file’s name) sent by the implementation as part of the update signal is picard_picard.desktop, to follow the naming of the .desktop file as Snap uses it (so the dock can correlate the update signal to an entry it is showing).

This works partially. Inspecting DBus with D-Feet shows me the com.canonical.Unity.LauncherEntry for the object org.musicbrainz.Picard. I can also use the query method of that interface with:

dbus-send --session --print-reply --dest=org.musicbrainz.Picard \
     / com.canonical.Unity.LauncherEntry.Query

Which gives me the output:

method return time=1609837349.829729 sender=:1.1656 -> destination=:1.1657 serial=9 reply_serial=2
   array [
      variant          string "application://picard_picard.desktop"
      variant          array [
            dict entry(
               string "progress"
               variant                   int32 0
            )
            dict entry(
               string "progress-visible"
               variant                   boolean false
            )
         ]
   ]

But as soon as the snapped app tries to emit the update singnal this apparmor error shows up in the journal:

Jan 05 09:51:05 zoidberg dbus-daemon[2771]: apparmor="DENIED" operation="dbus_signal"  bus="session" path="/" interface="com.canonical.Unity.LauncherEntry" member="Update" mask="send" name="org.freedesktop.DBus" pid=64498 label="snap.picard.picard" peer_pid=31926 peer_label="unconfined"

What do I miss? Is there some way to grant the app full permission to use this interface?

You shouldn’t need to define a dbus interface slot for this: instead use the unity7 plug.

Looking at the AppArmor rules for the unity7 interface, you should be able to send the Update signal:

What seems to be tripping you up is that the object path of the signal you’re sending. The AppArmor rule requires that it match /com/canonical/unity/launcherentry/[0-9]*, while the error seems to indicate you’re using a path of /.

If it’s an option, I would strongly recommend using the implementation in libunity as recommended in the wiki page you linked to. You should be able to use it from Python via gi.repository.Unity.LauncherEntry provided you’ve packaged the GIR type library with your snap. I wouldn’t be surprised if there are other traps waiting for you if you try to reimplement the API from scratch.

While Unity 7 is no longer a moving target, the library was considered the stable API rather than the wire protocol. I see some use of libdee in the LauncherEntry client code, and you definitely don’t want to try and reimplement that.

2 Likes

Thanks a lot. Changing the path in the app to /com/canonical/unity/launcherentry/1 fixed this and progress indicator is working now.

The unity7 plug was already in use. But the docs at The unity7 interface | Snapcraft documentation are very quiet on what this thing actually does :smiley:

The spec does not talk about having to publish this interface under any specific path, so far snapd is the only thing tripping over this. Replacing a working implementation because of a snapd quirk seems a bit much :smiley:

I also rather avoid adding a dependency on an external library for a simple interface like this. Especially not one that seems to be mostly unmaintained, is not available in all distributions and does seem very GLib specific (e.g. it does not seem possible to integrate this with Qt’s DBus implementation).

I was on the Unity API team working on some code adjacent to this back before Ubuntu Phone, and the prevailing policy was that the API was defined by the libraries rather than the wire protocol. This was before sandboxing and multiple runtimes/base snaps was a concern, so it was assumed every app could link to the same libunity.

At this point, there’s not much risk of the wire protocols changing (since Unity 7 is not seeing much active development), but it doesn’t change the fact that the definition of those protocols is “what the official client library implements”. If the official client library works with the snapd interface but your alternative version doesn’t, then that would be a bug in your implementation rather than the snapd interface.

Now looking at the libunity source again, I think the main additional features it implements not listed in the wiki page are:

  1. Signals are sent from /com/canonical/unity/launcherentry/$number, where $number is a hash of the desktop file ID. The exact object path is not important, and only being used to avoid internal collisions. Setting number=1 should be fine.

  2. There is code to watch for changes in ther ownership of the com.canonical.Unity bus name, and send out an Update signal with the full set of properties when a new owner has claimed the name. This is to cover the case of the window manager crashing or being restarted, so the new instance can update the launcher.

If you don’t care about the edge cases, you could probably do without (2).

1 Like

Thanks a lot for the background, this is very insightful.

I think the edge case of 2) is luckily not of much relevance to us. If the window manager restarts during a time we have a progress bar there would be frequent updates scheduled anyway. In a quick test restarting gnome-shell manually this seems to work all well. Anyway, good to know about that.