Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

forwarding system tray #22

Closed
totaam opened this issue Sep 10, 2011 · 25 comments
Closed

forwarding system tray #22

totaam opened this issue Sep 10, 2011 · 25 comments

Comments

@totaam
Copy link
Collaborator

totaam commented Sep 10, 2011

Issue migrated from trac ticket # 22

component: server | priority: major | resolution: worksforme | keywords: dbus

2011-09-10 19:21:48: pmarek created the issue


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

@totaam
Copy link
Collaborator Author

totaam commented Sep 10, 2011

2011-09-10 19:33:06: lindi commented


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.

@totaam
Copy link
Collaborator Author

totaam commented Sep 11, 2011

2011-09-11 08:32:37: pmarek commented


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!

@totaam
Copy link
Collaborator Author

totaam commented Sep 26, 2011

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

@totaam
Copy link
Collaborator Author

totaam commented Sep 26, 2011

2011-09-26 08:35:22: lindi commented


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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

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")
n.show()

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

2011-09-30 11:30:18: lindi commented


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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

2011-09-30 11:43:27: lindi commented


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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

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

@totaam
Copy link
Collaborator Author

totaam commented Sep 30, 2011

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

@totaam
Copy link
Collaborator Author

totaam commented Oct 13, 2011

2011-10-13 18:29:08: antoine commented


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

@totaam
Copy link
Collaborator Author

totaam commented Oct 14, 2011

2011-10-14 10:07:45: antoine commented


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?

@totaam
Copy link
Collaborator Author

totaam commented Nov 8, 2011

2011-11-08 20:08:33: pmarek commented


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.

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 04:19:56: antoine commented


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.

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 08:11:14: pmarek commented


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?

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 11:35:01: antoine commented


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

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 11:47:39: pmarek commented


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?

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 11:53:09: pmarek commented


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!

@totaam
Copy link
Collaborator Author

totaam commented Nov 9, 2011

2011-11-09 11:55:39: pmarek commented


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!

@totaam
Copy link
Collaborator Author

totaam commented Dec 1, 2011

2011-12-01 18:38:47: totaam commented


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

@totaam
Copy link
Collaborator Author

totaam commented Dec 1, 2011

Never mind:

import dbus

has to be:

import dbus.glib

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

@totaam
Copy link
Collaborator Author

totaam commented Dec 1, 2011

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

@totaam totaam closed this as completed Apr 30, 2012
@totaam
Copy link
Collaborator Author

totaam commented Oct 31, 2013

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

@totaam
Copy link
Collaborator Author

totaam commented Feb 21, 2019

See also #43, #406, #2161

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant