def load_existing_windows(self, system_tray): # Tray handler: self._tray = None if system_tray: try: self._tray = SystemTray() except Exception, e: log.error("cannot setup tray forwarding: %s", e, exc_info=True)
def load_existing_windows(self, system_tray): # Tray handler: self._tray = None if system_tray: try: self._tray = SystemTray() except Exception as e: log.error("cannot setup tray forwarding: %s", e, exc_info=True) ### Create our window managing data structures: self._desktop_manager = DesktopManager() self._wm.get_property("toplevel").add(self._desktop_manager) self._desktop_manager.show_all() ### Load in existing windows: for window in self._wm.get_property("windows"): self._add_new_window(window) root = gtk.gdk.get_default_root_window() for window in get_children(root): if X11Window.is_override_redirect( window.xid) and X11Window.is_mapped(window.xid): self._add_new_or_window(window)
def load_existing_windows(self, system_tray): # Tray handler: self._tray = None if system_tray: try: self._tray = SystemTray() except Exception as e: log.error("cannot setup tray forwarding: %s", e, exc_info=True) ### Create our window managing data structures: self._desktop_manager = DesktopManager() self._wm.get_property("toplevel").add(self._desktop_manager) self._desktop_manager.show_all() ### Load in existing windows: for window in self._wm.get_property("windows"): self._add_new_window(window) root = gtk.gdk.get_default_root_window() for window in get_children(root): if X11Window.is_override_redirect(window.xid) and X11Window.is_mapped(window.xid): self._add_new_or_window(window)
class XpraServer(gobject.GObject, X11ServerBase): __gsignals__ = { "xpra-child-map-event": one_arg_signal, "xpra-cursor-event": one_arg_signal, } def __init__(self, clobber): gobject.GObject.__init__(self) X11ServerBase.__init__(self, clobber) def init(self, opts): self.xsettings_enabled = opts.xsettings self.wm_name = opts.wm_name X11ServerBase.init(self, opts) def x11_init(self): X11ServerBase.x11_init(self) assert init_x11_filter() is True self._has_grab = 0 self._has_focus = 0 # Do this before creating the Wm object, to avoid clobbering its # selecting SubstructureRedirect. root = gtk.gdk.get_default_root_window() root.set_events(root.get_events() | gtk.gdk.SUBSTRUCTURE_MASK) root.property_change(gtk.gdk.atom_intern("XPRA_SERVER", False), gtk.gdk.atom_intern("STRING", False), 8, gtk.gdk.PROP_MODE_REPLACE, xpra.__version__) add_event_receiver(root, self) ### Create the WM object self._wm = Wm(self.clobber, self.wm_name) self._wm.connect("new-window", self._new_window_signaled) self._wm.connect("bell", self._bell_signaled) self._wm.connect("quit", lambda _: self.clean_quit(True)) self._wm.connect("show-desktop", self._show_desktop) #save default xsettings: self.default_xsettings = XSettingsHelper().get_settings() settingslog("default_xsettings=%s", self.default_xsettings) self._settings = {} self._xsettings_manager = None #for handling resize synchronization between client and server (this is not xsync!): self.last_client_configure_event = 0 self.snc_timer = 0 #cursor: self.default_cursor_data = None self.last_cursor_serial = None self.last_cursor_data = None self.send_cursor_pending = False def get_default_cursor(): self.default_cursor_data = X11Keyboard.get_cursor_image() cursorlog("get_default_cursor=%s", self.default_cursor_data) trap.swallow_synced(get_default_cursor) self._wm.enableCursors(True) def make_hello(self, source): capabilities = X11ServerBase.make_hello(self, source) if source.wants_features: capabilities["window.raise"] = True capabilities["window.resize-counter"] = True capabilities["window.configure.skip-geometry"] = True capabilities["pointer.grabs"] = True return capabilities def do_get_info(self, proto, server_sources, window_ids): info = X11ServerBase.do_get_info(self, proto, server_sources, window_ids) info["focused"] = self._has_focus info["grabbed"] = self._has_grab log("do_get_info: adding cursor=%s", self.last_cursor_data) #copy to prevent race: cd = self.last_cursor_data if cd is None: info["cursor"] = "None" else: info["cursor.is_default"] = bool( self.default_cursor_data and len(self.default_cursor_data) >= 8 and len(cd) >= 8 and cd[7] == cd[7]) #all but pixels: for i, x in enumerate(("x", "y", "width", "height", "xhot", "yhot", "serial", None, "name")): if x: v = cd[i] or "" info["cursor." + x] = v return info def get_ui_info(self, proto, wids, *args): info = X11ServerBase.get_ui_info(self, proto, wids, *args) #_NET_WM_NAME: wm = self._wm if wm: info["window-manager-name"] = wm.get_net_wm_name() #now cursor size info: display = gtk.gdk.display_get_default() pos = display.get_default_screen().get_root_window().get_pointer()[:2] info["cursor.position"] = pos for prop, size in { "default": display.get_default_cursor_size(), "max": display.get_maximal_cursor_size() }.items(): if size is None: continue info["cursor.%s_size" % prop] = size return info def get_window_info(self, window): info = X11ServerBase.get_window_info(self, window) info["focused"] = self._has_focus and self._window_to_id.get( window, -1) == self._has_focus info["grabbed"] = self._has_grab and self._window_to_id.get( window, -1) == self._has_grab return info def set_desktops(self, names): if self._wm: self._wm.set_desktop_list(names) def set_workarea(self, workarea): if self._wm: self._wm.set_workarea(workarea.x, workarea.y, workarea.width, workarea.height) def set_desktop_geometry(self, width, height): if self._wm: self._wm.set_desktop_geometry(width, height) def set_dpi(self, xdpi, ydpi): if self._wm: self._wm.set_dpi(xdpi, ydpi) def get_transient_for(self, window): transient_for = window.get_property("transient-for") log("get_transient_for window=%s, transient_for=%s", window, transient_for) if transient_for is None: return None xid = transient_for.xid log("transient_for.xid=%#x", xid) for w, wid in self._window_to_id.items(): if w.get_property("xid") == xid: log("found match, window id=%s", wid) return wid root = gtk.gdk.get_default_root_window() if root.xid == xid: log("transient-for using root") return -1 #-1 is the backwards compatible marker for root... log("not found transient_for=%s, xid=%#x", transient_for, xid) return None def is_shown(self, window): return self._desktop_manager.is_shown(window) def cleanup(self, *args): if self._tray: self._tray.cleanup() self._tray = None X11ServerBase.cleanup(self) cleanup_x11_filter() cleanup_all_event_receivers() if self._wm: self._wm.cleanup() self._wm = None if self._has_grab: #normally we set this value when we receive the NotifyUngrab #but at this point in the cleanup, we probably won't, so force set it: self._has_grab = 0 self.X11_ungrab() def cleanup_source(self, protocol): had_client = len(self._server_sources) > 0 X11ServerBase.cleanup_source(self, protocol) has_client = len(self._server_sources) > 0 if had_client and not has_client: #last client is gone: self.reset_settings() if self._has_grab: self.X11_ungrab() def load_existing_windows(self, system_tray): # Tray handler: self._tray = None if system_tray: try: self._tray = SystemTray() except Exception as e: log.error("cannot setup tray forwarding: %s", e, exc_info=True) ### Create our window managing data structures: self._desktop_manager = DesktopManager() self._wm.get_property("toplevel").add(self._desktop_manager) self._desktop_manager.show_all() ### Load in existing windows: for window in self._wm.get_property("windows"): self._add_new_window(window) root = gtk.gdk.get_default_root_window() for window in get_children(root): if X11Window.is_override_redirect( window.xid) and X11Window.is_mapped(window.xid): self._add_new_or_window(window) def send_windows_and_cursors(self, ss, sharing=False): # We send the new-window packets sorted by id because this sorts them # from oldest to newest -- and preserving window creation order means # that the earliest override-redirect windows will be on the bottom, # which is usually how things work. (I don't know that anyone cares # about this kind of correctness at all, but hey, doesn't hurt.) windowlog("send_windows_and_cursors(%s, %s) will send: %s", ss, sharing, self._id_to_window) for wid in sorted(self._id_to_window.keys()): window = self._id_to_window[wid] if not window.is_managed(): #we keep references to windows that aren't meant to be displayed.. continue #most of the code here is duplicated from the send functions #so we can send just to the new client and request damage #just for the new client too: if window.is_tray(): #code more or less duplicated from _send_new_tray_window_packet: w, h = window.get_property("geometry")[2:4] if ss.system_tray: ss.new_tray(wid, window, w, h) ss.damage(wid, window, 0, 0, w, h) elif not sharing: #park it outside the visible area window.move_resize(-200, -200, w, h) elif window.is_OR(): #code more or less duplicated from _send_new_or_window_packet: x, y, w, h = window.get_property("geometry") wprops = self.client_properties.get("%s|%s" % (wid, ss.uuid)) ss.new_window("new-override-redirect", wid, window, x, y, w, h, wprops) ss.damage(wid, window, 0, 0, w, h) else: #code more or less duplicated from send_new_window_packet: if not sharing: self._desktop_manager.hide_window(window) x, y, w, h = self._desktop_manager.window_geometry(window) wprops = self.client_properties.get("%s|%s" % (wid, ss.uuid)) ss.new_window("new-window", wid, window, x, y, w, h, wprops) #cursors: get sizes and send: display = gtk.gdk.display_get_default() self.cursor_sizes = display.get_default_cursor_size( ), display.get_maximal_cursor_size() cursorlog("cursor_sizes=%s", self.cursor_sizes) ss.send_cursor() def _new_window_signaled(self, wm, window): self._add_new_window(window) def do_xpra_child_map_event(self, event): windowlog("do_xpra_child_map_event(%s)", event) if event.override_redirect: self._add_new_or_window(event.window) def _add_new_window_common(self, window): windowlog("adding window %s", window) for prop in window.get_dynamic_property_names(): window.connect("notify::%s" % prop, self._update_metadata) wid = X11ServerBase._add_new_window_common(self, window) window.managed_connect("client-contents-changed", self._contents_changed) window.managed_connect("unmanaged", self._lost_window) window.managed_connect("raised", self._raised_window) window.managed_connect("initiate-moveresize", self._initiate_moveresize) window.managed_connect("grab", self._window_grab) window.managed_connect("ungrab", self._window_ungrab) return wid def _add_new_window(self, window): self._add_new_window_common(window) _, _, w, h, _ = window.get_property("client-window").get_geometry() x, y, _, _, _ = window.corral_window.get_geometry() windowlog("Discovered new ordinary window: %s (geometry=%s)", window, (x, y, w, h)) self._desktop_manager.add_window(window, x, y, w, h) window.connect("notify::geometry", self._window_resized_signaled) window.connect("notify::iconic", self._iconic_changed) self._send_new_window_packet(window) def _iconic_changed(self, window, pspec): #only defined for debugging purposes log("_iconic_changed(%s, %s) iconic=%s, shown=%s", window, pspec, window.get_property("iconic"), self._desktop_manager.is_shown(window)) def _window_resized_signaled(self, window, *args): nw, nh = window.get_property("actual-size") x, y = window.get_position() geom = self._desktop_manager.window_geometry(window) windowlog( "XpraServer._window_resized_signaled(%s,%s) position=%sx%s, actual-size=%sx%s, current geometry=%s", window, args, x, y, nw, nh, geom) if geom[:4] == [x, y, nw, nh]: #unchanged return geom[:4] = [x, y, nw, nh] lcce = self.last_client_configure_event if not self._desktop_manager.is_shown(window): self.size_notify_clients(window) return if self.snc_timer > 0: gobject.source_remove(self.snc_timer) #TODO: find a better way to choose the timer delay: #for now, we wait at least 100ms, up to 250ms if the client has just sent us a resize: #(lcce should always be in the past, so min(..) should be redundant here) delay = max(100, min(250, 250 + 1000 * (lcce - time.time()))) self.snc_timer = gobject.timeout_add(int(delay), self.size_notify_clients, window, lcce) def size_notify_clients(self, window, lcce=-1): log("size_notify_clients(%s, %s) last_client_configure_event=%s", window, lcce, self.last_client_configure_event) self.snc_timer = 0 wid = self._window_to_id.get(window) if lcce > 0 and lcce != self.last_client_configure_event or not wid: #we have received a new client resize since, #or the window is simply gone return geom = self._desktop_manager.window_geometry(window) x, y, nw, nh = geom[:4] resize_counter = self._desktop_manager.get_resize_counter(window, 1) for ss in self._server_sources.values(): ss.move_resize_window(self._window_to_id[window], window, x, y, nw, nh, resize_counter) #refresh to ensure the client gets the new window contents: #TODO: to save bandwidth, we should compare the dimensions and skip the refresh #if the window is smaller than before, or at least only send the new edges rather than the whole window ss.damage(wid, window, 0, 0, nw, nh, {}) def _add_new_or_window(self, raw_window): xid = raw_window.xid if raw_window.get_window_type() == gtk.gdk.WINDOW_TEMP: #ignoring one of gtk's temporary windows #all the windows we manage should be gtk.gdk.WINDOW_FOREIGN windowlog("ignoring TEMP window %#x", xid) return WINDOW_MODEL_KEY = "_xpra_window_model_" wid = raw_window.get_data(WINDOW_MODEL_KEY) window = self._id_to_window.get(wid) if window: if window.is_managed(): windowlog( "found existing window model %s for %#x, will refresh it", type(window), xid) geometry = window.get_property("geometry") _, _, w, h = geometry self._damage(window, 0, 0, w, h, options={"min_delay": 50}) return windowlog( "found existing model %s (but no longer managed!) for %#x", type(window), xid) #we could try to re-use the existing model and window ID, #but for now it is just easier to create a new one: self._lost_window(window) tray_window = get_tray_window(raw_window) windowlog("Discovered new override-redirect window: %#x (tray=%s)", xid, tray_window) try: if tray_window is not None: assert self._tray window = SystemTrayWindowModel(raw_window) wid = self._add_new_window_common(window) raw_window.set_data(WINDOW_MODEL_KEY, wid) window.call_setup() self._send_new_tray_window_packet(wid, window) else: window = OverrideRedirectWindowModel(raw_window) wid = self._add_new_window_common(window) raw_window.set_data(WINDOW_MODEL_KEY, wid) window.call_setup() window.connect("notify::geometry", self._or_window_geometry_changed) self._send_new_or_window_packet(window) except Unmanageable as e: if window: windowlog("window %s is not manageable: %s", window, e) #if window is set, we failed after instantiating it, #so we need to fail it manually: window.setup_failed(e) if window in self._window_to_id: self._lost_window(window, False) else: windowlog.warn("cannot add window %#x: %s", xid, e) #from now on, we return to the gtk main loop, #so we *should* get a signal when the window goes away def _or_window_geometry_changed(self, window, pspec=None): (x, y, w, h) = window.get_property("geometry")[:4] if w >= 32768 or h >= 32768: windowlog.error( "not sending new invalid window dimensions: %ix%i !", w, h) return windowlog("or_window_geometry_changed: %s (window=%s)", window.get_property("geometry"), window) wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.or_window_geometry(wid, window, x, y, w, h) def do_xpra_cursor_event(self, event): if not self.cursors: return if self.last_cursor_serial == event.cursor_serial: cursorlog( "ignoring cursor event %s with the same serial number %s", event, self.last_cursor_serial) return cursorlog("cursor_event: %s", event) self.last_cursor_serial = event.cursor_serial for ss in self._server_sources.values(): ss.send_cursor() return False def _bell_signaled(self, wm, event): log("bell signaled on window %s", event.window.xid) if not self.bell: return wid = 0 if event.window != gtk.gdk.get_default_root_window( ) and event.window_model is not None: try: wid = self._window_to_id[event.window_model] except: pass log("_bell_signaled(%s,%r) wid=%s", wm, event, wid) for ss in self._server_sources.values(): ss.bell(wid, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, event.bell_name or "") def _show_desktop(self, wm, show): log("show_desktop(%s, %s)", wm, show) for ss in self._server_sources.values(): ss.show_desktop(show) def _focus(self, server_source, wid, modifiers): focuslog("focus wid=%s has_focus=%s", wid, self._has_focus) if self._has_focus == wid: #nothing to do! return had_focus = self._id_to_window.get(self._has_focus) def reset_focus(): toplevel = None if self._wm: toplevel = self._wm.get_property("toplevel") focuslog("reset_focus() %s / %s had focus (toplevel=%s)", self._has_focus, had_focus, toplevel) self._clear_keys_pressed() # FIXME: kind of a hack: self._has_focus = 0 #toplevel may be None during cleanup! if toplevel: toplevel.reset_x_focus() if wid == 0: #wid==0 means root window return reset_focus() window = self._id_to_window.get(wid) if not window: #not found! (go back to root) return reset_focus() if window.is_OR(): focuslog.warn("focus(..) cannot focus OR window: %s", window) return focuslog("focus: giving focus to %s", window) #using idle_add seems to prevent some focus races: def give_focus(): if not window.is_managed(): return window.raise_window() window.give_client_focus() gobject.idle_add(give_focus) if server_source and modifiers is not None: focuslog("focus: will set modified mask to %s", modifiers) server_source.make_keymask_match(modifiers) self._has_focus = wid def get_focus(self): return self._has_focus def _send_new_window_packet(self, window): geometry = self._desktop_manager.window_geometry(window) self._do_send_new_window_packet("new-window", window, geometry) def _send_new_or_window_packet(self, window, options=None): geometry = window.get_property("geometry") self._do_send_new_window_packet("new-override-redirect", window, geometry) (_, _, w, h) = geometry self._damage(window, 0, 0, w, h, options=options) def _send_new_tray_window_packet(self, wid, window, options=None): (_, _, w, h) = window.get_property("geometry") for ss in self._server_sources.values(): ss.new_tray(wid, window, w, h) self._damage(window, 0, 0, w, h, options=options) def _update_metadata(self, window, pspec): windowlog("updating metadata on %s: %s", window, pspec) wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.window_metadata(wid, window, pspec.name) def _lost_window(self, window, wm_exiting=False): wid = self._window_to_id[window] windowlog("lost_window: %s - %s", wid, window) for ss in self._server_sources.values(): ss.lost_window(wid, window) del self._window_to_id[window] del self._id_to_window[wid] for ss in self._server_sources.values(): ss.remove_window(wid, window) def _contents_changed(self, window, event): if window.is_OR() or self._desktop_manager.visible(window): self._damage(window, event.x, event.y, event.width, event.height) def _window_grab(self, window, event): grab_id = self._window_to_id.get(window, -1) grablog( "window_grab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", window, event, self._has_grab, self._has_focus, grab_id) if grab_id < 0 or self._has_grab == grab_id: return self._has_grab = grab_id for ss in self._server_sources.values(): ss.pointer_grab(self._has_grab) def _window_ungrab(self, window, event): grab_id = self._window_to_id.get(window, -1) grablog( "window_ungrab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", window, event, self._has_grab, self._has_focus, grab_id) if not self._has_grab: return self._has_grab = 0 for ss in self._server_sources.values(): ss.pointer_ungrab(grab_id) def _initiate_moveresize(self, window, event): log("initiate_moveresize(%s, %s)", window, event) assert len(event.data) == 5 #x_root, y_root, direction, button, source_indication = event.data wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.initiate_moveresize(wid, window, *event.data) def _raised_window(self, window, event): wid = self._window_to_id[window] windowlog("raised window: %s (%s) wid=%s, current focus=%s", window, event, wid, self._has_focus) if self._has_focus == wid: return for ss in self._server_sources.values(): ss.raise_window(wid, window) def _set_window_state(self, proto, wid, window, new_window_state): metadatalog("set_window_state%s", (wid, window, new_window_state)) changes = [] for k in ("maximized", "above", "below", "fullscreen", "sticky", "shaded", "skip-pager", "skip-taskbar", "iconified"): if k in new_window_state: #stupid naming conflict (should have used the same at both ends): wpropname = {"iconified": "iconic"}.get(k, k) new_state = bool(new_window_state.get(k, False)) cur_state = bool(window.get_property(wpropname)) metadatalog( "set window state for '%s': current state=%s, new state=%s", k, cur_state, new_state) if cur_state != new_state: window.set_property(wpropname, new_state) changes.append(k) metadatalog("set_window_state: changes=%s", changes) return changes def _process_map_window(self, proto, packet): wid, x, y, w, h = packet[1:6] window = self._id_to_window.get(wid) if not window: windowlog("cannot map window %s: already removed!", wid) return assert not window.is_OR() windowlog("client mapped window %s - %s, at: %s", wid, window, (x, y, w, h)) if len(packet) >= 8: self._set_window_state(proto, wid, window, packet[7]) ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h) self._desktop_manager.configure_window(window, ax, ay, aw, ah) self._desktop_manager.show_window(window) if len(packet) >= 7: self._set_client_properties(proto, wid, window, packet[6]) self._damage(window, 0, 0, w, h) def _process_unmap_window(self, proto, packet): wid = packet[1] window = self._id_to_window.get(wid) if not window: log("cannot map window %s: already removed!", wid) return if len(packet) >= 4: #optional window_state added in 0.15 to update flags #during iconification events: self._set_window_state(proto, wid, window, packet[3]) assert not window.is_OR() windowlog("client unmapped window %s - %s", wid, window) for ss in self._server_sources.values(): ss.unmap_window(wid, window) window.unmap() iconified = len(packet) >= 3 and bool(packet[2]) if iconified and not window.get_property("iconic"): window.set_property("iconic", True) self._desktop_manager.hide_window(window) def _clamp_window(self, proto, wid, window, x, y, w, h): rw, rh = self.get_root_window_size() #clamp to root window size if x >= rw or y >= rh: log("clamping window position %ix%i to root window size %ix%i", x, y, rw, rh) x = max(0, min(x, rw - w)) y = max(0, min(y, rh - h)) #tell this client to honour the new location ss = self._server_sources.get(proto) if ss: resize_counter = self._desktop_manager.get_resize_counter( window, 1) ss.move_resize_window(wid, window, x, y, w, h, resize_counter) return x, y, w, h def _process_configure_window(self, proto, packet): wid, x, y, w, h = packet[1:6] resize_counter = 0 if len(packet) >= 8: resize_counter = packet[7] #some "configure-window" packets are only meant for metadata updates: skip_geometry = len(packet) >= 10 and packet[9] window = self._id_to_window.get(wid) windowlog("client configured window %s - %s, at: %s", wid, window, (x, y, w, h)) if not window: windowlog("cannot map window %s: already removed!", wid) return damage = False if window.is_tray(): assert self._tray if not skip_geometry: traylog("tray %s configured to: %s", window, (x, y, w, h)) self._tray.move_resize(window, x, y, w, h) damage = True else: assert not window.is_OR( ) or skip_geometry, "received a configure packet with geometry for OR window %s from %s: %s" % ( window, proto, packet) self.last_client_configure_event = time.time() if len(packet) >= 9: changes = self._set_window_state(proto, wid, window, packet[8]) damage = len(changes) > 0 if not skip_geometry: owx, owy, oww, owh = self._desktop_manager.window_geometry( window) windowlog( "_process_configure_window(%s) old window geometry: %s", packet[1:], (owx, owy, oww, owh)) ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h) self._desktop_manager.configure_window(window, ax, ay, aw, ah, resize_counter) damage |= owx != ax or owy != ay or oww != aw or owh != ah if len(packet) >= 7: self._set_client_properties(proto, wid, window, packet[6]) if damage: self._damage(window, 0, 0, w, h) def _set_client_properties(self, proto, wid, window, new_client_properties): """ Override so we can update the workspace on the window directly, instead of storing it as a client property """ workspace = new_client_properties.get("workspace") workspacelog("workspace from client properties %s: %s", new_client_properties, workspace) if workspace is not None: window.move_to_workspace(workspace) #we have handled it on the window directly, so remove it from client properties del new_client_properties["workspace"] #handle the rest as normal: X11ServerBase._set_client_properties(self, proto, wid, window, new_client_properties) """ override so we can raise the window under the cursor (gtk raise does not change window stacking, just focus) """ def _move_pointer(self, wid, pos): window = self._id_to_window.get(wid) if not window: mouselog("_move_pointer(%s, %s) invalid window id", wid, pos) else: mouselog("raising %s", window) window.raise_window() X11ServerBase._move_pointer(self, wid, pos) def _process_close_window(self, proto, packet): wid = packet[1] window = self._id_to_window.get(wid, None) windowlog("client closed window %s - %s", wid, window) if window: window.request_close() else: windowlog("cannot close window %s: it is already gone!", wid) def make_screenshot_packet(self): try: return self.do_make_screenshot_packet() except: log.error("make_screenshot_packet()", exc_info=True) return None def do_make_screenshot_packet(self): log("grabbing screenshot") regions = [] OR_regions = [] for wid in reversed(sorted(self._id_to_window.keys())): window = self._id_to_window.get(wid) log("screenshot: window(%s)=%s", wid, window) if window is None: continue if window.is_tray(): log("screenshot: skipping tray window %s", wid) continue if not window.is_managed(): log("screenshot: window %s is not/no longer managed", wid) continue if window.is_OR(): x, y = window.get_property("geometry")[:2] else: x, y = self._desktop_manager.window_geometry(window)[:2] log("screenshot: position(%s)=%s,%s", window, x, y) w, h = window.get_dimensions() log("screenshot: size(%s)=%sx%s", window, w, h) try: with xsync: img = window.get_image(0, 0, w, h) except: log.warn("screenshot: window %s could not be captured", wid) continue if img is None: log.warn("screenshot: no pixels for window %s", wid) continue log("screenshot: image=%s, size=%s", (img, img.get_size())) if img.get_pixel_format() not in ("RGB", "RGBA", "XRGB", "BGRX", "ARGB", "BGRA"): log.warn( "window pixels for window %s using an unexpected rgb format: %s", wid, img.get_pixel_format()) continue item = (wid, x, y, img) if window.is_OR(): OR_regions.append(item) elif self._has_focus == wid: #window with focus first (drawn last) regions.insert(0, item) else: regions.append(item) all_regions = OR_regions + regions if len(all_regions) == 0: log("screenshot: no regions found, returning empty 0x0 image!") return ["screenshot", 0, 0, "png", -1, ""] log("screenshot: found regions=%s, OR_regions=%s", len(regions), len(OR_regions)) #in theory, we could run the rest in a non-UI thread since we're done with GTK.. minx = min([x for (_, x, _, _) in all_regions]) miny = min([y for (_, _, y, _) in all_regions]) maxx = max([(x + img.get_width()) for (_, x, _, img) in all_regions]) maxy = max([(y + img.get_height()) for (_, _, y, img) in all_regions]) width = maxx - minx height = maxy - miny log("screenshot: %sx%s, min x=%s y=%s", width, height, minx, miny) from PIL import Image #@UnresolvedImport screenshot = Image.new("RGBA", (width, height)) for wid, x, y, img in reversed(all_regions): pixel_format = img.get_pixel_format() target_format = { "XRGB": "RGB", "BGRX": "RGB", "BGRA": "RGBA" }.get(pixel_format, pixel_format) pixels = img.get_pixels() #PIL cannot use the memoryview directly: if _memoryview and isinstance(pixels, _memoryview): pixels = pixels.tobytes() try: window_image = Image.frombuffer(target_format, (w, h), pixels, "raw", pixel_format, img.get_rowstride()) except: log.error("Error parsing window pixels in %s format", pixel_format, exc_info=True) continue tx = x - minx ty = y - miny screenshot.paste(window_image, (tx, ty)) buf = StringIOClass() screenshot.save(buf, "png") data = buf.getvalue() buf.close() packet = [ "screenshot", width, height, "png", width * 4, Compressed("png", data) ] log("screenshot: %sx%s %s", packet[1], packet[2], packet[-1]) return packet def reset_settings(self): if not self.xsettings_enabled: return settingslog("resetting xsettings to: %s", self.default_xsettings) self.set_xsettings(self.default_xsettings or (0, ())) def set_xsettings(self, v): if self._xsettings_manager is None: self._xsettings_manager = XSettingsManager() self._xsettings_manager.set_settings(v) def _get_antialias_hintstyle(self): ad = typedict(self.antialias) hintstyle = ad.strget("hintstyle", "").lower() if hintstyle in ("hintnone", "hintslight", "hintmedium", "hintfull"): #X11 clients can give us what we need directly: return hintstyle #win32 style contrast value: contrast = ad.intget("contrast", -1) if contrast > 1600: return "hintfull" elif contrast > 1000: return "hintmedium" elif contrast > 0: return "hintslight" return "hintnone" def update_server_settings(self, settings, reset=False): if not self.xsettings_enabled: settingslog("ignoring xsettings update: %s", settings) return if reset: #FIXME: preserve serial? (what happens when we change values which had the same serial?) self.reset_settings() self._settings = {} if self.default_xsettings: self._settings = self._default_xsettings[1] old_settings = dict(self._settings) settingslog("server_settings: old=%s, updating with=%s", nonl(old_settings), nonl(settings)) settingslog( "overrides: dpi=%s, double click time=%s, double click distance=%s", self.dpi, self.double_click_time, self.double_click_distance) settingslog("overrides: antialias=%s", self.antialias) self._settings.update(settings) root = gtk.gdk.get_default_root_window() for k, v in settings.items(): #cook the "resource-manager" value to add the DPI: if k == "resource-manager" and self.dpi > 0: value = v.decode("utf-8") #parse the resources into a dict: values = {} options = value.split("\n") for option in options: if not option: continue parts = option.split(":\t") if len(parts) != 2: continue values[parts[0]] = parts[1] values["Xft.dpi"] = self.dpi values["Xft/DPI"] = self.dpi * 1024 values["gnome.Xft/DPI"] = self.dpi * 1024 if self.antialias: ad = typedict(self.antialias) values.update({ "Xft.antialias": ad.intget("enabled", -1), "Xft.hinting": ad.intget("hinting", -1), "Xft.rgba": ad.strget("orientation", "none").lower(), "Xft.hintstyle": self._get_antialias_hintstyle() }) settingslog("server_settings: resource-manager values=%s", nonl(values)) #convert the dict back into a resource string: value = '' for vk, vv in values.items(): value += "%s:\t%s\n" % (vk, vv) #record the actual value used self._settings["resource-manager"] = value v = value.encode("utf-8") #cook xsettings to add double-click settings: #(as those may not be present in xsettings on some platforms.. like win32 and osx) if k == "xsettings-blob" and (self.double_click_time > 0 or self.double_click_distance != (-1, -1)): from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString def set_xsettings_value(name, value_type, value): #remove existing one, if any: serial, values = v new_values = [(_t, _n, _v, _s) for (_t, _n, _v, _s) in values if _n != name] new_values.append((value_type, name, value, 0)) return serial, new_values def set_xsettings_int(name, value): if value < 0: #not set, return v unchanged return v return set_xsettings_value(name, XSettingsTypeInteger, value) if self.dpi > 0: v = set_xsettings_int("Xft/DPI", self.dpi * 1024) if self.double_click_time > 0: v = set_xsettings_int("Net/DoubleClickTime", self.double_click_time) if self.antialias: ad = typedict(self.antialias) v = set_xsettings_int("Xft/Antialias", ad.intget("enabled", -1)) v = set_xsettings_int("Xft/Hinting", ad.intget("hinting", -1)) v = set_xsettings_value( "Xft/RGBA", XSettingsTypeString, ad.strget("orientation", "none").lower()) v = set_xsettings_value("Xft/HintStyle", XSettingsTypeString, self._get_antialias_hintstyle()) if self.double_click_distance != (-1, -1): #some platforms give us a value for each axis, #but X11 only has one, so take the average try: x, y = self.double_click_distance if x > 0 and y > 0: d = (x + y) // 2 d = max(1, min(128, d)) #sanitize it a bit v = set_xsettings_int("Net/DoubleClickDistance", d) except Exception as e: log.warn( "error setting double click distance from %s: %s", self.double_click_distance, e) if k not in old_settings or v != old_settings[k]: def root_set(p): settingslog("server_settings: setting %s to %s", nonl(p), nonl(v)) prop_set(root, p, "latin1", v.decode("utf-8")) if k == "xsettings-blob": self.set_xsettings(v) elif k == "resource-manager": root_set("RESOURCE_MANAGER") elif self.pulseaudio: if k == "pulse-cookie": root_set("PULSE_COOKIE") elif k == "pulse-id": root_set("PULSE_ID") elif k == "pulse-server": root_set("PULSE_SERVER")
class XpraServer(gobject.GObject, X11ServerBase): __gsignals__ = { "xpra-child-map-event": one_arg_signal, "xpra-cursor-event": one_arg_signal, } def __init__(self, clobber): gobject.GObject.__init__(self) X11ServerBase.__init__(self, clobber) def init(self, opts): self.xsettings_enabled = opts.xsettings self.wm_name = opts.wm_name X11ServerBase.init(self, opts) def x11_init(self): X11ServerBase.x11_init(self) assert init_x11_filter() is True self._has_grab = 0 self._has_focus = 0 # Do this before creating the Wm object, to avoid clobbering its # selecting SubstructureRedirect. root = gtk.gdk.get_default_root_window() root.set_events(root.get_events() | gtk.gdk.SUBSTRUCTURE_MASK) root.property_change(gtk.gdk.atom_intern("XPRA_SERVER", False), gtk.gdk.atom_intern("STRING", False), 8, gtk.gdk.PROP_MODE_REPLACE, xpra.__version__) add_event_receiver(root, self) ### Create the WM object self._wm = Wm(self.clobber, self.wm_name) self._wm.connect("new-window", self._new_window_signaled) self._wm.connect("bell", self._bell_signaled) self._wm.connect("quit", lambda _: self.clean_quit(True)) self._wm.connect("show-desktop", self._show_desktop) #save default xsettings: self.default_xsettings = XSettingsHelper().get_settings() settingslog("default_xsettings=%s", self.default_xsettings) self._settings = {} self._xsettings_manager = None #for handling resize synchronization between client and server (this is not xsync!): self.last_client_configure_event = 0 self.snc_timer = 0 #cursor: self.default_cursor_data = None self.last_cursor_serial = None self.last_cursor_data = None self.send_cursor_pending = False def get_default_cursor(): self.default_cursor_data = X11Keyboard.get_cursor_image() cursorlog("get_default_cursor=%s", self.default_cursor_data) trap.swallow_synced(get_default_cursor) self._wm.enableCursors(True) def make_hello(self, source): capabilities = X11ServerBase.make_hello(self, source) if source.wants_features: capabilities["window.raise"] = True capabilities["window.resize-counter"] = True capabilities["window.configure.skip-geometry"] = True capabilities["pointer.grabs"] = True return capabilities def do_get_info(self, proto, server_sources, window_ids): info = X11ServerBase.do_get_info(self, proto, server_sources, window_ids) info["focused"] = self._has_focus info["grabbed"] = self._has_grab log("do_get_info: adding cursor=%s", self.last_cursor_data) #copy to prevent race: cd = self.last_cursor_data if cd is None: info["cursor"] = "None" else: info["cursor.is_default"] = bool(self.default_cursor_data and len(self.default_cursor_data)>=8 and len(cd)>=8 and cd[7]==cd[7]) #all but pixels: for i, x in enumerate(("x", "y", "width", "height", "xhot", "yhot", "serial", None, "name")): if x: v = cd[i] or "" info["cursor." + x] = v return info def get_ui_info(self, proto, wids, *args): info = X11ServerBase.get_ui_info(self, proto, wids, *args) #_NET_WM_NAME: wm = self._wm if wm: info["window-manager-name"] = wm.get_net_wm_name() #now cursor size info: display = gtk.gdk.display_get_default() pos = display.get_default_screen().get_root_window().get_pointer()[:2] info["cursor.position"] = pos for prop, size in {"default" : display.get_default_cursor_size(), "max" : display.get_maximal_cursor_size()}.items(): if size is None: continue info["cursor.%s_size" % prop] = size return info def get_window_info(self, window): info = X11ServerBase.get_window_info(self, window) info["focused"] = self._has_focus and self._window_to_id.get(window, -1)==self._has_focus info["grabbed"] = self._has_grab and self._window_to_id.get(window, -1)==self._has_grab return info def set_desktops(self, names): if self._wm: self._wm.set_desktop_list(names) def set_workarea(self, workarea): if self._wm: self._wm.set_workarea(workarea.x, workarea.y, workarea.width, workarea.height) def set_desktop_geometry(self, width, height): if self._wm: self._wm.set_desktop_geometry(width, height) def set_dpi(self, xdpi, ydpi): if self._wm: self._wm.set_dpi(xdpi, ydpi) def get_transient_for(self, window): transient_for = window.get_property("transient-for") log("get_transient_for window=%s, transient_for=%s", window, transient_for) if transient_for is None: return None xid = transient_for.xid log("transient_for.xid=%#x", xid) for w,wid in self._window_to_id.items(): if w.get_property("xid")==xid: log("found match, window id=%s", wid) return wid root = gtk.gdk.get_default_root_window() if root.xid==xid: log("transient-for using root") return -1 #-1 is the backwards compatible marker for root... log("not found transient_for=%s, xid=%#x", transient_for, xid) return None def is_shown(self, window): return self._desktop_manager.is_shown(window) def cleanup(self, *args): if self._tray: self._tray.cleanup() self._tray = None X11ServerBase.cleanup(self) cleanup_x11_filter() cleanup_all_event_receivers() if self._wm: self._wm.cleanup() self._wm = None if self._has_grab: #normally we set this value when we receive the NotifyUngrab #but at this point in the cleanup, we probably won't, so force set it: self._has_grab = 0 self.X11_ungrab() def cleanup_source(self, protocol): had_client = len(self._server_sources)>0 X11ServerBase.cleanup_source(self, protocol) has_client = len(self._server_sources)>0 if had_client and not has_client: #last client is gone: self.reset_settings() if self._has_grab: self.X11_ungrab() def load_existing_windows(self, system_tray): # Tray handler: self._tray = None if system_tray: try: self._tray = SystemTray() except Exception as e: log.error("cannot setup tray forwarding: %s", e, exc_info=True) ### Create our window managing data structures: self._desktop_manager = DesktopManager() self._wm.get_property("toplevel").add(self._desktop_manager) self._desktop_manager.show_all() ### Load in existing windows: for window in self._wm.get_property("windows"): self._add_new_window(window) root = gtk.gdk.get_default_root_window() for window in get_children(root): if X11Window.is_override_redirect(window.xid) and X11Window.is_mapped(window.xid): self._add_new_or_window(window) def send_windows_and_cursors(self, ss, sharing=False): # We send the new-window packets sorted by id because this sorts them # from oldest to newest -- and preserving window creation order means # that the earliest override-redirect windows will be on the bottom, # which is usually how things work. (I don't know that anyone cares # about this kind of correctness at all, but hey, doesn't hurt.) windowlog("send_windows_and_cursors(%s, %s) will send: %s", ss, sharing, self._id_to_window) for wid in sorted(self._id_to_window.keys()): window = self._id_to_window[wid] if not window.is_managed(): #we keep references to windows that aren't meant to be displayed.. continue #most of the code here is duplicated from the send functions #so we can send just to the new client and request damage #just for the new client too: if window.is_tray(): #code more or less duplicated from _send_new_tray_window_packet: w, h = window.get_property("geometry")[2:4] if ss.system_tray: ss.new_tray(wid, window, w, h) ss.damage(wid, window, 0, 0, w, h) elif not sharing: #park it outside the visible area window.move_resize(-200, -200, w, h) elif window.is_OR(): #code more or less duplicated from _send_new_or_window_packet: x, y, w, h = window.get_property("geometry") wprops = self.client_properties.get("%s|%s" % (wid, ss.uuid)) ss.new_window("new-override-redirect", wid, window, x, y, w, h, wprops) ss.damage(wid, window, 0, 0, w, h) else: #code more or less duplicated from send_new_window_packet: if not sharing: self._desktop_manager.hide_window(window) x, y, w, h = self._desktop_manager.window_geometry(window) wprops = self.client_properties.get("%s|%s" % (wid, ss.uuid)) ss.new_window("new-window", wid, window, x, y, w, h, wprops) #cursors: get sizes and send: display = gtk.gdk.display_get_default() self.cursor_sizes = display.get_default_cursor_size(), display.get_maximal_cursor_size() cursorlog("cursor_sizes=%s", self.cursor_sizes) ss.send_cursor() def _new_window_signaled(self, wm, window): self._add_new_window(window) def do_xpra_child_map_event(self, event): windowlog("do_xpra_child_map_event(%s)", event) if event.override_redirect: self._add_new_or_window(event.window) def _add_new_window_common(self, window): windowlog("adding window %s", window) for prop in window.get_dynamic_property_names(): window.connect("notify::%s" % prop, self._update_metadata) wid = X11ServerBase._add_new_window_common(self, window) window.managed_connect("client-contents-changed", self._contents_changed) window.managed_connect("unmanaged", self._lost_window) window.managed_connect("raised", self._raised_window) window.managed_connect("initiate-moveresize", self._initiate_moveresize) window.managed_connect("grab", self._window_grab) window.managed_connect("ungrab", self._window_ungrab) return wid def _add_new_window(self, window): self._add_new_window_common(window) _, _, w, h, _ = window.get_property("client-window").get_geometry() x, y, _, _, _ = window.corral_window.get_geometry() windowlog("Discovered new ordinary window: %s (geometry=%s)", window, (x, y, w, h)) self._desktop_manager.add_window(window, x, y, w, h) window.connect("notify::geometry", self._window_resized_signaled) window.connect("notify::iconic", self._iconic_changed) self._send_new_window_packet(window) def _iconic_changed(self, window, pspec): #only defined for debugging purposes log("_iconic_changed(%s, %s) iconic=%s, shown=%s", window, pspec, window.get_property("iconic"), self._desktop_manager.is_shown(window)) def _window_resized_signaled(self, window, *args): nw, nh = window.get_property("actual-size") x, y = window.get_position() geom = self._desktop_manager.window_geometry(window) windowlog("XpraServer._window_resized_signaled(%s,%s) position=%sx%s, actual-size=%sx%s, current geometry=%s", window, args, x, y, nw, nh, geom) if geom[:4]==[x, y, nw, nh]: #unchanged return geom[:4] = [x, y, nw, nh] lcce = self.last_client_configure_event if not self._desktop_manager.is_shown(window): self.size_notify_clients(window) return if self.snc_timer>0: gobject.source_remove(self.snc_timer) #TODO: find a better way to choose the timer delay: #for now, we wait at least 100ms, up to 250ms if the client has just sent us a resize: #(lcce should always be in the past, so min(..) should be redundant here) delay = max(100, min(250, 250 + 1000 * (lcce-time.time()))) self.snc_timer = gobject.timeout_add(int(delay), self.size_notify_clients, window, lcce) def size_notify_clients(self, window, lcce=-1): log("size_notify_clients(%s, %s) last_client_configure_event=%s", window, lcce, self.last_client_configure_event) self.snc_timer = 0 wid = self._window_to_id.get(window) if lcce>0 and lcce!=self.last_client_configure_event or not wid: #we have received a new client resize since, #or the window is simply gone return geom = self._desktop_manager.window_geometry(window) x, y, nw, nh = geom[:4] resize_counter = self._desktop_manager.get_resize_counter(window, 1) for ss in self._server_sources.values(): ss.move_resize_window(self._window_to_id[window], window, x, y, nw, nh, resize_counter) #refresh to ensure the client gets the new window contents: #TODO: to save bandwidth, we should compare the dimensions and skip the refresh #if the window is smaller than before, or at least only send the new edges rather than the whole window ss.damage(wid, window, 0, 0, nw, nh, {}) def _add_new_or_window(self, raw_window): xid = raw_window.xid if raw_window.get_window_type()==gtk.gdk.WINDOW_TEMP: #ignoring one of gtk's temporary windows #all the windows we manage should be gtk.gdk.WINDOW_FOREIGN windowlog("ignoring TEMP window %#x", xid) return WINDOW_MODEL_KEY = "_xpra_window_model_" wid = raw_window.get_data(WINDOW_MODEL_KEY) window = self._id_to_window.get(wid) if window: if window.is_managed(): windowlog("found existing window model %s for %#x, will refresh it", type(window), xid) geometry = window.get_property("geometry") _, _, w, h = geometry self._damage(window, 0, 0, w, h, options={"min_delay" : 50}) return windowlog("found existing model %s (but no longer managed!) for %#x", type(window), xid) #we could try to re-use the existing model and window ID, #but for now it is just easier to create a new one: self._lost_window(window) tray_window = get_tray_window(raw_window) windowlog("Discovered new override-redirect window: %#x (tray=%s)", xid, tray_window) try: if tray_window is not None: assert self._tray window = SystemTrayWindowModel(raw_window) wid = self._add_new_window_common(window) raw_window.set_data(WINDOW_MODEL_KEY, wid) window.call_setup() self._send_new_tray_window_packet(wid, window) else: window = OverrideRedirectWindowModel(raw_window) wid = self._add_new_window_common(window) raw_window.set_data(WINDOW_MODEL_KEY, wid) window.call_setup() window.connect("notify::geometry", self._or_window_geometry_changed) self._send_new_or_window_packet(window) except Unmanageable as e: if window: windowlog("window %s is not manageable: %s", window, e) #if window is set, we failed after instantiating it, #so we need to fail it manually: window.setup_failed(e) if window in self._window_to_id: self._lost_window(window, False) else: windowlog.warn("cannot add window %#x: %s", xid, e) #from now on, we return to the gtk main loop, #so we *should* get a signal when the window goes away def _or_window_geometry_changed(self, window, pspec=None): (x, y, w, h) = window.get_property("geometry")[:4] if w>=32768 or h>=32768: windowlog.error("not sending new invalid window dimensions: %ix%i !", w, h) return windowlog("or_window_geometry_changed: %s (window=%s)", window.get_property("geometry"), window) wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.or_window_geometry(wid, window, x, y, w, h) def do_xpra_cursor_event(self, event): if not self.cursors: return if self.last_cursor_serial==event.cursor_serial: cursorlog("ignoring cursor event %s with the same serial number %s", event, self.last_cursor_serial) return cursorlog("cursor_event: %s", event) self.last_cursor_serial = event.cursor_serial for ss in self._server_sources.values(): ss.send_cursor() return False def _bell_signaled(self, wm, event): log("bell signaled on window %s", event.window.xid) if not self.bell: return wid = 0 if event.window!=gtk.gdk.get_default_root_window() and event.window_model is not None: try: wid = self._window_to_id[event.window_model] except: pass log("_bell_signaled(%s,%r) wid=%s", wm, event, wid) for ss in self._server_sources.values(): ss.bell(wid, event.device, event.percent, event.pitch, event.duration, event.bell_class, event.bell_id, event.bell_name or "") def _show_desktop(self, wm, show): log("show_desktop(%s, %s)", wm, show) for ss in self._server_sources.values(): ss.show_desktop(show) def _focus(self, server_source, wid, modifiers): focuslog("focus wid=%s has_focus=%s", wid, self._has_focus) if self._has_focus==wid: #nothing to do! return had_focus = self._id_to_window.get(self._has_focus) def reset_focus(): toplevel = None if self._wm: toplevel = self._wm.get_property("toplevel") focuslog("reset_focus() %s / %s had focus (toplevel=%s)", self._has_focus, had_focus, toplevel) self._clear_keys_pressed() # FIXME: kind of a hack: self._has_focus = 0 #toplevel may be None during cleanup! if toplevel: toplevel.reset_x_focus() if wid == 0: #wid==0 means root window return reset_focus() window = self._id_to_window.get(wid) if not window: #not found! (go back to root) return reset_focus() if window.is_OR(): focuslog.warn("focus(..) cannot focus OR window: %s", window) return focuslog("focus: giving focus to %s", window) #using idle_add seems to prevent some focus races: def give_focus(): if not window.is_managed(): return window.raise_window() window.give_client_focus() gobject.idle_add(give_focus) if server_source and modifiers is not None: focuslog("focus: will set modified mask to %s", modifiers) server_source.make_keymask_match(modifiers) self._has_focus = wid def get_focus(self): return self._has_focus def _send_new_window_packet(self, window): geometry = self._desktop_manager.window_geometry(window) self._do_send_new_window_packet("new-window", window, geometry) def _send_new_or_window_packet(self, window, options=None): geometry = window.get_property("geometry") self._do_send_new_window_packet("new-override-redirect", window, geometry) (_, _, w, h) = geometry self._damage(window, 0, 0, w, h, options=options) def _send_new_tray_window_packet(self, wid, window, options=None): (_, _, w, h) = window.get_property("geometry") for ss in self._server_sources.values(): ss.new_tray(wid, window, w, h) self._damage(window, 0, 0, w, h, options=options) def _update_metadata(self, window, pspec): windowlog("updating metadata on %s: %s", window, pspec) wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.window_metadata(wid, window, pspec.name) def _lost_window(self, window, wm_exiting=False): wid = self._window_to_id[window] windowlog("lost_window: %s - %s", wid, window) for ss in self._server_sources.values(): ss.lost_window(wid, window) del self._window_to_id[window] del self._id_to_window[wid] for ss in self._server_sources.values(): ss.remove_window(wid, window) def _contents_changed(self, window, event): if window.is_OR() or self._desktop_manager.visible(window): self._damage(window, event.x, event.y, event.width, event.height) def _window_grab(self, window, event): grab_id = self._window_to_id.get(window, -1) grablog("window_grab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", window, event, self._has_grab, self._has_focus, grab_id) if grab_id<0 or self._has_grab==grab_id: return self._has_grab = grab_id for ss in self._server_sources.values(): ss.pointer_grab(self._has_grab) def _window_ungrab(self, window, event): grab_id = self._window_to_id.get(window, -1) grablog("window_ungrab(%s, %s) has_grab=%s, has focus=%s, grab window=%s", window, event, self._has_grab, self._has_focus, grab_id) if not self._has_grab: return self._has_grab = 0 for ss in self._server_sources.values(): ss.pointer_ungrab(grab_id) def _initiate_moveresize(self, window, event): log("initiate_moveresize(%s, %s)", window, event) assert len(event.data)==5 #x_root, y_root, direction, button, source_indication = event.data wid = self._window_to_id[window] for ss in self._server_sources.values(): ss.initiate_moveresize(wid, window, *event.data) def _raised_window(self, window, event): wid = self._window_to_id[window] windowlog("raised window: %s (%s) wid=%s, current focus=%s", window, event, wid, self._has_focus) if self._has_focus==wid: return for ss in self._server_sources.values(): ss.raise_window(wid, window) def _set_window_state(self, proto, wid, window, new_window_state): metadatalog("set_window_state%s", (wid, window, new_window_state)) changes = [] for k in ("maximized", "above", "below", "fullscreen", "sticky", "shaded", "skip-pager", "skip-taskbar", "iconified"): if k in new_window_state: #stupid naming conflict (should have used the same at both ends): wpropname = {"iconified" : "iconic"}.get(k, k) new_state = bool(new_window_state.get(k, False)) cur_state = bool(window.get_property(wpropname)) metadatalog("set window state for '%s': current state=%s, new state=%s", k, cur_state, new_state) if cur_state!=new_state: window.set_property(wpropname, new_state) changes.append(k) metadatalog("set_window_state: changes=%s", changes) return changes def _process_map_window(self, proto, packet): wid, x, y, w, h = packet[1:6] window = self._id_to_window.get(wid) if not window: windowlog("cannot map window %s: already removed!", wid) return assert not window.is_OR() windowlog("client mapped window %s - %s, at: %s", wid, window, (x, y, w, h)) if len(packet)>=8: self._set_window_state(proto, wid, window, packet[7]) ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h) self._desktop_manager.configure_window(window, ax, ay, aw, ah) self._desktop_manager.show_window(window) if len(packet)>=7: self._set_client_properties(proto, wid, window, packet[6]) self._damage(window, 0, 0, w, h) def _process_unmap_window(self, proto, packet): wid = packet[1] window = self._id_to_window.get(wid) if not window: log("cannot map window %s: already removed!", wid) return if len(packet)>=4: #optional window_state added in 0.15 to update flags #during iconification events: self._set_window_state(proto, wid, window, packet[3]) assert not window.is_OR() windowlog("client unmapped window %s - %s", wid, window) for ss in self._server_sources.values(): ss.unmap_window(wid, window) window.unmap() iconified = len(packet)>=3 and bool(packet[2]) if iconified and not window.get_property("iconic"): window.set_property("iconic", True) self._desktop_manager.hide_window(window) def _clamp_window(self, proto, wid, window, x, y, w, h): rw, rh = self.get_root_window_size() #clamp to root window size if x>=rw or y>=rh: log("clamping window position %ix%i to root window size %ix%i", x, y, rw, rh) x = max(0, min(x, rw-w)) y = max(0, min(y, rh-h)) #tell this client to honour the new location ss = self._server_sources.get(proto) if ss: resize_counter = self._desktop_manager.get_resize_counter(window, 1) ss.move_resize_window(wid, window, x, y, w, h, resize_counter) return x, y, w, h def _process_configure_window(self, proto, packet): wid, x, y, w, h = packet[1:6] resize_counter = 0 if len(packet)>=8: resize_counter = packet[7] #some "configure-window" packets are only meant for metadata updates: skip_geometry = len(packet)>=10 and packet[9] window = self._id_to_window.get(wid) windowlog("client configured window %s - %s, at: %s", wid, window, (x, y, w, h)) if not window: windowlog("cannot map window %s: already removed!", wid) return damage = False if window.is_tray(): assert self._tray if not skip_geometry: traylog("tray %s configured to: %s", window, (x, y, w, h)) self._tray.move_resize(window, x, y, w, h) damage = True else: assert not window.is_OR() or skip_geometry, "received a configure packet with geometry for OR window %s from %s: %s" % (window, proto, packet) self.last_client_configure_event = time.time() if len(packet)>=9: changes = self._set_window_state(proto, wid, window, packet[8]) damage = len(changes)>0 if not skip_geometry: owx, owy, oww, owh = self._desktop_manager.window_geometry(window) windowlog("_process_configure_window(%s) old window geometry: %s", packet[1:], (owx, owy, oww, owh)) ax, ay, aw, ah = self._clamp_window(proto, wid, window, x, y, w, h) self._desktop_manager.configure_window(window, ax, ay, aw, ah, resize_counter) damage |= owx!=ax or owy!=ay or oww!=aw or owh!=ah if len(packet)>=7: self._set_client_properties(proto, wid, window, packet[6]) if damage: self._damage(window, 0, 0, w, h) def _set_client_properties(self, proto, wid, window, new_client_properties): """ Override so we can update the workspace on the window directly, instead of storing it as a client property """ workspace = new_client_properties.get("workspace") workspacelog("workspace from client properties %s: %s", new_client_properties, workspace) if workspace is not None: window.move_to_workspace(workspace) #we have handled it on the window directly, so remove it from client properties del new_client_properties["workspace"] #handle the rest as normal: X11ServerBase._set_client_properties(self, proto, wid, window, new_client_properties) """ override so we can raise the window under the cursor (gtk raise does not change window stacking, just focus) """ def _move_pointer(self, wid, pos): window = self._id_to_window.get(wid) if not window: mouselog("_move_pointer(%s, %s) invalid window id", wid, pos) else: mouselog("raising %s", window) window.raise_window() X11ServerBase._move_pointer(self, wid, pos) def _process_close_window(self, proto, packet): wid = packet[1] window = self._id_to_window.get(wid, None) windowlog("client closed window %s - %s", wid, window) if window: window.request_close() else: windowlog("cannot close window %s: it is already gone!", wid) def make_screenshot_packet(self): try: return self.do_make_screenshot_packet() except: log.error("make_screenshot_packet()", exc_info=True) return None def do_make_screenshot_packet(self): log("grabbing screenshot") regions = [] OR_regions = [] for wid in reversed(sorted(self._id_to_window.keys())): window = self._id_to_window.get(wid) log("screenshot: window(%s)=%s", wid, window) if window is None: continue if window.is_tray(): log("screenshot: skipping tray window %s", wid) continue if not window.is_managed(): log("screenshot: window %s is not/no longer managed", wid) continue if window.is_OR(): x, y = window.get_property("geometry")[:2] else: x, y = self._desktop_manager.window_geometry(window)[:2] log("screenshot: position(%s)=%s,%s", window, x, y) w, h = window.get_dimensions() log("screenshot: size(%s)=%sx%s", window, w, h) try: with xsync: img = window.get_image(0, 0, w, h) except: log.warn("screenshot: window %s could not be captured", wid) continue if img is None: log.warn("screenshot: no pixels for window %s", wid) continue log("screenshot: image=%s, size=%s", (img, img.get_size())) if img.get_pixel_format() not in ("RGB", "RGBA", "XRGB", "BGRX", "ARGB", "BGRA"): log.warn("window pixels for window %s using an unexpected rgb format: %s", wid, img.get_pixel_format()) continue item = (wid, x, y, img) if window.is_OR(): OR_regions.append(item) elif self._has_focus==wid: #window with focus first (drawn last) regions.insert(0, item) else: regions.append(item) all_regions = OR_regions+regions if len(all_regions)==0: log("screenshot: no regions found, returning empty 0x0 image!") return ["screenshot", 0, 0, "png", -1, ""] log("screenshot: found regions=%s, OR_regions=%s", len(regions), len(OR_regions)) #in theory, we could run the rest in a non-UI thread since we're done with GTK.. minx = min([x for (_,x,_,_) in all_regions]) miny = min([y for (_,_,y,_) in all_regions]) maxx = max([(x+img.get_width()) for (_,x,_,img) in all_regions]) maxy = max([(y+img.get_height()) for (_,_,y,img) in all_regions]) width = maxx-minx height = maxy-miny log("screenshot: %sx%s, min x=%s y=%s", width, height, minx, miny) from PIL import Image #@UnresolvedImport screenshot = Image.new("RGBA", (width, height)) for wid, x, y, img in reversed(all_regions): pixel_format = img.get_pixel_format() target_format = { "XRGB" : "RGB", "BGRX" : "RGB", "BGRA" : "RGBA"}.get(pixel_format, pixel_format) pixels = img.get_pixels() #PIL cannot use the memoryview directly: if _memoryview and isinstance(pixels, _memoryview): pixels = pixels.tobytes() try: window_image = Image.frombuffer(target_format, (w, h), pixels, "raw", pixel_format, img.get_rowstride()) except: log.error("Error parsing window pixels in %s format", pixel_format, exc_info=True) continue tx = x-minx ty = y-miny screenshot.paste(window_image, (tx, ty)) buf = StringIOClass() screenshot.save(buf, "png") data = buf.getvalue() buf.close() packet = ["screenshot", width, height, "png", width*4, Compressed("png", data)] log("screenshot: %sx%s %s", packet[1], packet[2], packet[-1]) return packet def reset_settings(self): if not self.xsettings_enabled: return settingslog("resetting xsettings to: %s", self.default_xsettings) self.set_xsettings(self.default_xsettings or (0, ())) def set_xsettings(self, v): if self._xsettings_manager is None: self._xsettings_manager = XSettingsManager() self._xsettings_manager.set_settings(v) def _get_antialias_hintstyle(self): ad = typedict(self.antialias) hintstyle = ad.strget("hintstyle", "").lower() if hintstyle in ("hintnone", "hintslight", "hintmedium", "hintfull"): #X11 clients can give us what we need directly: return hintstyle #win32 style contrast value: contrast = ad.intget("contrast", -1) if contrast>1600: return "hintfull" elif contrast>1000: return "hintmedium" elif contrast>0: return "hintslight" return "hintnone" def update_server_settings(self, settings, reset=False): if not self.xsettings_enabled: settingslog("ignoring xsettings update: %s", settings) return if reset: #FIXME: preserve serial? (what happens when we change values which had the same serial?) self.reset_settings() self._settings = {} if self.default_xsettings: self._settings = self._default_xsettings[1] old_settings = dict(self._settings) settingslog("server_settings: old=%s, updating with=%s", nonl(old_settings), nonl(settings)) settingslog("overrides: dpi=%s, double click time=%s, double click distance=%s", self.dpi, self.double_click_time, self.double_click_distance) settingslog("overrides: antialias=%s", self.antialias) self._settings.update(settings) root = gtk.gdk.get_default_root_window() for k, v in settings.items(): #cook the "resource-manager" value to add the DPI: if k=="resource-manager" and self.dpi>0: value = v.decode("utf-8") #parse the resources into a dict: values={} options = value.split("\n") for option in options: if not option: continue parts = option.split(":\t") if len(parts)!=2: continue values[parts[0]] = parts[1] values["Xft.dpi"] = self.dpi values["Xft/DPI"] = self.dpi*1024 values["gnome.Xft/DPI"] = self.dpi*1024 if self.antialias: ad = typedict(self.antialias) values.update({ "Xft.antialias" : ad.intget("enabled", -1), "Xft.hinting" : ad.intget("hinting", -1), "Xft.rgba" : ad.strget("orientation", "none").lower(), "Xft.hintstyle" : self._get_antialias_hintstyle()}) settingslog("server_settings: resource-manager values=%s", nonl(values)) #convert the dict back into a resource string: value = '' for vk, vv in values.items(): value += "%s:\t%s\n" % (vk, vv) #record the actual value used self._settings["resource-manager"] = value v = value.encode("utf-8") #cook xsettings to add double-click settings: #(as those may not be present in xsettings on some platforms.. like win32 and osx) if k=="xsettings-blob" and (self.double_click_time>0 or self.double_click_distance!=(-1, -1)): from xpra.x11.xsettings_prop import XSettingsTypeInteger, XSettingsTypeString def set_xsettings_value(name, value_type, value): #remove existing one, if any: serial, values = v new_values = [(_t,_n,_v,_s) for (_t,_n,_v,_s) in values if _n!=name] new_values.append((value_type, name, value, 0)) return serial, new_values def set_xsettings_int(name, value): if value<0: #not set, return v unchanged return v return set_xsettings_value(name, XSettingsTypeInteger, value) if self.dpi>0: v = set_xsettings_int("Xft/DPI", self.dpi*1024) if self.double_click_time>0: v = set_xsettings_int("Net/DoubleClickTime", self.double_click_time) if self.antialias: ad = typedict(self.antialias) v = set_xsettings_int("Xft/Antialias", ad.intget("enabled", -1)) v = set_xsettings_int("Xft/Hinting", ad.intget("hinting", -1)) v = set_xsettings_value("Xft/RGBA", XSettingsTypeString, ad.strget("orientation", "none").lower()) v = set_xsettings_value("Xft/HintStyle", XSettingsTypeString, self._get_antialias_hintstyle()) if self.double_click_distance!=(-1, -1): #some platforms give us a value for each axis, #but X11 only has one, so take the average try: x,y = self.double_click_distance if x>0 and y>0: d = (x+y)//2 d = max(1, min(128, d)) #sanitize it a bit v = set_xsettings_int("Net/DoubleClickDistance", d) except Exception as e: log.warn("error setting double click distance from %s: %s", self.double_click_distance, e) if k not in old_settings or v != old_settings[k]: def root_set(p): settingslog("server_settings: setting %s to %s", nonl(p), nonl(v)) prop_set(root, p, "latin1", v.decode("utf-8")) if k == "xsettings-blob": self.set_xsettings(v) elif k == "resource-manager": root_set("RESOURCE_MANAGER") elif self.pulseaudio: if k == "pulse-cookie": root_set("PULSE_COOKIE") elif k == "pulse-id": root_set("PULSE_ID") elif k == "pulse-server": root_set("PULSE_SERVER")