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 ...)
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.
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!
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):
dbus-launch
:
xpra "--start-child=dbus-launch firefox" start :100
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
")
I agree that xpra should probably not start the agents. I like modularity :-)
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()
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
Thanks, I just figured it out, I've got some code in progress that forwards it to to the other end via xpra + pynotify.
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
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()
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...
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:
Lots of improvements in r212 (and also for "bell" forwarding code):
ClientExtras
in platform code
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?
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.
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.
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?
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..)
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?
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!
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!
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
Never mind:
import dbus
has to be:
import dbus.glib
As the import has side effects... which make it all work.
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..
Renaming this ticket to match the work that was done on it, and re-opening the dbus forwarding feature request under #450
(setting correct milestone the work was completed in)
this ticket has been moved to: https://github.com/Xpra-org/xpra/issues/22