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 

9import os 

10import signal 

11import math 

12from collections import deque 

13from gi.repository import GObject, Gtk, Gdk, GdkX11 

14 

15from xpra.version_util import XPRA_VERSION 

16from xpra.util import updict, rindex, envbool, envint, typedict, AdHocStruct, WORKSPACE_NAMES 

17from xpra.os_util import memoryview_to_bytes, strtobytes, bytestostr, monotonic_time 

18from xpra.common import CLOBBER_UPGRADE, MAX_WINDOW_SIZE 

19from xpra.server import server_features 

20from xpra.server.source.windows_mixin import WindowsMixin 

21from xpra.gtk_common.gobject_util import one_arg_signal 

22from xpra.gtk_common.gtk_util import get_default_root_window, get_pixbuf_from_data 

23from xpra.x11.common import Unmanageable 

24from xpra.x11.gtk_x11.prop import prop_set 

25from xpra.x11.gtk_x11.tray import get_tray_window, SystemTray 

26from xpra.x11.gtk_x11.gdk_bindings import ( 

27 add_event_receiver, 

28 get_children, 

29 ) 

30from xpra.x11.bindings.window_bindings import X11WindowBindings #@UnresolvedImport 

31from xpra.x11.bindings.keyboard_bindings import X11KeyboardBindings #@UnresolvedImport 

32from xpra.x11.x11_server_base import X11ServerBase 

33from xpra.gtk_common.error import xsync, xswallow, xlog, XError 

34from xpra.log import Logger 

35 

36log = Logger("server") 

37focuslog = Logger("server", "focus") 

38grablog = Logger("server", "grab") 

39windowlog = Logger("server", "window") 

40geomlog = Logger("server", "window", "geometry") 

41traylog = Logger("server", "tray") 

42workspacelog = Logger("x11", "workspace") 

43metadatalog = Logger("x11", "metadata") 

44framelog = Logger("x11", "frame") 

45eventlog = Logger("x11", "events") 

46mouselog = Logger("x11", "mouse") 

47screenlog = Logger("x11", "screen") 

48 

49X11Window = X11WindowBindings() 

50X11Keyboard = X11KeyboardBindings() 

51 

52REPARENT_ROOT = envbool("XPRA_REPARENT_ROOT", True) 

53CONFIGURE_DAMAGE_RATE = envint("XPRA_CONFIGURE_DAMAGE_RATE", 250) 

54SHARING_SYNC_SIZE = envbool("XPRA_SHARING_SYNC_SIZE", True) 

55CLAMP_WINDOW_TO_ROOT = envbool("XPRA_CLAMP_WINDOW_TO_ROOT", False) 

56ALWAYS_RAISE_WINDOW = envbool("XPRA_ALWAYS_RAISE_WINDOW", False) 

57 

58WINDOW_SIGNALS = os.environ.get("XPRA_WINDOW_SIGNALS", "SIGINT,SIGTERM,SIGQUIT,SIGCONT,SIGUSR1,SIGUSR2").split(",") 

59 

60 

61class DesktopState: 

62 def __init__(self, geom, resize_counter=0, shown=False): 

63 self.geom = geom 

64 self.resize_counter = resize_counter 

65 self.shown = shown 

66 def __repr__(self): 

67 return "DesktopState(%s, %i, %s)" % (self.geom, self.resize_counter, self.shown) 

68 

69class DesktopManager(Gtk.Widget): 

70 def __init__(self): 

71 self._models = {} 

72 Gtk.Widget.__init__(self) 

73 self.set_property("can-focus", True) 

74 self.realize() 

75 

76 def __repr__(self): 

77 return "DesktopManager(%s)" % len(self._models) 

78 

79 ## For communicating with the main WM: 

80 

81 def add_window(self, model, x, y, w, h): 

82 self._models[model] = DesktopState([x, y, w, h]) 

83 model.managed_connect("unmanaged", self._unmanaged) 

84 model.managed_connect("ownership-election", self._elect_me) 

85 model.ownership_election() 

86 

87 def window_geometry(self, model): 

88 return self._models[model].geom 

89 

90 def update_window_geometry(self, model, x, y, w, h): 

91 self._models[model].geom = [x, y, w, h] 

92 

93 def get_resize_counter(self, window, inc=0): 

94 model = self._models[window] 

95 v = model.resize_counter+inc 

96 model.resize_counter = v 

97 return v 

98 

99 def show_window(self, model): 

100 self._models[model].shown = True 

101 model.ownership_election() 

102 if model.get_property("iconic"): 

103 model.set_property("iconic", False) 

104 

105 def is_shown(self, model): 

106 if model.is_OR() or model.is_tray(): 

107 return True 

108 return self._models[model].shown 

109 

110 def configure_window(self, win, x, y, w, h, resize_counter=0): 

111 log("DesktopManager.configure_window(%s, %s, %s, %s, %s, %s)", win, x, y, w, h, resize_counter) 

112 model = self._models[win] 

113 new_geom = [x, y, w, h] 

114 update_geometry = False 

115 if model.geom!=new_geom: 

116 if resize_counter>0 and resize_counter<model.resize_counter: 

117 log("resize ignored: counter %s vs %s", resize_counter, model.resize_counter) 

118 else: 

119 update_geometry = True 

120 model.geom = new_geom 

121 if not self.visible(win): 

122 model.shown = True 

123 win.map() 

124 #Note: this will fire a metadata change event, which will fire a message to the client(s), 

125 #which is wasteful when we only have one client and it is the one that configured the window, 

126 #but when we have multiple clients, this keeps things in sync 

127 if win.get_property("iconic"): 

128 win.set_property("iconic", False) 

129 if win.ownership_election(): 

130 #window has been configured already 

131 update_geometry = False 

132 if update_geometry: 

133 win.maybe_recalculate_geometry_for(self) 

134 

135 def hide_window(self, model): 

136 self._models[model].shown = False 

137 model.ownership_election() 

138 

139 def visible(self, model): 

140 return self._models[model].shown 

141 

142 ## For communicating with WindowModels: 

143 

144 def _unmanaged(self, model, _wm_exiting): 

145 del self._models[model] 

146 

147 def _elect_me(self, model): 

148 if self.visible(model): 

149 return (1, self) 

150 return (-1, self) 

151 

152 def take_window(self, _model, window): 

153 #log.info("take_window(%s, %s)", model, window) 

154 if not self.get_realized(): 

155 #with GTK3, the widget is never realized?? 

156 return 

157 gdkwin = self.get_window() 

158 assert gdkwin, "DesktopManager does not have a gdk window" 

159 if REPARENT_ROOT: 

160 parent = gdkwin.get_screen().get_root_window() 

161 else: 

162 parent = gdkwin 

163 log("take_window: reparenting %s to %s", window, parent) 

164 window.reparent(parent, 0, 0) 

165 

166 def window_size(self, model): 

167 w, h = self._models[model].geom[2:4] 

168 return w, h 

169 

170 def window_position(self, model, w=None, h=None): 

171 x, y, w0, h0 = self._models[model].geom 

172 if (w is not None and abs(w0-w)>1) or (h is not None and abs(h0-h)>1): 

173 log("Uh-oh, our size doesn't fit window sizing constraints: " 

174 "%sx%s vs %sx%s", w0, h0, w, h) 

175 return x, y 

176 

177GObject.type_register(DesktopManager) 

178 

179 

180class XpraServer(GObject.GObject, X11ServerBase): 

181 __gsignals__ = { 

182 "xpra-child-map-event" : one_arg_signal, 

183 "xpra-cursor-event" : one_arg_signal, 

184 "server-event" : one_arg_signal, 

185 } 

186 

187 def __init__(self, clobber): 

188 self.root_overlay = None 

189 self.repaint_root_overlay_timer = None 

190 self.configure_damage_timers = {} 

191 self._tray = None 

192 self._has_grab = 0 

193 self._has_focus = 0 

194 self._wm = None 

195 self.last_raised = None 

196 self.system_tray = False 

197 GObject.GObject.__init__(self) 

198 X11ServerBase.__init__(self, clobber) 

199 self.session_type = "seamless" 

200 

201 def init(self, opts): 

202 self.wm_name = opts.wm_name 

203 self.sync_xvfb = int(opts.sync_xvfb) 

204 self.system_tray = opts.system_tray 

205 super().init(opts) 

206 self.fake_xinerama = opts.fake_xinerama 

207 def log_server_event(_, event): 

208 eventlog("server-event: %s", event) 

209 self.connect("server-event", log_server_event) 

210 

211 def server_init(self): 

212 X11ServerBase.server_init(self) 

213 screenlog("server_init() clobber=%s, randr=%s, initial_resolution=%s", 

214 self.clobber, self.randr, self.initial_resolution) 

215 if self.randr and (self.initial_resolution or not self.clobber): 

216 from xpra.x11.vfb_util import set_initial_resolution, DEFAULT_VFB_RESOLUTION 

217 with xlog: 

218 set_initial_resolution(self.initial_resolution or DEFAULT_VFB_RESOLUTION) 

219 

220 def server_ready(self): 

221 if not X11Window.displayHasXComposite(): 

222 log.error("Xpra 'start' subcommand runs as a compositing manager") 

223 log.error(" it cannot use a display which lacks the XComposite extension!") 

224 return False 

225 #check for an existing window manager: 

226 from xpra.x11.gtk_x11.wm_check import wm_check 

227 return wm_check(self.wm_name, self.clobber & CLOBBER_UPGRADE) 

228 

229 def setup(self): 

230 X11ServerBase.setup(self) 

231 if self.system_tray: 

232 self.add_system_tray() 

233 

234 def x11_init(self): 

235 X11ServerBase.x11_init(self) 

236 

237 self._focus_history = deque(maxlen=100) 

238 # Do this before creating the Wm object, to avoid clobbering its 

239 # selecting SubstructureRedirect. 

240 root = get_default_root_window() 

241 root.set_events(root.get_events() | Gdk.EventMask.SUBSTRUCTURE_MASK) 

242 prop_set(root, "XPRA_SERVER", "latin1", strtobytes(XPRA_VERSION).decode()) 

243 add_event_receiver(root, self) 

244 if self.sync_xvfb>0: 

245 xid = root.get_xid() 

246 try: 

247 with xsync: 

248 self.root_overlay = X11Window.XCompositeGetOverlayWindow(xid) 

249 if self.root_overlay: 

250 #ugly: API expects a window object with a ".get_xid()" method 

251 window = AdHocStruct() 

252 def get_xid(): 

253 return self.root_overlay 

254 window.get_xid = root.get_xid 

255 prop_set(window, "WM_TITLE", "latin1", "RootOverlay") 

256 X11Window.AllowInputPassthrough(self.root_overlay) 

257 except Exception as e: 

258 log("XCompositeGetOverlayWindow(%#x)", xid, exc_info=True) 

259 log.error("Error setting up xvfb synchronization:") 

260 log.error(" %s", e) 

261 if self.root_overlay: 

262 with xswallow: 

263 X11Window.XCompositeReleaseOverlayWindow(self.root_overlay) 

264 self.root_overlay = None 

265 

266 ### Create the WM object 

267 with xsync: 

268 from xpra.x11.gtk_x11.wm import Wm 

269 self._wm = Wm(self.clobber, self.wm_name) 

270 if server_features.windows: 

271 self._wm.connect("new-window", self._new_window_signaled) 

272 self._wm.connect("quit", lambda _: self.clean_quit(True)) 

273 self._wm.connect("show-desktop", self._show_desktop) 

274 

275 #for handling resize synchronization between client and server (this is not xsync!): 

276 self.last_client_configure_event = 0 

277 self.snc_timer = 0 

278 

279 def do_cleanup(self): 

280 if self._tray: 

281 self._tray.cleanup() 

282 self._tray = None 

283 self.cancel_repaint_root_overlay() 

284 if self.root_overlay: 

285 with xswallow: 

286 X11Window.XCompositeReleaseOverlayWindow(self.root_overlay) 

287 self.root_overlay = None 

288 self.cancel_all_configure_damage() 

289 if self._wm: 

290 self._wm.cleanup() 

291 self._wm = None 

292 if self._has_grab: 

293 #normally we set this value when we receive the NotifyUngrab 

294 #but at this point in the cleanup, we probably won't, so force set it: 

295 self._has_grab = 0 

296 self.X11_ungrab() 

297 X11ServerBase.do_cleanup(self) 

298 

299 

300 def last_client_exited(self): 

301 #last client is gone: 

302 X11ServerBase.last_client_exited(self) 

303 if self._has_grab: 

304 self._has_grab = 0 

305 self.X11_ungrab() 

306 

307 

308 def update_size_constraints(self, minw, minh, maxw, maxh): 

309 wm = self._wm 

310 if wm: 

311 wm.set_size_constraints(minw, minh, maxw, maxh) 

312 elif server_features.windows: 

313 #update the default so Wm will use it 

314 #when we do intantiate it: 

315 from xpra.x11.gtk_x11 import wm 

316 wm.DEFAULT_SIZE_CONSTRAINTS = (0, 0, MAX_WINDOW_SIZE, MAX_WINDOW_SIZE) 

317 

318 

319 def init_packet_handlers(self): 

320 X11ServerBase.init_packet_handlers(self) 

321 self.add_packet_handler("window-signal", self._process_window_signal) 

322 

323 

324 def get_server_mode(self): 

325 return "GTK3 X11" 

326 

327 

328 def server_event(self, *args): 

329 super().server_event(*args) 

330 self.emit("server-event", args) 

331 

332 

333 def make_hello(self, source): 

334 capabilities = super().make_hello(source) 

335 if source.wants_features: 

336 capabilities["pointer.grabs"] = True 

337 updict(capabilities, "window", { 

338 "decorations" : True, #v4 clients assume this is enabled 

339 "frame-extents" : True, 

340 "configure.delta" : True, 

341 "signals" : WINDOW_SIGNALS, 

342 "dragndrop" : True, 

343 "states" : [ 

344 "iconified", "focused", "fullscreen", 

345 "above", "below", 

346 "sticky", "iconified", "maximized", 

347 ], 

348 }) 

349 return capabilities 

350 

351 

352 ########################################################################## 

353 # info: 

354 # 

355 def do_get_info(self, proto, server_sources): 

356 info = super().do_get_info(proto, server_sources) 

357 info.setdefault("state", {}).update({ 

358 "focused" : self._has_focus, 

359 "grabbed" : self._has_grab, 

360 }) 

361 return info 

362 

363 def get_ui_info(self, proto, wids=None, *args): 

364 info = super().get_ui_info(proto, wids, *args) 

365 #_NET_WM_NAME: 

366 wm = self._wm 

367 if wm: 

368 info.setdefault("state", {})["window-manager-name"] = wm.get_net_wm_name() 

369 return info 

370 

371 def get_window_info(self, window): 

372 info = super().get_window_info(window) 

373 info.update({ 

374 "focused" : self._has_focus and self._window_to_id.get(window, -1)==self._has_focus, 

375 "grabbed" : self._has_grab and self._window_to_id.get(window, -1)==self._has_grab, 

376 "shown" : self._desktop_manager.is_shown(window), 

377 }) 

378 try: 

379 info["client-geometry"] = self._desktop_manager.window_geometry(window) 

380 except: 

381 pass #OR or tray window 

382 return info 

383 

384 

385 ########################################################################## 

386 # Manage the virtual screen: 

387 # 

388 

389 def set_screen_geometry_attributes(self, w, h): 

390 #only run the default code if there are no clients, 

391 #when we have clients, this should have been done already 

392 #in the code that synchonizes the screen resolution 

393 if not self._server_sources: 

394 super().set_screen_geometry_attributes(w, h) 

395 

396 def set_desktops(self, names): 

397 wm = self._wm 

398 if wm: 

399 wm.set_desktop_list(names) 

400 

401 def set_workarea(self, workarea): 

402 wm = self._wm 

403 if wm: 

404 wm.set_workarea(workarea.x, workarea.y, workarea.width, workarea.height) 

405 

406 def set_desktop_geometry(self, width, height): 

407 wm = self._wm 

408 if wm: 

409 wm.set_desktop_geometry(width, height) 

410 

411 def set_dpi(self, xdpi, ydpi): 

412 wm = self._wm 

413 if wm: 

414 wm.set_dpi(xdpi, ydpi) 

415 

416 

417 def add_system_tray(self): 

418 # Tray handler: 

419 try: 

420 with xsync: 

421 self._tray = SystemTray() 

422 except Exception as e: 

423 log.error("cannot setup tray forwarding: %s", e, exc_info=True) 

424 

425 

426 ########################################################################## 

427 # Manage windows: 

428 # 

429 

430 def is_shown(self, window): 

431 return self._desktop_manager.is_shown(window) 

432 

433 def load_existing_windows(self): 

434 if not self._wm: 

435 return 

436 

437 ### Create our window managing data structures: 

438 self._desktop_manager = DesktopManager() 

439 add_to = self._wm.get_property("toplevel") 

440 #may be missing with GTK3.. 

441 if add_to: 

442 add_to.add(self._desktop_manager) 

443 self._desktop_manager.show_all() 

444 

445 ### Load in existing windows: 

446 for window in self._wm.get_property("windows"): 

447 self._add_new_window(window) 

448 

449 root = get_default_root_window() 

450 with xsync: 

451 children = get_children(root) 

452 for window in children: 

453 xid = window.get_xid() 

454 can_add = False 

455 with xlog: 

456 can_add = X11Window.is_override_redirect(xid) and X11Window.is_mapped(xid) 

457 if can_add: 

458 self._add_new_or_window(window) 

459 

460 def _lookup_window(self, wid): 

461 assert isinstance(wid, int), "window id value '%s' is a %s and not a number" % (wid, type(wid)) 

462 return self._id_to_window.get(wid) 

463 

464 def get_transient_for(self, window): 

465 transient_for = window.get_property("transient-for") 

466 log("get_transient_for window=%s, transient_for=%s", window, transient_for) 

467 if transient_for is None: 

468 return 0 

469 xid = transient_for.get_xid() 

470 log("transient_for.xid=%#x", xid) 

471 for w,wid in self._window_to_id.items(): 

472 if w.get_property("xid")==xid: 

473 log("found match, window id=%s", wid) 

474 return wid 

475 root = get_default_root_window() 

476 if root.get_xid()==xid: 

477 log("transient-for using root") 

478 return -1 #-1 is the backwards compatible marker for root... 

479 log("not found transient_for=%s, xid=%#x", transient_for, xid) 

480 return 0 

481 

482 

483 def parse_hello_ui_window_settings(self, ss, _caps): 

484 #FIXME: with multiple users, don't set any frame size? 

485 frame = None 

486 if isinstance(ss, WindowsMixin): 

487 window_frame_sizes = ss.window_frame_sizes 

488 framelog("parse_hello_ui_window_settings: client window_frame_sizes=%s", window_frame_sizes) 

489 if window_frame_sizes: 

490 frame = window_frame_sizes.inttupleget("frame", (0, 0, 0, 0), 4, 4) 

491 if self._wm: 

492 self._wm.set_default_frame_extents(frame) 

493 

494 

495 def send_initial_windows(self, ss, sharing=False): 

496 # We send the new-window packets sorted by id because this sorts them 

497 # from oldest to newest -- and preserving window creation order means 

498 # that the earliest override-redirect windows will be on the bottom, 

499 # which is usually how things work. (I don't know that anyone cares 

500 # about this kind of correctness at all, but hey, doesn't hurt.) 

501 windowlog("send_initial_windows(%s, %s) will send: %s", ss, sharing, self._id_to_window) 

502 for wid in sorted(self._id_to_window.keys()): 

503 window = self._id_to_window[wid] 

504 if not window.is_managed(): 

505 #we keep references to windows that aren't meant to be displayed.. 

506 continue 

507 #most of the code here is duplicated from the send functions 

508 #so we can send just to the new client and request damage 

509 #just for the new client too: 

510 if window.is_tray(): 

511 #code more or less duplicated from _send_new_tray_window_packet: 

512 w, h = window.get_dimensions() 

513 if ss.system_tray: 

514 ss.new_tray(wid, window, w, h) 

515 ss.damage(wid, window, 0, 0, w, h) 

516 elif not sharing: 

517 #park it outside the visible area 

518 window.move_resize(-200, -200, w, h) 

519 elif window.is_OR(): 

520 #code more or less duplicated from _send_new_or_window_packet: 

521 x, y, w, h = window.get_property("geometry") 

522 wprops = self.client_properties.get(wid, {}).get(ss.uuid) 

523 ss.new_window("new-override-redirect", wid, window, x, y, w, h, wprops) 

524 ss.damage(wid, window, 0, 0, w, h) 

525 else: 

526 #code more or less duplicated from send_new_window_packet: 

527 if not sharing: 

528 self._desktop_manager.hide_window(window) 

529 x, y, w, h = self._desktop_manager.window_geometry(window) 

530 wprops = self.client_properties.get(wid, {}).get(ss.uuid) 

531 ss.new_window("new-window", wid, window, x, y, w, h, wprops) 

532 

533 

534 def _new_window_signaled(self, _wm, window): 

535 self.last_raised = None 

536 self._add_new_window(window) 

537 

538 def do_xpra_child_map_event(self, event): 

539 windowlog("do_xpra_child_map_event(%s)", event) 

540 if event.override_redirect: 

541 self._add_new_or_window(event.window) 

542 

543 def _add_new_window_common(self, window): 

544 windowlog("adding window %s", window) 

545 wid = super()._add_new_window_common(window) 

546 window.managed_connect("client-contents-changed", self._contents_changed) 

547 window.managed_connect("unmanaged", self._lost_window) 

548 window.managed_connect("grab", self._window_grab) 

549 window.managed_connect("ungrab", self._window_ungrab) 

550 window.managed_connect("bell", self._bell_signaled) 

551 window.managed_connect("motion", self._motion_signaled) 

552 window.managed_connect("x11-property-changed", self._x11_property_changed) 

553 if not window.is_tray(): 

554 window.managed_connect("restack", self._restack_window) 

555 window.managed_connect("initiate-moveresize", self._initiate_moveresize) 

556 window_prop_set = getattr(window, "prop_set", None) 

557 if window_prop_set: 

558 try: 

