Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/notification_forwarder.py : 56%
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# -*- coding: utf-8 -*-
2# This file is part of Xpra.
3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
6#pylint: disable-msg=E1101
8import os.path
10from xpra.os_util import OSX, POSIX
11from xpra.util import ellipsizer
12from xpra.make_thread import start_thread
13from xpra.server.mixins.stub_server_mixin import StubServerMixin
14from xpra.log import Logger
16log = Logger("notify")
19"""
20Mixin for servers that forward notifications.
21"""
22class NotificationForwarder(StubServerMixin):
24 def __init__(self):
25 self.notifications_forwarder = None
26 self.notifications = False
28 def init(self, opts):
29 self.notifications = opts.notifications
31 def setup(self):
32 self.init_notification_forwarder()
34 def cleanup(self):
35 nf = self.notifications_forwarder
36 if nf:
37 self.notifications_forwarder = None
38 start_thread(nf.release, "notifier-release", daemon=True)
40 def get_info(self, _source=None) -> dict:
41 if not self.notifications_forwarder:
42 return {}
43 return {"notifications" : self.notifications_forwarder.get_info()}
45 def get_server_features(self, _source=None) -> dict:
46 return {
47 "notifications" : self.notifications,
48 "notifications.close" : self.notifications, #added in v2.3
49 "notifications.actions" : self.notifications, #added in v2.3
50 }
52 def parse_hello(self, ss, _caps, send_ui):
53 log("parse_hello(%s, {...}, %s) notifications_forwarder=%s", ss, send_ui, self.notifications_forwarder)
54 if send_ui and self.notifications_forwarder:
55 client_notification_actions = dict(
56 (s.uuid, getattr(s, "send_notifications_actions", False))
57 for s in self._server_sources.values()
58 )
59 log("client_notification_actions=%s", client_notification_actions)
60 self.notifications_forwarder.support_actions = any(v for v in client_notification_actions.values())
63 def init_notification_forwarder(self):
64 log("init_notification_forwarder() enabled=%s", self.notifications)
65 if self.notifications and POSIX and not OSX:
66 try:
67 from xpra.dbus.notifications_forwarder import register
68 self.notifications_forwarder = register(self.notify_callback, self.notify_close_callback)
69 if self.notifications_forwarder:
70 log.info("D-Bus notification forwarding is available")
71 log("%s", self.notifications_forwarder)
72 except Exception as e:
73 log("init_notification_forwarder()", exc_info=True)
74 self.notify_setup_error(e)
76 def notify_setup_error(self, exception):
77 log.warn("Warning: cannot forward notifications,")
78 if str(exception).endswith("is already claimed on the session bus"):
79 log.warn(" the interface is already claimed")
80 else:
81 log.warn(" failed to load or register our dbus notifications forwarder:")
82 for msg in str(exception).split(": "):
83 log.warn(" %s", msg)
84 log.warn(" if you do not have a dedicated dbus session for this xpra instance,")
85 log.warn(" use the 'notifications=no' option")
88 def notify_new_user(self, ss):
89 #tell other users:
90 log("notify_new_user(%s) sources=%s", ss, self._server_sources)
91 if not self._server_sources:
92 return
93 try:
94 from xpra.util import XPRA_NEW_USER_NOTIFICATION_ID
95 nid = XPRA_NEW_USER_NOTIFICATION_ID
96 from xpra.notifications.common import parse_image_path
97 from xpra.platform.paths import get_icon_filename
98 icon = parse_image_path(get_icon_filename("user"))
99 title = "User '%s' connected to the session" % (ss.name or ss.username or ss.uuid)
100 body = "\n".join(ss.get_connect_info())
101 for s in self._server_sources.values():
102 if s!=ss:
103 s.notify("", nid, "Xpra", 0, "", title, body, [], {}, 10*1000, icon)
104 except Exception as e:
105 log("%s(%s)", self.notify_new_user, ss, exc_info=True)
106 log.error("Error: failed to show notification of user login:")
107 log.error(" %s", e)
110 def notify_callback(self, dbus_id, nid, app_name, replaces_nid, app_icon, summary, body, actions, hints, expire_timeout):
111 try:
112 assert self.notifications_forwarder and self.notifications
113 icon = self.get_notification_icon(str(app_icon))
114 if os.path.isabs(str(app_icon)):
115 app_icon = ""
116 log("notify_callback%s icon=%s",
117 (dbus_id, nid, app_name, replaces_nid, app_icon,
118 summary, body, actions, hints, expire_timeout), ellipsizer(icon))
119 for ss in self._server_sources.values():
120 ss.notify(dbus_id, nid, app_name, replaces_nid, app_icon,
121 summary, body, actions, hints, expire_timeout, icon)
122 except Exception as e:
123 log("notify_callback failed", exc_info=True)
124 log.error("Error processing notification:")
125 log.error(" %s", e)
127 def get_notification_icon(self, _icon_string):
128 return []
130 def notify_close_callback(self, nid):
131 assert self.notifications_forwarder
132 log("notify_close_callback(%s)", nid)
133 for ss in self._server_sources.values():
134 ss.notify_close(int(nid))
137 def _process_set_notify(self, proto, packet):
138 assert self.notifications, "cannot toggle notifications: the feature is disabled"
139 ss = self.get_server_source(proto)
140 if ss:
141 ss.send_notifications = bool(packet[1])
143 def _process_notification_close(self, proto, packet):
144 assert self.notifications
145 nid, reason, text = packet[1:4]
146 ss = self.get_server_source(proto)
147 assert ss
148 log("closing notification %s: %s, %s", nid, reason, text)
149 try:
150 #remove client callback if we have one:
151 ss.notification_callbacks.pop(nid)
152 except KeyError:
153 if self.notifications_forwarder:
154 #regular notification forwarding:
155 active = self.notifications_forwarder.is_notification_active(nid)
156 log("notification-close nid=%s, reason=%s, text=%s, active=%s", nid, reason, text, active)
157 if active:
158 #an invalid type of the arguments can crash dbus!
159 assert int(nid)>=0
160 assert int(reason)>=0
161 self.notifications_forwarder.NotificationClosed(nid, reason)
163 def _process_notification_action(self, proto, packet):
164 assert self.notifications
165 nid, action_key = packet[1:3]
166 ss = self.get_server_source(proto)
167 assert ss
168 ss.user_event()
169 try:
170 #special client callback notification:
171 client_callback = ss.notification_callbacks.pop(nid)
172 except KeyError:
173 if self.notifications_forwarder:
174 #regular notification forwarding:
175 active = self.notifications_forwarder.is_notification_active(nid)
176 log("notification-action nid=%i, action key=%s, active=%s", nid, action_key, active)
177 if active:
178 self.notifications_forwarder.ActionInvoked(nid, action_key)
179 else:
180 log("notification callback for %s: %s", (nid, action_key), client_callback)
181 client_callback(nid, action_key)
184 def init_packet_handlers(self):
185 if self.notifications:
186 self.add_packet_handlers({
187 "notification-close" : self._process_notification_close,
188 "notification-action" : self._process_notification_action,
189 "set-notify" : self._process_set_notify,
190 })