Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/dbus/notifications_forwarder.py : 45%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2011-2018 Antoine Martin <antoine@xpra.org>
3# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
4# later version. See the file COPYING for details.
6import os
7import dbus.service
9from xpra.notifications.common import parse_image_data, parse_image_path
10from xpra.dbus.helper import dbus_to_native
11from xpra.util import envbool, csv
12from xpra.log import Logger
14log = Logger("dbus", "notify")
16BUS_NAME="org.freedesktop.Notifications"
17BUS_PATH="/org/freedesktop/Notifications"
19ACTIONS = envbool("XPRA_NOTIFICATIONS_ACTIONS", True)
22"""
23We register this class as handling notifications on the session dbus,
24optionally replacing an existing instance if one exists.
26The generalized callback signatures are:
27 notify_callback(dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout)
28 close_callback(nid)
29"""
30class DBUSNotificationsForwarder(dbus.service.Object):
32 def __init__(self, bus, notify_callback=None, close_callback=None):
33 self.bus = bus
34 self.notify_callback = notify_callback
35 self.close_callback = close_callback
36 self.active_notifications = set()
37 self.counter = 0
38 self.support_actions = True
39 self.dbus_id = os.environ.get("DBUS_SESSION_BUS_ADDRESS", "")
40 bus_name = dbus.service.BusName(BUS_NAME, bus=bus)
41 super().__init__(bus_name, BUS_PATH)
43 def get_info(self) -> dict:
44 return {
45 "active" : tuple(self.active_notifications),
46 "counter" : self.counter,
47 "actions" : self.support_actions,
48 "dbus-id" : self.dbus_id,
49 "bus-name" : BUS_NAME,
50 "bus-path" : BUS_PATH,
51 "capabilities" : self.do_get_capabilities(),
52 }
54 def next_id(self):
55 self.counter += 1
56 return self.counter
58 @dbus.service.method(BUS_NAME, in_signature='susssasa{sv}i', out_signature='u')
59 def Notify(self, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout):
60 if replaces_nid==0:
61 nid = self.next_id()
62 else:
63 nid = int(replaces_nid)
64 log("Notify%s nid=%s, counter=%i, callback=%s",
65 (app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout),
66 nid, self.counter, self.notify_callback)
67 self.active_notifications.add(nid)
68 if self.notify_callback:
69 try:
70 actions = tuple(str(x) for x in actions)
71 hints = self.parse_hints(hints)
72 args = (
73 self.dbus_id, int(nid), str(app_name),
74 int(replaces_nid), str(app_icon),
75 str(summary), str(body),
76 actions, hints, int(expire_timeout),
77 )
78 except Exception as e:
79 log.error("Error: failed to parse Notify arguments:")
80 log.error(" %s", e)
81 try:
82 self.notify_callback(*args)
83 except Exception as e:
84 log.error("Error calling notification handler", exc_info=True)
85 log("Notify returning %s", nid)
86 return nid
88 def parse_hints(self, dbus_hints):
89 hints = {}
90 h = dbus_to_native(dbus_hints)
91 for x in ("image-data", "icon_data"):
92 data = h.pop(x, None)
93 if data:
94 v = parse_image_data(data)
95 if v:
96 hints["image-data"] = v
97 break
98 if "image-data" not in hints:
99 image_path = h.pop("image-path", None)
100 if image_path:
101 v = parse_image_path(image_path)
102 if v:
103 hints["image-data"] = v
104 for x in ("action-icons", "category", "desktop-entry", "resident", "transient", "x", "y", "urgency"):
105 v = h.get(x)
106 if v is not None:
107 hints[x] = v
108 log("parse_hints(%s)=%s", dbus_hints, hints)
109 return hints
112 @dbus.service.method(BUS_NAME, out_signature='ssss')
113 def GetServerInformation(self):
114 #name, vendor, version, spec-version
115 from xpra import __version__
116 v = ["xpra-notification-proxy", "xpra", __version__, "0.9"]
117 log("GetServerInformation()=%s", v)
118 return v
120 @dbus.service.method(BUS_NAME, out_signature='as')
121 def GetCapabilities(self):
122 caps = self.do_get_capabilities()
123 log("GetCapabilities()=%s", csv(caps))
124 return caps
126 def do_get_capabilities(self):
127 caps = ["body", "icon-static"]
128 if ACTIONS and self.support_actions:
129 caps += ["actions", "action-icons"]
130 return caps
132 @dbus.service.method(BUS_NAME, in_signature='u')
133 def CloseNotification(self, nid):
134 log("CloseNotification(%s) callback=%s", nid, self.close_callback)
135 try:
136 self.active_notifications.remove(int(nid))
137 except KeyError:
138 return
139 else:
140 if self.close_callback:
141 self.close_callback(nid)
142 self.NotificationClosed(nid, 3) #3="The notification was closed by a call to CloseNotification"
144 def is_notification_active(self, nid):
145 return nid in self.active_notifications
147 @dbus.service.signal(BUS_NAME, signature='uu')
148 def NotificationClosed(self, nid, reason):
149 pass
151 @dbus.service.signal(BUS_NAME, signature='us')
152 def ActionInvoked(self, nid, action_key):
153 pass
156 def release(self):
157 try:
158 self.bus.release_name(BUS_NAME)
159 except dbus.exceptions.DBusException as e:
160 log("release()", exc_info=True)
161 log.error("Error releasing the dbus notification forwarder:")
162 for x in str(e).split(": "):
163 log.error(" %s", x)
165 def __str__(self):
166 return "DBUS-NotificationsForwarder(%s)" % BUS_NAME
169def register(notify_callback=None, close_callback=None, replace=False):
170 from xpra.dbus.common import init_session_bus
171 bus = init_session_bus()
172 flags = dbus.bus.NAME_FLAG_DO_NOT_QUEUE
173 if replace:
174 flags |= dbus.bus.NAME_FLAG_REPLACE_EXISTING
175 request = bus.request_name(BUS_NAME, flags)
176 log("notifications: bus name '%s', request=%s" % (BUS_NAME, request))
177 if request==dbus.bus.REQUEST_NAME_REPLY_EXISTS:
178 raise Exception("the name '%s' is already claimed on the session bus" % BUS_NAME)
179 return DBUSNotificationsForwarder(bus, notify_callback, close_callback)
182def main():
183 register()
184 from gi.repository import GLib
185 mainloop = GLib.MainLoop()
186 mainloop.run()
188if __name__ == "__main__":
189 main()