From 847c21a2693fa7f19e003fbc3c835b2ff0dcb1d5 Mon Sep 17 00:00:00 2001 From: Antoine Martin Date: Fri, 30 Sep 2011 12:13:09 +0000 Subject: [PATCH] support for forwarding notifications to the client git-svn-id: https://xpra.org/svn/Xpra/trunk@202 3bb7dfac-3a0b-4e04-842a-767bc560f471 --- src/xpra/client.py | 24 +++++++- src/xpra/darwin/gui.py | 1 + src/xpra/dbus_notifications_forwarder.py | 77 ++++++++++++++++++++++++ src/xpra/server.py | 24 +++++++- src/xpra/win32/gui.py | 1 + src/xpra/xposix/gui.py | 13 ++++ 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100755 src/xpra/dbus_notifications_forwarder.py diff --git a/src/xpra/client.py b/src/xpra/client.py index badbf2314d..90c8433291 100644 --- a/src/xpra/client.py +++ b/src/xpra/client.py @@ -22,7 +22,7 @@ from xpra.platform.gui import ClipboardProtocolHelper, ClientExtras, grok_modifier_map from xpra.scripts.main import ENCODINGS -from xpra.platform.gui import system_bell +from xpra.platform.gui import system_bell, notifications_wrapper import xpra default_capabilities = {"__prerelease_version": xpra.__version__} @@ -378,6 +378,13 @@ def __init__(self, conn, opts): if self.max_bandwidth>0.0 and self.jpegquality==0: """ jpegquality was not set, use a better start value """ self.jpegquality = 50 + + self.notifications = None + if notifications_wrapper: + try: + self.notifications = notifications_wrapper() + except ImportError, e: + log.error("failed to load notification wrapper (turning feature off) : %s", e) self._protocol = Protocol(conn, self.process_packet) ClientSource(self._protocol) @@ -543,6 +550,7 @@ def send_hello(self, hash=None): capabilities_request["xmodmap_data"] = self.xmodmap_data capabilities_request["cursors"] = True capabilities_request["bell"] = system_bell is not None + capabilities_request["notifications"] = self.notifications is not None (_, _, current_mask) = gtk.gdk.get_default_root_window().get_pointer() modifiers = self.mask_to_names(current_mask) log.debug("sending modifiers=%s" % str(modifiers)) @@ -650,6 +658,18 @@ def _process_bell(self, packet): window = self._id_to_window[id] system_bell(window, device, percent, pitch, duration, bell_class, bell_id, bell_name) + def _process_notify_show(self, packet): + (_, id, app_name, replaces_id, app_icon, summary, body, expire_timeout) = packet + log("_process_notify_show(%s,%s,%s,%s,%s,%s,%s) notifications wrapper=%s", id, app_name, replaces_id, app_icon, summary, body, expire_timeout, self.notifications) + if self.notifications: + self.notifications.notify(id, app_name, replaces_id, app_icon, summary, body, expire_timeout) + + def _process_notify_close(self, packet): + (_, id) = packet + log("_process_notify_close(%s)", id) + if self.notifications: + self.notifications.close_callback(id) + def _process_window_metadata(self, packet): (_, id, metadata) = packet window = self._id_to_window[id] @@ -686,6 +706,8 @@ def _process_gibberish(self, packet): "draw": _process_draw, "cursor": _process_cursor, "bell": _process_bell, + "notify_show": _process_notify_show, + "notify_close": _process_notify_close, "window-metadata": _process_window_metadata, "configure-override-redirect": _process_configure_override_redirect, "lost-window": _process_lost_window, diff --git a/src/xpra/darwin/gui.py b/src/xpra/darwin/gui.py index 795be254ff..c397f4b677 100644 --- a/src/xpra/darwin/gui.py +++ b/src/xpra/darwin/gui.py @@ -29,6 +29,7 @@ def get_keymap_spec(): return None,None,None system_bell = None +notifications_wrapper = None class ClipboardProtocolHelper(object): def __init__(self, send_packet_cb): diff --git a/src/xpra/dbus_notifications_forwarder.py b/src/xpra/dbus_notifications_forwarder.py new file mode 100755 index 0000000000..eb560e93d7 --- /dev/null +++ b/src/xpra/dbus_notifications_forwarder.py @@ -0,0 +1,77 @@ +# This file is part of Parti. +# Copyright (C) 2011 Antoine Martin +# Parti is released under the terms of the GNU GPL v2, or, at your option, any +# later version. See the file COPYING for details. + +import gtk +import dbus.service +from dbus.mainloop.glib import DBusGMainLoop + +from wimpiggy.log import Logger +log = Logger() + +BUS_NAME="org.freedesktop.Notifications" +BUS_PATH="/org/freedesktop/Notifications" + +""" +We register this class as handling notifications on the session dbus, +optionally replacing an existing instance if one exists. + +The generalized callback signatures are: + notify_callback(id, app_name, replaces_id, app_icon, summary, body, expire_timeout) + close_callback(id) +""" +class DBUSNotificationsForwarder(dbus.service.Object): + + CAPABILITIES = ["body", "icon-static"] + + def __init__(self, bus, notify_callback=None, close_callback=None): + self.notify_callback = notify_callback + self.close_callback = close_callback + self.counter = 1 + bus_name = dbus.service.BusName(BUS_NAME, bus=bus) + dbus.service.Object.__init__(self, bus_name, BUS_PATH) + + @dbus.service.method(BUS_NAME, in_signature='susssasa{sv}i', out_signature='u') + def Notify(self, app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout): + log("Notify(%s,%s,%s,%s,%s,%s,%s,%s)" % (app_name, replaces_id, app_icon, summary, body, actions, hints, expire_timeout)) + if replaces_id<=0: + self.counter += 1 + id = self.counter + else: + id = replaces_id + if self.notify_callback: + self.notify_callback(id, app_name, replaces_id, app_icon, summary, body, expire_timeout) + return self.counter + + @dbus.service.method(BUS_NAME, out_signature='ssss') + def GetServerInformation(self): + log("GetServerInformation()") + #name, vendor, version, spec-version + return ["xpra-notification-proxy", "xpra", "0.1", "0.9"] + + @dbus.service.method(BUS_NAME, out_signature='as') + def GetCapabilities(self): + log("GetCapabilities()") + return DBUSNotificationsForwarder.CAPABILITIES + + @dbus.service.method(BUS_NAME, in_signature='u') + def CloseNotification(self, id): + log("CloseNotification(%s)", id) + if self.close_callback: + self.close_callback(id) + +def register(notify_callback=None, close_callback=None, replace=True): + DBusGMainLoop(set_as_default=True) + bus = dbus.SessionBus() + if replace: + request = bus.request_name(BUS_NAME, dbus.bus.NAME_FLAG_REPLACE_EXISTING) + log("request_name(%s)=%s" % (BUS_NAME, request)) + return DBUSNotificationsForwarder(bus, notify_callback, close_callback) + +def main(): + register() + gtk.main() + +if __name__ == "__main__": + main() diff --git a/src/xpra/server.py b/src/xpra/server.py index a6a53a19d6..46ee46418d 100644 --- a/src/xpra/server.py +++ b/src/xpra/server.py @@ -6,7 +6,6 @@ # later version. See the file COPYING for details. # Todo: -# cursors # xsync resize stuff # shape? # any other interesting metadata? _NET_WM_TYPE, WM_TRANSIENT_FOR, etc.? @@ -336,6 +335,7 @@ def __init__(self, clobber, sockets, password_file, pulseaudio, clipboard, randr self.xkbmap_query = None self.xmodmap_data = None + self.send_notifications = False self.last_cursor_serial = None self.cursor_image = None #store list of currently pressed keys @@ -376,6 +376,14 @@ def __init__(self, clobber, sockets, password_file, pulseaudio, clipboard, randr log.info("randr enabled: %s" % self.randr) self.pulseaudio = pulseaudio + + try: + from xpra.dbus_notifications_forwarder import register + self.notifications_forwarder = register(self.notify_callback, self.notify_close_callback, replace=True) + log.info("using notification forwarder: %s", self.notifications_forwarder) + except Exception, e: + log.error("failed to load dbus notifications forwarder: %s", e) + self.notifications_forwarder = None ### All right, we're ready to accept customers: for sock in sockets: @@ -542,6 +550,16 @@ def _bell_signaled(self, wm, event): if self.send_bell: self._send(["bell", id, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, event.bell_name]) + def notify_callback(self, id, app_name, replaces_id, app_icon, summary, body, expire_timeout): + log("notify_callback(%s,%s,%s,%s,%s,%s,%s) send_notifications=%s", id, app_name, replaces_id, app_icon, summary, body, expire_timeout, self.send_notifications) + if self.send_notifications: + self._send(["notify_show", int(id), str(app_name), int(replaces_id), str(app_icon), str(summary), str(body), long(expire_timeout)]) + + def notify_close_callback(self, id): + log("notify_close_callback(%s)", id) + if self.send_notifications: + self._send(["notify_close", int(id)]) + def do_wimpiggy_child_map_event(self, event): raw_window = event.window if event.override_redirect: @@ -796,7 +814,7 @@ def _calculate_capabilities(self, client_capabilities): capabilities = {} for cap in ("deflate", "__prerelease_version", "challenge_response", "keymap", "xkbmap_query", "xmodmap_data", "modifiers", - "cursors", "bell", + "cursors", "bell", "notifications", "png_window_icons", "encodings", "encoding", "jpeg"): if cap in client_capabilities: capabilities[cap] = client_capabilities[cap] @@ -936,6 +954,8 @@ def login_failed(*args): self.set_keymap() self.send_cursors = capabilities.get("cursors", False) self.send_bell = capabilities.get("bell", False) + self.send_notifications = capabilities.get("notifications", False) + log.info("send_cursors=%s, send_bell=%s, send_notifications=%s", self.send_cursors, self.send_bell, self.send_notifications) self._wm.enableCursors(self.send_cursors) self.png_window_icons = capabilities.get("png_window_icons", False) # now we can set the modifiers to match the client diff --git a/src/xpra/win32/gui.py b/src/xpra/win32/gui.py index a57a1eeb0d..bcce543e2f 100644 --- a/src/xpra/win32/gui.py +++ b/src/xpra/win32/gui.py @@ -31,6 +31,7 @@ def get_keymap_spec(): return None,None,None system_bell = None +notifications_wrapper = None class ClipboardProtocolHelper(object): def __init__(self, send_packet_cb): diff --git a/src/xpra/xposix/gui.py b/src/xpra/xposix/gui.py index 86ad50c828..74985d5b56 100644 --- a/src/xpra/xposix/gui.py +++ b/src/xpra/xposix/gui.py @@ -84,3 +84,16 @@ def x11_system_bell(window, device, percent, pitch, duration, bell_class, bell_i system_bell = x11_system_bell except ImportError, e: log.error("cannot import device_bell (turning feature off) : %s", e) + +class notifications_wrapper: + def __init__(self): + import pynotify + pynotify.init("Xpra") + + def notify(self, id, app_name, replaces_id, app_icon, summary, body, expire_timeout): + import pynotify + n = pynotify.Notification("Title", "message") + n.show() + + def close_callback(self, id): + pass