559 window_prop_set("_XPRA_WID", "u32", wid) 

560 except: 

561 pass #this can fail if the window disappears 

562 #but we don't really care, it will get cleaned up soon enough 

563 return wid 

564 

565 

566 def _x11_property_changed(self, window, event): 

567 #name, dtype, dformat, value = event 

568 metadata = {"x11-property" : event} 

569 wid = self._window_to_id[window] 

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

571 ms = getattr(ss, "metadata_supported", ()) 

572 if "x11-property" in ms: 

573 ss.send("window-metadata", wid, metadata) 

574 

575 

576 def _add_new_window(self, window): 

577 self._add_new_window_common(window) 

578 x, y, w, h = window.get_property("geometry") 

579 log("Discovered new ordinary window: %s (geometry=%s)", window, (x, y, w, h)) 

580 self._desktop_manager.add_window(window, x, y, w, h) 

581 window.managed_connect("notify::geometry", self._window_resized_signaled) 

582 self._send_new_window_packet(window) 

583 

584 

585 def _window_resized_signaled(self, window, *args): 

586 x, y, nw, nh = window.get_property("geometry")[:4] 

587 geom = self._desktop_manager.window_geometry(window) 

588 geomlog("XpraServer._window_resized_signaled(%s,%s) geometry=%s, desktop manager geometry=%s", 

589 window, args, (x, y, nw, nh), geom) 

590 if geom==[x, y, nw, nh]: 

591 geomlog("XpraServer._window_resized_signaled: unchanged") 

592 #unchanged 

593 return 

594 self._desktop_manager.update_window_geometry(window, x, y, nw, nh) 

595 lcce = self.last_client_configure_event 

596 if not self._desktop_manager.is_shown(window): 

597 self.size_notify_clients(window) 

598 return 

599 if self.snc_timer>0: 

600 self.source_remove(self.snc_timer) 

601 #TODO: find a better way to choose the timer delay: 

602 #for now, we wait at least 100ms, up to 250ms if the client has just sent us a resize: 

603 #(lcce should always be in the past, so min(..) should be redundant here) 

604 delay = max(100, min(250, 250 + 1000 * (lcce-monotonic_time()))) 

605 self.snc_timer = self.timeout_add(int(delay), self.size_notify_clients, window, lcce) 

606 

607 def size_notify_clients(self, window, lcce=-1): 

608 geomlog("size_notify_clients(%s, %s) last_client_configure_event=%s", 

609 window, lcce, self.last_client_configure_event) 

610 self.snc_timer = 0 

611 wid = self._window_to_id.get(window) 

612 if not wid: 

613 geomlog("size_notify_clients: window is gone") 

614 return 

615 if lcce>0 and lcce!=self.last_client_configure_event: 

616 geomlog("size_notify_clients: we have received a new client resize since") 

617 return 

618 x, y, nw, nh = self._desktop_manager.window_geometry(window) 

619 resize_counter = self._desktop_manager.get_resize_counter(window, 1) 

620 wsources = [ss for ss in self._server_sources.values() if isinstance(ss, WindowsMixin)] 

621 for ss in wsources: 

622 ss.move_resize_window(wid, window, x, y, nw, nh, resize_counter) 

623 #refresh to ensure the client gets the new window contents: 

624 #TODO: to save bandwidth, we should compare the dimensions and skip the refresh 

625 #if the window is smaller than before, or at least only send the new edges rather than the whole window 

626 ss.damage(wid, window, 0, 0, nw, nh) 

627 

628 def _add_new_or_window(self, raw_window): 

629 xid = raw_window.get_xid() 

630 if self.root_overlay and self.root_overlay==xid: 

631 windowlog("ignoring root overlay window %#x", self.root_overlay) 

632 return 

633 if raw_window.get_window_type()==Gdk.WindowType.TEMP: 

634 #ignoring one of gtk's temporary windows 

635 #all the windows we manage should be Gdk.WINDOW_FOREIGN 

636 windowlog("ignoring TEMP window %#x", xid) 

637 return 

638 WINDOW_MODEL_KEY = "_xpra_window_model_" 

639 wid = getattr(raw_window, WINDOW_MODEL_KEY, None) 

640 window = self._id_to_window.get(wid) 

641 if window: 

642 if window.is_managed(): 

643 windowlog("found existing window model %s for %#x, will refresh it", type(window), xid) 

644 ww, wh = window.get_dimensions() 

645 self.refresh_window_area(window, 0, 0, ww, wh, options={"min_delay" : 50}) 

646 return 

647 windowlog("found existing model %s (but no longer managed!) for %#x", type(window), xid) 

648 #we could try to re-use the existing model and window ID, 

649 #but for now it is just easier to create a new one: 

650 self._lost_window(window) 

651 try: 

652 with xsync: 

653 geom = X11Window.getGeometry(xid) 

654 windowlog("Discovered new override-redirect window: %#x X11 geometry=%s", xid, geom) 

655 except Exception as e: 

656 windowlog("Window error (vanished already?): %s", e) 

657 return 

658 try: 

659 tray_window = get_tray_window(raw_window) 

660 if tray_window is not None: 

661 assert self._tray 

662 from xpra.x11.models.systray import SystemTrayWindowModel 

663 window = SystemTrayWindowModel(raw_window) 

664 wid = self._add_new_window_common(window) 

665 setattr(raw_window, WINDOW_MODEL_KEY, wid) 

666 window.call_setup() 

667 self._send_new_tray_window_packet(wid, window) 

668 else: 

669 from xpra.x11.models.or_window import OverrideRedirectWindowModel 

670 window = OverrideRedirectWindowModel(raw_window) 

671 wid = self._add_new_window_common(window) 

672 setattr(raw_window, WINDOW_MODEL_KEY, wid) 

673 window.call_setup() 

674 window.managed_connect("notify::geometry", self._or_window_geometry_changed) 

675 self._send_new_or_window_packet(window) 

676 except Unmanageable as e: 

677 if window: 

678 windowlog("window %s is not manageable: %s", window, e) 

679 #if window is set, we failed after instantiating it, 

680 #so we need to fail it manually: 

681 window.setup_failed(e) 

682 if window in self._window_to_id: 

683 self._lost_window(window, False) 

684 else: 

685 windowlog.warn("cannot add window %#x: %s", xid, e) 

686 #from now on, we return to the gtk main loop, 

687 #so we *should* get a signal when the window goes away 

688 

689 def _or_window_geometry_changed(self, window, _pspec=None): 

690 geom = window.get_property("geometry") 

691 x, y, w, h = geom 

692 if w>=32768 or h>=32768: 

693 geomlog.error("not sending new invalid window dimensions: %ix%i !", w, h) 

694 return 

695 geomlog("or_window_geometry_changed: %s (window=%s)", geom, window) 

696 wid = self._window_to_id[window] 

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

698 ss.or_window_geometry(wid, window, x, y, w, h) 

699 

700 

701 def add_control_commands(self): 

702 super().add_control_commands() 

703 from xpra.server.control_command import ArgsControlCommand 

704 cmd = ArgsControlCommand("show-all-windows", "make all the windows visible", validation=[]) 

705 def control_cb(): 

706 self.show_all_windows() 

707 return "%i windows shown" % len(self._id_to_window) 

708 cmd.do_run = control_cb 

709 self.control_commands[cmd.name] = cmd 

710 

711 def show_all_windows(self): 

712 for w in self._id_to_window.values(): 

713 self._desktop_manager.show_window(w) 

714 

715 

716 def _show_desktop(self, wm, show): 

717 log("show_desktop(%s, %s)", wm, show) 

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

