Browse Source

Implement D-Bus Activation of desktop files manually

This part of GIO library isn't suitable for short-lived processes (the
call is done asynchronously and may not reach the application before
qubes-desktop-run process is terminated). To fix this, implement dbus
activation manually, synchronously.
While at it, implement waiting for application to terminate (useful in
DispVM), by waiting for its dbus name to disappear.
dbus-python API isn't particularly nice, but don't switch to completely different
library as a stable update.

Fixes QubesOS/qubes-issues#2449
Marek Marczykowski-Górecki 5 years ago
parent
commit
75e54cd5ef
1 changed files with 34 additions and 3 deletions
  1. 34 3
      qubesagent/xdg.py

+ 34 - 3
qubesagent/xdg.py

@@ -1,3 +1,5 @@
+import functools
+
 from gi.repository import Gio  # pylint: disable=import-error
 from gi.repository import GLib  # pylint: disable=import-error
 import sys
@@ -6,22 +8,51 @@ import os
 def pid_callback(launcher, pid, pid_list):
     pid_list.append(pid)
 
+def dbus_name_change(loop, name, old_owner, new_owner):
+    if not new_owner:
+        loop.quit()
+
 def launch(desktop, *files, **kwargs):
     wait = kwargs.pop('wait', True)
     launcher = Gio.DesktopAppInfo.new_from_filename(desktop)
     try:
         import dbus
+        from dbus.mainloop.glib import DBusGMainLoop
         if hasattr(launcher, 'get_boolean'):
             activatable = launcher.get_boolean('DBusActivatable')
             if activatable:
+                loop = GLib.MainLoop()
+                DBusGMainLoop(set_as_default=True)
                 bus = dbus.SessionBus()
                 service_id = launcher.get_id()
                 # cut the .desktop suffix
-                service_id = service_id[:-8]
+                service_id = service_id[:-len('.desktop')]
+                # see D-Bus Activation Desktop entry specification
+                object_path = '/' + service_id.replace('.', '/').\
+                    replace('-', '_')
                 try:
-                    bus.start_service_by_name(service_id)
-                except dbus.DBusException:
+                    proxy = bus.get_object(service_id, object_path)
+                    match = bus.add_signal_receiver(
+                        functools.partial(dbus_name_change, loop),
+                        'NameOwnerChanged',
+                        dbus.BUS_DAEMON_IFACE,
+                        dbus.BUS_DAEMON_NAME,
+                        dbus.BUS_DAEMON_PATH)
+                    if files:
+                        proxy.Open(files, {},
+                            dbus_interface='org.freedesktop.Application')
+                    else:
+                        proxy.Activate({},
+                            dbus_interface='org.freedesktop.Application')
+                except dbus.DBusException as e:
+                    print(e)
+                    # fallback to non-dbus version
                     pass
+                else:
+                    if wait:
+                        loop.run()
+                    match.remove()
+                    return
     except ImportError:
         pass
     if wait: