def query_sound(): import subprocess command = get_sound_command()+["_sound_query"] _add_debug_args(command) kwargs = exec_kwargs() env = exec_env() env.update(get_sound_wrapper_env()) log("query_sound() command=%s, env=%s, kwargs=%s", command, env, kwargs) proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, env=env, **kwargs) out, err = proc.communicate(None) log("query_sound() process returned %s", proc.returncode) log("query_sound() out=%s, err=%s", out, err) if proc.returncode!=0: return typedict() d = typedict() for x in out.decode("utf8").splitlines(): kv = x.split("=", 1) if len(kv)==2: #ie: kv = ["decoders", "mp3,vorbis"] k,v = kv #fugly warning: all the other values are lists.. but this one is not: if k!=b"python.bits": v = [x.encode() for x in v.split(",")] #d["decoders"] = ["mp3", "vorbis"] d[k.encode()] = v log("query_sound()=%s", d) return d
def paint_and_vscroll(self, ydelta=10): print("paint_and_scroll(%i)" % (ydelta)) W, H = self.window.get_size() if ydelta>0: #scroll down, repaint the top: scrolls = (0, 0, W, H-ydelta, 0, ydelta), self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({"flush" : 1}), []) self.paint_crect(0, 0, W, ydelta, 0x80808080) else: #scroll up, repaint the bottom: scrolls = (0, -ydelta, W, H+ydelta, 0, ydelta), self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({"flush" : 1}), []) self.paint_crect(0, H-ydelta, W, -ydelta, 0xA0008020)
def __init__(self, client, group_leader, watcher_pid, wid, wx, wy, ww, wh, bw, bh, metadata, override_redirect, client_properties, border, max_window_size, default_cursor_data, pixel_depth): log("%s%s", type(self), (client, group_leader, watcher_pid, wid, wx, wy, ww, wh, bw, bh, metadata, override_redirect, client_properties, max_window_size, default_cursor_data, pixel_depth)) ClientWidgetBase.__init__(self, client, watcher_pid, wid, metadata.boolget("has-alpha")) self._override_redirect = override_redirect self.group_leader = group_leader self._pos = (wx, wy) self._size = (ww, wh) self._client_properties = client_properties self._set_initial_position = metadata.boolget("set-initial-position", False) self.size_constraints = typedict() self.geometry_hints = {} self._fullscreen = None self._maximized = False self._above = False self._below = False self._shaded = False self._sticky = False self._skip_pager = False self._skip_taskbar = False self._sticky = False self._iconified = False self._focused = False self.border = border self.cursor_data = None self.default_cursor_data = default_cursor_data self.max_window_size = max_window_size self.button_state = {} self.pixel_depth = pixel_depth #0 for default self.window_offset = None #actual vs reported coordinates self.init_window(metadata) self.setup_window(bw, bh) self.update_metadata(metadata)
def do_process_control_packet(self, proto, packet): log("process_control_packet(%s, %s)", proto, packet) packet_type = packet[0] if packet_type==Protocol.CONNECTION_LOST: log.info("Connection lost") if proto in self.potential_protocols: self.potential_protocols.remove(proto) return if packet_type=="hello": caps = typedict(packet[1]) if caps.boolget("challenge"): self.send_disconnect(proto, AUTHENTICATION_ERROR, "this socket does not use authentication") return generic_request = caps.strget("request") def is_req(mode): return generic_request==mode or caps.boolget("%s_request" % mode) if is_req("info"): proto.send_now(("hello", self.get_proxy_info(proto))) self.timeout_add(5*1000, self.send_disconnect, proto, CLIENT_EXIT_TIMEOUT, "info sent") return elif is_req("stop"): self.stop("socket request", None) return elif is_req("version"): version = XPRA_VERSION if caps.boolget("full-version-request"): version = full_version_str() proto.send_now(("hello", {"version" : version})) self.timeout_add(5*1000, self.send_disconnect, proto, CLIENT_EXIT_TIMEOUT, "version sent") return self.send_disconnect(proto, CONTROL_COMMAND_ERROR, "this socket only handles 'info', 'version' and 'stop' requests")
def _process_hello(self, proto, packet): capabilities = packet[1] c = typedict(capabilities) proto.set_compression_level(c.intget("compression_level", self.compression_level)) if use_rencode and c.boolget("rencode"): proto.enable_rencode() else: proto.enable_bencode() if c.boolget("lz4") and use_lz4 and self.compression_level==1: proto.enable_lz4() log("process_hello: capabilities=%s", capabilities) if c.boolget("version_request"): self.send_version_info(proto) return False auth_caps = self.verify_hello(proto, c) if auth_caps is not False: if c.boolget("info_request", False): self.send_hello_info(proto) return command_req = c.strlistget("command_request") if len(command_req)>0: #call from UI thread: self.idle_add(self.handle_command_request, proto, command_req) return #continue processing hello packet: try: self.hello_oked(proto, packet, c, auth_caps) except: log.error("server error processing new connection from %s", proto, exc_info=True) self.disconnect_client(proto, "server error accepting new connection")
def process_network_packet(self, proto, packet): log("process_network_packet: %s", packet) packet_type = bytestostr(packet[0]) def close(): t = self._close_timers.pop(proto, None) if t: proto.close() try: self._potential_protocols.remove(proto) except ValueError: pass def hello_reply(data): proto.send_now(["hello", data]) if packet_type=="hello": caps = typedict(packet[1]) proto.parse_remote_caps(caps) proto.enable_compressor_from_caps(caps) proto.enable_encoder_from_caps(caps) request = caps.strget("request") if request=="info": def send_info(): info = self.get_info() info["network"] = get_network_caps() hello_reply(info) #run in UI thread: self.idle_add(send_info) elif request=="id": hello_reply(self.get_id_info()) elif request=="detach": def protocol_closed(): self.disconnect_and_quit(EXIT_OK, "network request") proto.send_disconnect([DETACH_REQUEST], done_callback=protocol_closed) return elif request=="version": hello_reply({"version" : VERSION}) elif request=="command": command = caps.strtupleget("command_request") log("command request: %s", command) def process_control(): try: self._process_control(["control"]+list(command)) code = EXIT_OK response = "done" except Exception as e: code = EXIT_FAILURE response = str(e) hello_reply({"command_response" : (code, response)}) self.idle_add(process_control) else: log.info("request '%s' is not handled by this client", request) proto.send_disconnect([PROTOCOL_ERROR]) elif packet_type in (Protocol.CONNECTION_LOST, Protocol.GIBBERISH): close() return else: log.info("packet '%s' is not handled by this client", packet_type) proto.send_disconnect([PROTOCOL_ERROR]) #make sure the connection is closed: tid = self.timeout_add(REQUEST_TIMEOUT*1000, close) self._close_timers[proto] = tid
def do_process_control_packet(self, proto, packet): log("process_control_packet(%s, %s)", proto, packet) packet_type = packet[0] if packet_type==Protocol.CONNECTION_LOST: log.info("Connection lost") if proto in self.potential_protocols: self.potential_protocols.remove(proto) return if packet_type=="hello": caps = typedict(packet[1]) if caps.boolget("challenge"): self.send_disconnect(proto, AUTHENTICATION_ERROR, "this socket does not use authentication") return if caps.get("info_request", False): proto.send_now(("hello", self.get_proxy_info(proto))) self.timeout_add(5*1000, self.send_disconnect, proto, CLIENT_EXIT_TIMEOUT, "info sent") return elif caps.get("stop_request", False): self.stop("socket request", None) return elif caps.get("version_request", False): from xpra import __version__ proto.send_now(("hello", {"version" : __version__})) self.timeout_add(5*1000, self.send_disconnect, proto, CLIENT_EXIT_TIMEOUT, "version sent") return self.send_disconnect(proto, CONTROL_COMMAND_ERROR, "this socket only handles 'info', 'version' and 'stop' requests")
def init_window(self, metadata): self._backing = None self._metadata = typedict() # used for only sending focus events *after* the window is mapped: self._been_mapped = False self._override_redirect_windows = [] self.update_metadata(metadata)
def parse_client_caps(self, c: typedict): #general features: self.info_namespace = c.boolget("info-namespace") self.share = c.boolget("share") self.lock = c.boolget("lock") self.control_commands = c.strtupleget("control_commands") self.xdg_menu_update = c.boolget("xdg-menu-update") bandwidth_limit = c.intget("bandwidth-limit", 0) server_bandwidth_limit = self.server_bandwidth_limit if self.server_bandwidth_limit is None: server_bandwidth_limit = self.get_socket_bandwidth_limit( ) or bandwidth_limit self.bandwidth_limit = min(server_bandwidth_limit, bandwidth_limit) if self.bandwidth_detection: self.bandwidth_detection = c.boolget("bandwidth-detection", True) self.client_connection_data = c.dictget("connection-data", {}) ccd = typedict(self.client_connection_data) self.adapter_type = ccd.strget("adapter-type", "") self.jitter = ccd.intget("jitter", 0) bandwidthlog( "server bandwidth-limit=%s, client bandwidth-limit=%s, value=%s, detection=%s", server_bandwidth_limit, bandwidth_limit, self.bandwidth_limit, self.bandwidth_detection) if getattr(self, "mmap_size", 0) > 0: log("mmap enabled, ignoring bandwidth-limit") self.bandwidth_limit = 0
def set_icc_profile(self): if not SYNC_ICC: return ui_clients = [s for s in self._server_sources.values() if s.ui_client] if len(ui_clients) != 1: screenlog("%i UI clients, resetting ICC profile to default", len(ui_clients)) self.reset_icc_profile() return icc = typedict(ui_clients[0].icc) data = None for x in ("data", "icc-data", "icc-profile"): data = icc.strget(x) if data: break if not data: screenlog("no icc data found in %s", icc) self.reset_icc_profile() return screenlog("set_icc_profile() icc data for %s: %s (%i bytes)", ui_clients[0], hexstr(data or ""), len(data or "")) from xpra.x11.gtk_x11.prop import prop_set #each CARD32 contains just one 8-bit value - don't ask me why def o(x): try: return ord(x) except: return x prop_set(self.root_window, "_ICC_PROFILE", ["u32"], [o(x) for x in data]) prop_set(self.root_window, "_ICC_PROFILE_IN_X_VERSION", "u32", 0 * 100 + 4) #0.4 -> 0*100+4*1
def set_strut(self, strut): if not HAS_X11_BINDINGS: return log("strut=%s", strut) d = typedict(strut) values = [] for x in ("left", "right", "top", "bottom"): v = d.intget(x, 0) #handle scaling: if x in ("left", "right"): v = self._client.sx(v) else: v = self._client.sy(v) values.append(v) has_partial = False for x in ("left_start_y", "left_end_y", "right_start_y", "right_end_y", "top_start_x", "top_end_x", "bottom_start_x", "bottom_end_x"): if x in d: has_partial = True v = d.intget(x, 0) if x.find("_x"): v = self._client.sx(v) elif x.find("_y"): v = self._client.sy(v) values.append(v) log("setting strut=%s, has partial=%s", values, has_partial) def do_set_strut(): if has_partial: prop_set(self.get_window(), "_NET_WM_STRUT_PARTIAL", ["u32"], values) prop_set(self.get_window(), "_NET_WM_STRUT", ["u32"], values[:4]) self.when_realized("strut", do_set_strut)
def parse_client_caps(self, c): #self.ui_client = c.boolget("ui_client", True) self.send_windows = self.ui_client and c.boolget("windows", True) self.pointer_grabs = c.boolget("pointer.grabs") self.send_cursors = self.send_windows and c.boolget("cursors") self.cursor_encodings = c.strlistget("encodings.cursor") self.send_bell = c.boolget("bell") self.window_initiate_moveresize = c.boolget( "window.initiate-moveresize") self.system_tray = c.boolget("system_tray") self.metadata_supported = c.strlistget("metadata.supported", DEFAULT_METADATA_SUPPORTED) self.window_frame_sizes = typedict( c.dictget("window.frame_sizes") or {}) log("cursors=%s (encodings=%s), bell=%s, notifications=%s", self.send_cursors, self.cursor_encodings, self.send_bell, self.send_notifications) log("client uuid %s", self.uuid) #window filters: try: for object_name, property_name, operator, value in c.listget( "window-filters"): self.add_window_filter(object_name, property_name, operator, value) except Exception as e: filterslog.error("Error parsing window-filters: %s", e)
def capsauth(self, a, challenge_response=None, client_salt=None): caps = typedict() if challenge_response is not None: caps["challenge_response"] = challenge_response if client_salt is not None: caps["challenge_client_salt"] = client_salt return a.authenticate(caps)
def parse_client_caps(self, c): self.send_windows = c.boolget("ui_client", True) and c.boolget( "windows", True) self.pointer_grabs = c.boolget("pointer.grabs") self.send_cursors = self.send_windows and c.boolget("cursors") self.cursor_encodings = c.strtupleget("encodings.cursor") self.send_bell = c.boolget("bell") self.system_tray = c.boolget("system_tray") self.metadata_supported = c.strtupleget("metadata.supported", DEFAULT_METADATA_SUPPORTED) log("metadata supported=%s", self.metadata_supported) self.window_frame_sizes = typedict(c.dictget("window.frame_sizes", {})) self.window_min_size = c.inttupleget("window.min-size", (0, 0)) self.window_max_size = c.inttupleget("window.max-size", (0, 0)) self.window_restack = c.boolget("window.restack", False) log("cursors=%s (encodings=%s), bell=%s", self.send_cursors, self.cursor_encodings, self.send_bell) #window filters: try: for object_name, property_name, operator, value in c.tupleget( "window-filters"): self.add_window_filter(object_name, property_name, operator, value) except Exception as e: filterslog.error("Error parsing window-filters: %s", e)
def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, options, callbacks): target_rgb_formats = self.RGB_MODES #as some video formats like vpx can forward transparency #also we could skip the csc step in some cases: pixel_format = img.get_pixel_format() cd = self._csc_decoder if cd is not None: if cd.get_src_format() != pixel_format: log("do_video_paint csc: switching src format from %s to %s", cd.get_src_format(), pixel_format) self.do_clean_csc_decoder() elif cd.get_dst_format() not in target_rgb_formats: log("do_video_paint csc: switching dst format from %s to %s", cd.get_dst_format(), target_rgb_formats) self.do_clean_csc_decoder() elif cd.get_src_width() != enc_width or cd.get_src_height( ) != enc_height: log( "do_video_paint csc: switching src size from %sx%s to %sx%s", enc_width, enc_height, cd.get_src_width(), cd.get_src_height()) self.do_clean_csc_decoder() elif cd.get_dst_width() != width or cd.get_dst_height() != height: log( "do_video_paint csc: switching src size from %sx%s to %sx%s", width, height, cd.get_dst_width(), cd.get_dst_height()) self.do_clean_csc_decoder() if self._csc_decoder is None: #use higher quality csc to compensate for lower quality source #(which generally means that we downscaled via YUV422P or lower) #or when upscaling the video: q = options.intget("quality", 50) csc_speed = int( min(100, 100 - q, 100.0 * (enc_width * enc_height) / (width * height))) cd = self.make_csc(enc_width, enc_height, pixel_format, width, height, target_rgb_formats, csc_speed) log("do_video_paint new csc decoder: %s", cd) self._csc_decoder = cd rgb_format = cd.get_dst_format() rgb = cd.convert_image(img) log("do_video_paint rgb using %s.convert_image(%s)=%s", cd, img, rgb) img.free() assert rgb.get_planes() == 0, "invalid number of planes for %s: %s" % ( rgb_format, rgb.get_planes()) #make a new options dict and set the rgb format: paint_options = typedict(options) #this will also take care of firing callbacks (from the UI thread): def paint(): data = rgb.get_pixels() rowstride = rgb.get_rowstride() try: self.do_paint_rgb(rgb_format, data, x, y, width, height, rowstride, paint_options, callbacks) finally: rgb.free() self.idle_add(paint)
def _set_client_properties(self, proto, wid, window, new_client_properties): """ Allows us to keep window properties for a client after disconnection. (we keep it in a map with the client's uuid as key) """ ss = self._server_sources.get(proto) if ss: ss.set_client_properties(wid, window, typedict(new_client_properties)) #filter out encoding properties, which are expected to be set everytime: ncp = {} for k, v in new_client_properties.items(): if v is None: log.warn("removing invalid None property for %s", k) continue if not k.startswith(b"encoding"): ncp[k] = v if ncp: log( "set_client_properties updating window %s of source %s with %s", wid, ss.uuid, ncp) client_properties = self.client_properties.setdefault( wid, {}).setdefault(ss.uuid, {}) client_properties.update(ncp)
def _process_hello(self, proto, packet): capabilities = packet[1] c = typedict(capabilities) proto.set_compression_level(c.intget("compression_level", self.compression_level)) proto.enable_compressor_from_caps(c) if not proto.enable_encoder_from_caps(c): #this should never happen: #if we got here, we parsed a packet from the client! #(maybe the client used an encoding it claims not to support?) self.disconnect_client(proto, PROTOCOL_ERROR, "failed to negotiate a packet encoder") return log("process_hello: capabilities=%s", capabilities) if c.boolget("version_request"): self.send_version_info(proto) return auth_caps = self.verify_hello(proto, c) if auth_caps is not False: if c.boolget("info_request", False): self.send_hello_info(proto) return command_req = c.strlistget("command_request") if len(command_req)>0: #call from UI thread: self.idle_add(self.handle_command_request, proto, command_req) return #continue processing hello packet in UI thread: self.idle_add(self.call_hello_oked, proto, packet, c, auth_caps)
def split_vscroll(self, i=1): W, H = self.window.get_size() scrolls = [ (0, H//2, W, H//2-i, 0, i), (0, i, W, H//2-i, 0, -i), ] self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({}), [])
def __init__(self, client, group_leader, wid, x, y, ww, wh, bw, bh, metadata, override_redirect, client_properties, border, max_window_size): log("%s%s", type(self), (client, group_leader, wid, x, y, ww, wh, bw, bh, metadata, override_redirect, client_properties, max_window_size)) ClientWidgetBase.__init__(self, client, wid, metadata.boolget("has-alpha")) self._override_redirect = override_redirect self.group_leader = group_leader self._pos = (x, y) self._size = (ww, wh) self._client_properties = client_properties self._set_initial_position = False self.size_constraints = typedict() self.geometry_hints = {} self._fullscreen = None self._maximized = False self._above = False self._below = False self._shaded = False self._sticky = False self._skip_pager = False self._skip_taskbar = False self._sticky = False self._iconified = False self._focused = False self.border = border self.max_window_size = max_window_size self.button_state = {} self.init_window(metadata) self.setup_window(bw, bh) self.update_metadata(metadata)
def process_server_packet(self, proto, packet): packet_type = packet[0] log("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="disconnect": log("got disconnect from server: %s", packet[1]) if self.exit: self.server_protocol.close() else: self.stop("disconnect from server: %s" % packet[1]) elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: from xpra.net.crypto import crypto_backend_init, new_cipher_caps, DEFAULT_PADDING crypto_backend_init() padding_options = self.caps.strlistget("cipher.padding.options", [DEFAULT_PADDING]) auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key, padding_options) caps.update(auth_caps) #may need to bump packet size: proto.max_packet_size = maxw*maxh*4*4 file_transfer = self.caps.boolget("file-transfer") and c.boolget("file-transfer") file_size_limit = max(self.caps.intget("file-size-limit"), c.intget("file-size-limit")) file_max_packet_size = int(file_transfer) * (1024 + file_size_limit*1024*1024) self.client_protocol.max_packet_size = max(self.client_protocol.max_packet_size, file_max_packet_size) self.server_protocol.max_packet_size = max(self.server_protocol.max_packet_size, file_max_packet_size) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(self.get_proxy_info(proto)) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return #we do want to reformat cursor packets... #as they will have been uncompressed by the network layer already: elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] self._packet_recompress(packet, 8, "cursor") elif packet_type=="window-icon": self._packet_recompress(packet, 5, "icon") self.queue_client_packet(packet)
def do_video_paint(self, img, x, y, enc_width, enc_height, width, height, options, callbacks): target_rgb_formats = self.RGB_MODES # as some video formats like vpx can forward transparency # also we could skip the csc step in some cases: pixel_format = img.get_pixel_format() cd = self._csc_decoder if cd is not None: if cd.get_src_format() != pixel_format: log("do_video_paint csc: switching src format from %s to %s", cd.get_src_format(), pixel_format) self.do_clean_csc_decoder() elif cd.get_dst_format() not in target_rgb_formats: log("do_video_paint csc: switching dst format from %s to %s", cd.get_dst_format(), target_rgb_formats) self.do_clean_csc_decoder() elif cd.get_src_width() != enc_width or cd.get_src_height() != enc_height: log( "do_video_paint csc: switching src size from %sx%s to %sx%s", enc_width, enc_height, cd.get_src_width(), cd.get_src_height(), ) self.do_clean_csc_decoder() elif cd.get_dst_width() != width or cd.get_dst_height() != height: log( "do_video_paint csc: switching src size from %sx%s to %sx%s", width, height, cd.get_dst_width(), cd.get_dst_height(), ) self.do_clean_csc_decoder() if self._csc_decoder is None: # use higher quality csc to compensate for lower quality source # (which generally means that we downscaled via YUV422P or lower) # or when upscaling the video: q = options.intget("quality", 50) csc_speed = int(min(100, 100 - q, 100.0 * (enc_width * enc_height) / (width * height))) cd = self.make_csc(enc_width, enc_height, pixel_format, width, height, target_rgb_formats, csc_speed) log("do_video_paint new csc decoder: %s", cd) self._csc_decoder = cd rgb_format = cd.get_dst_format() rgb = cd.convert_image(img) log("do_video_paint rgb using %s.convert_image(%s)=%s", cd, img, rgb) img.free() assert rgb.get_planes() == 0, "invalid number of planes for %s: %s" % (rgb_format, rgb.get_planes()) # make a new options dict and set the rgb format: paint_options = typedict(options) paint_options["rgb_format"] = rgb_format # this will also take care of firing callbacks (from the UI thread): def paint(): data = rgb.get_pixels() rowstride = rgb.get_rowstride() try: self.do_paint_rgb(rgb_format, data, x, y, width, height, rowstride, paint_options, callbacks) finally: rgb.free() self.idle_add(paint)
def __init__(self, window_class, client, wid, W=630, H=480, animate=True): self.wid = wid self.window = window_class(client, None, wid, 10, 10, W, H, W, H, typedict({}), False, typedict({}), 0, None) self.window.show() self.paint_rect(0, 0, W, H, chr(255)*4*W*H) self.paint_rect(W//2-16, H//2-16, 32, 32, chr(0)*4*32*32) self.animate = animate self.damage = False client.handle_key_action = self.handle_key_action
def __init__(self, window_class, client, wid=1, W=630, H=480): self.wid = wid self.window = window_class(client, None, wid, 10, 10, W, H, W, H, typedict({}), False, typedict({}), 0, None) self.window.show() self.paint_rect(0, 0, W, H, chr(255)*4*W*H) self.paint_rect(W//2-16, H//2-16, 64, 64, chr(0)*4*64*64) self.counter = 0 self.delta_x = 0 self.delta_y = 0
def _process_challenge(self, packet): authlog("processing challenge: %s", packet[1:]) def warn_server_and_exit(code, message, server_message="authentication failed"): authlog.error("Error: authentication failed:") authlog.error(" %s", message) self.disconnect_and_quit(code, server_message) if not self.password_file and not os.environ.get('XPRA_PASSWORD'): warn_server_and_exit(EXIT_PASSWORD_REQUIRED, "this server requires authentication, please provide a password", "no password available") return password = self.load_password() if not password: warn_server_and_exit(EXIT_PASSWORD_FILE_ERROR, "failed to load password from file %s" % self.password_file, "no password available") return salt = packet[1] if self.encryption: assert len(packet)>=3, "challenge does not contain encryption details to use for the response" server_cipher = typedict(packet[2]) key = self.get_encryption_key() if key is None: warn_server_and_exit(EXIT_ENCRYPTION, "the server does not use any encryption", "client requires encryption") return if not self.set_server_encryption(server_cipher, key): return #all server versions support a client salt, #they also tell us which digest to use: digest = packet[3] client_salt = get_hex_uuid()+get_hex_uuid() #TODO: use some key stretching algorigthm? (meh) try: from xpra.codecs.xor.cyxor import xor_str #@UnresolvedImport salt = xor_str(salt, client_salt) except: salt = xor(salt, client_salt) if digest==b"hmac": import hmac, hashlib def s(v): try: return v.encode() except: return str(v) password = s(password) salt = s(salt) challenge_response = hmac.HMAC(password, salt, digestmod=hashlib.md5).hexdigest() elif digest==b"xor": #don't send XORed password unencrypted: if not self._protocol.cipher_out and not ALLOW_UNENCRYPTED_PASSWORDS: warn_server_and_exit(EXIT_ENCRYPTION, "server requested digest %s, cowardly refusing to use it without encryption" % digest, "invalid digest") return challenge_response = xor(password, salt) else: warn_server_and_exit(EXIT_PASSWORD_REQUIRED, "server requested an unsupported digest: %s" % digest, "invalid digest") return if digest: authlog("%s(%s, %s)=%s", digest, binascii.hexlify(password), binascii.hexlify(salt), challenge_response) self.password_sent = True self.remove_packet_handlers("challenge") self.send_hello(challenge_response, client_salt)
def paint_image(self, coding, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ # log("paint_image(%s, %s bytes, %s, %s, %s, %s, %s, %s)", coding, len(img_data), x, y, width, height, options, callbacks) PIL = get_codec("PIL") assert PIL.Image, "PIL.Image not found" buf = BytesIOClass(img_data) img = PIL.Image.open(buf) assert img.mode in ("L", "P", "RGB", "RGBA"), "invalid image mode: %s" % img.mode transparency = options.get("transparency", -1) if img.mode == "P": if transparency >= 0: # this deals with alpha without any extra work img = img.convert("RGBA") else: img = img.convert("RGB") elif img.mode == "L": if transparency >= 0: # why do we have to deal with alpha ourselves?? def mask_value(a): if a != transparency: return 255 return 0 mask = PIL.Image.eval(img, mask_value) mask = mask.convert("L") def nomask_value(a): if a != transparency: return a return 0 img = PIL.Image.eval(img, nomask_value) img = img.convert("RGBA") img.putalpha(mask) else: img = img.convert("RGB") # use tobytes() if present, fallback to tostring(): data_fn = getattr(img, "tobytes", getattr(img, "tostring", None)) raw_data = data_fn("raw", img.mode) paint_options = typedict(options) rgb_format = img.mode if rgb_format == "RGB": # PIL flattens the data to a continuous straightforward RGB format: rowstride = width * 3 img_data = self.process_delta(raw_data, width, height, rowstride, options) elif rgb_format == "RGBA": rowstride = width * 4 img_data = self.process_delta(raw_data, width, height, rowstride, options) else: raise Exception("invalid image mode: %s" % img.mode) paint_options["rgb_format"] = rgb_format self.idle_add(self.do_paint_rgb, rgb_format, img_data, x, y, width, height, rowstride, paint_options, callbacks) return False
def _process_hello(self, packet): if not self.password_sent and self.password_file: self.warn_and_quit(EXIT_NO_AUTHENTICATION, "the server did not request our password") return try: self.server_capabilities = packet[1] log("processing hello from server: %s", self.server_capabilities) c = typedict(self.server_capabilities) self.parse_server_capabilities(c) except Exception, e: self.warn_and_quit(EXIT_FAILURE, "error processing hello packet from server: %s" % e)
def _process_hello(self, packet): if not self.password_sent and (self.password_file or os.environ.get('XPRA_PASSWORD')): self.warn_and_quit(EXIT_NO_AUTHENTICATION, "the server did not request our password") return try: self.server_capabilities = typedict(packet[1]) log("processing hello from server: %s", self.server_capabilities) self.server_connection_established() except Exception as e: log.info("error in hello packet", exc_info=True) self.warn_and_quit(EXIT_FAILURE, "error processing hello packet from server: %s" % e)
def _process_send_file(self, packet): #the remote end is sending us a file basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:8] options = typedict(options) if printit: l = printlog assert self.printing else: l = filelog assert self.file_transfer l("receiving file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) assert filesize>0, "invalid file size: %s" % filesize if filesize>self.file_size_limit*1024*1024: l.error("Error: file '%s' is too large:", basefilename) l.error(" %iMB, the file size limit is %iMB", filesize//1024//1024, self.file_size_limit) return filename, fd = safe_open_download_file(basefilename, mimetype) self.file_descriptors.add(fd) chunk_id = options.get("file-chunk-id") if chunk_id: if len(self.receive_chunks_in_progress)>=MAX_CONCURRENT_FILES: self.send("ack-file-chunk", chunk_id, False, "too many file transfers in progress", 0) os.close(fd) return digest = hashlib.sha1() chunk = 0 timer = self.timeout_add(CHUNK_TIMEOUT, self._check_chunk_receiving, chunk_id, chunk) chunk_state = [time.time(), fd, filename, mimetype, printit, openit, filesize, options, digest, 0, timer, chunk] self.receive_chunks_in_progress[chunk_id] = chunk_state self.send("ack-file-chunk", chunk_id, True, "", chunk) return #not chunked, full file: assert file_data, "no data!" if len(file_data)!=filesize: l.error("Error: invalid data size for file '%s'", basefilename) l.error(" received %i bytes, expected %i bytes", len(file_data), filesize) return #check digest if present: def check_digest(algo="sha1", libfn=hashlib.sha1): digest = options.get(algo) if digest: u = libfn() u.update(file_data) l("%s digest: %s - expected: %s", algo, u.hexdigest(), digest) self.check_digest(basefilename, u.hexdigest(), digest, algo) check_digest("sha1", hashlib.sha1) check_digest("md5", hashlib.md5) try: os.write(fd, file_data) finally: os.close(fd) self.do_process_downloaded_file(filename, mimetype, printit, openit, filesize, options)
def query_sound(): import subprocess command = get_sound_command()+["_sound_query"] _add_debug_args(command) kwargs = exec_kwargs() env = exec_env() env.update(get_sound_wrapper_env()) log("query_sound() command=%s, env=%s, kwargs=%s", command, env, kwargs) proc = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=None, env=env, **kwargs) out, err = proc.communicate(None) log("query_sound() process returned %s", proc.returncode) log("query_sound() out=%s, err=%s", out, err) if proc.returncode!=0: return typedict() d = typedict() for x in out.decode("utf8").splitlines(): kv = x.split("=", 1) if len(kv)==2: #ie: kv = ["decoders", "mp3,vorbis"] d[kv[0].encode()] = [x.encode() for x in kv[1].split(",")] #d["decoders"] = ["mp3", "vorbis"] log("query_sound()=%s", d) return d
def movearound(self, ydelta=1): self.counter += 1 RADIUS = 128 target_x = int(math.sin(self.counter/10.0) * RADIUS) target_y = int(math.cos(self.counter/10.0) * RADIUS) dx = target_x - self.delta_x dy = target_y - self.delta_y W, H = self.window.get_size() scrolls = (RADIUS, RADIUS, W-RADIUS*2, H-RADIUS*2, dx, dy), self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({"flush" : 0}), []) self.delta_x = target_x self.delta_y = target_y return True
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 process_server_packet(self, proto, packet): packet_type = packet[0] debug("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) proto.max_packet_size = maxw*maxh*4 caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key) caps.update(auth_caps) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(get_server_info("proxy.")) info.update(get_thread_info("proxy.", proto)) info.update(self.get_encoder_info()) elif packet_type=="lost-window": wid = packet[1] #mark it as lost so we can drop any current/pending frames self.lost_windows.add(wid) #queue it so it gets cleaned safely (for video encoders mostly): self.encode_queue.put(packet) #and fall through so tell the client immediately elif packet_type=="draw": #use encoder thread: self.encode_queue.put(packet) #which will queue the packet itself when done: return elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=9: pixels = packet[8] if len(pixels)<64: packet[8] = str(pixels) else: packet[8] = compressed_wrapper("cursor", pixels) self.queue_client_packet(packet)
def process_server_packet(self, proto, packet): packet_type = packet[0] debug("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) proto.max_packet_size = maxw*maxh*4 caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key) caps.update(auth_caps) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: #note: this is only seen by the client application #"xpra info" is a new connection, which talks to the proxy server... info = packet[1] info.update(get_server_info("proxy.")) info.update(get_thread_info("proxy.", proto)) info.update(self.get_encoder_info()) elif packet_type=="lost-window": wid = packet[1] ve = self.video_encoders.get(wid) if ve: ve.clean() del self.video_encoders[wid] elif packet_type=="draw": self.process_draw(packet) elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=9: pixels = packet[8] if len(pixels)<64: packet[8] = str(pixels) else: packet[8] = compressed_wrapper("cursor", pixels) self.queue_client_packet(packet)
def init_window(self, metadata): self._backing = None self._metadata = typedict() # used for only sending focus events *after* the window is mapped: self._been_mapped = False self._override_redirect_windows = [] if "workspace" in self._client_properties: workspace = self._client_properties.get("workspace") if workspace is not None: workspacelog("workspace from client properties: %s", workspace) #client properties override application specified workspace value on init only: metadata["workspace"] = int(workspace) self._window_workspace = WORKSPACE_UNSET #will get set in set_metadata if present self._desktop_workspace = self.get_desktop_workspace() workspacelog("init_window(..) workspace=%s, current workspace=%s", self._window_workspace, self._desktop_workspace) if self.max_window_size and b"size-constraints" not in metadata: #this ensures that we will set size-constraints and honour max_window_size: metadata[b"size-constraints"] = {} self.update_metadata(metadata)
def _process_hello(self, proto, packet): capabilities = packet[1] c = typedict(capabilities) proto.set_compression_level(c.intget("compression_level", self.compression_level)) proto.enable_compressor_from_caps(c) if not proto.enable_encoder_from_caps(c): #this should never happen: #if we got here, we parsed a packet from the client! #(maybe the client used an encoding it claims not to support?) self.disconnect_client(proto, PROTOCOL_ERROR, "failed to negotiate a packet encoder") return log("process_hello: capabilities=%s", capabilities) if c.boolget("version_request"): self.send_version_info(proto) return auth_caps = self.verify_hello(proto, c) if auth_caps is not False: if c.boolget("info_request", False): flatten = not c.boolget("info-namespace", False) self.send_hello_info(proto, flatten) return command_req = c.strlistget("command_request") if len(command_req)>0: #call from UI thread: self.idle_add(self.handle_command_request, proto, *command_req) return #continue processing hello packet: try: if SIMULATE_SERVER_HELLO_ERROR: raise Exception("Simulating a server error") self.hello_oked(proto, packet, c, auth_caps) except ClientException as e: log.error("Error setting up new connection for") log.error(" %s:", proto) log.error(" %s", e) self.disconnect_client(proto, SERVER_ERROR, str(e)) except Exception as e: #log exception but don't disclose internal details to the client log.error("server error processing new connection from %s: %s", proto, e, exc_info=True) self.disconnect_client(proto, SERVER_ERROR, "error accepting new connection")
def process_server_packet(self, proto, packet): packet_type = packet[0] debug("process_server_packet: %s", packet_type) if packet_type==Protocol.CONNECTION_LOST: self.stop("server connection lost", proto) return elif packet_type=="hello": c = typedict(packet[1]) maxw, maxh = c.intpair("max_desktop_size", (4096, 4096)) proto.max_packet_size = maxw*maxh*4 caps = self.filter_server_caps(c) #add new encryption caps: if self.cipher: auth_caps = new_cipher_caps(self.client_protocol, self.cipher, self.encryption_key) caps.update(auth_caps) packet = ("hello", caps) elif packet_type=="info-response": #adds proxy info: info = packet[1] info.update(get_server_info("proxy.")) info.update(get_thread_info("proxy.", proto)) elif packet_type=="draw": #packet = ["draw", wid, x, y, outw, outh, coding, data, self._damage_packet_sequence, outstride, client_options] #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) coding = packet[6] if coding!="mmap": data = packet[7] packet[7] = Compressed("%s pixels" % coding, data) elif packet_type=="cursor": #packet = ["cursor", x, y, width, height, xhot, yhot, serial, pixels, name] #or: #packet = ["cursor", ""] if len(packet)>=9: pixels = packet[8] if len(pixels)<64: packet[8] = str(pixels) else: packet[8] = compressed_wrapper("cursor", pixels) self.queue_client_packet(packet)
def paint_webp_using_webm(self, img_data, x, y, width, height, options, callbacks): """ can be called from any thread """ dec_webm = get_codec("dec_webm") assert dec_webm is not None, "webp decoder not found" paint_options = typedict(options) if options.get("has_alpha", False): decode = dec_webm.DecodeRGBA rowstride = width*4 paint_rgb = self.do_paint_rgb32 paint_options["rgb_format"] = "RGBA" else: decode = dec_webm.DecodeRGB rowstride = width*3 paint_rgb = self.do_paint_rgb24 paint_options["rgb_format"] = "RGB" log("paint_webp(%s) using decode=%s, paint=%s, paint_options=%s", ("%s bytes" % len(img_data), x, y, width, height, options, callbacks), decode, paint_rgb, paint_options) rgb_data = decode(img_data) pixels = str(rgb_data.bitmap) self.idle_add(paint_rgb, pixels, x, y, width, height, rowstride, paint_options, callbacks) return False
def set_strut(self, strut): if not HAS_X11_BINDINGS: return log("strut=%s", strut) d = typedict(strut) values = [] for x in ("left", "right", "top", "bottom"): values.append(d.intget(x, 0)) has_partial = False for x in ("left_start_y", "left_end_y", "right_start_y", "right_end_y", "top_start_x", "top_end_x", "bottom_start_x", "bottom_end_x"): if x in d: has_partial = True values.append(d.intget(x, 0)) log("setting strut=%s, has partial=%s", values, has_partial) if not self.is_realized(): self.realize() if has_partial: prop_set(self.get_window(), "_NET_WM_STRUT_PARTIAL", ["u32"], values) prop_set(self.get_window(), "_NET_WM_STRUT", ["u32"], values[:4])
def scrollup(self, ydelta=1): if not self.animate: return True print("scrollup(%s) damage=%s" % (ydelta, self.damage)) W, H = self.window.get_size() scrolls = (0, ydelta, W, H-ydelta, 0, -ydelta), self.window.draw_region(0, 0, W, H, "scroll", scrolls, W*4, 0, typedict({"flush" : 0}), []) dots = [] v = int(time.time()*10000) for _ in range(W*ydelta): CB = 0xFF << ((self.wid % 4) * 8) c = struct.pack("@I", v & 0xFFFFFFFF & ~CB) dots.append(c) img_data = b"".join(dots) self.paint_rect(0, H-ydelta, W, ydelta, img_data) if self.damage: c = struct.pack("@I", 0xFFFFFFFF) img_data = c*10*16 self.paint_rect(W-10, H//2-8-16, 10, 16, img_data) c = struct.pack("@I", 0x000000FF) img_data = c*10*16 self.paint_rect(W-10, H//2-8, 10, 16, img_data) return True
def _process_send_file(self, packet): #send-file basefilename, printit, openit, filesize, 0, data) from xpra.platform.features import DOWNLOAD_PATH basefilename, mimetype, printit, openit, filesize, file_data, options = packet[1:11] filelog("received file: %s", [basefilename, mimetype, printit, openit, filesize, "%s bytes" % len(file_data), options]) options = typedict(options) if printit: assert self.printing else: assert self.file_transfer assert filesize>0 and file_data if len(file_data)!=filesize: log.error("Error: invalid data size for file '%s'", basefilename) log.error(" received %i bytes, expected %i bytes", len(file_data), filesize) return if filesize>self.file_size_limit*1024*1024: log.error("Error: file '%s' is too large:", basefilename) log.error(" %iMB, the file size limit is %iMB", filesize//1024//1024, self.file_size_limit) return #check digest if present: digest = options.get("sha1") if digest: import hashlib u = hashlib.sha1() u.update(file_data) filelog("sha1 digest: %s - expected: %s", u.hexdigest(), digest) assert digest==u.hexdigest(), "invalid file digest %s (expected %s)" % (u.hexdigest(), digest) #make sure we use a filename that does not exist already: wanted_filename = os.path.abspath(os.path.join(os.path.expanduser(DOWNLOAD_PATH), os.path.basename(basefilename))) EXTS = {"application/postscript" : "ps", "application/pdf" : "pdf", } ext = EXTS.get(mimetype) if ext: #on some platforms (win32), #we want to force an extension #so that the file manager can display them properly when you double-click on them if not wanted_filename.endswith("."+ext): wanted_filename += "."+ext filename = wanted_filename base = 0 while os.path.exists(filename): filelog("cannot save file as %s: file already exists", filename) root, ext = os.path.splitext(wanted_filename) base += 1 filename = root+("-%s" % base)+ext flags = os.O_CREAT | os.O_RDWR | os.O_EXCL try: flags |= os.O_BINARY #@UndefinedVariable (win32 only) except: pass fd = os.open(filename, flags) try: os.write(fd, file_data) finally: os.close(fd) filelog.info("downloaded %s bytes to %s file %s", filesize, mimetype, filename) if printit: printer = options.strget("printer") title = options.strget("title") print_options = options.dictget("options") #TODO: how do we print multiple copies? #copies = options.intget("copies") #whitelist of options we can forward: safe_print_options = dict((k,v) for k,v in print_options.items() if k in ("PageSize", "Resolution")) printlog("safe print options(%s) = %s", options, safe_print_options) self._print_file(filename, printer, title, safe_print_options) return elif openit: #run the command in a new thread #so we can block waiting for the subprocess to exit #(ensures that we do reap the process) import thread thread.start_new_thread(self._open_file, (filename, ))