class XRootPropWatcher(gobject.GObject): __gsignals__ = { "root-prop-changed": n_arg_signal(2), "wimpiggy-property-notify-event": n_arg_signal(1), } def __init__(self, props): gobject.GObject.__init__(self) self._props = props self._root = gtk.gdk.get_default_root_window() add_event_receiver(self._root, self) def do_wimpiggy_property_notify_event(self, event): if event.atom in self._props: self._notify(event.atom) def _notify(self, prop): v = prop_get(gtk.gdk.get_default_root_window(), prop, "latin1", ignore_errors=True) self.emit("root-prop-changed", prop, v) def notify_all(self): for prop in self._props: self._notify(prop)
class SoundSource(SoundPipeline): __gsignals__ = { "new-buffer": n_arg_signal(2), } def __init__(self, src_type=DEFAULT_SRC, src_options={}, codec=MP3, encoder_options={}): assert src_type in SOURCES encoder, fmt = get_encoder_formatter(codec) SoundPipeline.__init__(self, codec) self.src_type = src_type source_str = plugin_str(src_type, src_options) encoder_str = plugin_str(encoder, encoder_options) pipeline_els = [source_str] if AUDIOCONVERT: pipeline_els += ["audioconvert"] if AUDIORESAMPLE: pipeline_els += [ "audioresample", "audio/x-raw-int,rate=44100,channels=2" ] pipeline_els += [encoder_str, fmt, "appsink name=sink"] self.setup_pipeline_and_bus(pipeline_els) self.sink = self.pipeline.get_by_name("sink") self.sink.set_property("emit-signals", True) self.sink.set_property("max-buffers", 10) self.sink.set_property("drop", False) self.sink.set_property("sync", True) self.sink.set_property("qos", False) self.sink.connect("new-buffer", self.on_new_buffer) self.sink.connect("new-preroll", self.on_new_preroll) def cleanup(self): SoundPipeline.cleanup(self) self.src_type = "" self.sink = None def on_new_preroll(self, appsink): buf = appsink.emit('pull-preroll') debug('new preroll: %s bytes', len(buf)) self.emit_buffer(buf) def on_new_buffer(self, bus): buf = self.sink.emit("pull-buffer") self.emit_buffer(buf) def emit_buffer(self, buf): """ convert pygst structure into something more generic for the wire """ #none of the metadata is really needed at present, but it may be in the future: #metadata = {"caps" : buf.get_caps().to_string(), # "size" : buf.size, # "timestamp" : buf.timestamp, # "duration" : buf.duration, # "offset" : buf.offset, # "offset_end": buf.offset_end} metadata = {} self.emit("new-buffer", buf.data, metadata)
class XpraClient(XpraClientBase): __gsignals__ = { "clipboard-toggled": n_arg_signal(0), "keyboard-sync-toggled": n_arg_signal(0), } def __init__(self, conn, opts): XpraClientBase.__init__(self, opts) self.start_time = time.time() self._window_to_id = {} self._id_to_window = {} self.title = opts.title self.readonly = opts.readonly self.session_name = opts.session_name self.compression_level = opts.compression_level self.auto_refresh_delay = opts.auto_refresh_delay self.max_bandwidth = opts.max_bandwidth if self.max_bandwidth > 0.0 and self.jpegquality == 0: """ jpegquality was not set, use a better start value """ self.jpegquality = 50 self.dpi = int(opts.dpi) #statistics: self.server_start_time = -1 self.server_platform = "" self.server_actual_desktop_size = None self.server_randr = False self.pixel_counter = maxdeque(maxlen=100) self.server_latency = maxdeque(maxlen=100) self.server_load = None self.client_latency = maxdeque(maxlen=100) self.last_ping_echoed_time = 0 #features: self.toggle_cursors_bell_notify = False self.toggle_keyboard_sync = False self.window_configure = False self._client_extras = ClientExtras(self, opts) self.client_supports_notifications = opts.notifications and self._client_extras.can_notify( ) self.client_supports_clipboard = opts.clipboard and self._client_extras.supports_clipboard( ) and not self.readonly self.client_supports_cursors = opts.cursors self.client_supports_bell = opts.bell self.notifications_enabled = self.client_supports_notifications self.clipboard_enabled = self.client_supports_clipboard self.cursors_enabled = self.client_supports_cursors self.bell_enabled = self.client_supports_bell #mmap: self.mmap_enabled = False self.mmap = None self.mmap_token = None self.mmap_file = None self.mmap_size = 0 self.supports_mmap = opts.mmap and ( "rgb24" in ENCODINGS) and self._client_extras.supports_mmap() if self.supports_mmap: self.init_mmap(opts.mmap_group, conn.filename) self.init_packet_handlers() self.ready(conn) #keyboard: self.keyboard_sync = opts.keyboard_sync self.key_repeat_delay = -1 self.key_repeat_interval = -1 self.keys_pressed = {} self._remote_version = None self._keymap_changing = False try: self._keymap = gdk.keymap_get_default() except: self._keymap = None self._do_keys_changed() self.key_shortcuts = self.parse_shortcuts(opts.key_shortcuts) self.send_hello() if self._keymap: self._keymap.connect("keys-changed", self._keys_changed) self._focused = None def compute_receive_bandwidth(delay): bytecount = self._protocol.input_bytecount bw = ( (bytecount - self.last_input_bytecount) / 1024) * 1000 / delay self.last_input_bytecount = bytecount log.debug("Bandwidth is ", bw, "kB/s, max ", self.max_bandwidth, "kB/s") q = self.jpegquality if bw > self.max_bandwidth: q -= 10 elif bw < self.max_bandwidth: q += 5 q = max(10, min(95, q)) self.send_jpeg_quality(q) return True if (self.max_bandwidth): self.last_input_bytecount = 0 gobject.timeout_add(2000, compute_receive_bandwidth, 2000) if opts.send_pings: gobject.timeout_add(1000, self.send_ping) else: gobject.timeout_add(20 * 1000, self.send_ping) def init_mmap(self, mmap_group, socket_filename): log("init_mmap(%s, %s)", mmap_group, socket_filename) try: import mmap import tempfile import uuid from stat import S_IRUSR, S_IWUSR, S_IRGRP, S_IWGRP mmap_dir = os.getenv("TMPDIR", "/tmp") if not os.path.exists(mmap_dir): raise Exception("TMPDIR %s does not exist!" % mmap_dir) #create the mmap file, the mkstemp that is called via NamedTemporaryFile ensures #that the file is readable and writable only by the creating user ID temp = tempfile.NamedTemporaryFile(prefix="xpra.", suffix=".mmap", dir=mmap_dir) #keep a reference to it so it does not disappear! self._mmap_temp_file = temp self.mmap_file = temp.name fd = temp.file.fileno() #set the group permissions and gid if the mmap-group option is specified if mmap_group and type(socket_filename) == str and os.path.exists( socket_filename): s = os.stat(socket_filename) os.fchown(fd, -1, s.st_gid) os.fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) self.mmap_size = max(4096, mmap.PAGESIZE) * 32 * 1024 #generally 128MB log("using mmap file %s, fd=%s, size=%s", self.mmap_file, fd, self.mmap_size) os.lseek(fd, self.mmap_size - 1, os.SEEK_SET) assert os.write(fd, '\x00') os.lseek(fd, 0, os.SEEK_SET) self.mmap = mmap.mmap(fd, length=self.mmap_size) #write the 16 byte token one byte at a time - no endianness self.mmap_token = uuid.uuid4().int log.debug("mmap_token=%s", self.mmap_token) v = self.mmap_token for i in range(0, 16): poke = ctypes.c_ubyte.from_buffer(self.mmap, 512 + i) poke.value = v % 256 v = v >> 8 assert v == 0 except Exception, e: log.error("failed to setup mmap: %s", e) self.supports_mmap = False self.clean_mmap() self.mmap = None self.mmap_file = None self.mmap_size = 0
class ClipboardProxy(gtk.Invisible): __gsignals__ = { # arguments: (selection, target) "get-clipboard-from-remote": ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_PYOBJECT, (gobject.TYPE_PYOBJECT, ) * 2, ), # arguments: (selection,) "send-clipboard-token": n_arg_signal(1), } def __init__(self, selection): gtk.Invisible.__init__(self) self.add_events(gtk.gdk.PROPERTY_CHANGE_MASK) self._selection = selection self._clipboard = gtk.Clipboard(selection=selection) self._have_token = False def do_selection_request_event(self, event): log.debug("do_selection_request_event(%s)", event) try: from wimpiggy.prop import prop_get from wimpiggy.error import trap except ImportError: gtk.Invisible.do_selection_request_event(self, event) return # Black magic: the superclass default handler for this signal # implements all the hards parts of selection handling, occasionally # calling back to the do_selection_get handler (below) to actually get # the data to be sent. However, it only does this for targets that # have been registered ahead of time; other targets fall through to a # default implementation that cannot be overridden. So, we swoop in # ahead of time and add whatever target was requested to the list of # targets we want to handle! # # Special cases (magic targets defined by ICCCM): # TIMESTAMP: the remote side has a different timeline than us, so # sending TIMESTAMPS across the wire doesn't make any sense. We # ignore TIMESTAMP requests, and let them fall through to GTK+'s # default handler. # TARGET: GTK+ has default handling for this, but we don't want to # use it. Fortunately, if we tell GTK+ that we can handle TARGET # requests, then it will pass them on to us rather than fall # through to the default handler. # MULTIPLE: Ugh. To handle this properly, we need to go out # ourselves and fetch the magic property off the requesting window # (with proper error trapping and all), and interpret its # contents. Probably doable (FIXME), just a pain. # # Another special case is that if an app requests the contents of a # clipboard that it currently owns, then GTK+ will short-circuit the # normal logic and request the contents directly (i.e. it calls # gtk_selection_invoke_handler) -- without giving us a chance to # assert that we can handle the requested sort of target. Fortunately, # Xpra never needs to request the clipboard when it owns it, so that's # okay. assert str(event.selection) == self._selection target = str(event.target) if target == "TIMESTAMP": pass elif target == "MULTIPLE": def get_targets(targets): atoms = prop_get(event.window, event.property, ["multiple-conversion"]) log("MULTIPLE clipboard atoms: %r", atoms) targets += atoms[::2] targets = [] trap.swallow(get_targets, targets) log("MULTIPLE clipboard targets: %r", targets) for target in targets: self.selection_add_target(self._selection, target, 0) else: self.selection_add_target(self._selection, target, 0) gtk.Invisible.do_selection_request_event(self, event) # This function is called by GTK+ when we own the clipboard and a local # app is requesting its contents: 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. log.debug("do_selection_get(%s,%s,%s)", selection_data, info, time) assert self._selection == str(selection_data.selection) target = str(selection_data.target) result = self.emit("get-clipboard-from-remote", self._selection, target) if result is not None and result["type"] is not None: log.debug( "do_selection_get(%s,%s,%s) calling selection_data.set(%s,%s,%s)", selection_data, info, time, result["type"], result["format"], len(result["data"])) selection_data.set(result["type"], result["format"], result["data"]) else: log("remote selection fetch timed out") def do_selection_clear_event(self, event): # Someone else on our side has the selection log.debug("do_selection_clear_event(%s) selection=%s", event, self._selection) self._have_token = False # Emit a signal -> send a note to the other side saying "hey its # ours now" # Send off the anti-token. self.emit("send-clipboard-token", self._selection) gtk.Invisible.do_selection_clear_event(self, event) def got_token(self): # We got the anti-token. log.debug("got token") self._have_token = True if not self.selection_owner_set(self._selection): # I don't know how this can actually fail, given that we pass # CurrentTime, but just in case: log.warn("Failed to acquire local clipboard %s; " % (self._selection, ) + "will not be able to pass local apps " + "contents of remote clipboard") # This function is called by the xpra core when the peer has requested the # contents of this clipboard: def get_contents(self, target, cb): log.debug("get_contents(%s,%s)", target, cb) if self._have_token: log.warn("Our peer requested the contents of the clipboard, but " + "*I* thought *they* had it... weird.") cb(None, None, None) def unpack(clipboard, selection_data, data): if selection_data is None: cb(None, None, None) else: cb(str(selection_data.type), selection_data.format, selection_data.data) self._clipboard.request_contents(target, unpack)
class XpraClientBase(gobject.GObject): """Base class for Xpra clients. Provides the glue code for: * sending packets via Protocol * handling packets received via _process_packet """ __gsignals__ = { "handshake-complete": n_arg_signal(0), "received-gibberish": n_arg_signal(1), } def __init__(self, opts): gobject.GObject.__init__(self) self.exit_code = None self.password_file = opts.password_file self.encoding = opts.encoding self.jpegquality = opts.jpegquality self._protocol = None self.server_capabilities = {} self.init_packet_handlers() def ready(self, conn): log.debug("ready(%s)", conn) self._protocol = Protocol(conn, self.process_packet) ClientSource(self._protocol) self._protocol.start() def init_packet_handlers(self): self._packet_handlers = { "challenge": self._process_challenge, "disconnect": self._process_disconnect, "hello": self._process_hello, "set_deflate": self._process_set_deflate, Protocol.CONNECTION_LOST: self._process_connection_lost, Protocol.GIBBERISH: self._process_gibberish, } def send_hello(self, challenge_response=None): hello = self.make_hello(challenge_response) log.debug("send_hello(%s) packet=%s", challenge_response, hello) self.send(["hello", hello]) def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) if challenge_response: capabilities["challenge_response"] = challenge_response if self.encoding: capabilities["encoding"] = self.encoding capabilities["encodings"] = ENCODINGS if self.jpegquality: capabilities["jpeg"] = self.jpegquality capabilities["platform"] = sys.platform capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["rencode"] = has_rencode capabilities["server-window-resize"] = True capabilities["randr_notify"] = False #only client.py cares about this return capabilities def send(self, packet): if self._protocol and self._protocol.source: self._protocol.source.queue_ordinary_packet(packet) def send_now(self, packet): if self._protocol and self._protocol.source: self._protocol.source.queue_priority_packet(packet) def cleanup(self): if self._protocol: self._protocol.close() self._protocol = None def run(self): raise Exception("override me!") def quit(self, exit_code=0): if self.exit_code is None: self.exit_code = exit_code raise Exception("override me!") def _process_disconnect(self, packet): log.error("server requested disconnect: %s", packet[1:]) self.quit(0) def _process_connection_lost(self, packet): log.error("Connection lost") self.quit(1) def _process_challenge(self, packet): if not self.password_file: log.error("password is required by the server") self.quit(2) return import hmac try: passwordFile = open(self.password_file, "rU") password = passwordFile.read() passwordFile.close() while password.endswith("\n") or password.endswith("\r"): password = password[:-1] except IOError, e: log.error("failed to open password file %s: %s", self.password_file, e) self.quit(3) return salt = packet[1] challenge_response = hmac.HMAC(password, salt) self.send_hello(challenge_response.hexdigest())
class XpraClient(gobject.GObject): __gsignals__ = { "handshake-complete": n_arg_signal(0), "received-gibberish": n_arg_signal(1), } def __init__(self, conn, compression_level): gobject.GObject.__init__(self) self._window_to_id = {} self._id_to_window = {} self._protocol = Protocol(conn, self.process_packet) ClientSource(self._protocol) capabilities_request = dict(default_capabilities) if compression_level: capabilities_request["deflate"] = compression_level root_w, root_h = gtk.gdk.get_default_root_window().get_size() capabilities_request["desktop_size"] = [root_w, root_h] self.send(["hello", capabilities_request]) self._keymap = gtk.gdk.keymap_get_default() self._keymap.connect("keys-changed", self._keys_changed) self._keys_changed() self._xsettings_watcher = None self._root_props_watcher = None # FIXME: these should perhaps be merged. self._clipboard_helper = ClipboardProtocolHelper(self.send) self._client_extras = ClientExtras(self.send) self._focused = None def run(self): gtk_main_quit_on_fatal_exceptions_enable() gtk.main() def _keys_changed(self, *args): self._modifier_map = grok_modifier_map(gtk.gdk.display_get_default()) def update_focus(self, id, gotit): if gotit and self._focused is not id: self.send(["focus", id]) self._focused = id if not gotit and self._focused is id: self.send(["focus", 0]) self._focused = None def mask_to_names(self, mask): return mask_to_names(mask, self._modifier_map) def send(self, packet): self._protocol.source.queue_ordinary_packet(packet) def send_positional(self, packet): self._protocol.source.queue_positional_packet(packet) def send_mouse_position(self, packet): self._protocol.source.queue_mouse_position_packet(packet) def _process_hello(self, packet): (_, capabilities) = packet if "deflate" in capabilities: self._protocol.enable_deflate(capabilities["deflate"]) if capabilities.get("__prerelease_version") != xpra.__version__: log.error("sorry, I only know how to talk to v%s servers", xpra.__version__) gtk.main_quit() return if "desktop_size" in capabilities: avail_w, avail_h = capabilities["desktop_size"] root_w, root_h = gtk.gdk.get_default_root_window().get_size() if (avail_w, avail_h) < (root_w, root_h): log.warn("Server's virtual screen is too small -- " "(server: %sx%s vs. client: %sx%s)\n" "You may see strange behavior.\n" "Please complain to " "*****@*****.**" % (avail_w, avail_h, root_w, root_h)) self._clipboard_helper.send_all_tokens() self._client_extras.handshake_complete(capabilities) self.emit("handshake-complete") def _process_new_common(self, packet, override_redirect): (_, id, x, y, w, h, metadata) = packet window = ClientWindow(self, id, x, y, w, h, metadata, override_redirect) self._id_to_window[id] = window self._window_to_id[window] = id window.show_all() def _process_new_window(self, packet): self._process_new_common(packet, False) def _process_new_override_redirect(self, packet): self._process_new_common(packet, True) def _process_draw(self, packet): (_, id, x, y, width, height, coding, data) = packet window = self._id_to_window[id] assert coding == "rgb24" window.draw(x, y, width, height, data) def _process_window_metadata(self, packet): (_, id, metadata) = packet window = self._id_to_window[id] window.update_metadata(metadata) def _process_configure_override_redirect(self, packet): (_, id, x, y, w, h) = packet window = self._id_to_window[id] window.move_resize(x, y, w, h) def _process_lost_window(self, packet): (_, id) = packet window = self._id_to_window[id] del self._id_to_window[id] del self._window_to_id[window] window.destroy() def _process_connection_lost(self, packet): log.error("Connection lost") gtk_main_quit_really() def _process_gibberish(self, packet): [_, data] = packet self.emit("received-gibberish", data) _packet_handlers = { "hello": _process_hello, "new-window": _process_new_window, "new-override-redirect": _process_new_override_redirect, "draw": _process_draw, "window-metadata": _process_window_metadata, "configure-override-redirect": _process_configure_override_redirect, "lost-window": _process_lost_window, # "clipboard-*" packets are handled by a special case below. Protocol.CONNECTION_LOST: _process_connection_lost, Protocol.GIBBERISH: _process_gibberish, } def process_packet(self, proto, packet): packet_type = packet[0] if (isinstance(packet_type, str) and packet_type.startswith("clipboard-")): self._clipboard_helper.process_clipboard_packet(packet) else: self._packet_handlers[packet_type](self, packet)
class XpraClientBase(gobject.GObject): """Base class for Xpra clients. Provides the glue code for: * sending packets via Protocol * handling packets received via _process_packet """ __gsignals__ = { "handshake-complete": n_arg_signal(0), "received-gibberish": n_arg_signal(1), } def __init__(self, opts): gobject.GObject.__init__(self) self.password_file = opts.password_file self.encoding = opts.encoding self.jpegquality = opts.jpegquality self._protocol = None self.init_packet_handlers() def ready(self, conn): log.debug("ready(%s)", conn) self._protocol = Protocol(conn, self.process_packet) ClientSource(self._protocol) def init_packet_handlers(self): self._packet_handlers = { "challenge": self._process_challenge, "disconnect": self._process_disconnect, "hello": self._process_hello, "set_deflate": self._process_set_deflate, Protocol.CONNECTION_LOST: self._process_connection_lost, Protocol.GIBBERISH: self._process_gibberish, } def send_hello(self, challenge_response=None): hello = self.make_hello(challenge_response) log.debug("send_hello(%s) packet=%s", challenge_response, hello) self.send(["hello", hello]) def make_hello(self, challenge_response=None): capabilities = {"version": xpra.__version__} if challenge_response: capabilities["challenge_response"] = challenge_response if self.encoding: capabilities["encoding"] = self.encoding capabilities["encodings"] = ENCODINGS if self.jpegquality: capabilities["jpeg"] = self.jpegquality capabilities["packet_size"] = True #will be removed (only for compatibility with old versions): capabilities["dynamic_compression"] = True capabilities["__prerelease_version"] = xpra.__version__ return capabilities def send(self, packet): self._protocol.source.queue_ordinary_packet(packet) def send_now(self, packet): self._protocol.source.queue_priority_packet(packet) def cleanup(self): if self._protocol: self._protocol.close() self._protocol = None def run(self): raise Exception("override me!") def quit(self, *args): raise Exception("override me!") def _process_disconnect(self, packet): log.error("server requested disconnect: %s", packet[1:]) self.quit() return def _process_challenge(self, packet): if not self.password_file: log.error("password is required by the server") self.quit() return import hmac try: passwordFile = open(self.password_file, "rU") password = passwordFile.read() except IOError, e: log.error("failed to open password file %s: %s", self.password_file, e) self.quit() return salt = packet[1] challenge_response = hmac.HMAC(password, salt) self.send_hello(challenge_response.hexdigest())
class XpraClient(XpraClientBase): __gsignals__ = { "clipboard-toggled": n_arg_signal(0), } def __init__(self, conn, opts): XpraClientBase.__init__(self, opts) self.start_time = time.time() self._window_to_id = {} self._id_to_window = {} title = opts.title if opts.title_suffix is not None: title = "@title@ %s" % opts.title_suffix self.title = title self.readonly = opts.readonly self.session_name = opts.session_name self.compression_level = opts.compression_level self.auto_refresh_delay = opts.auto_refresh_delay self.max_bandwidth = opts.max_bandwidth if self.max_bandwidth>0.0 and self.jpegquality==0: """ jpegquality was not set, use a better start value """ self.jpegquality = 50 self.server_capabilities = {} self.can_ping = False self.mmap_enabled = False self.server_start_time = -1 self.server_platform = "" self.server_actual_desktop_size = None self.server_desktop_size = None self.server_randr = False self.pixel_counter = deque(maxlen=100) self.server_latency = deque(maxlen=100) self.server_load = None self.client_latency = deque(maxlen=100) self.bell_enabled = True self.notifications_enabled = True self.send_damage_sequence = False self.clipboard_enabled = False self.mmap = None self.mmap_token = None self.mmap_file = None self.mmap_size = 0 self._client_extras = ClientExtras(self, opts) self.clipboard_enabled = not self.readonly and opts.clipboard and self._client_extras.supports_clipboard() self.supports_mmap = opts.mmap and self._client_extras.supports_mmap() if self.supports_mmap: try: import mmap import tempfile import uuid import ctypes from stat import S_IRUSR,S_IWUSR mmap_dir = os.getenv("TMPDIR", "/tmp") if not os.path.exists(mmap_dir): raise Exception("TMPDIR %s does not exist!" % mmap_dir) temp = tempfile.NamedTemporaryFile(prefix="xpra.", suffix=".mmap", dir=mmap_dir) #keep a reference to it so it does not disappear! self._mmap_temp_file = temp self.mmap_file = temp.name #ensure that the permissions are strict: os.chmod(self.mmap_file, S_IRUSR|S_IWUSR) self.mmap_size = max(4096, mmap.PAGESIZE)*32*1024 #generally 128MB fd = temp.file.fileno() log("using mmap file %s, fd=%s, size=%s", self.mmap_file, fd, self.mmap_size) os.lseek(fd, self.mmap_size-1, os.SEEK_SET) assert os.write(fd, '\x00') os.lseek(fd, 0, os.SEEK_SET) self.mmap = mmap.mmap(fd, length=self.mmap_size) #write the 16 byte token one byte at a time - no endianness self.mmap_token = uuid.uuid4().int log.debug("mmap_token=%s", self.mmap_token) v = self.mmap_token for i in range(0,16): poke = ctypes.c_ubyte.from_buffer(self.mmap, 512+i) poke.value = v % 256 v = v>>8 assert v==0 except Exception, e: log.error("failed to setup mmap: %s", e) self.supports_mmap = False self.clean_mmap() self.mmap = None self.mmap_file = None self.mmap_size = 0 self.init_packet_handlers() self.ready(conn) self.keyboard_sync = opts.keyboard_sync self.key_repeat_modifiers = False self.key_repeat_delay = -1 self.key_repeat_interval = -1 self.keys_pressed = {} self.send_nuisance_modifiers = False self.keyboard_as_properties = False self._raw_keycodes_feature = False self._raw_keycodes_full = False self._focus_modifiers_feature = False self._remote_version = None self._keymap_changing = False self._keymap = gtk.gdk.keymap_get_default() self._do_keys_changed() self.key_shortcuts = self.parse_shortcuts(opts.key_shortcuts) self.send_hello() self._keymap.connect("keys-changed", self._keys_changed) self._xsettings_watcher = None self._root_props_watcher = None self._focused = None def compute_receive_bandwidth(delay): bw = (self._protocol._recv_counter / 1024) * 1000/ delay; self._protocol._recv_counter = 0; log.debug("Bandwidth is ", bw, "kB/s, max ", self.max_bandwidth, "kB/s") q = self.jpegquality if bw > self.max_bandwidth: q -= 10 elif bw < self.max_bandwidth: q += 5 q = max(10, min(95 ,q)) self.send_jpeg_quality(q) return True if (self.max_bandwidth): gobject.timeout_add(2000, compute_receive_bandwidth, 2000);
class ClipboardProxy(gtk.Invisible): __gsignals__ = { # arguments: (selection, target) "get-clipboard-from-remote": ( gobject.SIGNAL_RUN_LAST, gobject.TYPE_PYOBJECT, (gobject.TYPE_PYOBJECT, ) * 2, ), # arguments: (selection,) "send-clipboard-token": n_arg_signal(1), } def __init__(self, selection): gtk.Invisible.__init__(self) self.add_events(PROPERTY_CHANGE_MASK) self._selection = selection self._clipboard = gtk.Clipboard(selection=selection) self._have_token = False #clients that need a new token for every owner-change: (ie: win32) self._greedy_client = False #semaphore to block the sending of the token when we change the owner ourselves: self._block_owner_change = False self._clipboard.connect("owner-change", self.do_owner_changed) def set_greedy_client(self, greedy): debug("%s.set_greedy_client(%s)", self, greedy) self._greedy_client = greedy def __str__(self): return "ClipboardProxy(%s)" % self._selection def do_owner_changed(self, *args): debug("do_owner_changed(%s) greedy_client=%s, block_owner_change=%s", args, self._greedy_client, self._block_owner_change) if self._greedy_client and not self._block_owner_change: self._have_token = False self.emit("send-clipboard-token", self._selection) def do_selection_request_event(self, event): debug("do_selection_request_event(%s)", event) # Black magic: the superclass default handler for this signal # implements all the hards parts of selection handling, occasionally # calling back to the do_selection_get handler (below) to actually get # the data to be sent. However, it only does this for targets that # have been registered ahead of time; other targets fall through to a # default implementation that cannot be overridden. So, we swoop in # ahead of time and add whatever target was requested to the list of # targets we want to handle! # # Special cases (magic targets defined by ICCCM): # TIMESTAMP: the remote side has a different timeline than us, so # sending TIMESTAMPS across the wire doesn't make any sense. We # ignore TIMESTAMP requests, and let them fall through to GTK+'s # default handler. # TARGET: GTK+ has default handling for this, but we don't want to # use it. Fortunately, if we tell GTK+ that we can handle TARGET # requests, then it will pass them on to us rather than fall # through to the default handler. # MULTIPLE: Ugh. To handle this properly, we need to go out # ourselves and fetch the magic property off the requesting window # (with proper error trapping and all), and interpret its # contents. Probably doable (FIXME), just a pain. # # Another special case is that if an app requests the contents of a # clipboard that it currently owns, then GTK+ will short-circuit the # normal logic and request the contents directly (i.e. it calls # gtk_selection_invoke_handler) -- without giving us a chance to # assert that we can handle the requested sort of target. Fortunately, # Xpra never needs to request the clipboard when it owns it, so that's # okay. assert str(event.selection) == self._selection target = str(event.target) if target == "TIMESTAMP": pass elif target == "MULTIPLE": try: from wimpiggy.prop import prop_get except ImportError: debug( "MULTIPLE for property '%s' not handled due to missing wimpiggy bindings", event.property) gtk.Invisible.do_selection_request_event(self, event) return atoms = prop_get(event.window, event.property, ["multiple-conversion"]) debug("MULTIPLE clipboard atoms: %r", atoms) if atoms: targets = atoms[::2] for t in targets: self.selection_add_target(self._selection, t, 0) else: debug("target for %s: %r", self._selection, target) self.selection_add_target(self._selection, target, 0) debug("do_selection_request_event(%s) target=%s, selection=%s", event, target, self._selection) gtk.Invisible.do_selection_request_event(self, event) # This function is called by GTK+ when we own the clipboard and a local # app is requesting its contents: 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. debug("do_selection_get(%s, %s, %s) selection=%s", selection_data, info, time, selection_data.selection) assert self._selection == str(selection_data.selection) target = str(selection_data.target) result = self.emit("get-clipboard-from-remote", self._selection, target) if result is None or result["type"] is None: debug("remote selection fetch timed out or empty") return data = result["data"] dformat = result["format"] dtype = result["type"] debug( "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 "")) selection_data.set(dtype, dformat, data) def do_selection_clear_event(self, event): # Someone else on our side has the selection debug("do_selection_clear_event(%s) selection=%s", event, self._selection) #if greedy_client is set, do_owner_changed will fire the token #so don't bother sending it now (same if we don't have it) send = ((self._greedy_client and not self._block_owner_change) or self._have_token) self._have_token = False # Emit a signal -> send a note to the other side saying "hey its # ours now" # Send off the anti-token. if send: self.emit("send-clipboard-token", self._selection) gtk.Invisible.do_selection_clear_event(self, event) def got_token(self, targets): # We got the anti-token. debug("got token, selection=%s, targets=%s", self._selection, targets) self._have_token = True if self._greedy_client: self._block_owner_change = True if not self.selection_owner_set(self._selection): # I don't know how this can actually fail, given that we pass # CurrentTime, but just in case: log.warn("Failed to acquire local clipboard %s; " % (self._selection, ) + "will not be able to pass local apps " + "contents of remote clipboard") if self._block_owner_change: #re-enable the flag via idle_add so events like do_owner_changed #get a chance to run first. def remove_block(*args): self._block_owner_change = False gobject.idle_add(remove_block) # This function is called by the xpra core when the peer has requested the # contents of this clipboard: def get_contents(self, target, cb): debug("get_contents(%s,%s) selection=%s", target, cb, self._selection) if self._have_token: log.warn("Our peer requested the contents of the clipboard, but " + "*I* thought *they* had it... weird.") cb(None, None, None) return if target == "TARGETS": #handle TARGETS using "request_targets" def got_targets(c, targets, *args): debug("got_targets(%s, %s, %s)", c, targets, args) cb("ATOM", 32, targets) self._clipboard.request_targets(got_targets) return def unpack(clipboard, selection_data, user_data): debug("unpack: %s", type(selection_data)) if selection_data is None: cb(None, None, None) return debug("unpack(..) type=%s, format=%s, data=%s:%s", selection_data.type, selection_data.format, type(selection_data.data), len(selection_data.data or "")) cb(str(selection_data.type), selection_data.format, selection_data.data) self._clipboard.request_contents(target, unpack)
class XpraClientBase(gobject.GObject): """Base class for Xpra clients. Provides the glue code for: * sending packets via Protocol * handling packets received via _process_packet """ __gsignals__ = { "handshake-complete": n_arg_signal(0), "first-ui-received": n_arg_signal(0), "received-gibberish": n_arg_signal(1), } def __init__(self, opts): gobject.GObject.__init__(self) self.exit_code = None self.compression_level = opts.compression_level self.password = None self.password_file = opts.password_file self.encoding = opts.encoding self.quality = opts.quality self._protocol = None self.server_capabilities = {} self._remote_version = None self._remote_revision = None self.init_packet_handlers() def ready(self, conn): log.debug("ready(%s)", conn) self._protocol = Protocol(conn, self.process_packet) self._protocol.set_compression_level(self.compression_level) ClientSource(self._protocol) self._protocol.start() def init_packet_handlers(self): self._packet_handlers = {} self._ui_packet_handlers = { "challenge": self._process_challenge, "disconnect": self._process_disconnect, "hello": self._process_hello, "set_deflate": self._process_set_deflate, Protocol.CONNECTION_LOST: self._process_connection_lost, Protocol.GIBBERISH: self._process_gibberish, } def send_hello(self, challenge_response=None): hello = self.make_hello(challenge_response) log.debug("send_hello(%s) packet=%s", challenge_response, hello) self.send(["hello", hello]) def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) if challenge_response: capabilities["challenge_response"] = challenge_response if self.encoding: capabilities["encoding"] = self.encoding capabilities["encodings"] = ENCODINGS if self.quality >= 0: capabilities["jpeg"] = self.quality capabilities["quality"] = self.quality capabilities["platform"] = sys.platform capabilities["client_type"] = "Python/Gobject" capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["rencode"] = has_rencode capabilities["server-window-resize"] = True u = hashlib.sha512() u.update(str(get_machine_id())) if os.name == "posix": u.update("/") u.update(str(os.getuid())) u.update("/") u.update(str(os.getgid())) capabilities["uuid"] = u.hexdigest() capabilities["randr_notify"] = False #only client.py cares about this capabilities["windows"] = False #only client.py cares about this return capabilities def idle_send(self, packet): gobject.idle_add(self.send, packet) def send(self, packet): if self._protocol and self._protocol.source: self._protocol.source.queue_ordinary_packet(packet) def send_now(self, packet): if self._protocol and self._protocol.source: self._protocol.source.queue_priority_packet(packet) def cleanup(self): if self._protocol: self._protocol.close() self._protocol = None def run(self): raise Exception("override me!") def quit(self, exit_code=0): if self.exit_code is None: self.exit_code = exit_code raise Exception("override me!") def warn_and_quit(self, exit_code, warning): log.warn(warning) self.quit(exit_code) def _process_disconnect(self, packet): if len(packet) == 2: info = packet[1] else: info = packet[1:] self.warn_and_quit(EXIT_OK, "server requested disconnect: %s" % info) def _process_connection_lost(self, packet): self.warn_and_quit(EXIT_CONNECTION_LOST, "Connection lost") def _process_challenge(self, packet): if not self.password_file and not self.password: self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "password is required by the server") return if not self.password: self.load_password() log("password read from file %s is %s", self.password_file, self.password) if self.password: salt = packet[1] import hmac challenge_response = hmac.HMAC(self.password, salt) self.send_hello(challenge_response.hexdigest()) def load_password(self): try: passwordFile = open(self.password_file, "rU") self.password = passwordFile.read() passwordFile.close() while self.password.endswith("\n") or self.password.endswith("\r"): self.password = self.password[:-1] except IOError, e: self.warn_and_quit( EXIT_PASSWORD_FILE_ERROR, "failed to open password file %s: %s" % (self.password_file, e))
class XpraClientBase(gobject.GObject): """Base class for Xpra clients. Provides the glue code for: * sending packets via Protocol * handling packets received via _process_packet """ __gsignals__ = { "handshake-complete": n_arg_signal(0), "first-ui-received" : n_arg_signal(0), "received-gibberish": n_arg_signal(1), } def __init__(self, opts): gobject.GObject.__init__(self) self.exit_code = None self.compression_level = opts.compression_level self.password = None self.password_file = opts.password_file self.password_sent = False self.encoding = opts.encoding self.encryption = opts.encryption self.quality = opts.quality self.min_quality = opts.min_quality self.speed = opts.speed self.min_speed = opts.min_speed #protocol stuff: self._protocol = None self._priority_packets = [] self._ordinary_packets = [] self._mouse_position = None #server state and caps: self.server_capabilities = {} self._remote_version = None self._remote_revision = None self.make_uuid() self.init_packet_handlers() def ready(self, conn): log.debug("ready(%s)", conn) self._protocol = Protocol(conn, self.process_packet, self.next_packet) self._protocol.large_packets.append("keymap-changed") self._protocol.large_packets.append("server-settings") self._protocol.set_compression_level(self.compression_level) self._protocol.start() self.have_more = self._protocol.source_has_more def init_packet_handlers(self): self._packet_handlers = { "hello": self._process_hello, } self._ui_packet_handlers = { "challenge": self._process_challenge, "disconnect": self._process_disconnect, "set_deflate": self._process_set_deflate, Protocol.CONNECTION_LOST: self._process_connection_lost, Protocol.GIBBERISH: self._process_gibberish, } def send_hello(self, challenge_response=None): hello = self.make_hello(challenge_response) log.debug("send_hello(%s) packet=%s", challenge_response, hello) self.send("hello", hello) def make_hello(self, challenge_response=None): capabilities = {} add_version_info(capabilities) if challenge_response: assert self.password capabilities["challenge_response"] = challenge_response if self.encryption: assert self.encryption in ENCRYPTION_CIPHERS capabilities["cipher"] = self.encryption iv = uuid.uuid4().hex[:16] capabilities["cipher.iv"] = iv key_salt = uuid.uuid4().hex capabilities["cipher.key_salt"] = key_salt iterations = 1000 capabilities["cipher.key_stretch_iterations"] = iterations self._protocol.set_cipher_in(self.encryption, iv, self.get_password(), key_salt, iterations) if self.encoding: capabilities["encoding"] = self.encoding capabilities["encodings"] = ENCODINGS if self.quality>0: capabilities["jpeg"] = self.quality capabilities["quality"] = self.quality capabilities["encoding.quality"] = self.quality if self.min_quality>0: capabilities["encoding.min-quality"] = self.min_quality if self.speed>=0: capabilities["speed"] = self.speed capabilities["encoding.speed"] = self.speed if self.min_speed>=0: capabilities["encoding.min-speed"] = self.min_speed capabilities["platform"] = sys.platform capabilities["client_type"] = "Python/Gobject" capabilities["raw_packets"] = True capabilities["chunked_compression"] = True capabilities["rencode"] = has_rencode capabilities["server-window-resize"] = True capabilities["hostname"] = socket.gethostname() capabilities["uuid"] = self.uuid capabilities["randr_notify"] = False #only client.py cares about this capabilities["windows"] = False #only client.py cares about this return capabilities def make_uuid(self): try: import hashlib u = hashlib.sha1() except: #try python2.4 variant: import sha u = sha.new() def uupdate(ustr): u.update(ustr.encode("utf-8")) uupdate(get_machine_id()) if os.name=="posix": uupdate(u"/") uupdate(str(os.getuid())) uupdate(u"/") uupdate(str(os.getgid())) self.uuid = u.hexdigest() def send(self, *parts): self._ordinary_packets.append(parts) self.have_more() def send_now(self, *parts): self._priority_packets.append(parts) self.have_more() def send_positional(self, packet): self._ordinary_packets.append(packet) self._mouse_position = None self.have_more() def send_mouse_position(self, packet): self._mouse_position = packet self.have_more() def have_more(self): #this function is overridden in ready() p = self._protocol if p and p.source: p.source_has_more() def next_packet(self): if self._priority_packets: packet = self._priority_packets.pop(0) elif self._ordinary_packets: packet = self._ordinary_packets.pop(0) elif self._mouse_position is not None: packet = self._mouse_position self._mouse_position = None else: packet = None has_more = packet is not None and \ (bool(self._priority_packets) or bool(self._ordinary_packets) \ or self._mouse_position is not None) return packet, None, None, has_more def cleanup(self): if self._protocol: self._protocol.close() self._protocol = None def run(self): raise Exception("override me!") def quit(self, exit_code=0): if self.exit_code is None: self.exit_code = exit_code raise Exception("override me!") def warn_and_quit(self, exit_code, warning): log.warn(warning) self.quit(exit_code) def _process_disconnect(self, packet): if len(packet)==2: info = packet[1] else: info = packet[1:] self.warn_and_quit(EXIT_OK, "server requested disconnect: %s" % info) def _process_connection_lost(self, packet): self.warn_and_quit(EXIT_CONNECTION_LOST, "Connection lost") def _process_challenge(self, packet): if not self.password_file and not self.password: self.warn_and_quit(EXIT_PASSWORD_REQUIRED, "password is required by the server") return if not self.password: self.load_password() assert self.password salt = packet[1] if self.encryption: assert len(packet)>=3, "challenge does not contain encryption details to use for the response" server_cipher = packet[2] self.set_server_encryption(server_cipher) import hmac challenge_response = hmac.HMAC(self.password, salt) password_hash = challenge_response.hexdigest() self.password_sent = True self.send_hello(password_hash) def set_server_encryption(self, props): cipher = props.get("cipher") cipher_iv = props.get("cipher.iv") key_salt = props.get("cipher.key_salt") iterations = props.get("cipher.key_stretch_iterations") if not cipher or not cipher_iv: self.warn_and_quit(EXIT_ENCRYPTION, "the server does not use or support encryption/password, cannot continue with %s cipher" % self.encryption) return False if cipher not in ENCRYPTION_CIPHERS: self.warn_and_quit(EXIT_ENCRYPTION, "unsupported server cipher: %s, allowed ciphers: %s" % (cipher, ", ".join(ENCRYPTION_CIPHERS))) return False self._protocol.set_cipher_out(cipher, cipher_iv, self.get_password(), key_salt, iterations) def get_password(self): if self.password is None: self.load_password() return self.password def load_password(self): try: passwordFile = open(self.password_file, "rU") self.password = passwordFile.read() passwordFile.close() while self.password.endswith("\n") or self.password.endswith("\r"): self.password = self.password[:-1] except IOError, e: self.warn_and_quit(EXIT_PASSWORD_FILE_ERROR, "failed to open password file %s: %s" % (self.password_file, e)) log("password read from file %s is %s", self.password_file, self.password)