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# This file is part of Xpra. 

2# Copyright (C) 2012-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. 

5 

6from gi.repository import GObject, Gdk, GdkX11 

7 

8from xpra.util import envint 

9from xpra.gtk_common.gobject_util import one_arg_signal 

10from xpra.gtk_common.error import xswallow, xsync, xlog 

11from xpra.x11.gtk_x11.prop import prop_set, prop_get 

12from xpra.gtk_common.gtk_util import ( 

13 get_default_root_window, GDKWindow, 

14 ) 

15from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport 

16from xpra.x11.gtk_x11.gdk_bindings import ( 

17 add_event_receiver, #@UnresolvedImport 

18 remove_event_receiver, #@UnresolvedImport 

19 ) 

20from xpra.log import Logger 

21 

22X11Window = X11WindowBindings() 

23 

24log = Logger("x11", "tray") 

25 

26 

27CurrentTime = constants["CurrentTime"] 

28XNone = constants["XNone"] 

29StructureNotifyMask = constants["StructureNotifyMask"] 

30 

31 

32XEMBED_VERSION = 0 

33 

34# XEmbed 

35XEMBED_EMBEDDED_NOTIFY = 0 

36XEMBED_WINDOW_ACTIVATE = 1 

37XEMBED_WINDOW_DEACTIVATE = 2 

38XEMBED_REQUEST_FOCUS = 3 

39XEMBED_FOCUS_IN = 4 

40XEMBED_FOCUS_OUT = 5 

41XEMBED_FOCUS_NEXT = 6 

42XEMBED_FOCUS_PREV = 7 

43# 8-9 were used for XEMBED_GRAB_KEY/XEMBED_UNGRAB_KEY */ 

44XEMBED_MODALITY_ON = 10 

45XEMBED_MODALITY_OFF = 11 

46XEMBED_REGISTER_ACCELERATOR = 12 

47XEMBED_UNREGISTER_ACCELERATOR = 13 

48XEMBED_ACTIVATE_ACCELERATOR = 14 

49# A detail code is required for XEMBED_FOCUS_IN. The following values are valid: 

50# Details for XEMBED_FOCUS_IN: 

51XEMBED_FOCUS_CURRENT = 0 

52XEMBED_FOCUS_FIRST = 1 

53XEMBED_FOCUS_LAST = 2 

54 

55SELECTION = "_NET_SYSTEM_TRAY_S0" 

56TRAY_VISUAL = "_NET_SYSTEM_TRAY_VISUAL" 

57TRAY_ORIENTATION = "_NET_SYSTEM_TRAY_ORIENTATION" 

58 

59TRAY_ORIENTATION_HORZ = 0 

60TRAY_ORIENTATION_VERT = 1 

61 

62XPRA_TRAY_WINDOW_PROPERTY = "_xpra_tray_window_" 

63 

64SYSTEM_TRAY_REQUEST_DOCK = 0 

65SYSTEM_TRAY_BEGIN_MESSAGE = 1 

66SYSTEM_TRAY_CANCEL_MESSAGE = 2 

67 

68#TRANSPARENCY = False 

69TRANSPARENCY = True 

70 

71#Java can send this message to the tray (no idea why): 

72IGNORED_MESSAGE_TYPES = ("_GTK_LOAD_ICONTHEMES", ) 

73 

74 

75MAX_TRAY_SIZE = envint("XPRA_MAX_TRAY_SIZE", 64) 

76 

77 

78def get_tray_window(tray_window): 

79 return getattr(tray_window, XPRA_TRAY_WINDOW_PROPERTY, None) 

80 

81def set_tray_window(tray_window, window): 

82 setattr(tray_window, XPRA_TRAY_WINDOW_PROPERTY, window.get_xid()) 

83 

84def set_tray_visual(tray_window, gdk_visual): 

85 prop_set(tray_window, TRAY_VISUAL, "visual", gdk_visual) 

86 

87def set_tray_orientation(tray_window, orientation): 

88 prop_set(tray_window, TRAY_ORIENTATION, "u32", orientation) 

89 

90 

91class SystemTray(GObject.GObject): 

92 """ This is an X11 system tray area, 

93 owning the "_NET_SYSTEM_TRAY_S0" selection, 

94 X11 client applications can request to embed their tray icon in it, 

95 the xpra server can request to "move_resize" to where the xpra client has it mapped. 

96 """ 

97 

98 __gsignals__ = { 

99 "xpra-unmap-event": one_arg_signal, 

100 "xpra-client-message-event": one_arg_signal, 

101 } 

