Hide keyboard shortcuts

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 

7 

8import os.path 

9 

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 

15 

16log = Logger("notify") 

17 

18 

19""" 

20Mixin for servers that forward notifications. 

21""" 

22class NotificationForwarder(StubServerMixin): 

23 

24 def __init__(self): 

25 self.notifications_forwarder = None 

26 self.notifications = False 

27 

28 def init(self, opts): 

29 self.notifications = opts.notifications 

30 

31 def setup(self): 

32 self.init_notification_forwarder() 

33 

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) 

39 

40 def get_info(self, _source=None) -> dict: 

41 if not self.notifications_forwarder: 

42 return {} 

43 return {"notifications" : self.notifications_forwarder.get_info()} 

44 

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 } 

51 

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

61 

62 

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) 

75 

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

86 

87 

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) 

108 

109 

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) 

126 

127 def get_notification_icon(self, _icon_string): 

128 return [] 

129 

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

135 

136 

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

142 

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) 

162 

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) 

182 

183 

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