Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/server.py : 38%
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.
9import os
10import signal
11import math
12from collections import deque
13from gi.repository import GObject, Gtk, Gdk, GdkX11
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
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")
49X11Window = X11WindowBindings()
50X11Keyboard = X11KeyboardBindings()
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)
58WINDOW_SIGNALS = os.environ.get("XPRA_WINDOW_SIGNALS", "SIGINT,SIGTERM,SIGQUIT,SIGCONT,SIGUSR1,SIGUSR2").split(",")
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)
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()
76 def __repr__(self):
77 return "DesktopManager(%s)" % len(self._models)
79 ## For communicating with the main WM:
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()
87 def window_geometry(self, model):
88 return self._models[model].geom
90 def update_window_geometry(self, model, x, y, w, h):
91 self._models[model].geom = [x, y, w, h]
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
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)
105 def is_shown(self, model):
106 if model.is_OR() or model.is_tray():
107 return True
108 return self._models[model].shown
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)
135 def hide_window(self, model):
136 self._models[model].shown = False
137 model.ownership_election()
139 def visible(self, model):
140 return self._models[model].shown
142 ## For communicating with WindowModels:
144 def _unmanaged(self, model, _wm_exiting):
145 del self._models[model]
147 def _elect_me(self, model):
148 if self.visible(model):
149 return (1, self)
150 return (-1, self)
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)
166 def window_size(self, model):
167 w, h = self._models[model].geom[2:4]
168 return w, h
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
177GObject.type_register(DesktopManager)
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 }
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"
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)
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)
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)
229 def setup(self):
230 X11ServerBase.setup(self)
231 if self.system_tray:
232 self.add_system_tray()
234 def x11_init(self):
235 X11ServerBase.x11_init(self)
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
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)
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
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)
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()
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)
319 def init_packet_handlers(self):
320 X11ServerBase.init_packet_handlers(self)
321 self.add_packet_handler("window-signal", self._process_window_signal)
324 def get_server_mode(self):
325 return "GTK3 X11"
328 def server_event(self, *args):
329 super().server_event(*args)
330 self.emit("server-event", args)
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
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
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
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
385 ##########################################################################
386 # Manage the virtual screen:
387 #
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)
396 def set_desktops(self, names):
397 wm = self._wm
398 if wm:
399 wm.set_desktop_list(names)
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)
406 def set_desktop_geometry(self, width, height):
407 wm = self._wm
408 if wm:
409 wm.set_desktop_geometry(width, height)
411 def set_dpi(self, xdpi, ydpi):
412 wm = self._wm
413 if wm:
414 wm.set_dpi(xdpi, ydpi)
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)
426 ##########################################################################
427 # Manage windows:
428 #
430 def is_shown(self, window):
431 return self._desktop_manager.is_shown(window)
433 def load_existing_windows(self):
434 if not self._wm:
435 return
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()
445 ### Load in existing windows:
446 for window in self._wm.get_property("windows"):
447 self._add_new_window(window)
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)
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)
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
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)
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)
534 def _new_window_signaled(self, _wm, window):
535 self.last_raised = None
536 self._add_new_window(window)
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)
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
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)
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)
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)
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)
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
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)
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
711 def show_all_windows(self):
712 for w in self._id_to_window.values():
713 self._desktop_manager.show_window(w)
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)
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()
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
769 def get_focus(self):
770 return self._has_focus
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)
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)
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)
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()
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})
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)
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)
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)
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)
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
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)
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)
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()
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
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)
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)
1063 def cancel_configure_damage(self, wid):
1064 timer = self.configure_damage_timers.pop(wid, None)
1065 if timer:
1066 self.source_remove(timer)
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)
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)
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)
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()
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)
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)
1156 ##########################################################################
1157 # paint the root overlay
1158 # so the server-side root window gets updated if this feature is enabled
1159 #
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()
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)
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)
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
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)
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(":"))
1375GObject.type_register(XpraServer)