719 ss.show_desktop(show) 

720 

721 

722 def _focus(self, server_source, wid, modifiers): 

723 focuslog("focus wid=%s has_focus=%s", wid, self._has_focus) 

724 if self.last_raised!=wid: 

725 self.last_raised = None 

726 if self._has_focus==wid: 

727 #nothing to do! 

728 return 

729 self._focus_history.append(wid) 

730 hfid = self._has_focus 

731 had_focus = self._id_to_window.get(hfid) 

732 def reset_focus(): 

733 toplevel = None 

734 if self._wm: 

735 toplevel = self._wm.get_property("toplevel") 

736 focuslog("reset_focus() %s / %s had focus (toplevel=%s)", hfid, had_focus, toplevel) 

737 #this will call clear_keys_pressed() if the server is an InputServer: 

738 self.reset_focus() 

739 # FIXME: kind of a hack: 

740 self._has_focus = 0 

741 #toplevel may be None during cleanup! 

742 if toplevel: 

743 toplevel.reset_x_focus() 

744 

745 if wid == 0: 

746 #wid==0 means root window 

747 return reset_focus() 

748 window = self._id_to_window.get(wid) 

749 if not window: 

750 #not found! (go back to root) 

751 return reset_focus() 

752 if window.is_OR(): 

753 focuslog.warn("Warning: cannot focus OR window: %s", window) 

754 return 

755 if not window.is_managed(): 

756 focuslog.warn("Warning: window %s is no longer managed!", window) 

757 return 

758 focuslog("focus: giving focus to %s", window) 

759 with xswallow: 

760 window.raise_window() 

761 window.give_client_focus() 

762 if server_source and modifiers is not None: 

763 make_keymask_match = getattr(server_source, "make_keymask_match", None) 

764 if make_keymask_match: 

765 focuslog("focus: will set modified mask to %s using %s", modifiers, make_keymask_match) 

766 make_keymask_match(modifiers) 

767 self._has_focus = wid 

768 

769 def get_focus(self): 

770 return self._has_focus 

771 

772 

773 def _send_new_window_packet(self, window): 

774 geometry = self._desktop_manager.window_geometry(window) 

775 self._do_send_new_window_packet("new-window", window, geometry) 

776 

777 def _send_new_or_window_packet(self, window): 

778 geometry = window.get_property("geometry") 

779 self._do_send_new_window_packet("new-override-redirect", window, geometry) 

780 self.refresh_window(window) 

781 

782 def _send_new_tray_window_packet(self, wid, window): 

783 ww, wh = window.get_dimensions() 

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

785 ss.new_tray(wid, window, ww, wh) 

786 self.refresh_window(window) 

787 

788 

789 def _lost_window(self, window, wm_exiting=False): 

790 wid = self._remove_window(window) 

791 self.cancel_configure_damage(wid) 

792 if not wm_exiting: 

793 self.repaint_root_overlay() 

794 

795 def _contents_changed(self, window, event): 

796 if window.is_OR() or window.is_tray() or self._desktop_manager.visible(window): 

797 self.refresh_window_area(window, event.x, event.y, event.width, event.height, options={"damage" : True}) 

798 

799 

800 def _window_grab(self, window, event): 

801 grab_id = self._window_to_id.get(window, -1) 

802 grablog("window_grab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", 

803 window, event, self._has_grab, self._has_focus, grab_id) 

804 if grab_id<0 or self._has_grab==grab_id: 

805 return 

806 self._has_grab = grab_id 

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

808 ss.pointer_grab(self._has_grab) 

809 

810 def _window_ungrab(self, window, event): 

811 grab_id = self._window_to_id.get(window, -1) 

812 grablog("window_ungrab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", 

813 window, event, self._has_grab, self._has_focus, grab_id) 

814 if not self._has_grab: 

815 return 

816 self._has_grab = 0 

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

818 ss.pointer_ungrab(grab_id) 

819 

820 

821 def _initiate_moveresize(self, window, event): 

822 log("initiate_moveresize(%s, %s)", window, event) 

823 assert len(event.data)==5 

824 #x_root, y_root, direction, button, source_indication = event.data 

825 wid = self._window_to_id[window] 

826 #find clients that handle windows: 

827 wsources = [ss for ss in self._server_sources.values() if isinstance(ss, WindowsMixin)] 

828 if not wsources: 

829 return 

830 #prefer the "UI driver" if we find it: 

831 driversources = [ss for ss in wsources if self.ui_driver==ss.uuid] 

832 if driversources: 

833 driversources[0].initiate_moveresize(wid, window, *event.data) 

834 return 

835 #otherwise, fallback to the first one: 

836 wsources[0].initiate_moveresize(wid, window, *event.data) 

837 

838 

839 def _restack_window(self, window, detail, sibling): 

840 wid = self._window_to_id[window] 

841 focuslog("restack window(%s) wid=%s, current focus=%s", (window, detail, sibling), wid, self._has_focus) 

842 if self.last_raised!=wid: 

843 #ensure we will raise the window for the next pointer event 

844 self.last_raised = None 

845 if detail==0 and self._has_focus==wid: #Above=0 

846 return 

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

848 if isinstance(ss, WindowsMixin): 

849 ss.restack_window(wid, window, detail, sibling) 

850 

851 

852 def _set_window_state(self, proto, wid, window, new_window_state): 

853 assert proto in self._server_sources 

854 if not new_window_state: 

855 return [] 

856 nws = typedict(new_window_state) 

857 metadatalog("set_window_state%s", (wid, window, new_window_state)) 

858 changes = [] 

859 if "frame" in new_window_state: 

860 #the size of the window frame may have changed 

861 frame = nws.inttupleget("frame", (0, 0, 0, 0)) 

862 window.set_property("frame", frame) 

863 #boolean: but not a wm_state and renamed in the model... (iconic vs inconified!) 

864 iconified = nws.boolget("iconified", None) 

865 if iconified is not None: 

866 if window.is_OR(): 

867 log("ignoring iconified=%s on OR window %s", iconified, window) 

868 else: 

869 if window.get_property("iconic")!=bool(iconified): 

870 window.set_property("iconic", iconified) 

871 changes.append("iconified") 

872 #handle wm_state virtual booleans: 

873 for k in ( 

874 "maximized", "above", 

875 "below", "fullscreen", 

876 "sticky", "shaded", 

877 "skip-pager", "skip-taskbar", "focused", 

878 ): 

879 #metadatalog.info("window.get_property=%s", window.get_property) 

880 new_state = nws.boolget(k, None) 

881 if new_state is None: 

882 continue 

883 cur_state = bool(window.get_property(k)) 

884 #metadatalog.info("set window state for '%s': current state=%s, new state=%s", k, cur_state, new_state) 

885 if cur_state!=new_state: 

886 window.update_wm_state(k, new_state) 

887 changes.append(k) 

888 metadatalog("set_window_state: changes=%s", changes) 

889 return changes 

890 

891 

892 def get_window_position(self, window): 

893 #used to adjust the pointer position with multiple clients 

894 if window is None or window.is_OR() or window.is_tray(): 

895 return None 

896 return self._desktop_manager.window_position(window) 

897 

898 

899 def _process_map_window(self, proto, packet): 

900 wid, x, y, w, h = packet[1:6] 

901 window = self._lookup_window(wid) 

902 if not window: 

903 windowlog("cannot map window %i: not found, already removed?", wid) 

904 return 

905 if window.is_OR(): 

906 windowlog.warn("Warning: received map event on OR window %s", wid) 

907 return 

