Xpra: Ticket #22: forwarding system tray

For even more complete integration into the client session it would be nice to get the dbus socket forwarded, too.

I don't know much about dbus ... but IIRC this would mean that the registered applications would have to be stored, so that on re-connection the dbus login can be re-done.

It would be too much to hope for automatic dbus reconnect in each application, although it would surely be the cleanest way if libdbus just did all that.

Furthermore there could be a way to intercept "exec" calls ... there are quite a few programs that just call other programs, eg. when clicking a link a call to the firefox executable is made. But if this application runs via xpra, but firefox is on the client, this fails - unless there's some easy way to forward these calls, too.

(Perhaps it would be enough to have a xpra option, like "xpra run-on-client <cmdline>" for this - either the call can be configured in the application, or a shell script in a well-chosen PATH could do the forward call. There should be some mechanism for that in xpra, as the exec forwarding via ssh is not that easy to configure ...)



Sat, 10 Sep 2011 18:33:06 GMT - Timo Juhani Lindfors:

I think you should study dbus more. Applications generally die if they lose connection to dbus-daemon. I think we want to run dbus-daemon on both server and client and then forward the interesting parts like org.freedesktop.Notifications.


Sun, 11 Sep 2011 07:32:36 GMT - pmarek:

Well, yes, I don't really know that much about dbus.

I thought that xpra would open the dbus socket, and store/forward the needed information - but if the forwarding works via dbus -- even better, we just need to start it with the right options on "xpra start", and establish a link to the client!


Mon, 26 Sep 2011 04:37:50 GMT - Antoine Martin:

As lindi pointed out, we can't afford to lose the connection to the dbus-daemon so each end will have to run its own daemon and need to be able to forward from one to the other. Forwarding is non-trivial from what I can see, as we need to listen for specific messages, we can't just generically listen for everything... Unless we also enumerate all the signals registered with dbus? And even then, there are probably quite a few messages that should not be forwarded as they only make sense on one end.

The dbus documentation is rather lacking, so is the dbus-python doc The best code examples I found are:

Finally, dbus needs to be started by something and I don't think that the xpra server should be responsible for that (then again, I may be biased as this is a large part of what winswitch does). Otherwise, you also have to start all the agents (ssh, gpg, ...) and parse some xdg directories. This all seems out of scope and can be done by (see caveats below):

The only problem with these two solutions is how the xpra server can then locate the dbus server instance to connect to.. (and this also applies when starting with "--use-display")


Mon, 26 Sep 2011 07:35:22 GMT - Timo Juhani Lindfors:

I agree that xpra should probably not start the agents. I like modularity :-)


Fri, 30 Sep 2011 09:09:44 GMT - Antoine Martin: status changed

A good start would be desktop notifications: also here (openmoko)

We need to "claim" this bus name (how we do that seems totally undocumented...)

Then this can easily be tested with:

#!/usr/bin/python
import pynotify
pynotify.init("Test Notifications")
n = pynotify.Notification("Title", "message")
n.show()

Fri, 30 Sep 2011 10:30:17 GMT - Timo Juhani Lindfors:

Quick and dirty proof of concept :-)

#!/usr/bin/python
import gtk
import dbus
import dbus.service
import gobject
import subprocess
import pipes
from dbus.mainloop.glib import DBusGMainLoop
class MyDBUSService(dbus.service.Object):
    def __init__(self):
        bus_name = dbus.service.BusName('org.freedesktop.Notifications', bus=dbus.SessionBus())
        dbus.service.Object.__init__(self, bus_name, '/org/freedesktop/Notifications')
    @dbus.service.method('org.freedesktop.Notifications')
    def GetCapabilities(self):
        return {}
    @dbus.service.method('org.freedesktop.Notifications', in_signature='sisssa{is}a{is}i', out_signature='u')
    def Notify(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout):
        print("Notify")
        cmd = ["ssh", "fomalhaut2", "env DBUS_SESSION_BUS_ADDRESS=unix:abstract=/tmp/dbus-DDF4ihBQNH,guid=d56317a9c7b0c986a0d6a083002d98df freedesktop-notifications-send", pipes.quote(app_name), str(replaces_id), pipes.quote(app_icon), pipes.quote(summary), pipes.quote(body), "UNSUPPORTED", "UNSUPPORTED", str(expire_timeout)]
        print(repr(cmd))
        subprocess.call(cmd)
        return 0
    @dbus.service.method('org.freedesktop.Notifications', in_signature='i')
    def CloseNotification(self, notification_id):
        print("close %s" % repr(notification_id))
        return
    @dbus.service.method('org.freedesktop.Notifications', out_signature='ssss')
    def GetServerInformation(self):
        return ["Notification Daemon", "GNOME", "0.5.0", "1.1"]
DBusGMainLoop(set_as_default=True)
myservice = MyDBUSService()
gtk.main()
# does not notice if notification-daemon is already running

Fri, 30 Sep 2011 10:32:28 GMT - Antoine Martin:

Thanks, I just figured it out, I've got some code in progress that forwards it to to the other end via xpra + pynotify.


Fri, 30 Sep 2011 10:43:26 GMT - Timo Juhani Lindfors:

If you want fewer dependencies you can also directly do

#!/usr/bin/python
import dbus
import dbus.glib
import gobject
import sys
def cbReply(*a):
    print("reply %s" % repr(a))
    loop.quit()
def cbError(*a):
    print("error %s" % repr(a))
    loop.quit()
bus = dbus.SessionBus()
obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
iface = dbus.Interface(obj, 'org.freedesktop.Notifications')
#print(sys.argv[1:])
app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout = sys.argv[1:]
replaces_id = int(replaces_id)
expire_timeout = int(expire_timeout)
iface.Notify(app_name, replaces_id, app_icon, summary, body, [], [], expire_timeout,
             reply_handler = cbReply,
             error_handler = cbError)
loop = gobject.MainLoop()
loop.run()

to send notifications


Fri, 30 Sep 2011 10:56:09 GMT - Antoine Martin:

I ended up with code very similar to yours, but the signature is slightly different, here is the code I used to find the right values:

import gobject
gobject.threads_init()
from dbus import glib
glib.init_threads()
import dbus
bus = dbus.SessionBus()
remote_object = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")
print ("Introspection data:\n")
print remote_object.Introspect()

Fri, 30 Sep 2011 11:00:06 GMT - Antoine Martin:

If someone stumbles on here looking for the code to use for claiming a dbus name, it is in the: dbus spec under "org.freedesktop.DBus.RequestName". Something like:

request = bus.request_name(BUS_NAME, dbus.bus.NAME_FLAG_REPLACE_EXISTING)

flags may vary...


Fri, 30 Sep 2011 12:20:32 GMT - Antoine Martin:

Mostly done in r202 for *nix.