102 

103 def __init__(self): 

104 GObject.GObject.__init__(self) 

105 self.tray_window = None 

106 self.window_trays = {} 

107 self.tray_windows = {} 

108 self.setup_tray_window() 

109 

110 def cleanup(self): 

111 log("SystemTray.cleanup()") 

112 root = get_default_root_window() 

113 def undock(window): 

114 log("undocking %s", window) 

115 wxid = window.get_xid() 

116 rxid = root.get_xid() 

117 X11Window.Unmap(wxid) 

118 X11Window.Reparent(wxid, rxid, 0, 0) 

119 with xlog: 

120 owner = X11Window.XGetSelectionOwner(SELECTION) 

121 if owner==self.tray_window.get_xid(): 

122 X11Window.XSetSelectionOwner(0, SELECTION) 

123 log("SystemTray.cleanup() reset %s selection owner to %#x", 

124 SELECTION, X11Window.XGetSelectionOwner(SELECTION)) 

125 else: 

126 log.warn("Warning: we were no longer the tray selection owner") 

127 remove_event_receiver(self.tray_window, self) 

128 tray_windows = self.tray_windows 

129 self.tray_windows = {} 

130 for window, tray_window in tray_windows.items(): 

131 with xswallow: 

132 undock(window) 

133 tray_window.destroy() 

134 self.tray_window.destroy() 

135 self.tray_window = None 

136 log("SystemTray.cleanup() done") 

137 

138 def setup_tray_window(self): 

139 display = Gdk.Display.get_default() 

140 root = get_default_root_window() 

141 screen = root.get_screen() 

142 owner = X11Window.XGetSelectionOwner(SELECTION) 

143 log("setup tray: current selection owner=%#x", owner) 

144 if owner!=XNone: 

145 raise Exception("%s already owned by %s" % (SELECTION, owner)) 

146 visual = screen.get_system_visual() 

147 if TRANSPARENCY: 

148 visual = screen.get_rgba_visual() 

149 if visual is None: 

150 log.warn("setup tray: using rgb visual fallback") 

151 visual = screen.get_rgb_visual() 

152 assert visual is not None, "failed to obtain visual" 

153 self.tray_window = GDKWindow(root, width=1, height=1, 

154 title="Xpra-SystemTray", 

155 visual=visual) 

156 xtray = self.tray_window.get_xid() 

157 set_tray_visual(self.tray_window, visual) 

158 set_tray_orientation(self.tray_window, TRAY_ORIENTATION_HORZ) 

159 log("setup tray: tray window %#x", xtray) 

160 display.request_selection_notification(Gdk.Atom.intern(SELECTION, False)) 

161 try: 

162 with xsync: 

163 setsel = X11Window.XSetSelectionOwner(xtray, SELECTION) 

164 log("setup tray: set selection owner returned %s, owner=%#x", 

165 setsel, X11Window.XGetSelectionOwner(SELECTION)) 

166 event_mask = StructureNotifyMask 

167 log("setup tray: sending client message") 

168 xid = root.get_xid() 

169 X11Window.sendClientMessage(xid, xid, False, event_mask, "MANAGER", 

170 CurrentTime, SELECTION, xtray) 

171 owner = X11Window.XGetSelectionOwner(SELECTION) 

172 assert owner==xtray, "we failed to get ownership of the tray selection" 

173 add_event_receiver(self.tray_window, self) 

174 log("setup tray: done") 

175 except Exception: 

176 log("setup_tray failure", exc_info=True) 

177 self.cleanup() 

178 raise 

179 

180 def do_xpra_client_message_event(self, event): 

181 if event.message_type=="_NET_SYSTEM_TRAY_OPCODE" and event.window==self.tray_window and event.format==32: 

182 opcode = event.data[1] 

183 if opcode==SYSTEM_TRAY_REQUEST_DOCK: 

184 xid = event.data[2] 

185 log("tray docking request from %#x", xid) 

186 window = GdkX11.X11Window.foreign_new_for_display(event.display, xid) 

187 log("tray docking window %s", window) 

188 if window: 

189 from gi.repository import GLib 

190 GLib.idle_add(self.dock_tray, xid) 

191 elif opcode==SYSTEM_TRAY_BEGIN_MESSAGE: 

192 timeout = event.data[2] 

193 mlen = event.data[3] 

194 mid = event.data[4] 

195 log.info("tray begin message timeout=%s, mlen=%s, mid=%s - not handled yet!", timeout, mlen, mid) 

