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) 2011 Serviware (Arthur Huillet, <ahuillet@serviware.com>) 

4# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

5# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

6# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

7# later version. See the file COPYING for details. 

8 

9#pylint: disable=wrong-import-position 

10 

11import sys 

12import os.path 

13import gi 

14gi.require_version('Gdk', '3.0') 

15gi.require_version('Gtk', '3.0') 

16gi.require_version('Pango', '1.0') 

17from gi.repository import GLib, Gdk, Gtk 

18 

19from xpra.util import flatten_dict, envbool 

20from xpra.os_util import monotonic_time 

21from xpra.gtk_common.gobject_compat import register_os_signals 

22from xpra.server import server_features 

23from xpra.server.server_base import ServerBase 

24from xpra.gtk_common.gtk_util import ( 

25 get_gtk_version_info, get_root_size, 

26 ) 

27from xpra.log import Logger 

28 

29UI_THREAD_WATCHER = envbool("XPRA_UI_THREAD_WATCHER") 

30 

31log = Logger("server", "gtk") 

32screenlog = Logger("server", "screen") 

33cursorlog = Logger("server", "cursor") 

34notifylog = Logger("notify") 

35 

36 

37class GTKServerBase(ServerBase): 

38 """ 

39 This is the base class for servers. 

40 It provides all the generic functions but is not tied 

41 to a specific backend (X11 or otherwise). 

42 See X11ServerBase, XpraServer and XpraX11ShadowServer 

43 """ 

44 

45 def __init__(self): 

46 log("GTKServerBase.__init__()") 

47 self.idle_add = GLib.idle_add 

48 self.timeout_add = GLib.timeout_add 

49 self.source_remove = GLib.source_remove 

50 self.cursor_suspended = False 

51 self.ui_watcher = None 

52 ServerBase.__init__(self) 

53 

54 def watch_keymap_changes(self): 

55 ### Set up keymap change notification: 

56 display = Gdk.Display.get_default() 

57 keymap = Gdk.Keymap.get_for_display(display) 

58 keymap.connect("keys-changed", self._keys_changed) 

59 

60 def install_signal_handlers(self, callback): 

61 sstr = "%s server" % self.get_server_mode() 

62 register_os_signals(callback, sstr) 

63 from xpra.gtk_common.gobject_compat import register_SIGUSR_signals 

64 register_SIGUSR_signals(sstr) 

65 

66 def do_quit(self): 

67 log("do_quit: calling Gtk.main_quit") 

68 Gtk.main_quit() 

69 log("do_quit: Gtk.main_quit done") 

70 #from now on, we can't rely on the main loop: 

71 from xpra.os_util import register_SIGUSR_signals 

72 register_SIGUSR_signals() 

73 

74 def do_cleanup(self): 

75 ServerBase.do_cleanup(self) 

76 self.close_gtk_display() 

77 uiw = self.ui_watcher 

78 if uiw: 

79 uiw.stop() 

80 

81 def close_gtk_display(self): 

82 # Close our display(s) first, so the server dying won't kill us. 

83 # (if gtk has been loaded) 

84 gdk_mod = sys.modules.get("gtk.gdk") or sys.modules.get("gi.repository.Gdk") 

85 #bug 2328: python3 shadow server segfault on Ubuntu 16.04 

86 #also crashes on Ubuntu 20.04 

87 close = envbool("XPRA_CLOSE_GTK_DISPLAY", False) 

88 log("close_gtk_display() close=%s, gdk_mod=%s", 

89 close, gdk_mod) 

90 if close and gdk_mod: 

91 displays = Gdk.DisplayManager.get().list_displays() 

92 log("close_gtk_display() displays=%s", displays) 

93 for d in displays: 

94 log("close_gtk_display() closing %s", d) 

95 d.close() 

96 

97 

98 def do_run(self): 

99 if UI_THREAD_WATCHER: 

100 from xpra.platform.ui_thread_watcher import get_UI_watcher 

101 self.ui_watcher = get_UI_watcher(GLib.timeout_add, GLib.source_remove) 

102 self.ui_watcher.start() 

103 if server_features.windows: 

104 display = Gdk.Display.get_default() 

105 i=0 

106 while i<display.get_n_screens(): 

107 screen = display.get_screen(i) 

108 screen.connect("size-changed", self._screen_size_changed) 

109 screen.connect("monitors-changed", self._monitors_changed) 

110 i += 1 

111 log("do_run() calling %s", Gtk.main) 

112 Gtk.main() 

113 log("do_run() end of gtk.main()") 

114 

115 

116 def make_hello(self, source): 

117 capabilities = super().make_hello(source) 

118 if source.wants_display: 

119 display = Gdk.Display.get_default() 

120 max_size = tuple(display.get_maximal_cursor_size()) 

121 capabilities.update({ 

122 "display" : display.get_name(), 

123 "cursor.default_size" : display.get_default_cursor_size(), 

124 "cursor.max_size" : max_size, 

125 }) 

126 if source.wants_versions: 

127 capabilities.update(flatten_dict(get_gtk_version_info())) 

128 return capabilities 

129 

130 def get_ui_info(self, proto, *args): 

131 info = super().get_ui_info(proto, *args) 

132 info.setdefault("server", {}).update({ 

133 "display" : Gdk.Display.get_default().get_name(), 

134 "root_window_size" : self.get_root_window_size(), 

135 }) 

136 info.setdefault("cursor", {}).update(self.get_ui_cursor_info()) 

137 return info 

138 

139 

140 def suspend_cursor(self, proto): 

141 #this is called by shadow and desktop servers 

142 #when we're receiving pointer events but the pointer 

143 #is no longer over the active window area, 

144 #so we have to tell the client to switch back to the default cursor 

145 if self.cursor_suspended: 

146 return 

147 self.cursor_suspended = True 

148 ss = self.get_server_source(proto) 

149 if ss: 

150 ss.cancel_cursor_timer() 

151 ss.send_empty_cursor() 

152 

153 def restore_cursor(self, proto): 

154 #see suspend_cursor 

155 if not self.cursor_suspended: 

156 return 

157 self.cursor_suspended = False 

158 ss = self.get_server_source(proto) 

159 if ss: 

160 ss.send_cursor() 

161 

162 

163 def send_initial_cursors(self, ss, _sharing=False): 

164 #cursors: get sizes and send: 

165 display = Gdk.Display.get_default() 

166 self.cursor_sizes = int(display.get_default_cursor_size()), display.get_maximal_cursor_size() 

167 cursorlog("send_initial_cursors() cursor_sizes=%s", self.cursor_sizes) 

168 ss.send_cursor() 

169 

170 def get_ui_cursor_info(self) -> dict: 

171 #(from UI thread) 

172 #now cursor size info: 

173 display = Gdk.Display.get_default() 

174 pos = display.get_default_screen().get_root_window().get_pointer()[-3:-1] 

175 cinfo = {"position" : pos} 

176 for prop, size in { 

177 "default" : display.get_default_cursor_size(), 

178 "max" : tuple(display.get_maximal_cursor_size()), 

179 }.items(): 

180 if size is None: 

181 continue 

182 cinfo["%s_size" % prop] = size 

183 return cinfo 

184 

185 def do_get_info(self, proto, *args): 

186 start = monotonic_time() 

187 info = super().do_get_info(proto, *args) 

188 vi = get_gtk_version_info() 

189 vi["type"] = "Python/gtk" 

190 info.setdefault("server", {}).update(vi) 

191 log("GTKServerBase.do_get_info took %ims", (monotonic_time()-start)*1000) 

192 return info 

193 

194 def get_root_window_size(self): 

195 return get_root_size() 

196 

197 def get_max_screen_size(self): 

198 max_w, max_h = get_root_size() 

199 return max_w, max_h 

200 

201 def configure_best_screen_size(self): 

202 root_w, root_h = get_root_size() 

203 return root_w, root_h 

204 

205 def calculate_workarea(self, maxw, maxh): 

206 screenlog("calculate_workarea(%s, %s)", maxw, maxh) 

207 workarea = Gdk.Rectangle() 

208 workarea.width = maxw 

209 workarea.height = maxh 

210 for ss in self._server_sources.values(): 

211 screen_sizes = ss.screen_sizes 

212 screenlog("calculate_workarea() screen_sizes(%s)=%s", ss, screen_sizes) 