This is using pynotify for now (quick and dirty), this whole area will need to be re-worked anyway when adding support for osx, win32, growl, appindicators, etc.. There is no unified notification API, fortunately I have already done all this platform code once for winswitch in winswitch/ui/notification_util.py - I also made sure the code is self contained (the imports aren't important at all), so we should be able to re-use that without too much effort.

Also, still left to do:


Thu, 13 Oct 2011 17:29:08 GMT - Antoine Martin:

Lots of improvements in r212 (and also for "bell" forwarding code):


Fri, 14 Oct 2011 09:07:45 GMT - Antoine Martin:

r218 adds support for notifications on windows

What other dbus messages do we want to forward? Suggestions? Maybe running a "dbus-snoop" could shed some light?


Tue, 08 Nov 2011 20:08:33 GMT - pmarek:

Hmm, I'd like to see xchat notification being relayed to my desktop.

What I mean are the blinking icon in the tray (which doesn't exist over xpra at all), and the balloon notices when eg. my nick is used in a channel.


Wed, 09 Nov 2011 04:19:56 GMT - Antoine Martin:

The balloon notices are there if the xpra session has its own dbus-session (which it will do if you start it via winswitch)

The tray icon is unlikely to ever be supported as there are just too many APIs for them (StatusIcon / appindicator / win32 tray / ..) and they generally support overriding.


Wed, 09 Nov 2011 08:11:13 GMT - pmarek:

Hmmm, I tried to install winswitch ... and got this:

Need to get 91.7 MB of archives. After this operation, 244 MB of additional disk space will be used.

Do I have to use winswitch? I'm currently using "xpra attach", and this works fine ... can't I use some commandline parameter to get dbus forwarded to the "current" desktop?


Wed, 09 Nov 2011 11:35:00 GMT - Antoine Martin:

You don't have to use winswitch, and you don't need to install all those dependencies either if you do install it. Unfortunately, there are no (supported) "Recommends" for RPMs so most things are listed as hard dependencies. If you are using DEBs, most things are listed as "Recommended" (and therefore pulled too) because people complained that the software was not usable (features missing) without them... I just can't win this battle with package managers. However you can install from source, the dependencies in that case are minimal.

The other option is to start the dbus-session yourself before starting the xpra server session. (but if you go down that route, you quickly end up re-inventing winswitch..)


Wed, 09 Nov 2011 11:47:38 GMT - pmarek:

So, as I've currently got a connection active -- is it enough to start dbus there, and then do an "xpra upgrade" to replace the server while keeping the xvfb (and the running applications) alive?


Wed, 09 Nov 2011 11:53:08 GMT - pmarek:

Hmmm .. the upgrade doesn't seem to work, I cannot connect a client anymore.

But killing the "upgrade" process also terminated the running xvfb ;/ So I'm back to simply starting that.

Well, I can see what you mean with "re-inventing winswitch" ;) (BTW, that is with Debian, and these packages are all required ...)

Thanks for the help!


Wed, 09 Nov 2011 11:55:39 GMT - pmarek:

I stand corrected again ... with --no-install-recommends I get

Need to get 6,395 kB/6,518 kB of archives. After this operation, 31.0 MB of additional disk space will be used.

Thanks!


Thu, 01 Dec 2011 18:38:47 GMT - Antoine Martin:

Need a little help here, this patch adds dbus notifications with code almost identical to what is used in winswitch, yet it does not work: no errors, no warnings... and no notifications either!?

Index: xpra/xposix/gui.py
===================================================================
--- xpra/xposix/gui.py	(revision 310)
+++ xpra/xposix/gui.py	(working copy)
@@ -32,7 +32,9 @@
         self.setup_tray(opts.tray_icon)
         self.setup_xprops(opts.pulseaudio)
         self.setup_x11_bell()
-        self.setup_pynotify()
+        self.has_dbusnotify = False
+        self.has_pynotify = False
+        self.setup_dbusnotify() or self.setup_pynotify()
         self.setup_clipboard_helper(ClipboardProtocolHelper)
     def exit(self):
@@ -150,15 +152,29 @@
         if not self.setup_statusicon(tray_icon_filename):
             log.error("failed to setup system-tray")
+    def setup_dbusnotify(self):
+        self.dbus_id = os.environ.get("DBUS_SESSION_BUS_ADDRESS", "")
+        try:
+            import dbus
+            bus = dbus.SessionBus()
+            obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
+            self.dbusnotify = dbus.Interface(obj, 'org.freedesktop.Notifications')
+            self.has_dbusnotify = True
+            log.info("using dbusnotify: %s", self.dbusnotify)
+        except Exception, e:
+            log.error("cannot import pynotify wrapper (turning notifications off) : %s", e)
+        return self.has_dbusnotify
+
     def setup_pynotify(self):
         self.dbus_id = os.environ.get("DBUS_SESSION_BUS_ADDRESS", "")
-        self.has_pynotify = False
         try:
             import pynotify
             pynotify.init("Xpra")
             self.has_pynotify = True
+            log("using pynotify: %s", pynotify)
         except ImportError, e:
             log.error("cannot import pynotify wrapper (turning notifications off) : %s", e)
+        return self.has_pynotify
     def setup_x11_bell(self):
         self.has_x11_bell = False
@@ -206,18 +222,38 @@
         device_bell(window, device, bell_class, bell_id, percent, bell_name)
     def can_notify(self):
-        return  self.has_pynotify
+        return  self.has_dbusnotify or self.has_pynotify
     def show_notify(self, dbus_id, id, app_name, replaces_id, app_icon, summary, body, expire_timeout):
         if self.dbus_id==dbus_id:
             log.error("remote dbus instance is the same as our local one, "
                       "cannot forward notification to ourself as this would create a loop")
             return
-        import pynotify
-        n = pynotify.Notification(summary, body)
-        n.set_urgency(pynotify.URGENCY_LOW)
-        n.set_timeout(expire_timeout)
-        n.show()
+        if self.has_dbusnotify:
+            def cbReply(*args):
+                log.info("notification reply: %s", args)
+                return False
+            def cbError(*args):
+                log.error("notification error: %s", args)
+                return False
+            try:
+                self.dbusnotify.Notify("Xpra", 0, app_icon, summary, body, [], [], expire_timeout,
+                     reply_handler = cbReply,
+                     error_handler = cbError)
+                log.info("show notify done via dbus: summary=%s", summary)
+            except:
+                log.error("dbus notify failed", exc_info=True)
+        elif self.has_pynotify:
+            try:
+                import pynotify
+                n = pynotify.Notification(summary, body)
+                n.set_urgency(pynotify.URGENCY_LOW)
+                n.set_timeout(expire_timeout)
+                n.show()
+            except:
+                log.error("pynotify failed", exc_info=True)
+        else:
+            log.error("notification cannot be displayed, no backend support!")
     def close_notify(self, id):
         pass

Thu, 01 Dec 2011 19:43:53 GMT - Antoine Martin:

Never mind:

import dbus

has to be:

import dbus.glib

As the import has side effects... which make it all work.


Thu, 01 Dec 2011 19:56:20 GMT - Antoine Martin:

ability to use dbus.glib for forwarding notifications to the client in r323

Not sure how to tell the packagers about the update to the package's dependencies, I am updating my own build files..


Mon, 30 Apr 2012 14:12:05 GMT - pmarek: status changed; resolution set


Thu, 31 Oct 2013 12:38:24 GMT - Antoine Martin: summary changed

Renaming this ticket to match the work that was done on it, and re-opening the dbus forwarding feature request under #450


Mon, 19 May 2014 12:41:29 GMT - Antoine Martin: milestone changed

(setting correct milestone the work was completed in)


Thu, 21 Feb 2019 13:37:38 GMT - Antoine Martin:

See also #43, #406, #2161


Sat, 23 Jan 2021 04:43:26 GMT - migration script:

this ticket has been moved to: https://github.com/Xpra-org/xpra/issues/22