xpra icon
Bug tracker and wiki

This bug tracker and wiki are being discontinued
please use https://github.com/Xpra-org/xpra instead.

Opened 10 years ago

Closed 9 years ago

Last modified 3 months ago

#22 closed defect (worksforme)

forwarding system tray

Reported by: pmarek Owned by: Antoine Martin
Priority: major Milestone: 0.0.7.x
Component: server Version:
Keywords: dbus Cc:


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 ...)

Change History (28)

comment:1 Changed 10 years ago by 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.

comment:2 Changed 10 years ago by 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!

comment:3 Changed 10 years ago by 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):

  • starting the real command via dbus-launch:
    xpra "--start-child=dbus-launch firefox" start :100
  • starting dbus (and other desktop helpers) via a wrapper script:
    xpra --xvfb=Xvfb-dbus-wrapper ...

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")

comment:4 Changed 10 years ago by Timo Juhani Lindfors

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

comment:5 Changed 10 years ago by Antoine Martin

Status: newaccepted

A good start would be desktop notifications: org.freedesktop.Notifications and also here (openmoko)

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

Then this can easily be tested with:

import pynotify
pynotify.init("Test Notifications")
n = pynotify.Notification("Title", "message")

comment:6 Changed 10 years ago by Timo Juhani Lindfors

Quick and dirty proof of concept :-)

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')
    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):
        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)]
        return 0
    @dbus.service.method('org.freedesktop.Notifications', in_signature='i')
    def CloseNotification(self, notification_id):
        print("close %s" % repr(notification_id))
    @dbus.service.method('org.freedesktop.Notifications', out_signature='ssss')
    def GetServerInformation(self):
        return ["Notification Daemon", "GNOME", "0.5.0", "1.1"]
myservice = MyDBUSService()
# does not notice if notification-daemon is already running

comment:7 Changed 10 years ago by 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.

comment:8 Changed 10 years ago by Timo Juhani Lindfors

If you want fewer dependencies you can also directly do

import dbus
import dbus.glib
import gobject
import sys

def cbReply(*a):
    print("reply %s" % repr(a))
def cbError(*a):
    print("error %s" % repr(a))
bus = dbus.SessionBus()
obj = bus.get_object('org.freedesktop.Notifications', '/org/freedesktop/Notifications')
iface = dbus.Interface(obj, 'org.freedesktop.Notifications')

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()

to send notifications

comment:9 Changed 10 years ago by 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
from dbus import glib

import dbus
bus = dbus.SessionBus()

remote_object = bus.get_object("org.freedesktop.Notifications", "/org/freedesktop/Notifications")

print ("Introspection data:\n")
print remote_object.Introspect()

comment:10 Changed 10 years ago by 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...

comment:11 Changed 10 years ago by 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:

  • add server and/or client on/off command line switch? (+ man page update)
  • add optional dependencies on python-dbus and python-notify to packages
  • add info to website

comment:12 Changed 10 years ago by Antoine Martin

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

  • moved all bell/notification code to ClientExtras in platform code
  • dbus notifications forwarder now passes the DBUS_SESSION_BUS_ADDRESS as "dbus_id" so we can ensure we don't create a loop!
  • implemented notifications for osx using Growl

comment:13 Changed 10 years ago by 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?

Last edited 9 years ago by Antoine Martin (previous) (diff)

comment:14 Changed 9 years ago by 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.

comment:15 Changed 9 years ago by 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.

comment:16 Changed 9 years ago by 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?

comment:17 Changed 9 years ago by 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..)

comment:18 Changed 9 years ago by 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?

comment:19 Changed 9 years ago by 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!

comment:20 Changed 9 years ago by 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.


comment:21 Changed 9 years ago by 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_pynotify()
+        self.has_dbusnotify = False
+        self.has_pynotify = False
+        self.setup_dbusnotify() or self.setup_pynotify()
     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
             import pynotify
             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")
-        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):

comment:22 Changed 9 years ago by Antoine Martin

Never mind:

import dbus

has to be:

import dbus.glib

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

comment:23 Changed 9 years ago by 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..

comment:24 Changed 9 years ago by pmarek

Resolution: worksforme
Status: acceptedclosed

comment:25 Changed 7 years ago by Antoine Martin

Summary: Forwarding dbus connection and similarforwarding system tray

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

comment:26 Changed 7 years ago by Antoine Martin

Milestone: future0.0.7.x

(setting correct milestone the work was completed in)

comment:27 Changed 2 years ago by Antoine Martin

See also #43, #406, #2161

comment:28 Changed 3 months ago by migration script

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

Note: See TracTickets for help on using tickets.