196 elif opcode==SYSTEM_TRAY_CANCEL_MESSAGE: 

197 mid = event.data[2] 

198 log.info("tray cancel message for mid=%s - not handled yet!", mid) 

199 elif event.message_type=="_NET_SYSTEM_TRAY_MESSAGE_DATA": 

200 assert event.format==8 

201 log.info("tray message data - not handled yet!") 

202 elif event.message_type in IGNORED_MESSAGE_TYPES: 

203 log("do_xpra_client_message_event(%s) in ignored message type list", event) 

204 else: 

205 log.info("do_xpra_client_message_event(%s)", event) 

206 

207 def dock_tray(self, xid): 

208 log("dock_tray(%#x)", xid) 

209 try: 

210 with xsync: 

211 X11Window.getGeometry(xid) 

212 self.do_dock_tray(xid) 

213 except Exception as e: 

214 log.warn("Warning: failed to dock tray %#x:", xid) 

215 log.warn(" %s", e) 

216 log.warn(" the application may retry later") 

217 

218 def do_dock_tray(self, xid): 

219 root = get_default_root_window() 

220 window = GdkX11.X11Window.foreign_new_for_display(root.get_display(), xid) 

221 if window is None: 

222 log.warn("could not find gdk window for tray window %#x", xid) 

223 return 

224 log("dock_tray: root=%s, window=%s", root, window) 

225 w, h = window.get_geometry()[2:4] 

226 log("dock_tray: geometry=%s", (w, h)) 

227 if w==0 and h==0: 

228 log("dock_tray: invalid tray geometry, ignoring this request") 

229 return 

230 em = Gdk.EventMask 

231 event_mask = em.STRUCTURE_MASK | em.EXPOSURE_MASK | em.PROPERTY_CHANGE_MASK 

232 window.set_events(event_mask=event_mask) 

233 add_event_receiver(window, self) 

234 w = max(1, min(MAX_TRAY_SIZE, w)) 

235 h = max(1, min(MAX_TRAY_SIZE, h)) 

236 title = prop_get(window, "_NET_WM_NAME", "utf8", ignore_errors=True) 

237 if title is None: 

238 title = prop_get(window, "WM_NAME", "latin1", ignore_errors=True) 

239 if title is None: 

240 title = "" 

241 xid = root.get_xid() 

242 log("dock_tray(%#x) gdk window=%#x, geometry=%s, title=%s", 

243 xid, xid, window.get_geometry(), title) 

244 visual = window.get_visual() 

245 tray_window = GDKWindow(root, width=w, height=h, 

246 event_mask = event_mask, 

247 title=title, 

248 x=-200, y=-200, 

249 override_redirect=True, 

250 visual=visual) 

251 log("dock_tray(%#x) setting tray properties", xid) 

252 set_tray_window(tray_window, window) 

253 tray_window.show() 

254 self.tray_windows[window] = tray_window 

255 self.window_trays[tray_window] = window 

256 log("dock_tray(%#x) resizing and reparenting", xid) 

257 window.resize(w, h) 

258 xwin = window.get_xid() 

259 xtray = tray_window.get_xid() 

260 X11Window.Withdraw(xwin) 

261 X11Window.Reparent(xwin, xtray, 0, 0) 

262 X11Window.MapRaised(xwin) 

263 log("dock_tray(%#x) new tray container window %#x", xid, xtray) 

264 rect = Gdk.Rectangle() 

265 rect.width = w 

266 rect.height = h 

267 tray_window.invalidate_rect(rect, True) 

268 X11Window.send_xembed_message(xwin, XEMBED_EMBEDDED_NOTIFY, 0, xtray, XEMBED_VERSION) 

269 

270 def move_resize(self, window, x, y, w, h): 

271 #see SystemTrayWindowModel.move_resize: 

272 window.move_resize(x, y, w, h) 

273 embedded_window = self.window_trays[window.client_window] 

274 embedded_window.resize(w, h) 

275 log("system tray moved to %sx%s and resized to %sx%s", x, y, w, h) 

276 

277 def do_xpra_unmap_event(self, event): 

278 tray_window = self.tray_windows.get(event.window) 

279 log("SystemTray.do_xpra_unmap_event(%s) container window=%s", event, tray_window) 

280 if tray_window: 

281 tray_window.destroy() 

282 del self.tray_windows[event.window] 

283 del self.window_trays[tray_window] 

284 

285GObject.type_register(SystemTray)