def process_client_message_event(self, event): # FIXME # Need to listen for: # _NET_CURRENT_DESKTOP # _NET_WM_PING responses # and maybe: # _NET_RESTACK_WINDOW # _NET_WM_STATE (more fully) if event.message_type == "_NET_CLOSE_WINDOW": log.info("_NET_CLOSE_WINDOW received by %s", self) self.request_close() return True if event.message_type == "_NET_REQUEST_FRAME_EXTENTS": framelog("_NET_REQUEST_FRAME_EXTENTS") self._handle_frame_changed() return True if event.message_type == "_NET_MOVERESIZE_WINDOW": #this is overriden in WindowModel, skipped everywhere else: geomlog("_NET_MOVERESIZE_WINDOW skipped on %s (data=%s)", self, event.data) return True if event.message_type == "": log("empty message type: %s", event) if first_time("empty-x11-window-message-type-%#x" % event.window.get_xid()): log.warn( "Warning: empty message type received for window %#x:", event.window.get_xid()) log.warn(" %s", event) log.warn(" further messages will be silently ignored") return True #not handled: return False
def get_root_size(): if OSX: #the easy way: root = get_default_root_window() w, h = root.get_geometry()[2:4] else: #GTK3 on win32 triggers this warning: #"GetClientRect failed: Invalid window handle." #if we try to use the root window, #and on Linux with Wayland, we get bogus values... screen = Gdk.Screen.get_default() if screen is None: return 1920, 1024 w = screen.get_width() h = screen.get_height() if w <= 0 or h <= 0 or w > 32768 or h > 32768: if first_time("Gtk root window dimensions"): log.warn( "Warning: Gdk returned invalid root window dimensions: %ix%i", w, h) w, h = 1920, 1080 log.warn(" using %ix%i instead", w, h) if WIN32: log.warn(" no access to the display?") return w, h
def get_root_size(): if WIN32 or (POSIX and not OSX): #FIXME: hopefully, we can remove this code once GTK3 on win32 is fixed? #we do it the hard way because the root window geometry is invalid on win32: #and even just querying it causes this warning: #"GetClientRect failed: Invalid window handle." screen = Gdk.Screen.get_default() if screen is None: return 1920, 1024 w = screen.get_width() h = screen.get_height() else: #the easy way for platforms that work out of the box: root = get_default_root_window() w, h = root.get_geometry()[2:4] if w <= 0 or h <= 0 or w > 32768 or h > 32768: if first_time("Gtk root window dimensions"): log.warn( "Warning: Gdk returned invalid root window dimensions: %ix%i", w, h) w, h = 1920, 1080 log.warn(" using %ix%i instead", w, h) if WIN32: log.warn(" no access to the display?") return w, h
def set_visual(window, alpha: bool = True) -> bool: screen = window.get_screen() if alpha: visual = screen.get_rgba_visual() else: visual = screen.get_system_visual() alphalog("set_visual(%s, %s) screen=%s, visual=%s", window, alpha, screen, visual) #we can't do alpha on win32 with plain GTK, #(though we handle it in the opengl backend) if WIN32 or not first_time("no-rgba"): l = alphalog else: l = alphalog.warn if alpha and visual is None or (not WIN32 and not screen.is_composited()): l("Warning: cannot handle window transparency") if visual is None: l(" no RGBA visual") else: assert not screen.is_composited() l(" screen is not composited") return None alphalog("set_visual(%s, %s) using visual %s", window, alpha, visual) if visual: window.set_visual(visual) return visual
def select_device(preferred_device_id=-1, min_compute=0): log("select_device(%s, %s)", preferred_device_id, min_compute) for device_id in (preferred_device_id, get_pref("device-id")): if device_id is not None and device_id >= 0: dct = make_device_context(device_id) if dct: device, context, tpct = dct context.pop() context.detach() if min_compute > 0: compute = compute_capability(device) if compute < min_compute: log.warn( "Warning: GPU device %i only supports compute %#x", device_id, compute) if tpct < MIN_FREE_MEMORY: log.warn("Warning: GPU device %i is low on memory: %i%%", device_id, tpct) return device_id, device load_balancing = get_pref("load-balancing") log("load-balancing=%s", load_balancing) if load_balancing == "round-robin": return select_round_robin(min_compute) if load_balancing != "memory" and first_time("cuda-load-balancing"): log.warn("Warning: invalid load balancing value '%s'", load_balancing) return select_best_free_memory(min_compute)
def gravity_copy_coords(self, oldw, oldh, bw, bh): sx = sy = dx = dy = 0 def center_y(): if bh>=oldh: #take the whole source, paste it in the middle return 0, (bh-oldh)//2 #skip the edges of the source, paste all of it return (oldh-bh)//2, 0 def center_x(): if bw>=oldw: return 0, (bw-oldw)//2 return (oldw-bw)//2, 0 def east_x(): if bw>=oldw: return 0, bw-oldw return oldw-bw, 0 def west_x(): return 0, 0 def north_y(): return 0, 0 def south_y(): if bh>=oldh: return 0, bh-oldh return oldh-bh, 0 g = self.gravity if not g or g==NorthWestGravity: #undefined (or 0), use NW sx, dx = west_x() sy, dy = north_y() elif g==NorthGravity: sx, dx = center_x() sy, dy = north_y() elif g==NorthEastGravity: sx, dx = east_x() sy, dy = north_y() elif g==WestGravity: sx, dx = west_x() sy, dy = center_y() elif g==CenterGravity: sx, dx = center_x() sy, dy = center_y() elif g==EastGravity: sx, dx = east_x() sy, dy = center_y() elif g==SouthWestGravity: sx, dx = west_x() sy, dy = south_y() elif g==SouthGravity: sx, dx = center_x() sy, dy = south_y() elif g==SouthEastGravity: sx, dx = east_x() sy, dy = south_y() elif g==StaticGravity: if first_time("StaticGravity-%i" % self.wid): log.warn("Warning: static gravity is not handled") w = min(bw, oldw) h = min(bh, oldh) return sx, sy, dx, dy, w, h
def parse_ws_frame(self, buf): if not buf: self._read_queue_put(buf) return if self.ws_data: ws_data = self.ws_data+buf self.ws_data = b"" else: ws_data = buf log("parse_ws_frame(%i bytes) total buffer is %i bytes", len(buf), len(ws_data)) while ws_data and not self._closed: parsed = decode_hybi(ws_data) if parsed is None: log("parse_ws_frame(%i bytes) not enough data", len(ws_data)) #not enough data to get a full websocket frame, #save it for later: self.ws_data = ws_data return opcode, payload, processed, fin = parsed ws_data = ws_data[processed:] log("parse_ws_frame(%i bytes) payload=%i bytes, processed=%i, remaining=%i, opcode=%s, fin=%s", len(buf), len(payload), processed, len(ws_data), OPCODES.get(opcode, opcode), fin) if opcode==OPCODE_CONTINUE: assert self.ws_payload_opcode and self.ws_payload, "continuation frame does not follow a partial frame" self.ws_payload.append(payload) if not fin: #wait for more continue #join all the frames and process the payload: full_payload = b"".join(memoryview_to_bytes(v) for v in self.ws_payload) self.ws_payload = [] opcode = self.ws_payload_opcode self.ws_payload_opcode = 0 else: if self.ws_payload and self.ws_payload_opcode: raise Exception("expected a continuation frame not %s" % OPCODES.get(opcode, opcode)) full_payload = payload if not fin: if opcode not in (OPCODE_BINARY, OPCODE_TEXT): raise Exception("cannot handle fragmented '%s' frames" % OPCODES.get(opcode, opcode)) #fragmented, keep this payload for later self.ws_payload_opcode = opcode self.ws_payload.append(payload) continue if opcode==OPCODE_BINARY: self._read_queue_put(full_payload) elif opcode==OPCODE_TEXT: if first_time("ws-text-frame-from-%s" % self._conn): log.warn("Warning: handling text websocket frame as binary") self._read_queue_put(full_payload) elif opcode==OPCODE_CLOSE: self._process_ws_close(full_payload) elif opcode==OPCODE_PING: self._process_ws_ping(full_payload) elif opcode==OPCODE_PONG: self._process_ws_pong(full_payload) else: log.warn("Warning unhandled websocket opcode '%s'", OPCODES.get(opcode, "%#x" % opcode)) log("payload=%r", payload)
def do_selection_get(self, selection_data, info, time): # Either call selection_data.set() or don't, and then return. # In practice, send a call across the wire, then block in a recursive # main loop. def nodata(): selectiondata_set(selection_data, "STRING", 8, "") if not self._enabled or not self._can_receive: nodata() return if not self._have_token: try: return gtk.Invisible.do_selection_get(self, selection_data, info, time) except Exception as e: log("gtk.Invisible.do_selection_get", exc_info=True) if first_time("selection-%s-not-implemented" % self._selection): log.warn("Warning: limited clipboard support for %s", self._selection) if is_Wayland(): log.warn( " looks like a Wayland implementation limitation") log.warn(" %s", e) nodata() return selection = selectiondata_get_selection(selection_data) target = selectiondata_get_target(selection_data) log("do_selection_get(%s, %s, %s) selection=%s", selection_data, info, time, selection) self._selection_get_events += 1 assert str(selection) == self._selection, "expected %s but got %s" % ( selection, self._selection) self._request_contents_events += 1 result = self.emit("get-clipboard-from-remote", self._selection, target) if result is None or result["type"] is None: log("remote selection fetch timed out or empty") nodata() return data = result["data"] dformat = result["format"] dtype = result["type"] if dtype: dtype = bytestostr(dtype) log( "do_selection_get(%s,%s,%s) calling selection_data.set(%s, %s, %s:%s)", selection_data, info, time, dtype, dformat, type(data), len(data or "")) boc = self._block_owner_change self._block_owner_change = True if is_gtk3() and dtype in ("UTF8_STRING", "STRING") and dformat == 8: #GTK3 workaround: can only use set_text and only on the clipboard? s = bytestostr(data) self._clipboard.set_text(s, len(s)) else: selectiondata_set(selection_data, dtype, dformat, data) if boc is False: glib.idle_add(self.remove_block)
def check_auth_publickey(self, username, key): log("check_auth_publickey(%s, %r) pubkey_auth=%s", username, key, self.pubkey_auth) if not self.pubkey_auth: return paramiko.AUTH_FAILED if not POSIX or getuid() != 0: import getpass sysusername = getpass.getuser() if sysusername != username: log.warn("Warning: ssh password authentication failed,") log.warn(" username does not match:") log.warn(" expected '%s', got '%s'", sysusername, username) return paramiko.AUTH_FAILED authorized_keys_filename = osexpand(AUTHORIZED_KEYS) if not os.path.exists(authorized_keys_filename) or not os.path.isfile( authorized_keys_filename): log("file '%s' does not exist", authorized_keys_filename) return paramiko.AUTH_FAILED import base64 import binascii fingerprint = key.get_fingerprint() hex_fingerprint = binascii.hexlify(fingerprint) log("looking for key fingerprint '%s' in '%s'", hex_fingerprint, authorized_keys_filename) count = 0 with open(authorized_keys_filename, "rb") as f: for line in f: if line.startswith("#"): continue line = line.strip("\n\r") try: key = base64.b64decode( line.strip().split()[1].encode('ascii')) except Exception as e: log("ignoring line '%s': %s", line, e) continue import hashlib for hash_algo in AUTHORIZED_KEYS_HASHES: hash_instance = None try: hash_class = getattr(hashlib, hash_algo) #ie: hashlib.md5 hash_instance = hash_class( key ) #can raise ValueError (ie: on FIPS compliant systems) except ValueError: hash_instance = None if not hash_instance: if first_time("hash-%s-missing" % hash_algo): log.warn("Warning: unsupported hash '%s'", hash_algo) continue fp_plain = hash_instance.hexdigest() log("%s(%s)=%s", hash_algo, line, fp_plain) if fp_plain == hex_fingerprint: return paramiko.OPEN_SUCCEEDED count += 1 log("no match in %i keys from '%s'", count, authorized_keys_filename) return paramiko.AUTH_FAILED
def process_challenge_kerberos(self, packet): digest = packet[3] if not digest.startswith(b"kerberos:"): authlog("%s is not a kerberos challenge", digest) #not a kerberos challenge return False try: if WIN32: import winkerberos as kerberos else: import kerberos except ImportError as e: authlog("import (win)kerberos", exc_info=True) if first_time("no-kerberos"): authlog.warn( "Warning: kerberos challenge handler is not supported:") authlog.warn(" %s", e) return False service = bytestostr(digest.split(b":", 1)[1]) if service not in KERBEROS_SERVICES and "*" not in KERBEROS_SERVICES: authlog.warn("Warning: invalid kerberos request for service '%s'", service) authlog.warn(" services supported: %s", csv(KERBEROS_SERVICES)) return False authlog("kerberos service=%s", service) def log_kerberos_exception(e): try: for x in e.args: if isinstance(x, (list, tuple)): try: log.error(" %s", csv(x)) continue except: pass authlog.error(" %s", x) except Exception: authlog.error(" %s", e) try: r, ctx = kerberos.authGSSClientInit(service) assert r == 1, "return code %s" % r except Exception as e: authlog("kerberos.authGSSClientInit(%s)", service, exc_info=True) authlog.error("Error: cannot initialize kerberos client:") log_kerberos_exception(e) return False try: kerberos.authGSSClientStep(ctx, "") except Exception as e: authlog("kerberos.authGSSClientStep", exc_info=True) authlog.error("Error: kerberos client authentication failure:") log_kerberos_exception(e) return False token = kerberos.authGSSClientResponse(ctx) authlog("kerberos token=%s", token) self.send_challenge_reply(packet, token) return True
def notify_dpi_warning(self, body): sources = tuple(self._server_sources.values()) if len(sources) == 1: ss = sources[0] if first_time("DPI-warning-%s" % ss.uuid): sources[0].may_notify(XPRA_DPI_NOTIFICATION_ID, "DPI Issue", body, icon_name="font")
def rgb_reformat(image, rgb_formats, supports_transparency): """ convert the RGB pixel data into a format supported by the client """ #need to convert to a supported format! PIL = get_codec("PIL") pixel_format = bytestostr(image.get_pixel_format()) pixels = image.get_pixels() assert pixels, "failed to get pixels from %s" % image if not PIL or pixel_format in ("r210", "BGR565"): #try to fallback to argb module #(required for r210 which is not handled by PIL directly) assert argb_swap, "no argb codec" log("rgb_reformat: using argb_swap for %s", image) return argb_swap(image, rgb_formats, supports_transparency) if supports_transparency: modes = PIL_conv.get(pixel_format) else: modes = PIL_conv_noalpha.get(pixel_format) assert modes, "no PIL conversion from %s" % (pixel_format) target_rgb = [(im, om) for (im, om) in modes if om in rgb_formats] if len(target_rgb) == 0: log("rgb_reformat: no matching target modes for converting %s to %s", image, rgb_formats) #try argb module: assert argb_swap, "no argb codec" if argb_swap(image, rgb_formats, supports_transparency): return True warning_key = "rgb_reformat(%s, %s, %s)" % (pixel_format, rgb_formats, supports_transparency) if first_time(warning_key): log.warn("Warning: cannot convert %s to one of: %s" % (pixel_format, rgb_formats)) return False input_format, target_format = target_rgb[0] start = monotonic_time() w = image.get_width() h = image.get_height() #PIL cannot use the memoryview directly: if isinstance(pixels, memoryview): pixels = pixels.tobytes() #log("rgb_reformat: converting %s from %s to %s using PIL, %i bytes", image, input_format, target_format, len(pixels)) img = Image.frombuffer(target_format, (w, h), pixels, "raw", input_format, image.get_rowstride()) rowstride = w * len( target_format) #number of characters is number of bytes per pixel! data = img.tobytes("raw", target_format) assert len( data) == rowstride * h, "expected %s bytes in %s format but got %s" % ( rowstride * h, len(data)) image.set_pixels(data) image.set_rowstride(rowstride) image.set_pixel_format(target_format) end = monotonic_time() log( "rgb_reformat(%s, %s, %s) converted from %s (%s bytes) to %s (%s bytes) in %.1fms, rowstride=%s", image, rgb_formats, supports_transparency, pixel_format, len(pixels), target_format, len(data), (end - start) * 1000.0, rowstride) return True
def hello_request(self): if first_time("hello-request"): #this can be called again if we receive a challenge, #but only print this message once: errwrite("requesting new session, please wait") self.timeout_add(1 * 1000, self.dots) return { "start-new-session": self.start_new_session, #tells proxy servers we don't want to connect to the real / new instance: "connect": False, }
def mmap_send(mmap, mmap_size, image, rgb_formats, supports_transparency): if mmap_write is None: if first_time("mmap_write missing"): log.warn("Warning: cannot use mmap, no write method support") return None if image.get_pixel_format() not in rgb_formats: if not rgb_reformat(image, rgb_formats, supports_transparency): warning_key = "mmap_send(%s)" % image.get_pixel_format() if first_time(warning_key): log.warn("Waening: cannot use mmap to send %s" % image.get_pixel_format()) return None start = monotonic_time() data = image.get_pixels() assert data, "failed to get pixels from %s" % image mmap_data, mmap_free_size = mmap_write(mmap, mmap_size, data) elapsed = monotonic_time()-start+0.000000001 #make sure never zero! log("%s MBytes/s - %s bytes written to mmap in %.1f ms", int(len(data)/elapsed/1024/1024), len(data), 1000*elapsed) if mmap_data is None: return None #replace pixels with mmap info: return mmap_data, mmap_free_size, len(data)
def _get_qrencode_fn(): try: from PIL import Image from qrencode import encode def qrencode_fn(s): return encode(s)[2] return qrencode_fn except ImportError: try: from PIL import Image from ctypes import ( Structure, POINTER, cdll, create_string_buffer, c_int, c_char_p, ) from ctypes.util import find_library class QRCode(Structure): _fields_ = ( ("version", c_int), ("width", c_int), ("data", c_char_p), ) PQRCode = POINTER(QRCode) lib_file = None for lib_name in ("libqrencode", "qrencode"): lib_file = find_library(lib_name) if lib_file: break if not lib_file: if first_time("libqrencode"): log.warn("Warning: libqrencode not found") return None libqrencode = cdll.LoadLibrary(lib_file) encodeString8bit = libqrencode.QRcode_encodeString8bit encodeString8bit.argtypes = (c_char_p, c_int, c_int) encodeString8bit.restype = PQRCode QRcode_free = libqrencode.QRcode_free QRcode_free.argtypes = (PQRCode,) def qrencode_ctypes_fn(s): data = create_string_buffer(s.encode("latin1")) qrcode = encodeString8bit(data, 0, 0).contents try: size = qrcode.width pixels = bytearray(size*size) pdata = qrcode.data for i in range(size*size): pixels[i] = 0 if (pdata[i] & 0x1) else 255 return Image.frombytes('L', (size, size), bytes(pixels)) finally: QRcode_free(qrcode) return qrencode_ctypes_fn except Exception: log.error("failed to load qrencode via ctypes", exc_info=True) return None
def do_load_xdg_menu_data(): try: from xdg.Menu import parse, Menu, ParsingError except ImportError: log("do_load_xdg_menu_data()", exc_info=True) if first_time("no-python-xdg"): log.warn("Warning: cannot use application menu data:") log.warn(" no python-xdg module") return None menu = None error = None with OSEnvContext(): #see ticket #2340, #invalid values for XDG_CONFIG_DIRS can cause problems, #so try unsetting it if we can't load the menus with it: for cd in (False, True): if cd: try: del os.environ["XDG_CONFIG_DIRS"] except KeyError: continue #see ticket #2174, #things may break if the prefix is not set, #and it isn't set when logging in via ssh for prefix in (None, "", "gnome-", "kde-"): if prefix is not None: os.environ["XDG_MENU_PREFIX"] = prefix try: menu = parse() break except Exception as e: log("do_load_xdg_menu_data()", exc_info=True) error = e menu = None if menu is None: if error: log.error("Error parsing xdg menu data:") log.error(" %s", error) log.error(" this is either a bug in python-xdg,") log.error(" or an invalid system menu configuration") return None menu_data = {} for submenu in menu.getEntries(): if isinstance(submenu, Menu) and submenu.Visible: name = submenu.getName() try: menu_data[name] = load_xdg_menu(submenu) except Exception as e: log("load_xdg_menu_data()", exc_info=True) log.error("Error loading submenu '%s':", name) log.error(" %s", e) return menu_data
def get_element_properties(self, element, *properties): info = {} for x in properties: try: v = element.get_property(x) if v >= 0: info[x] = v except TypeError as e: if first_time("gst-property-%s" % x): log("'%s' not found in %r", x, self) log.warn("Warning: %s", e) except Exception as e: log.warn("Warning: %s (%s)", e, type(e)) return info
def load_icon_from_file(filename): log("load_icon_from_file(%s)", filename) if filename.endswith("xpm"): from PIL import Image try: img = Image.open(filename) buf = BytesIO() img.save(buf, "PNG") pngicondata = buf.getvalue() buf.close() return pngicondata, "png" except ValueError as e: log("Image.open(%s)", filename, exc_info=True) except Exception as e: log("Image.open(%s)", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) #fallback to PixbufLoader: try: from xpra.gtk_common.gtk_util import pixbuf_save_to_memory data = load_binary_file(filename) from gi.repository import GdkPixbuf loader = GdkPixbuf.PixbufLoader() loader.write(data) loader.close() pixbuf = loader.get_pixbuf() pngicondata = pixbuf_save_to_memory(pixbuf, "png") return pngicondata, "png" except Exception as e: log("pixbuf error loading %s", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) icondata = load_binary_file(filename) if not icondata: return None if filename.endswith("svg") and len(icondata)>MAX_ICON_SIZE//2: #try to resize it size = len(icondata) pngdata = svg_to_png(filename, icondata) if pngdata: log("reduced size of SVG icon %s, from %i bytes to %i bytes as PNG", filename, size, len(pngdata)) icondata = pngdata filename = filename[:-3]+"png" log("got icon data from '%s': %i bytes", filename, len(icondata)) if len(icondata)>MAX_ICON_SIZE and first_time("icon-size-warning-%s" % filename): global large_icons large_icons.append((filename, len(icondata))) return icondata, os.path.splitext(filename)[1].lstrip(".")
def load_Rsvg(): global _Rsvg if _Rsvg is None: import gi #pylint: disable=import-outside-toplevel try: gi.require_version('Rsvg', '2.0') from gi.repository import Rsvg log("load_Rsvg() Rsvg=%s", Rsvg) _Rsvg = Rsvg except (ValueError, ImportError) as e: if first_time("no-rsvg"): log.warn("Warning: cannot resize svg icons,") log.warn(" the Rsvg bindings were not found:") log.warn(" %s", e) _Rsvg = False return _Rsvg
def get_interface_info(_fd, iface): log = logger() from xpra.platform.win32.comtypes_util import QuietenLogging with QuietenLogging(): try: from comtypes import CoInitialize #@UnresolvedImport CoInitialize() from comtypes.client import CreateObject #@UnresolvedImport o = CreateObject("WbemScripting.SWbemLocator") s = o.ConnectServer(".", "root\\cimv2") query = "SELECT * FROM Win32_NetworkAdapter WHERE GUID='%s'" % iface res = s.ExecQuery(query) log("ExecQuery(%s) returned %i rows", query, res.Count) if res.Count == 1: for r in res: props = {} for k, ik, conv in ( ("AdapterType", "adapter-type", str), ("Caption", "caption", str), ("Description", "description", str), ("DeviceID", "id", int), ("GUID", "GUID", str), ("Index", "index", int), ("Name", "name", str), ("ProductName", "product-name", str), ("Speed", "speed", int), ): try: v = conv(r.Properties_[k].Value) except Exception as e: log.error( "Error retrieving '%s' from network adapter record:", k) log.error(" %s", e) else: props[ik] = v log("get_interface_info(%s)=%s" % (iface, props)) return props except Exception as e: log("get_interface_info(%s)", iface, exc_info=True) from xpra.util import first_time if first_time("win32-network-query"): log.error("Error: failed to query network interface:") log.error(" %s", e) return {}
def set_screen_sizes(self, screen_sizes): log("set_screen_sizes(%s)", screen_sizes) self.screen_sizes = screen_sizes or [] #validate dpi / screen size in mm #(ticket 2480: GTK3 on macos can return bogus values) MIN_DPI = envint("XPRA_MIN_DPI", 10) MAX_DPI = envint("XPRA_MIN_DPI", 500) def dpi(size_pixels, size_mm): if size_mm == 0: return 0 return int(size_pixels * 254 / size_mm / 10) for i, screen in enumerate(screen_sizes): if len(screen) < 10: continue sw, sh, wmm, hmm, monitors = screen[1:6] xdpi = dpi(sw, wmm) ydpi = dpi(sh, hmm) if xdpi < MIN_DPI or xdpi > MAX_DPI or ydpi < MIN_DPI or ydpi > MAX_DPI: warn = first_time("invalid-screen-size-%ix%i" % (wmm, hmm)) if warn: log.warn( "Warning: sanitizing invalid screen size %ix%i mm", wmm, hmm) if monitors: #[plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm] wmm = sum(monitor[5] for monitor in monitors) hmm = sum(monitor[6] for monitor in monitors) xdpi = dpi(sw, wmm) ydpi = dpi(sh, hmm) if xdpi < MIN_DPI or xdpi > MAX_DPI or ydpi < MIN_DPI or ydpi > MAX_DPI: #still invalid, generate one from DPI=96 wmm = iround(sw * 254 / 10 / 96.0) hmm = iround(sh * 254 / 10 / 96.0) if warn: log.warn(" using %ix%i mm", wmm, hmm) screen = list(screen) screen[3] = wmm screen[4] = hmm screen_sizes[i] = tuple(screen) log("client validated screen sizes: %s", screen_sizes)
def load_icon_from_file(filename): log("load_icon_from_file(%s)", filename) if filename.endswith("xpm"): try: from xpra.gtk_common.gobject_compat import import_pixbufloader from xpra.gtk_common.gtk_util import pixbuf_save_to_memory data = load_binary_file(filename) loader = import_pixbufloader()() loader.write(data) loader.close() pixbuf = loader.get_pixbuf() pngicondata = pixbuf_save_to_memory(pixbuf, "png") return pngicondata, "png" except Exception as e: log("pixbuf error loading %s", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) #try PIL: from PIL import Image try: img = Image.open(filename) except Exception as e: log("Image.open(%s)", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) return None buf = BytesIO() img.save(buf, "PNG") pngicondata = buf.getvalue() buf.close() return pngicondata, "png" icondata = load_binary_file(filename) if not icondata: return None log("got icon data from '%s': %i bytes", filename, len(icondata)) if len(icondata) > MAX_ICON_SIZE and first_time( "icon-size-warning-%s" % filename): log.warn("Warning: icon is quite large (%i KB):", len(icondata) // 1024) log.warn(" '%s'", filename) return icondata, os.path.splitext(filename)[1].lstrip(".")
def process_challenge_gss(self, packet): digest = packet[3] if not digest.startswith(b"gss:"): #not a gss challenge authlog("%s is not a gss challenge", digest) return False try: import gssapi if OSX and False: from gssapi.raw import (cython_converters, cython_types, oids) assert cython_converters and cython_types and oids except ImportError as e: authlog("import gssapi", exc_info=True) if first_time("no-kerberos"): authlog.warn("Warning: gss authentication not supported:") authlog.warn(" %s", e) return False service = bytestostr(digest.split(b":", 1)[1]) if service not in GSS_SERVICES and "*" not in GSS_SERVICES: authlog.warn("Warning: invalid GSS request for service '%s'", service) authlog.warn(" services supported: %s", csv(GSS_SERVICES)) return False authlog("gss service=%s", service) service_name = gssapi.Name(service) try: ctx = gssapi.SecurityContext(name=service_name, usage="initiate") token = ctx.step() except Exception as e: authlog("gssapi failure", exc_info=True) authlog.error("Error: gssapi client authentication failure:") try: #split on colon for x in str(e).split(":", 2): authlog.error(" %s", x.lstrip(" ")) except: authlog.error(" %s", e) return False authlog("gss token=%s", repr(token)) self.send_challenge_reply(packet, token) return True
def load_icon_from_file(filename, max_size=MAX_ICON_SIZE): if os.path.isdir(filename): log("load_icon_from_file(%s, %i) path is a directory!", filename, max_size) return None log("load_icon_from_file(%s, %i)", filename, max_size) if filename.endswith("xpm"): from PIL import Image #pylint: disable=import-outside-toplevel try: img = Image.open(filename) buf = BytesIO() img.save(buf, "PNG") pngicondata = buf.getvalue() buf.close() return pngicondata, "png" except ValueError as e: log("Image.open(%s)", filename, exc_info=True) except Exception as e: log("Image.open(%s)", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) icondata = load_binary_file(filename) if not icondata: return None if filename.endswith("svg") and max_size and len(icondata) > max_size: #try to resize it size = len(icondata) pngdata = svg_to_png(filename, icondata) if pngdata: log( "reduced size of SVG icon %s, from %i bytes to %i bytes as PNG", filename, size, len(pngdata)) icondata = pngdata filename = filename[:-3] + "png" log("got icon data from '%s': %i bytes", filename, len(icondata)) if max_size > 0 and len(icondata) > max_size and first_time( "icon-size-warning-%s" % filename): global large_icons large_icons.append((filename, len(icondata))) return icondata, os.path.splitext(filename)[1].lstrip(".")
def load_icon_from_file(filename): log("load_icon_from_file(%s)", filename) if filename.endswith("xpm"): try: from xpra.gtk_common.gobject_compat import import_pixbufloader from xpra.gtk_common.gtk_util import pixbuf_save_to_memory data = load_binary_file(filename) loader = import_pixbufloader()() loader.write(data) loader.close() pixbuf = loader.get_pixbuf() pngicondata = pixbuf_save_to_memory(pixbuf, "png") return pngicondata, "png" except Exception as e: log("pixbuf error loading %s", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) #try PIL: from PIL import Image try: img = Image.open(filename) except Exception as e: log("Image.open(%s)", filename, exc_info=True) log.error("Error loading '%s':", filename) log.error(" %s", e) return None buf = BytesIO() img.save(buf, "PNG") pngicondata = buf.getvalue() buf.close() return pngicondata, "png" icondata = load_binary_file(filename) if not icondata: return None if filename.endswith("svg") and len(icondata) > MAX_ICON_SIZE // 2: #try to resize it try: size = len(icondata) import cairo import gi try: gi.require_version('Rsvg', '2.0') except ValueError as e: if first_time("no-rsvg"): log.warn( "Warning: cannot resize svg icons, Rsvg bindings not found:" ) log.warn(" %s", e) else: from gi.repository import Rsvg img = cairo.ImageSurface(cairo.FORMAT_ARGB32, 128, 128) ctx = cairo.Context(img) handle = Rsvg.Handle.new_from_data(icondata) handle.render_cairo(ctx) buf = BytesIO() img.write_to_png(buf) icondata = buf.getvalue() buf.close() log( "reduced size of SVG icon %s, from %i bytes to %i bytes as PNG", filename, size, len(icondata)) filename = filename[:-3] + "png" except ImportError: log("cannot convert svg", exc_info=True) except Exception: log.error("Error: failed to convert svg icon", exc_info=True) log("got icon data from '%s': %i bytes", filename, len(icondata)) if len(icondata) > MAX_ICON_SIZE and first_time( "icon-size-warning-%s" % filename): log.warn("Warning: icon is quite large (%i KB):", len(icondata) // 1024) log.warn(" '%s'", filename) return icondata, os.path.splitext(filename)[1].lstrip(".")
def get_screen_sizes(xscale=1, yscale=1): from xpra.platform.gui import get_workarea, get_workareas def xs(v): return iround(v / xscale) def ys(v): return iround(v / yscale) def swork(*workarea): return xs(workarea[0]), ys(workarea[1]), xs(workarea[2]), ys( workarea[3]) display = Gdk.Display.get_default() if not display: return () MIN_DPI = envint("XPRA_MIN_DPI", 10) MAX_DPI = envint("XPRA_MIN_DPI", 500) def dpi(size_pixels, size_mm): if size_mm == 0: return 0 return int(size_pixels * 254 / size_mm / 10) n_screens = display.get_n_screens() get_n_monitors = getattr(display, "get_n_monitors", None) if get_n_monitors: #GTK 3.22: always just one screen n_monitors = get_n_monitors() workareas = get_workareas() if workareas and len(workareas) != n_monitors: screenlog(" workareas: %s", workareas) screenlog( " number of monitors does not match number of workareas!") workareas = [] monitors = [] for j in range(n_monitors): monitor = display.get_monitor(j) geom = monitor.get_geometry() manufacturer, model = monitor.get_manufacturer( ), monitor.get_model() if manufacturer and model: plug_name = "%s %s" % (manufacturer, model) elif manufacturer: plug_name = manufacturer elif model: plug_name = model else: plug_name = "%i" % j wmm, hmm = monitor.get_width_mm(), monitor.get_height_mm() monitor = [ plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm ] screenlog(" monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(swork(*w)) monitors.append(tuple(monitor)) screen = display.get_default_screen() sw, sh = screen.get_width(), screen.get_height() work_x, work_y, work_width, work_height = swork(0, 0, sw, sh) workarea = get_workarea() #pylint: disable=assignment-from-none if workarea: work_x, work_y, work_width, work_height = swork(*workarea) #pylint: disable=not-an-iterable screenlog(" workarea=%s", workarea) wmm = screen.get_width_mm() hmm = screen.get_height_mm() xdpi = dpi(sw, wmm) ydpi = dpi(sh, hmm) if xdpi < MIN_DPI or xdpi > MAX_DPI or ydpi < MIN_DPI or ydpi > MAX_DPI: warn = first_time("invalid-screen-size-%ix%i" % (wmm, hmm)) if warn: log.warn("Warning: ignoring invalid screen size %ix%imm", wmm, hmm) if n_monitors > 0: wmm = sum( display.get_monitor(i).get_width_mm() for i in range(n_monitors)) hmm = sum( display.get_monitor(i).get_height_mm() for i in range(n_monitors)) xdpi = dpi(sw, wmm) ydpi = dpi(sh, hmm) if xdpi < MIN_DPI or xdpi > MAX_DPI or ydpi < MIN_DPI or ydpi > MAX_DPI: #still invalid, generate one from DPI=96 wmm = iround(sw * 25.4 / 96) hmm = iround(sh * 25.4 / 96) if warn: log.warn(" using %ix%i mm", wmm, hmm) item = (screen.make_display_name(), xs(sw), ys(sh), wmm, hmm, monitors, work_x, work_y, work_width, work_height) screenlog(" screen: %s", item) screen_sizes = [item] else: i = 0 screen_sizes = [] #GTK2 or GTK3<3.22: screenlog("get_screen_sizes(%f, %f) found %s screens", xscale, yscale, n_screens) while i < n_screens: screen = display.get_screen(i) j = 0 monitors = [] workareas = [] #native "get_workareas()" is only valid for a single screen (but describes all the monitors) #and it is only implemented on win32 right now #other platforms only implement "get_workarea()" instead, which is reported against the screen n_monitors = screen.get_n_monitors() screenlog(" screen %s has %s monitors", i, n_monitors) if n_screens == 1: workareas = get_workareas() if workareas and len(workareas) != n_monitors: screenlog(" workareas: %s", workareas) screenlog( " number of monitors does not match number of workareas!" ) workareas = [] while j < screen.get_n_monitors(): geom = screen.get_monitor_geometry(j) plug_name = "" if hasattr(screen, "get_monitor_plug_name"): plug_name = screen.get_monitor_plug_name(j) or "" wmm = -1 if hasattr(screen, "get_monitor_width_mm"): wmm = screen.get_monitor_width_mm(j) hmm = -1 if hasattr(screen, "get_monitor_height_mm"): hmm = screen.get_monitor_height_mm(j) monitor = [ plug_name, xs(geom.x), ys(geom.y), xs(geom.width), ys(geom.height), wmm, hmm ] screenlog(" monitor %s: %s", j, monitor) if workareas: w = workareas[j] monitor += list(swork(*w)) monitors.append(tuple(monitor)) j += 1 work_x, work_y, work_width, work_height = swork( 0, 0, screen.get_width(), screen.get_height()) workarea = get_workarea() #pylint: disable=assignment-from-none if workarea: work_x, work_y, work_width, work_height = swork(*workarea) #pylint: disable=not-an-iterable screenlog(" workarea=%s", workarea) item = (screen.make_display_name(), xs(screen.get_width()), ys(screen.get_height()), screen.get_width_mm(), screen.get_height_mm(), monitors, work_x, work_y, work_width, work_height) screenlog(" screen %s: %s", i, item) screen_sizes.append(item) i += 1 return screen_sizes
def load_xdg_menu_data(): try: from xdg.Menu import parse, Menu #pylint: disable=import-outside-toplevel except ImportError: log("load_xdg_menu_data()", exc_info=True) if first_time("no-python-xdg"): log.warn("Warning: cannot use application menu data:") log.warn(" no python-xdg module") return None menu = None error = None #see ticket #2340, #invalid values for XDG_CONFIG_DIRS can cause problems, #so try unsetting it if we can't load the menus with it: for cd in (False, True): with OSEnvContext(): if cd: if not os.environ.pop("XDG_CONFIG_DIRS", ""): #was already unset continue #see ticket #2174, #things may break if the prefix is not set, #and it isn't set when logging in via ssh for prefix in (None, "", "gnome-", "kde-"): if prefix is not None: os.environ["XDG_MENU_PREFIX"] = prefix try: log( "parsing xdg menu data for prefix %r with XDG_CONFIG_DIRS=%s and XDG_MENU_PREFIX=%s", prefix, os.environ.get("XDG_CONFIG_DIRS"), os.environ.get("XDG_MENU_PREFIX")) menu = parse() break except Exception as e: log("load_xdg_menu_data()", exc_info=True) error = e menu = None if menu: break if menu is None: if error: log.error("Error parsing xdg menu data:") log.error(" %s", error) log.error(" this is either a bug in python-xdg,") log.error(" or an invalid system menu configuration") return None menu_data = {} entries = tuple(menu.getEntries()) log("%s.getEntries()=%s", menu, entries) if len(entries) == 1 and entries[0].Submenus: entries = entries[0].Submenus log("using submenus %s", entries) for i, submenu in enumerate(entries): if not isinstance(submenu, Menu): log("entry '%s' is not a submenu", submenu) continue name = submenu.getName() log("* %-3i %s", i, name) if not submenu.Visible: log(" submenu '%s' is not visible", name) continue try: md = load_xdg_menu(submenu) if md: menu_data[name] = md else: log(" no menu data for %s", name) except Exception as e: log("load_xdg_menu_data()", exc_info=True) log.error("Error loading submenu '%s':", name) log.error(" %s", e) if LOAD_APPLICATIONS: from xdg.Menu import MenuEntry entries = {} for d in LOAD_APPLICATIONS: for f in os.listdir(d): if not f.endswith(".desktop"): continue try: me = MenuEntry(f, d) except Exception: log("failed to load %s from %s", f, d, exc_info=True) else: ed = load_xdg_entry(me.DesktopEntry) if not ed: continue name = ed.get("Name") #ensure we don't already have it in another submenu: for menu_category in menu_data.values(): if name in menu_category.get("Entries", {}): ed = None break if ed: entries[name] = ed log("entries(%s)=%s", LOAD_APPLICATIONS, remove_icons(entries)) if entries: #add an 'Applications' menu if we don't have one: md = menu_data.get("Applications") if not md: md = { "Name": "Applications", } menu_data["Applications"] = md md.setdefault("Entries", {}).update(entries) return menu_data
def do_selection_request_event(self, event): #an app is requesting clipboard data from us log("do_selection_request_event(%s)", event) requestor = event.requestor if not requestor: log.warn( "Warning: clipboard selection request without a window, dropped" ) return wininfo = self.get_wininfo(requestor.get_xid()) prop = event.property target = str(event.target) log("clipboard request for %s from window %#x: %s, target=%s, prop=%s", self._selection, requestor.get_xid(), wininfo, target, prop) if not target: log.warn("Warning: ignoring clipboard request without a TARGET") log.warn(" coming from %s", wininfo) return if not prop: log.warn("Warning: ignoring clipboard request without a property") log.warn(" coming from %s", wininfo) return def nodata(): self.set_selection_response(requestor, target, prop, "STRING", 8, b"", time=event.time) if not self._enabled: nodata() return if wininfo and wininfo.strip("'") in BLACKLISTED_CLIPBOARD_CLIENTS: if first_time("clipboard-blacklisted:%s" % wininfo.strip("'")): log.warn( "receiving clipboard requests from blacklisted client %s", wininfo) log.warn(" all requests will be silently ignored") log("responding with nodata for blacklisted client '%s'", wininfo) return if not self.owned: log.warn("Warning: clipboard selection request received,") log.warn(" coming from %s", wininfo) log.warn(" but we don't own the selection,") log.warn(" sending an empty reply") nodata() return if not self._can_receive: log.warn("Warning: clipboard selection request received,") log.warn(" coming from %s", wininfo) log.warn(" but receiving remote data is disabled,") log.warn(" sending an empty reply") nodata() return if must_discard(target): log.info("clipboard %s rejecting request for invalid target '%s'", self._selection, target) log.info(" coming from %s", wininfo) nodata() return if target == "TARGETS": if self.targets: log("using existing TARGETS value as response: %s", self.targets) xatoms = strings_to_xatoms(self.targets) self.set_selection_response(requestor, target, prop, "ATOM", 32, xatoms, event.time) return if "TARGETS" not in self.remote_requests: self.emit("send-clipboard-request", self._selection, "TARGETS") #when appending, the time may not be honoured #and we may reply with data from an older request self.remote_requests.setdefault("TARGETS", []).append( (requestor, target, prop, event.time)) return req_target = target if self.targets and target not in self.targets: if first_time("client-%s-invalidtarget-%s" % (wininfo, target)): l = log.info else: l = log.debug l("client %s is requesting an unknown target: '%s'", wininfo, target) translated_targets = TRANSLATED_TARGETS.get(target, ()) can_translate = tuple(x for x in translated_targets if x in self.targets) if can_translate: req_target = can_translate[0] l(" using '%s' instead", req_target) else: l(" valid targets: %s", csv(self.targets)) if must_discard_extra(target): l(" dropping the request") nodata() return target_data = self.target_data.get(req_target) if target_data and self._have_token: #we have it already dtype, dformat, data = target_data dtype = bytestostr(dtype) log("setting target data for '%s': %s, %s, %s (%s)", target, dtype, dformat, ellipsizer(data), type(data)) self.set_selection_response(requestor, target, prop, dtype, dformat, data, event.time) return waiting = self.remote_requests.setdefault(req_target, []) if waiting: log("already waiting for '%s' remote request: %s", req_target, waiting) else: self.emit("send-clipboard-request", self._selection, req_target) waiting.append((requestor, target, prop, event.time))
def process_draw(self, packet): wid, x, y, width, height, encoding, pixels, _, rowstride, client_options = packet[ 1:11] #never modify mmap packets if encoding in (b"mmap", b"scroll"): return True client_options = typedict(client_options) #we have a proxy video packet: rgb_format = client_options.strget("rgb_format", "") enclog("proxy draw: encoding=%s, client_options=%s", encoding, client_options) def send_updated(encoding, compressed_data, updated_client_options): #update the packet with actual encoding data used: packet[6] = encoding packet[7] = compressed_data packet[10] = updated_client_options enclog("returning %s bytes from %s, options=%s", len(compressed_data), len(pixels), updated_client_options) return wid not in self.lost_windows def passthrough(strip_alpha=True): enclog( "proxy draw: %s passthrough (rowstride: %s vs %s, strip alpha=%s)", rgb_format, rowstride, client_options.intget("rowstride", 0), strip_alpha) if strip_alpha: #passthrough as plain RGB: Xindex = rgb_format.upper().find("X") if Xindex >= 0 and len(rgb_format) == 4: #force clear alpha (which may be garbage): newdata = bytearray(pixels) for i in range(len(pixels) / 4): newdata[i * 4 + Xindex] = chr(255) packet[9] = client_options.intget("rowstride", 0) cdata = bytes(newdata) else: cdata = pixels new_client_options = {"rgb_format": rgb_format} else: #preserve cdata = pixels new_client_options = client_options wrapped = Compressed("%s pixels" % encoding, cdata) #rgb32 is always supported by all clients: return send_updated("rgb32", wrapped, new_client_options) proxy_video = client_options.boolget("proxy", False) if PASSTHROUGH_RGB and (encoding in ("rgb32", "rgb24") or proxy_video): #we are dealing with rgb data, so we can pass it through: return passthrough(proxy_video) if not self.video_encoder_types or not client_options or not proxy_video: #ensure we don't try to re-compress the pixel data in the network layer: #(re-add the "compressed" marker that gets lost when we re-assemble packets) packet[7] = Compressed("%s pixels" % encoding, packet[7]) return True #video encoding: find existing encoder ve = self.video_encoders.get(wid) if ve: if ve in self.lost_windows: #we cannot clean the video encoder here, there may be more frames queue up #"lost-window" in encode_loop will take care of it safely return False #we must verify that the encoder is still valid #and scrap it if not (ie: when window is resized) if ve.get_width() != width or ve.get_height() != height: enclog( "closing existing video encoder %s because dimensions have changed from %sx%s to %sx%s", ve, ve.get_width(), ve.get_height(), width, height) ve.clean() ve = None elif ve.get_encoding() != encoding: enclog( "closing existing video encoder %s because encoding has changed from %s to %s", ve.get_encoding(), encoding) ve.clean() ve = None #scaling and depth are proxy-encoder attributes: scaling = client_options.inttupleget("scaling", (1, 1)) depth = client_options.intget("depth", 24) rowstride = client_options.intget("rowstride", rowstride) quality = client_options.intget("quality", -1) speed = client_options.intget("speed", -1) timestamp = client_options.intget("timestamp") image = ImageWrapper(x, y, width, height, pixels, rgb_format, depth, rowstride, planes=ImageWrapper.PACKED) if timestamp is not None: image.set_timestamp(timestamp) #the encoder options are passed through: encoder_options = client_options.dictget("options", {}) if not ve: #make a new video encoder: spec = self._find_video_encoder(encoding, rgb_format) if spec is None: #no video encoder! enc_pillow = get_codec("enc_pillow") if not enc_pillow: if first_time("no-video-no-PIL-%s" % rgb_format): enclog.warn( "Warning: no video encoder found for rgb format %s", rgb_format) enclog.warn(" sending as plain RGB") return passthrough(True) enclog("no video encoder available: sending as jpeg") coding, compressed_data, client_options = enc_pillow.encode( "jpeg", image, quality, speed, False)[:3] return send_updated(coding, compressed_data, client_options) enclog("creating new video encoder %s for window %s", spec, wid) ve = spec.make_instance() #dst_formats is specified with first frame only: dst_formats = client_options.strtupleget("dst_formats") if dst_formats is not None: #save it in case we timeout the video encoder, #so we can instantiate it again, even from a frame no>1 self.video_encoders_dst_formats = dst_formats else: if not self.video_encoders_dst_formats: raise Exception( "BUG: dst_formats not specified for proxy and we don't have it either" ) dst_formats = self.video_encoders_dst_formats ve.init_context(width, height, rgb_format, dst_formats, encoding, quality, speed, scaling, {}) self.video_encoders[wid] = ve self.video_encoders_last_used_time[wid] = monotonic_time( ) #just to make sure this is always set #actual video compression: enclog("proxy compression using %s with quality=%s, speed=%s", ve, quality, speed) data, out_options = ve.compress_image(image, quality, speed, encoder_options) #pass through some options if we don't have them from the encoder #(maybe we should also use the "pts" from the real server?) for k in ("timestamp", "rgb_format", "depth", "csc"): if k not in out_options and k in client_options: out_options[k] = client_options[k] self.video_encoders_last_used_time[wid] = monotonic_time() return send_updated(ve.get_encoding(), Compressed(encoding, data), out_options)