class ManagerSelection(gobject.GObject): __gsignals__ = { "selection-lost": no_arg_signal, "xpra-destroy-event": one_arg_signal, } def __str__(self): return "ManagerSelection(%s)" % self.atom def __init__(self, selection): gobject.GObject.__init__(self) self.atom = selection self.clipboard = GetClipboard(selection) self._xwindow = None def _owner(self): return X11WindowBindings().XGetSelectionOwner(self.atom) def owned(self): "Returns True if someone owns the given selection." return self._owner() != XNone # If the selection is already owned, then raise AlreadyOwned rather # than stealing it. IF_UNOWNED = "if_unowned" # If the selection is already owned, then steal it, and then block until # the previous owner has signaled that they are done cleaning up. FORCE = "force" # If the selection is already owned, then steal it and return immediately. # Created for the use of tests. FORCE_AND_RETURN = "force_and_return" def acquire(self, when): old_owner = self._owner() if when is self.IF_UNOWNED and old_owner != XNone: raise AlreadyOwned if is_gtk3(): set_clipboard_data(self.clipboard, "VERSION") else: self.clipboard.set_with_data([("VERSION", 0, 0)], self._get, self._clear, None) # Having acquired the selection, we have to announce our existence # (ICCCM 2.8, still). The details here probably don't matter too # much; I've never heard of an app that cares about these messages, # and metacity actually gets the format wrong in several ways (no # MANAGER or owner_window atoms). But might as well get it as right # as possible. # To announce our existence, we need: # -- the timestamp we arrived at # -- the manager selection atom # -- the window that registered the selection # Of course, because Gtk is doing so much magic for us, we have to do # some weird tricks to get at these. # Ask ourselves when we acquired the selection: contents = wait_for_contents(self.clipboard, "TIMESTAMP") ts_data = selectiondata_get_data(contents) #data is a timestamp, X11 datatype is Time which is CARD32, #(which is 64 bits on 64-bit systems!) Lsize = calcsize("@L") if len(ts_data)==Lsize: ts_num = unpack("@L", ts_data[:Lsize])[0] else: ts_num = 0 #CurrentTime log.warn("invalid data for 'TIMESTAMP': %s", ([hex(ord(x)) for x in ts_data])) # Calculate the X atom for this selection: selection_xatom = get_xatom(self.atom) # Ask X what window we used: self._xwindow = X11WindowBindings().XGetSelectionOwner(self.atom) root = self.clipboard.get_display().get_default_screen().get_root_window() xid = get_xwindow(root) X11WindowBindings().sendClientMessage(xid, xid, False, StructureNotifyMask, "MANAGER", ts_num, selection_xatom, self._xwindow) if old_owner != XNone and when is self.FORCE: # Block in a recursive mainloop until the previous owner has # cleared out. try: with xsync: window = get_pywindow(self.clipboard, old_owner) window.set_events(window.get_events() | STRUCTURE_MASK) log("got window") except XError: log("Previous owner is already gone, not blocking") else: log("Waiting for previous owner to exit...") add_event_receiver(window, self) gtk_main() log("...they did.") window = get_pywindow(self.clipboard, self._xwindow) window.set_title("Xpra-ManagerSelection") if is_gtk3(): #we can't use set_with_data(..), #so we have to listen for owner-change: self.clipboard.connect("owner-change", self._owner_change) def _owner_change(self, clipboard, event): log("owner_change(%s, %s)", clipboard, event) if str(event.selection)!=self.atom: #log("_owner_change(..) not our selection: %s vs %s", event.selection, self.atom) return if event.owner: owner = event.owner.get_xid() if owner==self._xwindow: log("_owner_change(..) we still own %s", event.selection) return if self._xwindow: self._xwindow = None self.emit("selection-lost") def do_xpra_destroy_event(self, event): remove_event_receiver(event.window, self) gtk_main_quit() def _get(self, _clipboard, outdata, _which, _userdata): # We are compliant with ICCCM version 2.0 (see section 4.3) outdata.set("INTEGER", 32, pack("@ii", 2, 0)) def _clear(self, _clipboard, _userdata): self._xwindow = None self.emit("selection-lost") def window(self): if self._xwindow is None: return None return get_pywindow(self.clipboard, self._xwindow)
class ClipboardInstance(object): def __init__(self, selection, _log): self.clipboard = GetClipboard(selection) self.selection = selection self._log = _log self.owned_label = label() self.get_targets = gtk.combo_box_new_text() self.get_targets.set_sensitive(False) self.get_targets.connect("changed", self.get_target_changed) self.set_targets = gtk.combo_box_new_text() self.set_targets.append_text("STRING") self.set_targets.append_text("UTF8_STRING") self.set_targets.set_active(0) self.set_targets.connect("changed", self.set_target_changed) self.value_label = label() self.value_entry = gtk.Entry() self.value_entry.set_max_length(100) self.value_entry.set_width_chars(32) self.clear_label_btn = gtk.Button("X") self.clear_label_btn.connect("clicked", self.clear_label) self.clear_entry_btn = gtk.Button("X") self.clear_entry_btn.connect("clicked", self.clear_entry) self.get_get_targets_btn = gtk.Button("Get Targets") self.get_get_targets_btn.connect("clicked", self.do_get_targets) self.get_target_btn = gtk.Button("Get Target") self.get_target_btn.connect("clicked", self.do_get_target) self.get_target_btn.set_sensitive(False) self.set_target_btn = gtk.Button("Set Target") self.set_target_btn.connect("clicked", self.do_set_target) self.get_string_btn = gtk.Button("Get String") self.get_string_btn.connect("clicked", self.do_get_string) self.set_string_btn = gtk.Button("Set String") self.set_string_btn.connect("clicked", self.do_set_string) self.clipboard.connect("owner-change", self.owner_changed) self.log("ready") def __repr__(self): return "ClipboardInstance(%s)" % self.selection def log(self, msg): self._log(self.selection, msg) def clear_entry(self, *_args): self.value_entry.set_text("") def clear_label(self, *_args): self.value_label.set_text("") def get_targets_callback(self, _c, targets, *_args): self.log("got targets: %s" % str(targets)) if hasattr(targets, "name"): self.log("target is atom: %s" % targets.name()) targets = [] filtered = [ x for x in (targets or []) if x not in ("MULTIPLE", "TARGETS") ] ct = self.get_targets.get_active_text() if not ct: #choose a good default target: for x in ("STRING", "UTF8_STRING"): if x in filtered: ct = x break self.get_targets.get_model().clear() self.get_targets.set_sensitive(True) i = 0 for t in filtered: self.get_targets.append_text(t) if t == ct: self.get_targets.set_active(i) i += 1 self.get_targets.show_all() def do_get_targets(self, *_args): self.clipboard.request_targets(self.get_targets_callback, None) def get_target_changed(self, _cb): target = self.get_targets.get_active_text() self.get_target_btn.set_sensitive(bool(target)) def set_target_changed(self, cb): pass def ellipsis(self, val): if len(val) > 24: return val[:24] + ".." return val def selection_value_callback(self, _cb, selection_data, *_args): #print("selection_value_callback(%s, %s, %s)" % (cb, selection_data, args)) try: if selection_data.data is None: s = "" else: s = "type=%s, format=%s, data=%s" % ( selection_data.type, selection_data.format, self.ellipsis(re.escape(selection_data.data))) except TypeError: try: s = self.ellipsis("\\".join( [str(x) for x in bytearray(selection_data.data)])) except Exception: s = "!ERROR! binary data?" self.log("Got selection data: '%s'" % s) self.value_label.set_text(s) def do_get_target(self, *_args): self.clear_label() target = self.get_targets.get_active_text() self.log("Requesting %s" % target) self.clipboard.request_contents(target, self.selection_value_callback, None) def selection_clear_cb(self, _clipboard, _data): #print("selection_clear_cb(%s, %s)", clipboard, data) self.log("Selection has been cleared") def selection_get_callback(self, _clipboard, selectiondata, _info, *_args): #log("selection_get_callback(%s, %s, %s, %s) targets=%s", # clipboard, selectiondata, info, args, selectiondata.get_targets()) value = self.value_entry.get_text() self.log("Answering selection request with value: '%s'" % self.ellipsis(value)) selectiondata.set("STRING", 8, value) def do_set_target(self, *_args): target = self.set_targets.get_active_text() self.log("Target set to %s" % target) self.clipboard.set_with_data([(target, 0, 0)], self.selection_get_callback, self.selection_clear_cb) def string_value_callback(self, _cb, value, *_args): if value is None: value = "" assert isinstance(value, str), "value is not a string!" self.log("Got string selection data: '%s'" % value) self.value_label.set_text(self.ellipsis(value)) def do_get_string(self, *_args): #self.log("do_get_string%s on %s.%s" % (args, self, self.clipboard)) self.clipboard.request_text(self.string_value_callback, None) def do_set_string(self, *_args): self.clipboard.set_text(self.ellipsis(self.value_entry.get_text())) def owner_changed(self, _cb, event): r = {} if not is_gtk3(): r = { gtk.gdk.OWNER_CHANGE_CLOSE: "close", gtk.gdk.OWNER_CHANGE_DESTROY: "destroy", gtk.gdk.OWNER_CHANGE_NEW_OWNER: "new owner", } owner = self.clipboard.get_owner() #print("xid=%s, owner=%s" % (self.value_entry.get_window().xid, event.owner)) weownit = (owner is not None) if weownit: owner_info = "(us)" else: owner_info = hex(event.owner) self.log("Owner changed, reason: %s, new owner=%s" % (r.get(event.reason, event.reason), owner_info))
class ClipboardInstance(object): def __init__(self, selection, _log): self.clipboard = GetClipboard(selection) self.selection = selection self._log = _log self.owned_label = label() self.get_targets = gtk.combo_box_new_text() self.get_targets.set_sensitive(False) self.get_targets.connect("changed", self.get_target_changed) self.set_targets = gtk.combo_box_new_text() self.set_targets.append_text("STRING") self.set_targets.append_text("UTF8_STRING") self.set_targets.set_active(0) self.set_targets.connect("changed", self.set_target_changed) self.value_label = label() self.value_entry = gtk.Entry() self.value_entry.set_max_length(100) self.value_entry.set_width_chars(32) self.clear_label_btn = gtk.Button("X") self.clear_label_btn.connect("clicked", self.clear_label) self.clear_entry_btn = gtk.Button("X") self.clear_entry_btn.connect("clicked", self.clear_entry) self.get_get_targets_btn = gtk.Button("Get Targets") self.get_get_targets_btn.connect("clicked", self.do_get_targets) self.get_target_btn = gtk.Button("Get Target") self.get_target_btn.connect("clicked", self.do_get_target) self.get_target_btn.set_sensitive(False) self.set_target_btn = gtk.Button("Set Target") self.set_target_btn.connect("clicked", self.do_set_target) self.get_string_btn = gtk.Button("Get String") self.get_string_btn.connect("clicked", self.do_get_string) self.set_string_btn = gtk.Button("Set String") self.set_string_btn.connect("clicked", self.do_set_string) self.clipboard.connect("owner-change", self.owner_changed) self.log("ready") def __repr__(self): return "ClipboardInstance(%s)" % self.selection def log(self, msg): self._log(self.selection, msg) def clear_entry(self, *args): self.value_entry.set_text("") def clear_label(self, *args): self.value_label.set_text("") def get_targets_callback(self, c, targets, *args): self.log("got targets: %s" % str(targets)) if hasattr(targets, "name"): self.log("target is atom: %s" % targets.name()) targets = [] filtered = [x for x in (targets or []) if x not in ("MULTIPLE", "TARGETS")] ct = self.get_targets.get_active_text() if not ct: #choose a good default target: for x in ("STRING", "UTF8_STRING"): if x in filtered: ct = x break self.get_targets.get_model().clear() self.get_targets.set_sensitive(True) i = 0 for t in filtered: self.get_targets.append_text(t) if t==ct: self.get_targets.set_active(i) i += 1 self.get_targets.show_all() def do_get_targets(self, *args): self.clipboard.request_targets(self.get_targets_callback, None) def get_target_changed(self, cb): target = self.get_targets.get_active_text() self.get_target_btn.set_sensitive(bool(target)) def set_target_changed(self, cb): pass def ellipsis(self, val): if len(val)>24: return val[:24]+".." return val def selection_value_callback(self, cb, selection_data, *args): #print("selection_value_callback(%s, %s, %s)" % (cb, selection_data, args)) try: if selection_data.data is None: s = "" else: s = "type=%s, format=%s, data=%s" % ( selection_data.type, selection_data.format, self.ellipsis(re.escape(selection_data.data))) except TypeError: try: s = self.ellipsis("\\".join([str(x) for x in bytearray(selection_data.data)])) except: s = "!ERROR! binary data?" self.log("Got selection data: '%s'" % s) self.value_label.set_text(s) def do_get_target(self, *args): self.clear_label() target = self.get_targets.get_active_text() self.log("Requesting %s" % target) self.clipboard.request_contents(target, self.selection_value_callback, None) def selection_clear_cb(self, clipboard, data): #print("selection_clear_cb(%s, %s)", clipboard, data) self.log("Selection has been cleared") def selection_get_callback(self, clipboard, selectiondata, info, *args): #print("selection_get_callback(%s, %s, %s, %s) targets=%s" % (clipboard, selectiondata, info, args, selectiondata.get_targets())) value = self.value_entry.get_text() self.log("Answering selection request with value: '%s'" % self.ellipsis(value)) selectiondata.set("STRING", 8, value) def do_set_target(self, *args): target = self.set_targets.get_active_text() self.log("Target set to %s" % target) self.clipboard.set_with_data([(target, 0, 0)], self.selection_get_callback, self.selection_clear_cb) def string_value_callback(self, cb, value, *args): if value is None: value = "" assert type(value)==str, "value is not a string!" self.log("Got string selection data: '%s'" % value) self.value_label.set_text(self.ellipsis(value)) def do_get_string(self, *args): #self.log("do_get_string%s on %s.%s" % (args, self, self.clipboard)) self.clipboard.request_text(self.string_value_callback, None) def do_set_string(self, *args): self.clipboard.set_text(self.ellipsis(self.value_entry.get_text())) def owner_changed(self, cb, event): r = {} if not is_gtk3(): r = {gtk.gdk.OWNER_CHANGE_CLOSE : "close", gtk.gdk.OWNER_CHANGE_DESTROY : "destroy", gtk.gdk.OWNER_CHANGE_NEW_OWNER : "new owner"} owner = self.clipboard.get_owner() #print("xid=%s, owner=%s" % (self.value_entry.get_window().xid, event.owner)) weownit = (owner is not None) if weownit: owner_info="(us)" else: owner_info = hex(event.owner) self.log("Owner changed, reason: %s, new owner=%s" % ( r.get(event.reason, event.reason), owner_info))