908 ss = self.get_server_source(proto) 

909 if ss is None: 

910 return 

911 geomlog("client %s mapped window %i - %s, at: %s", ss, wid, window, (x, y, w, h)) 

912 self._window_mapped_at(proto, wid, window, (x, y, w, h)) 

913 if len(packet)>=7: 

914 self._set_client_properties(proto, wid, window, packet[6]) 

915 if not self.ui_driver: 

916 self.set_ui_driver(ss) 

917 if self.ui_driver==ss.uuid or not self._desktop_manager.is_shown(window): 

918 if len(packet)>=8: 

919 self._set_window_state(proto, wid, window, packet[7]) 

920 ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h) 

921 self._desktop_manager.configure_window(window, ax, ay, aw, ah) 

922 self._desktop_manager.show_window(window) 

923 self.refresh_window_area(window, 0, 0, w, h) 

924 

925 

926 def _process_unmap_window(self, proto, packet): 

927 wid = packet[1] 

928 window = self._lookup_window(wid) 

929 if not window: 

930 log("cannot unmap window %i: not found, already removed?", wid) 

931 return 

932 assert not window.is_OR() 

933 ss = self.get_server_source(proto) 

934 if ss is None: 

935 return 

936 self._window_mapped_at(proto, wid, window) 

937 #if self.ui_driver!=ss.uuid: 

938 # return 

939 if len(packet)>=4: 

940 #optional window_state added in 0.15 to update flags 

941 #during iconification events: 

942 self._set_window_state(proto, wid, window, packet[3]) 

943 if self._desktop_manager.is_shown(window): 

944 geomlog("client %s unmapped window %s - %s", ss, wid, window) 

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

946 if isinstance(ss, WindowsMixin): 

947 ss.unmap_window(wid, window) 

948 window.unmap() 

949 iconified = len(packet)>=3 and bool(packet[2]) 

950 if iconified and not window.get_property("iconic"): 

951 window.set_property("iconic", True) 

952 self._desktop_manager.hide_window(window) 

953 self.repaint_root_overlay() 

954 

955 def _clamp_window(self, proto, wid, window, x, y, w, h, resize_counter=0): 

956 if not CLAMP_WINDOW_TO_ROOT: 

957 return x, y, w, h 

958 rw, rh = self.get_root_window_size() 

959 #clamp to root window size 

960 if x>=rw or y>=rh: 

961 log("clamping window position %ix%i to root window size %ix%i", x, y, rw, rh) 

962 x = max(0, min(x, rw-w)) 

963 y = max(0, min(y, rh-h)) 

964 #tell this client to honour the new location 

965 ss = self.get_server_source(proto) 

966 if ss: 

967 resize_counter = max(resize_counter, self._desktop_manager.get_resize_counter(window, 1)) 

968 ss.move_resize_window(wid, window, x, y, w, h, resize_counter) 

969 return x, y, w, h 

970 

971 def _process_configure_window(self, proto, packet): 

972 wid, x, y, w, h = packet[1:6] 

973 window = self._lookup_window(wid) 

974 if not window: 

975 geomlog("cannot configure window %i: not found, already removed?", wid) 

976 return 

977 ss = self.get_server_source(proto) 

978 if not ss: 

979 return 

980 #some "configure-window" packets are only meant for metadata updates: 

981 skip_geometry = len(packet)>=10 and packet[9] 

982 if not skip_geometry: 

983 self._window_mapped_at(proto, wid, window, (x, y, w, h)) 

984 if len(packet)>=7: 

985 cprops = packet[6] 

986 if cprops: 

987 metadatalog("window client properties updates: %s", cprops) 

988 self._set_client_properties(proto, wid, window, cprops) 

989 if not self.ui_driver: 

990 self.set_ui_driver(ss) 

991 is_ui_driver = self.ui_driver==ss.uuid 

992 shown = self._desktop_manager.is_shown(window) 

993 if window.is_OR() or window.is_tray() or skip_geometry or self.readonly: 

994 size_changed = False 

995 else: 

996 oww, owh = self._desktop_manager.window_size(window) 

997 size_changed = oww!=w or owh!=h 

998 if is_ui_driver or size_changed or not shown: 

999 damage = False 

1000 if is_ui_driver and len(packet)>=13 and server_features.input_devices and not self.readonly: 

1001 pwid = packet[10] 

1002 pointer = packet[11] 

1003 modifiers = packet[12] 

1004 if pwid==wid and window.is_OR(): 

1005 #some clients may send the OR window wid 

1006 #this causes focus issues (see #1999) 

1007 pwid = -1 

1008 mouselog("configure pointer data: %s", (pwid, pointer, modifiers)) 

1009 if self._process_mouse_common(proto, pwid, pointer): 

1010 #only update modifiers if the window is in focus: 

1011 if self._has_focus==wid: 

1012 self._update_modifiers(proto, wid, modifiers) 

1013 if window.is_tray(): 

1014 assert self._tray 

1015 if not skip_geometry and not self.readonly: 

1016 traylog("tray %s configured to: %s", window, (x, y, w, h)) 

1017 self._tray.move_resize(window, x, y, w, h) 

1018 damage = True 

1019 else: 

1020 assert skip_geometry or not window.is_OR(), "received a configure packet with geometry for OR window %s from %s: %s" % (window, proto, packet) 

1021 self.last_client_configure_event = monotonic_time() 

1022 if is_ui_driver and len(packet)>=9: 

1023 changes = self._set_window_state(proto, wid, window, packet[8]) 

1024 if changes: 

1025 damage = True 

1026 if not skip_geometry: 

1027 owx, owy, oww, owh = self._desktop_manager.window_geometry(window) 

1028 resize_counter = 0 

1029 if len(packet)>=8: 

1030 resize_counter = packet[7] 

1031 geomlog("_process_configure_window(%s) old window geometry: %s", packet[1:], (owx, owy, oww, owh)) 

1032 ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h, resize_counter) 

1033 self._desktop_manager.configure_window(window, ax, ay, aw, ah, resize_counter) 

1034 resized = oww!=aw or owh!=ah 

1035 if resized and SHARING_SYNC_SIZE: 

1036 #try to ensure this won't trigger a resizing loop: 

1037 counter = max(0, resize_counter-1) 

1038 for s in self._server_sources.values(): 

1039 if s!=ss and isinstance(s, WindowsMixin): 

1040 s.resize_window(wid, window, aw, ah, resize_counter=counter) 

1041 damage |= owx!=ax or owy!=ay or resized 

1042 if not shown and not skip_geometry: 

1043 self._desktop_manager.show_window(window) 

1044 damage = True 

1045 self.repaint_root_overlay() 

1046 else: 

1047 damage = True 

1048 if damage: 

1049 self.schedule_configure_damage(wid) 

1050 

1051 def schedule_configure_damage(self, wid): 

1052 #rate-limit the damage events 

1053 timer = self.configure_damage_timers.get(wid) 

1054 if timer: 

1055 return #we already have one pending 

1056 def damage(): 

1057 self.configure_damage_timers.pop(wid, None) 

1058 window = self._lookup_window(wid) 

1059 if window and window.is_managed(): 

1060 self.refresh_window(window) 

1061 self.configure_damage_timers[wid] = self.timeout_add(CONFIGURE_DAMAGE_RATE, damage) 

1062 

1063 def cancel_configure_damage(self, wid): 

1064 timer = self.configure_damage_timers.pop(wid, None) 

1065 if timer: 

1066 self.source_remove(timer) 

1067 

1068 def cancel_all_configure_damage(self): 

