def do_xpra_client_message_event(self, event): if event.message_type == "_NET_SYSTEM_TRAY_OPCODE" and event.window == self.tray_window and event.format == 32: opcode = event.data[1] SYSTEM_TRAY_REQUEST_DOCK = 0 SYSTEM_TRAY_BEGIN_MESSAGE = 1 SYSTEM_TRAY_CANCEL_MESSAGE = 2 if opcode == SYSTEM_TRAY_REQUEST_DOCK: xid = event.data[2] log("tray docking request from %#x", xid) trap.call_synced(self.dock_tray, xid) elif opcode == SYSTEM_TRAY_BEGIN_MESSAGE: timeout = event.data[2] mlen = event.data[3] mid = event.data[4] log.info( "tray begin message timeout=%s, mlen=%s, mid=%s - not handled yet!", timeout, mlen, mid) elif opcode == SYSTEM_TRAY_CANCEL_MESSAGE: mid = event.data[2] log.info("tray cancel message for mid=%s - not handled yet!", mid) elif event.message_type == "_NET_SYSTEM_TRAY_MESSAGE_DATA": assert event.format == 8 log.info("tray message data - not handled yet!") elif event.message_type in IGNORED_MESSAGE_TYPES: log( "do_xpra_client_message_event(%s) in ignored message type list", event) else: log.info("do_xpra_client_message_event(%s)", event)
def do_xpra_client_message_event(self, event): if event.message_type=="_NET_SYSTEM_TRAY_OPCODE" and event.window==self.tray_window and event.format==32: opcode = event.data[1] SYSTEM_TRAY_REQUEST_DOCK = 0 SYSTEM_TRAY_BEGIN_MESSAGE = 1 SYSTEM_TRAY_CANCEL_MESSAGE = 2 if opcode==SYSTEM_TRAY_REQUEST_DOCK: xid = event.data[2] log("tray docking request from %#x", xid) trap.call_synced(self.dock_tray, xid) elif opcode==SYSTEM_TRAY_BEGIN_MESSAGE: timeout = event.data[2] mlen = event.data[3] mid = event.data[4] log.info("tray begin message timeout=%s, mlen=%s, mid=%s - not handled yet!", timeout, mlen, mid) elif opcode==SYSTEM_TRAY_CANCEL_MESSAGE: mid = event.data[2] log.info("tray cancel message for mid=%s - not handled yet!", mid) elif event.message_type=="_NET_SYSTEM_TRAY_MESSAGE_DATA": assert event.format==8 log.info("tray message data - not handled yet!") elif event.message_type in IGNORED_MESSAGE_TYPES: log("do_xpra_client_message_event(%s) in ignored message type list", event) else: log.info("do_xpra_client_message_event(%s)", event)
def X11_ungrab(self): grablog("X11_ungrab") def do_X11_ungrab(): X11Core.UngrabKeyboard() X11Core.UngrabPointer() trap.call_synced(do_X11_ungrab)
def initiate_moveresize(self, x_root, y_root, direction, button, source_indication): log("initiate_moveresize%s", (x_root, y_root, direction, button, source_indication)) assert HAS_X11_BINDINGS, "cannot handle initiate-moveresize without X11 bindings" event_mask = SubstructureNotifyMask | SubstructureRedirectMask def wm_moveresize(): from xpra.gtk_common.gobject_compat import get_xid root = self.get_window().get_screen().get_root_window() root_xid = get_xid(root) xwin = get_xid(self.get_window()) X11Core.UngrabPointer() X11Window.sendClientMessage(root_xid, xwin, False, event_mask, "_NET_WM_MOVERESIZE", x_root, y_root, direction, button, source_indication) trap.call_synced(wm_moveresize)
def _process_button_action(self, proto, packet): ss = self._server_sources.get(proto) if ss is None: return wid, button, pressed, pointer, modifiers = packet[1:6] self._process_mouse_common(proto, wid, pointer, modifiers) ss.user_event() try: trap.call_synced(X11Keyboard.xtest_fake_button, button, pressed) except XError: err = "Failed to pass on (un)press of mouse button %s" % button if button >= 4: err += " (perhaps your Xvfb does not support mousewheels?)" log.warn(err)
def _process_button_action(self, proto, packet): ss = self._server_sources.get(proto) if ss is None: return wid, button, pressed, pointer, modifiers = packet[1:6] self._process_mouse_common(proto, wid, pointer, modifiers) ss.user_event() try: trap.call_synced(X11Keyboard.xtest_fake_button, button, pressed) except XError: err = "Failed to pass on (un)press of mouse button %s" % button if button>=4: err += " (perhaps your Xvfb does not support mousewheels?)" log.warn(err)
def cleanup(self): #this must be called from the UI thread! remove_event_receiver(self._root, self) self._root.set_events(self._saved_event_mask) if self._own_x11_filter: #only remove the x11 filter if we initialized it (ie: when running in client) try: trap.call_synced(cleanup_x11_filter) except Exception, e: log.error("failed to remove x11 event filter: %s", e) try: trap.call_synced(cleanup_all_event_receivers) except Exception, e: log.error("failed to remove event receivers: %s", e)
def prop_get(target, key, etype, ignore_errors=False, raise_xerrors=False): if isinstance(etype, list): scalar_type = etype[0] else: scalar_type = etype (_, atom, _, _, _, _) = _prop_types[scalar_type] try: data = trap.call_synced(X11Window.XGetWindowProperty, get_xwindow(target), key, atom, etype) if data is None: if not ignore_errors: log("Missing property %s (%s)", key, etype) return None except XError: if raise_xerrors: raise log.info("Missing window %s or wrong property type %s (%s)", target, key, etype, exc_info=True) return None except PropertyError: if not ignore_errors: log.info("Missing property or wrong property type %s (%s)", key, etype, exc_info=True) return None try: return _prop_decode(target, etype, data) except: if not ignore_errors: log.warn("Error parsing property %s (type %s); this may be a" + " misbehaving application, or bug in Xpra\n" + " Data: %r[...?]", key, etype, data[:160], exc_info=True) raise
def set_workspace(self): if not self._can_set_workspace: return -1 root = self.gdk_window().get_screen().get_root_window() ndesktops = self.get_workspace_count() workspacelog("%s.set_workspace() workspace=%s ndesktops=%s", self, self._window_workspace, ndesktops) if ndesktops is None or ndesktops<=1 or self._window_workspace<0: return -1 workspace = max(0, min(ndesktops-1, self._window_workspace)) event_mask = SubstructureNotifyMask | SubstructureRedirectMask def send(): from xpra.gtk_common.gobject_compat import get_xid root_xid = get_xid(root) xwin = get_xid(self.gdk_window()) X11Window.sendClientMessage(root_xid, xwin, False, event_mask, "_NET_WM_DESKTOP", workspace, CurrentTime, 0, 0, 0) trap.call_synced(send) return workspace
def get_settings(self): owner = self.xsettings_owner() log("Fetching current XSettings data, owner=%s", owner) if owner is None: return None try: return trap.call_synced(prop_get, owner, XSETTINGS, XSETTINGS_TYPE) except XError: log("X error while fetching XSettings data; ignored") return None
def xsettings_owner(self): owner_x = X11Window.XGetSelectionOwner(self._selection) log("XGetSelectionOwner(%s)=%s", self._selection, owner_x) if owner_x == XNone: return None try: return trap.call_synced(get_pywindow, self._clipboard, owner_x) except XError: log("X error while fetching owner of XSettings data; ignored") return None
def apply_xmodmap(instructions): try: unset = trap.call_synced(X11Keyboard.set_xmodmap, instructions) except: log.error("apply_xmodmap", exc_info=True) unset = instructions if unset is None: #None means an X11 error occurred, re-do all: unset = instructions return unset
def set_workspace(self): if not self._can_set_workspace: return -1 root = self.gdk_window().get_screen().get_root_window() ndesktops = self.get_workspace_count() workspacelog("%s.set_workspace() workspace=%s ndesktops=%s", self, self._window_workspace, ndesktops) if ndesktops is None or ndesktops <= 1 or self._window_workspace < 0: return -1 workspace = max(0, min(ndesktops - 1, self._window_workspace)) event_mask = SubstructureNotifyMask | SubstructureRedirectMask def send(): from xpra.gtk_common.gobject_compat import get_xid root_xid = get_xid(root) xwin = get_xid(self.gdk_window()) X11Window.sendClientMessage(root_xid, xwin, False, event_mask, "_NET_WM_DESKTOP", workspace, CurrentTime, 0, 0, 0) trap.call_synced(send) return workspace
def prop_set(target, key, etype, value): trap.call_synced(X11Window.XChangeProperty, get_xwindow(target), key, _prop_encode(target, etype, value))
def acquire(self, when): old_owner = self._owner() if when is self.IF_UNOWNED and old_owner != XNone: raise AlreadyOwned self.clipboard.set_with_data([("VERSION", 0, 0)], self._get, self._clear, None) # Having acquired the selection, we have to announce our existence # (ICCCM 2.8, still). The details here probably don't matter too # much; I've never heard of an app that cares about these messages, # and metacity actually gets the format wrong in several ways (no # MANAGER or owner_window atoms). But might as well get it as right # as possible. # To announce our existence, we need: # -- the timestamp we arrived at # -- the manager selection atom # -- the window that registered the selection # Of course, because Gtk is doing so much magic for us, we have to do # some weird tricks to get at these. # Ask ourselves when we acquired the selection: ts_data = self.clipboard.wait_for_contents("TIMESTAMP").data #data is a timestamp, X11 datatype is Time which is CARD32, #(which is 64 bits on 64-bit systems!) Lsize = calcsize("@L") if len(ts_data)==Lsize: ts_num = unpack("@L", ts_data[:Lsize])[0] else: ts_num = 0 #CurrentTime log.warn("invalid data for 'TIMESTAMP': %s", ([hex(ord(x)) for x in ts_data])) # Calculate the X atom for this selection: selection_xatom = get_xatom(self.atom) # Ask X what window we used: self._xwindow = X11Window.XGetSelectionOwner(self.atom) root = self.clipboard.get_display().get_default_screen().get_root_window() X11Window.sendClientMessage(root.xid, root.xid, False, StructureNotifyMask, "MANAGER", ts_num, selection_xatom, self._xwindow, 0, 0) if old_owner != XNone and when is self.FORCE: # Block in a recursive mainloop until the previous owner has # cleared out. def getwin(): window = get_pywindow(self.clipboard, old_owner) window.set_events(window.get_events() | gtk.gdk.STRUCTURE_MASK) return window try: window = trap.call_synced(getwin) log("got window") except XError: log("Previous owner is already gone, not blocking") else: log("Waiting for previous owner to exit...") add_event_receiver(window, self) gtk.main() log("...they did.") window = get_pywindow(self.clipboard, self._xwindow) window.set_title("Xpra-ManagerSelection")
def fake_key(self, keycode, press): keylog("fake_key(%s, %s)", keycode, press) trap.call_synced(X11Keyboard.xtest_fake_key, keycode, press)
def _manage_client(self, gdkwindow): try: if gdkwindow not in self._windows: trap.call_synced(self.do_manage_client, gdkwindow) except Exception, e: log("failed to manage client %s: %s", gdkwindow, e)
def set_screen_size(self, desired_w, desired_h): root_w, root_h = gtk.gdk.get_default_root_window().get_size() if desired_w == root_w and desired_h == root_h and not self.fake_xinerama: return root_w, root_h #unlikely: perfect match already! #find the "physical" screen dimensions, so we can calculate the required dpi #(and do this before changing the resolution) wmm, hmm = 0, 0 client_w, client_h = 0, 0 sss = self._server_sources.values() for ss in sss: for s in ss.screen_sizes: if len(s) >= 10: #display_name, width, height, width_mm, height_mm, monitors, work_x, work_y, work_width, work_height client_w = max(client_w, s[1]) client_h = max(client_h, s[2]) wmm = max(wmm, s[3]) hmm = max(hmm, s[4]) xdpi = self.default_dpi or self.dpi or 96 ydpi = self.default_dpi or self.dpi or 96 if wmm > 0 and hmm > 0 and client_w > 0 and client_h > 0: #calculate "real" dpi using integer calculations: xdpi = client_w * 254 / wmm / 10 ydpi = client_h * 254 / hmm / 10 log("calculated DPI: %s x %s (from w: %s / %s, h: %s / %s)", xdpi, ydpi, client_w, wmm, client_h, hmm) self.set_dpi(xdpi, ydpi) #try to find the best screen size to resize to: new_size = None for w, h in RandR.get_screen_sizes(): if w < desired_w or h < desired_h: continue #size is too small for client if new_size: ew, eh = new_size if ew * eh < w * h: continue #we found a better (smaller) candidate already new_size = w, h if not new_size: log.warn("resolution not found for %sx%s", desired_w, desired_h) return root_w, root_h log("best resolution for client(%sx%s) is: %s", desired_w, desired_h, new_size) #now actually apply the new settings: w, h = new_size xinerama_changed = self.save_fakexinerama_config() #we can only keep things unchanged if xinerama was also unchanged #(many apps will only query xinerama again if they get a randr notification) if (w == root_w and h == root_h) and not xinerama_changed: log.info("best resolution matching %sx%s is unchanged: %sx%s", desired_w, desired_h, w, h) return root_w, root_h try: if (w == root_w and h == root_h) and xinerama_changed: #xinerama was changed, but the RandR resolution will not be... #and we need a RandR change to force applications to re-query it #so we temporarily switch to another resolution to force #the change! (ugly! but this works) temp = {} for tw, th in RandR.get_screen_sizes(): if tw != w or th != h: #use the number of extra pixels as key: #(so we can choose the closest resolution) temp[abs((tw * th) - (w * h))] = (tw, th) if len(temp) == 0: log.warn( "cannot find a temporary resolution for Xinerama workaround!" ) else: k = sorted(temp.keys())[0] tw, th = temp[k] log.info( "temporarily switching to %sx%s as a Xinerama workaround", tw, th) RandR.set_screen_size(tw, th) log.debug("calling RandR.set_screen_size(%s, %s)", w, h) trap.call_synced(RandR.set_screen_size, w, h) log.debug("calling RandR.get_screen_size()") root_w, root_h = RandR.get_screen_size() log.debug("RandR.get_screen_size()=%s,%s", root_w, root_h) if root_w != w or root_h != h: log.error( "odd, failed to set the new resolution, " "tried to set it to %sx%s and ended up with %sx%s", w, h, root_w, root_h) else: msg = "server virtual display now set to %sx%s" % (root_w, root_h) if desired_w != root_w or desired_h != root_h: msg += " (best match for %sx%s)" % (desired_w, desired_h) log.info(msg) def show_dpi(): sizes_mm = RandR.get_screen_sizes_mm() #ie: [(1280, 1024)] assert len(sizes_mm) > 0 wmm = sum([x[0] for x in sizes_mm]) / len(sizes_mm) hmm = sum([x[1] for x in sizes_mm]) / len(sizes_mm) actual_xdpi = int(root_w * 25.4 / wmm + 0.5) actual_ydpi = int(root_h * 25.4 / hmm + 0.5) if actual_xdpi == xdpi and actual_ydpi == ydpi: log.info("DPI set to %s x %s", xdpi, ydpi) else: #should this be a warning: l = log.info maxdelta = max(abs(actual_xdpi - xdpi), abs(actual_ydpi - ydpi)) if maxdelta >= 10: l = log.warn l("DPI set to %s x %s (wanted %s x %s)", actual_xdpi, actual_ydpi, xdpi, ydpi) if maxdelta >= 10: l(" you may experience scaling problems, such as huge or small fonts, etc" ) l(" to fix this issue, try the dpi switch, or use a patched Xdummy driver" ) #show dpi via idle_add so server has time to change the screen size (mm) self.idle_add(show_dpi) except Exception, e: log.error("ouch, failed to set new resolution: %s", e, exc_info=True)
def acquire(self, when): old_owner = self._owner() if when is self.IF_UNOWNED and old_owner != XNone: raise AlreadyOwned self.clipboard.set_with_data([("VERSION", 0, 0)], self._get, self._clear, None) # Having acquired the selection, we have to announce our existence # (ICCCM 2.8, still). The details here probably don't matter too # much; I've never heard of an app that cares about these messages, # and metacity actually gets the format wrong in several ways (no # MANAGER or owner_window atoms). But might as well get it as right # as possible. # To announce our existence, we need: # -- the timestamp we arrived at # -- the manager selection atom # -- the window that registered the selection # Of course, because Gtk is doing so much magic for us, we have to do # some weird tricks to get at these. # Ask ourselves when we acquired the selection: ts_data = self.clipboard.wait_for_contents("TIMESTAMP").data #data is a timestamp, X11 datatype is Time which is CARD32, #(which is 64 bits on 64-bit systems!) Lsize = calcsize("@L") if len(ts_data) == Lsize: ts_num = unpack("@L", ts_data[:Lsize])[0] else: ts_num = 0 #CurrentTime log.warn("invalid data for 'TIMESTAMP': %s", ([hex(ord(x)) for x in ts_data])) # Calculate the X atom for this selection: selection_xatom = get_xatom(self.atom) # Ask X what window we used: self._xwindow = X11Window.XGetSelectionOwner(self.atom) root = self.clipboard.get_display().get_default_screen( ).get_root_window() X11Window.sendClientMessage(root.xid, root.xid, False, StructureNotifyMask, "MANAGER", ts_num, selection_xatom, self._xwindow, 0, 0) if old_owner != XNone and when is self.FORCE: # Block in a recursive mainloop until the previous owner has # cleared out. def getwin(): window = get_pywindow(self.clipboard, old_owner) window.set_events(window.get_events() | gtk.gdk.STRUCTURE_MASK) return window try: window = trap.call_synced(getwin) log("got window") except XError: log("Previous owner is already gone, not blocking") else: log("Waiting for previous owner to exit...") add_event_receiver(window, self) gtk.main() log("...they did.") window = get_pywindow(self.clipboard, self._xwindow) window.set_title("Xpra-ManagerSelection")
def set_screen_size(self, desired_w, desired_h): root_w, root_h = gtk.gdk.get_default_root_window().get_size() if desired_w==root_w and desired_h==root_h and not self.fake_xinerama: return root_w,root_h #unlikely: perfect match already! #find the "physical" screen dimensions, so we can calculate the required dpi #(and do this before changing the resolution) wmm, hmm = 0, 0 client_w, client_h = 0, 0 sss = self._server_sources.values() for ss in sss: for s in ss.screen_sizes: if len(s)>=10: #display_name, width, height, width_mm, height_mm, monitors, work_x, work_y, work_width, work_height client_w = max(client_w, s[1]) client_h = max(client_h, s[2]) wmm = max(wmm, s[3]) hmm = max(hmm, s[4]) xdpi = self.default_dpi or self.dpi or 96 ydpi = self.default_dpi or self.dpi or 96 if wmm>0 and hmm>0 and client_w>0 and client_h>0: #calculate "real" dpi using integer calculations: xdpi = client_w * 254 / wmm / 10 ydpi = client_h * 254 / hmm / 10 log("calculated DPI: %s x %s (from w: %s / %s, h: %s / %s)", xdpi, ydpi, client_w, wmm, client_h, hmm) self.set_dpi(xdpi, ydpi) #try to find the best screen size to resize to: new_size = None for w,h in RandR.get_screen_sizes(): if w<desired_w or h<desired_h: continue #size is too small for client if new_size: ew,eh = new_size if ew*eh<w*h: continue #we found a better (smaller) candidate already new_size = w,h if not new_size: log.warn("resolution not found for %sx%s", desired_w, desired_h) return root_w, root_h log("best resolution for client(%sx%s) is: %s", desired_w, desired_h, new_size) #now actually apply the new settings: w, h = new_size xinerama_changed = self.save_fakexinerama_config() #we can only keep things unchanged if xinerama was also unchanged #(many apps will only query xinerama again if they get a randr notification) if (w==root_w and h==root_h) and not xinerama_changed: log.info("best resolution matching %sx%s is unchanged: %sx%s", desired_w, desired_h, w, h) return root_w, root_h try: if (w==root_w and h==root_h) and xinerama_changed: #xinerama was changed, but the RandR resolution will not be... #and we need a RandR change to force applications to re-query it #so we temporarily switch to another resolution to force #the change! (ugly! but this works) temp = {} for tw,th in RandR.get_screen_sizes(): if tw!=w or th!=h: #use the number of extra pixels as key: #(so we can choose the closest resolution) temp[abs((tw*th) - (w*h))] = (tw, th) if len(temp)==0: log.warn("cannot find a temporary resolution for Xinerama workaround!") else: k = sorted(temp.keys())[0] tw, th = temp[k] log.info("temporarily switching to %sx%s as a Xinerama workaround", tw, th) RandR.set_screen_size(tw, th) log.debug("calling RandR.set_screen_size(%s, %s)", w, h) trap.call_synced(RandR.set_screen_size, w, h) log.debug("calling RandR.get_screen_size()") root_w, root_h = RandR.get_screen_size() log.debug("RandR.get_screen_size()=%s,%s", root_w, root_h) if root_w!=w or root_h!=h: log.error("odd, failed to set the new resolution, " "tried to set it to %sx%s and ended up with %sx%s", w, h, root_w, root_h) else: msg = "server virtual display now set to %sx%s" % (root_w, root_h) if desired_w!=root_w or desired_h!=root_h: msg += " (best match for %sx%s)" % (desired_w, desired_h) log.info(msg) def show_dpi(): sizes_mm = RandR.get_screen_sizes_mm() #ie: [(1280, 1024)] assert len(sizes_mm)>0 wmm = sum([x[0] for x in sizes_mm]) / len(sizes_mm) hmm = sum([x[1] for x in sizes_mm]) / len(sizes_mm) actual_xdpi = int(root_w * 25.4 / wmm + 0.5) actual_ydpi = int(root_h * 25.4 / hmm + 0.5) if actual_xdpi==xdpi and actual_ydpi==ydpi: log.info("DPI set to %s x %s", xdpi, ydpi) else: log.info("DPI set to %s x %s (wanted %s x %s)", actual_xdpi, actual_ydpi, xdpi, ydpi) #show dpi via idle_add so server has time to change the screen size (mm) self.idle_add(show_dpi) except Exception, e: log.error("ouch, failed to set new resolution: %s", e, exc_info=True)