213 if not screen_sizes: 

214 continue 

215 for display in screen_sizes: 

216 #avoid error with old/broken clients: 

217 if not display or not isinstance(display, (list, tuple)): 

218 continue 

219 #display: [':0.0', 2560, 1600, 677, 423, [['DFP2', 0, 0, 2560, 1600, 646, 406]], 0, 0, 2560, 1574] 

220 if len(display)>=10: 

221 work_x, work_y, work_w, work_h = display[6:10] 

222 display_workarea = Gdk.Rectangle() 

223 display_workarea.x = work_x 

224 display_workarea.y = work_y 

225 display_workarea.width = work_w 

226 display_workarea.height = work_h 

227 screenlog("calculate_workarea() found %s for display %s", display_workarea, display[0]) 

228 success, workarea = workarea.intersect(display_workarea) 

229 if not success: 

230 log.warn("Warning: failed to calculate workarea") 

231 log.warn(" as intersection of %s and %s", (maxw, maxh), (work_x, work_y, work_w, work_h)) 

232 #sanity checks: 

233 screenlog("calculate_workarea(%s, %s) workarea=%s", maxw, maxh, workarea) 

234 if workarea.width==0 or workarea.height==0 or workarea.width>=32768-8192 or workarea.height>=32768-8192: 

235 screenlog.warn("Warning: failed to calculate a common workarea") 

236 screenlog.warn(" using the full display area: %ix%i", maxw, maxh) 

237 workarea = Gdk.Rectangle() 

238 workarea.width = maxw 

239 workarea.height = maxh 

240 self.set_workarea(workarea) 

241 

242 def set_workarea(self, workarea): 

243 pass 

244 

245 def set_desktop_geometry(self, width, height): 

246 pass 

247 

248 def set_dpi(self, xdpi, ydpi): 

249 pass 

250 

251 

252 def _move_pointer(self, _wid, pos, *_args): 

253 x, y = pos 

254 display = Gdk.Display.get_default() 

255 display.warp_pointer(display.get_default_screen(), x, y) 

256 

257 def do_process_button_action(self, *args): 

258 pass 

259 

260 

261 def _process_map_window(self, proto, packet): 

262 log.info("_process_map_window(%s, %s)", proto, packet) 

263 

264 def _process_unmap_window(self, proto, packet): 

265 log.info("_process_unmap_window(%s, %s)", proto, packet) 

266 

267 def _process_close_window(self, proto, packet): 

268 log.info("_process_close_window(%s, %s)", proto, packet) 

269 

270 def _process_configure_window(self, proto, packet): 

271 log.info("_process_configure_window(%s, %s)", proto, packet) 

272 

273 

274 def get_notification_icon(self, icon_string): 

275 #the string may be: 

276 # * a path which we will load using pillow 

277 # * a name we lookup in the current them 

278 if not icon_string: 

279 return () 

280 img = None 

281 from PIL import Image 

282 if os.path.isabs(icon_string): 

283 if os.path.exists(icon_string) and os.path.isfile(icon_string): 

284 img = Image.open(icon_string) 

285 w, h = img.size 

286 else: 

287 #try to find it in the theme: 

288 theme = Gtk.IconTheme.get_default() 

289 if theme: 

290 try: 

291 icon = theme.load_icon(icon_string, Gtk.IconSize.BUTTON, 0) 

292 except Exception as e: 

293 notifylog("failed to load icon '%s' from default theme: %s", icon_string, e) 

294 else: 

295 data = icon.get_pixels() 

296 w = icon.get_width() 

297 h = icon.get_height() 

298 rowstride = icon.get_rowstride() 

299 mode = "RGB" 

300 if icon.get_has_alpha(): 

301 mode = "RGBA" 

302 img = Image.frombytes(mode, (w, h), data, "raw", mode, rowstride) 

303 if img: 

304 if w>256 or h>256: 

305 img = img.resize((256, 256), Image.ANTIALIAS) 

306 w = h = 256 

307 from io import BytesIO 

308 buf = BytesIO() 

309 img.save(buf, "PNG") 

310 cpixels = buf.getvalue() 

311 buf.close() 

312 return "png", w, h, cpixels 

313 return ()