1069 timers = tuple(self.configure_damage_timers.values()) 

1070 self.configure_damage_timers = {} 

1071 for timer in timers: 

1072 self.source_remove(timer) 

1073 

1074 

1075 def _set_client_properties(self, proto, wid, window, new_client_properties): 

1076 """ 

1077 Override so we can update the workspace on the window directly, 

1078 instead of storing it as a client property 

1079 """ 

1080 workspace = typedict(new_client_properties).intget("workspace", None) 

1081 def wn(w): 

1082 return WORKSPACE_NAMES.get(w, w) 

1083 workspacelog("workspace from client properties %s: %s", new_client_properties, wn(workspace)) 

1084 if workspace is not None: 

1085 window.move_to_workspace(workspace) 

1086 #we have handled it on the window directly, so remove it from client properties 

1087 try: 

1088 del new_client_properties[b"workspace"] 

1089 except KeyError: 

1090 del new_client_properties["workspace"] 

1091 #handle the rest as normal: 

1092 super()._set_client_properties(proto, wid, window, new_client_properties) 

1093 

1094 

1095 """ override so we can raise the window under the cursor 

1096 (gtk raise does not change window stacking, just focus) """ 

1097 def _move_pointer(self, wid, pos, *args): 

1098 if wid>0 and (self.last_raised!=wid or ALWAYS_RAISE_WINDOW): 

1099 window = self._lookup_window(wid) 

1100 if not window: 

1101 mouselog("_move_pointer(%s, %s) invalid window id", wid, pos) 

1102 else: 

1103 self.last_raised = wid 

1104 mouselog("raising %s", window) 

1105 with xswallow: 

1106 window.raise_window() 

1107 super()._move_pointer(wid, pos, *args) 

1108 

1109 

1110 def _process_close_window(self, proto, packet): 

1111 assert proto in self._server_sources 

1112 wid = packet[1] 

1113 window = self._lookup_window(wid) 

1114 windowlog("client closed window %s - %s", wid, window) 

1115 if window: 

1116 window.request_close() 

1117 else: 

1118 windowlog("cannot close window %s: it is already gone!", wid) 

1119 self.repaint_root_overlay() 

1120 

1121 

1122 def _process_window_signal(self, proto, packet): 

1123 assert proto in self._server_sources 

1124 wid = packet[1] 

1125 sig = bytestostr(packet[2]) 

1126 if sig not in WINDOW_SIGNALS: 

1127 log.warn("Warning: window signal '%s' not handled", sig) 

1128 return 

1129 w = self._lookup_window(wid) 

1130 if not w: 

1131 log.warn("Warning: window %s not found", wid) 

1132 return 

1133 pid = w.get_property("pid") 

1134 log("window-signal %s for wid=%i, pid=%s", sig, wid, pid) 

1135 if not pid: 

1136 log.warn("Warning: no pid found for window %s, cannot send %s", wid, sig) 

1137 return 

1138 try: 

1139 sigval = getattr(signal, sig) #ie: signal.SIGINT 

1140 os.kill(pid, sigval) 

1141 log.info("sent signal %s to pid %i for window %i", sig, pid, wid) 

1142 except Exception as e: 

1143 log("_process_window_signal(%s, %s)", proto, packet, exc_info=True) 

1144 log.error("Error: failed to send signal %s to pid %i for window %i", sig, pid, wid) 

1145 log.error(" %s", e) 

1146 

1147 

1148 def refresh_window_area(self, window, x, y, width, height, options=None): 

1149 super().refresh_window_area(window, x, y, width, height, options) 

1150 if self.root_overlay: 

1151 image = window.get_image(x, y, width, height) 

1152 if image: 

1153 self.update_root_overlay(window, x, y, image) 

1154 

1155 

1156 ########################################################################## 

1157 # paint the root overlay 

1158 # so the server-side root window gets updated if this feature is enabled 

1159 # 

1160 

1161 def update_root_overlay(self, window, x, y, image): 

1162 display = Gdk.Display.get_default() 

1163 overlaywin = GdkX11.X11Window.foreign_new_for_display(display, self.root_overlay) 

1164 wx, wy = window.get_property("geometry")[:2] 

1165 #FIXME: we should paint the root overlay directly 

1166 # either using XCopyArea or XShmPutImage, 

1167 # using GTK and having to unpremultiply then convert to RGB is just too slooooow 

1168 width = image.get_width() 

1169 height = image.get_height() 

1170 rowstride = image.get_rowstride() 

1171 img_data = image.get_pixels() 

1172 rgb_format = image.get_pixel_format() 

1173 from xpra.codecs.argb.argb import ( #@UnresolvedImport 

1174 unpremultiply_argb, bgra_to_rgba, bgra_to_rgbx, r210_to_rgbx, bgr565_to_rgbx #@UnresolvedImport 

1175 ) 

1176 from cairo import OPERATOR_OVER, OPERATOR_SOURCE #pylint: disable=no-name-in-module 

1177 log("update_root_overlay%s rgb_format=%s, img_data=%i (%s)", 

1178 (window, x, y, image), rgb_format, len(img_data), type(img_data)) 

1179 operator = OPERATOR_SOURCE 

1180 if rgb_format=="BGRA": 

1181 img_data = unpremultiply_argb(img_data) 

1182 img_data = bgra_to_rgba(img_data) 

1183 operator = OPERATOR_OVER 

1184 elif rgb_format=="BGRX": 

1185 img_data = bgra_to_rgbx(img_data) 

1186 elif rgb_format=="r210": 

1187 #lossy... 

1188 img_data = r210_to_rgbx(img_data, width, height, rowstride, width*4) 

1189 rowstride = width*4 

1190 elif rgb_format=="BGR565": 

1191 img_data = bgr565_to_rgbx(img_data) 

1192 rowstride *= 2 

1193 else: 

1194 raise Exception("xync-xvfb root overlay paint code does not handle %s pixel format" % image.get_pixel_format()) 

1195 img_data = memoryview_to_bytes(img_data) 

1196 log("update_root_overlay%s painting rectangle %s", (window, x, y, image), (wx+x, wy+y, width, height)) 

1197 pixbuf = get_pixbuf_from_data(img_data, True, width, height, rowstride) 

1198 cr = overlaywin.cairo_create() 

1199 cr.new_path() 

1200 cr.rectangle(wx+x, wy+y, width, height) 

1201 cr.clip() 

1202 Gdk.cairo_set_source_pixbuf(cr, pixbuf, wx+x, wy+y) 

1203 cr.set_operator(operator) 

1204 cr.paint() 

1205 image.free() 

1206 

1207 def repaint_root_overlay(self): 

1208 if not self.root_overlay: 

1209 return 

1210 log("repaint_root_overlay() root_overlay=%s, due=%s, sync-xvfb=%ims", 

1211 self.root_overlay, self.repaint_root_overlay_timer, self.sync_xvfb) 

1212 if self.repaint_root_overlay_timer: 

1213 return 

1214 self.repaint_root_overlay_timer = self.timeout_add(self.sync_xvfb, self.do_repaint_root_overlay) 

1215 

1216 def cancel_repaint_root_overlay(self): 

1217 rrot = self.repaint_root_overlay_timer 

1218 if rrot: 

1219 self.repaint_root_overlay_timer = None 

1220 self.source_remove(rrot) 

1221 

1222 def do_repaint_root_overlay(self): 

1223 self.repaint_root_overlay_timer = None 

1224 root_width, root_height = self.get_root_window_size() 

1225 display = Gdk.Display.get_default() 

