Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/notifications/dbus_notifier.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-2019 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
8from xpra.util import ellipsizer, csv
9from xpra.os_util import bytestostr
10from xpra.dbus.helper import native_to_dbus
11from xpra.notifications.notifier_base import NotifierBase, log
12try:
13 #new recommended way of using the glib main loop:
14 from dbus.mainloop.glib import DBusGMainLoop
15 DBusGMainLoop(set_as_default=True)
16except ImportError:
17 #beware: this import has side-effects:
18 import dbus.glib
19 assert dbus.glib
20import dbus.exceptions
22NOTIFICATION_APP_NAME = os.environ.get("XPRA_NOTIFICATION_APP_NAME", "%s (via Xpra)")
25def DBUS_Notifier_factory(*args):
26 try:
27 return DBUS_Notifier(*args)
28 except Exception as e:
29 log.warn("failed to instantiate the dbus notification handler:")
30 if str(e).startswith("org.freedesktop.DBus.Error.ServiceUnknown:"):
31 log.warn(" you may need to start a notification service for 'org.freedesktop.Notifications'")
32 else:
33 log.warn(" %s", e)
34 log.warn(" disable notifications to avoid this warning")
35 return None
37class DBUS_Notifier(NotifierBase):
39 def __init__(self, *args):
40 super().__init__(*args)
41 self.app_name_format = NOTIFICATION_APP_NAME
42 self.last_notification = None
43 self.actual_notification_id = {}
44 self.setup_dbusnotify()
45 self.handles_actions = True
47 def setup_dbusnotify(self):
48 self.dbus_session = dbus.SessionBus()
49 FD_NOTIFICATIONS = 'org.freedesktop.Notifications'
50 self.org_fd_notifications = self.dbus_session.get_object(FD_NOTIFICATIONS, '/org/freedesktop/Notifications')
51 self.org_fd_notifications.connect_to_signal("NotificationClosed", self.NotificationClosed)
52 self.org_fd_notifications.connect_to_signal("ActionInvoked", self.ActionInvoked)
54 #connect_to_signal("HelloSignal", hello_signal_handler, dbus_interface="com.example.TestService", arg0="Hello")
55 self.dbusnotify = dbus.Interface(self.org_fd_notifications, FD_NOTIFICATIONS)
56 log("using dbusnotify: %s(%s)", type(self.dbusnotify), FD_NOTIFICATIONS)
57 caps = tuple(str(x) for x in self.dbusnotify.GetCapabilities())
58 log("capabilities=%s", csv(caps))
59 self.handles_actions = "actions" in caps
60 log("dbus.get_default_main_loop()=%s", dbus.get_default_main_loop())
62 def cleanup(self):
63 nids = list(self.actual_notification_id.items())
64 self.actual_notification_id = {}
65 for nid, actual_id in nids:
66 self.do_close(nid, actual_id)
67 NotifierBase.cleanup(self)
70 def show_notify(self, dbus_id, tray, nid,
71 app_name, replaces_nid, app_icon,
72 summary, body, actions, hints, timeout, icon):
73 if not self.dbus_check(dbus_id):
74 return
75 self.may_retry = True
76 try:
77 icon_string = self.get_icon_string(nid, app_icon, icon)
78 log("get_icon_string%s=%s", (nid, app_icon, ellipsizer(icon)), icon_string)
79 if app_name=="Xpra":
80 #don't show "Xpra (via Xpra)"
81 app_str = "Xpra"
82 else:
83 try:
84 app_str = self.app_name_format % app_name
85 except TypeError:
86 app_str = app_name or "Xpra"
87 self.last_notification = (
88 dbus_id, tray, nid, app_name, replaces_nid,
89 app_icon, summary, body, timeout, icon,
90 )
91 def NotifyReply(notification_id):
92 log("NotifyReply(%s) for nid=%i", notification_id, nid)
93 self.actual_notification_id[nid] = int(notification_id)
94 dbus_hints = self.parse_hints(hints)
95 log("calling %s%s", self.dbusnotify.Notify,
96 (app_str, 0, icon_string, summary, body, actions, dbus_hints, timeout))
97 self.dbusnotify.Notify(app_str, 0, icon_string, summary, body, actions, dbus_hints, timeout,
98 reply_handler = NotifyReply,
99 error_handler = self.NotifyError)
100 except Exception:
101 log.error("Error: dbus notify failed", exc_info=True)
103 def _find_nid(self, actual_id : int):
104 aid = int(actual_id)
105 for k,v in self.actual_notification_id.items():
106 if v==aid:
107 return k
108 return None
110 def noparse_hints(self, h) -> dict:
111 return h
113 def parse_hints(self, h) -> dict:
114 hints = {}
115 for x in ("action-icons", "category", "desktop-entry", "resident", "transient", "x", "y", "urgency"):
116 v = h.get(x)
117 if v is not None:
118 hints[x] = native_to_dbus(v)
119 image_data = h.get("image-data")
120 if image_data and bytestostr(image_data[0])=="png":
121 try:
122 from xpra.codecs.pillow.decoder import open_only
123 img_data = image_data[3]
124 img = open_only(img_data, ("png",))
125 w, h = img.size
126 channels = len(img.mode)
127 rowstride = w*channels
128 has_alpha = img.mode=="RGBA"
129 pixel_data = bytearray(img.tobytes("raw", img.mode))
130 log.info("pixel_data=%s", type(pixel_data))
131 args = w, h, rowstride, has_alpha, 8, channels, pixel_data
132 hints["image-data"] = tuple(native_to_dbus(x) for x in args)
133 #hints["image-data"] = args
134 except Exception as e:
135 log("parse_hints(%s) error on image-data=%s", h, image_data, exc_info=True)
136 log.error("Error parsing notification image:")
137 log.error(" %s", e)
138 log("parse_hints(%s)=%s", h, hints)
139 #return dbus.types.Dictionary(hints, signature="sv")
140 return hints
143 def NotificationClosed(self, actual_id : int, reason):
144 nid = self._find_nid(actual_id)
145 reason_str = {
146 1 : "expired",
147 2 : "dismissed by the user",
148 3 : "closed by a call to CloseNotification",
149 4 : "Undefined/reserved reasons",
150 }.get(int(reason), str(reason))
151 log("NotificationClosed(%s, %s) nid=%s, reason=%s", actual_id, reason, nid, reason_str)
152 if nid:
153 self.actual_notification_id.pop(nid, None)
154 self.clean_notification(nid)
155 if self.closed_cb:
156 self.closed_cb(nid, int(reason), reason_str)
158 def ActionInvoked(self, actual_id : int, action):
159 nid = self._find_nid(actual_id)
160 log("ActionInvoked(%s, %s) nid=%s", actual_id, action, nid)
161 if nid:
162 if self.action_cb:
163 self.action_cb(nid, str(action))
165 def NotifyError(self, dbus_error, *_args):
166 try:
167 if isinstance(dbus_error, dbus.exceptions.DBusException):
168 message = dbus_error.get_dbus_message()
169 dbus_error_name = dbus_error.get_dbus_name()
170 if dbus_error_name!="org.freedesktop.DBus.Error.ServiceUnknown":
171 log.error("unhandled dbus exception: %s, %s", message, dbus_error_name)
172 return False
174 if not self.may_retry:
175 log.error("Error: cannot send notification via dbus,")
176 log.error(" check that you notification service is operating properly")
177 return False
178 self.may_retry = False
180 log.info("trying to re-connect to the notification service")
181 #try to connect to the notification again (just once):
182 self.setup_dbusnotify()
183 #and retry:
184 self.show_notify(*self.last_notification)
185 except Exception:
186 log("cannot filter error", exc_info=True)
187 log.error("Error processing notification:")
188 log.error(" %s", dbus_error)
189 return False
191 def close_notify(self, nid : int):
192 actual_id = self.actual_notification_id.get(nid)
193 if actual_id is None:
194 log("close_notify(%i) actual notification not found, already closed?", nid)
195 return
196 log("close_notify(%i) actual id=%s", nid, actual_id)
197 self.do_close(nid, actual_id)
199 def do_close(self, nid : int, actual_id : int):
200 log("do_close_notify(%i)", actual_id)
201 def CloseNotificationReply():
202 self.actual_notification_id.pop(nid, None)
203 def CloseNotificationError(dbus_error, *_args):
204 log.warn("Error: error closing notification:")
205 log.warn(" %s", dbus_error)
206 self.dbusnotify.CloseNotification(actual_id,
207 reply_handler = CloseNotificationReply,
208 error_handler = CloseNotificationError)
211def main():
212 from gi.repository import GLib, Gtk
213 def show():
214 n = DBUS_Notifier_factory()
215 #actions = ["0", "Hello", "1", "Bye"]
216 actions = []
217 n.show_notify("", None, 0, "Test", 0, "", "Summary", "Body line1\nline2...",
218 actions, {}, 0, "")
219 return False
220 GLib.idle_add(show)
221 GLib.timeout_add(20000, Gtk.main_quit)
222 Gtk.main()
225if __name__ == "__main__":
226 main()