1226 overlaywin = GdkX11.X11Window.foreign_new_for_display(display, self.root_overlay) 

1227 log("overlaywin: %s", overlaywin.get_geometry()) 

1228 cr = overlaywin.cairo_create() 

1229 def fill_grey_rect(shade, x, y, w, h): 

1230 log("paint_grey_rect%s", (shade, x, y, w, h)) 

1231 cr.new_path() 

1232 cr.set_source_rgb(*shade) 

1233 cr.rectangle(x, y, w, h) 

1234 cr.fill() 

1235 def paint_grey_rect(shade, x, y, w, h): 

1236 log("paint_grey_rect%s", (shade, x, y, w, h)) 

1237 cr.new_path() 

1238 cr.set_line_width(2) 

1239 cr.set_source_rgb(*shade) 

1240 cr.rectangle(x, y, w, h) 

1241 cr.stroke() 

1242 #clear to black 

1243 fill_grey_rect((0, 0, 0), 0, 0, root_width, root_height) 

1244 sources = [source for source in self._server_sources.values() if source.ui_client] 

1245 ss = None 

1246 if len(sources)==1: 

1247 ss = sources[0] 

1248 if ss.screen_sizes and len(ss.screen_sizes)==1: 

1249 screen1 = ss.screen_sizes[0] 

1250 if len(screen1)>=10: 

1251 display_name, width, height, width_mm, height_mm, \ 

1252 monitors, work_x, work_y, work_width, work_height = screen1[:10] 

1253 assert display_name or width_mm or height_mm or True #just silences pydev warnings 

1254 #paint dark grey background for display dimensions: 

1255 fill_grey_rect((0.2, 0.2, 0.2), 0, 0, width, height) 

1256 paint_grey_rect((0.2, 0.2, 0.4), 0, 0, width, height) 

1257 #paint lighter grey background for workspace dimensions: 

1258 paint_grey_rect((0.5, 0.5, 0.5), work_x, work_y, work_width, work_height) 

1259 #paint each monitor with even lighter shades of grey: 

1260 for m in monitors: 

1261 if len(m)<7: 

1262 continue 

1263 plug_name, plug_x, plug_y, plug_width, plug_height, plug_width_mm, plug_height_mm = m[:7] 

1264 assert plug_name or plug_width_mm or plug_height_mm or True #just silences pydev warnings 

1265 paint_grey_rect((0.7, 0.7, 0.7), plug_x, plug_y, plug_width, plug_height) 

1266 if len(m)>=10: 

1267 dwork_x, dwork_y, dwork_width, dwork_height = m[7:11] 

1268 paint_grey_rect((1, 1, 1), dwork_x, dwork_y, dwork_width, dwork_height) 

1269 #now paint all the windows on top: 

1270 order = {} 

1271 focus_history = tuple(self._focus_history) 

1272 for wid, window in self._id_to_window.items(): 

1273 prio = int(self._has_focus==wid)*32768 + int(self._has_grab==wid)*65536 

1274 if prio==0: 

1275 try: 

1276 prio = rindex(focus_history, wid) 

1277 except: 

1278 pass #not in focus history! 

1279 order[(prio, wid)] = window 

1280 log("do_repaint_root_overlay() has_focus=%s, has_grab=%s, windows in order=%s", 

1281 self._has_focus, self._has_grab, order) 

1282 for k in sorted(order): 

1283 window = order[k] 

1284 x, y, w, h = window.get_property("geometry")[:4] 

1285 image = window.get_image(0, 0, w, h) 

1286 if image: 

1287 self.update_root_overlay(window, 0, 0, image) 

1288 frame = window.get_property("frame") 

1289 if frame and tuple(frame)!=(0, 0, 0, 0): 

1290 left, right, top, bottom = frame 

1291 #always add a little something so we can see the edge: 

1292 left = max(1, left) 

1293 right = max(1, right) 

1294 top = max(1, top) 

1295 bottom = max(1, bottom) 

1296 rectangles = ( 

1297 (x-left, y, left, h, True), #left side 

1298 (x-left, y-top, w+left+right, top, True), #top 

1299 (x+w, y, right, h, True), #right 

1300 (x-left, y+h, w+left+right, bottom, True), #bottom 

1301 ) 

1302 else: 

1303 rectangles = ( 

1304 (x, y, w, h, False), 

1305 ) 

1306 log("rectangles for window frame=%s and geometry=%s : %s", frame, (x, y, w, h), rectangles) 

1307 for x, y, w, h, fill in rectangles: 

1308 cr.new_path() 

1309 cr.set_source_rgb(0.1, 0.1, 0.1) 

1310 cr.set_line_width(1) 

1311 cr.rectangle(x, y, w, h) 

1312 if fill: 

1313 cr.fill() 

1314 else: 

1315 cr.stroke() 

1316 #FIXME: use server mouse position, and use current cursor shape 

1317 if ss: 

1318 mlp = getattr(ss, "mouse_last_position", (0, 0)) 

1319 if mlp!=(0, 0): 

1320 x, y = mlp 

1321 cr.set_source_rgb(1.0, 0.5, 0.7) 

1322 cr.new_path() 

1323 cr.arc(x, y, 10.0, 0, 2.0 * math.pi) 

1324 cr.stroke_preserve() 

1325 cr.set_source_rgb(0.3, 0.4, 0.6) 

1326 cr.fill() 

1327 return False 

1328 

1329 

1330 def do_make_screenshot_packet(self): 

1331 log("grabbing screenshot") 

1332 regions = [] 

1333 OR_regions = [] 

1334 for wid in reversed(sorted(self._id_to_window.keys())): 

1335 window = self._id_to_window.get(wid) 

1336 log("screenshot: window(%s)=%s", wid, window) 

1337 if window is None: 

1338 continue 

1339 if not window.is_managed(): 

1340 log("screenshot: window %s is not/no longer managed", wid) 

1341 continue 

1342 x, y, w, h = window.get_property("geometry")[:4] 

1343 log("screenshot: geometry(%s)=%s", window, (x, y, w, h)) 

1344 try: 

1345 with xsync: 

1346 img = window.get_image(0, 0, w, h) 

1347 except XError: 

1348 log("%s.get_image%s", window, (0, 0, w, h), exc_info=True) 

1349 log.warn("screenshot: window %s could not be captured", wid) 

1350 continue 

1351 if img is None: 

1352 log.warn("screenshot: no pixels for window %s", wid) 

1353 continue 

1354 log("screenshot: image=%s, size=%s", img, img.get_size()) 

1355 if img.get_pixel_format() not in ("RGB", "RGBA", "XRGB", "BGRX", "ARGB", "BGRA"): 

1356 log.warn("window pixels for window %s using an unexpected rgb format: %s", wid, img.get_pixel_format()) 

1357 continue 

1358 item = (wid, x, y, img) 

1359 if window.is_OR() or window.is_tray(): 

1360 OR_regions.append(item) 

1361 elif self._has_focus==wid: 

1362 #window with focus first (drawn last) 

1363 regions.insert(0, item) 

1364 else: 

1365 regions.append(item) 

1366 log("screenshot: found regions=%s, OR_regions=%s", len(regions), len(OR_regions)) 

1367 return self.make_screenshot_packet_from_regions(OR_regions+regions) 

1368 

1369 

1370 def make_dbus_server(self): 

1371 from xpra.x11.dbus.x11_dbus_server import X11_DBUS_Server 

1372 return X11_DBUS_Server(self, os.environ.get("DISPLAY", "").lstrip(":")) 

1373 

1374 

1375GObject.type_register(XpraServer)