Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/x11/gtk_x11/selection.py : 70%
Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of Xpra.
2# Copyright (C) 2008, 2009 Nathaniel Smith <njs@pobox.com>
3# Copyright (C) 2018-2019 Antoine Martin <antoine@xpra.org>
4# Xpra is released under the terms of the GNU GPL v2, or, at your option, any
5# later version. See the file COPYING for details.
7# According to ICCCM 2.8/4.3, a window manager for screen N is a client which
8# acquires the selection WM_S<N>. If another client already has this
9# selection, we can either abort or steal it. Once we have it, if someone
10# else steals it, then we should exit.
12import sys
13from struct import unpack, calcsize
14from gi.repository import GObject, Gtk, Gdk, GLib
16from xpra.gtk_common.gobject_util import no_arg_signal, one_arg_signal
17from xpra.gtk_common.error import xsync, XError
18from xpra.x11.bindings.window_bindings import constants, X11WindowBindings #@UnresolvedImport
19from xpra.x11.gtk_x11.gdk_bindings import (
20 get_xatom, #@UnresolvedImport
21 get_pywindow, #@UnresolvedImport
22 add_event_receiver, #@UnresolvedImport
23 remove_event_receiver, #@UnresolvedImport
24 )
25from xpra.exit_codes import EXIT_TIMEOUT
26from xpra.util import envint
27from xpra.log import Logger
29log = Logger("x11", "util")
31SELECTION_EXIT_TIMEOUT = envint("XPRA_SELECTION_EXIT_TIMEOUT", 20)
33StructureNotifyMask = constants["StructureNotifyMask"]
34XNone = constants["XNone"]
37class AlreadyOwned(Exception):
38 pass
40class ManagerSelection(GObject.GObject):
41 __gsignals__ = {
42 "selection-lost": no_arg_signal,
44 "xpra-destroy-event": one_arg_signal,
45 }
47 def __str__(self):
48 return "ManagerSelection(%s)" % self.atom
50 def __init__(self, selection):
51 GObject.GObject.__init__(self)
52 self.atom = selection
53 atom = Gdk.Atom.intern(selection, False)
54 self.clipboard = Gtk.Clipboard.get(atom)
55 self._xwindow = None
56 self.exit_timer = None
58 def _owner(self):
59 return X11WindowBindings().XGetSelectionOwner(self.atom)
61 def owned(self):
62 "Returns True if someone owns the given selection."
63 return self._owner() != XNone
65 # If the selection is already owned, then raise AlreadyOwned rather
66 # than stealing it.
67 IF_UNOWNED = "if_unowned"
68 # If the selection is already owned, then steal it, and then block until
69 # the previous owner has signaled that they are done cleaning up.
70 FORCE = "force"
71 # If the selection is already owned, then steal it and return immediately.
72 # Created for the use of tests.
73 FORCE_AND_RETURN = "force_and_return"
74 def acquire(self, when):
75 old_owner = self._owner()
76 if when is self.IF_UNOWNED and old_owner != XNone:
77 raise AlreadyOwned
79 #we can only set strings with GTK3,
80 # we should try to be compliant with ICCCM version 2.0 (see section 4.3)
81 # and use this format instead:
82 # outdata.set("INTEGER", 32, pack("@ii", 2, 0))
83 thestring = "VERSION"
84 self.clipboard.set_text(thestring, len(thestring))
86 # Having acquired the selection, we have to announce our existence
87 # (ICCCM 2.8, still). The details here probably don't matter too
88 # much; I've never heard of an app that cares about these messages,
89 # and metacity actually gets the format wrong in several ways (no
90 # MANAGER or owner_window atoms). But might as well get it as right
91 # as possible.
93 # To announce our existence, we need:
94 # -- the timestamp we arrived at
95 # -- the manager selection atom
96 # -- the window that registered the selection
97 # Of course, because Gtk is doing so much magic for us, we have to do
98 # some weird tricks to get at these.
100 # Ask ourselves when we acquired the selection:
101 timestamp_atom = Gdk.Atom.intern("TIMESTAMP", False)
102 contents = self.clipboard.wait_for_contents(timestamp_atom)
103 ts_data = contents.get_data()
104 log("ManagerSelection.acquire(%s) %s.wait_for_contents(%s)=%s",
105 when, self.clipboard, timestamp_atom, ts_data)
107 #data is a timestamp, X11 datatype is Time which is CARD32,
108 #(which is 64 bits on 64-bit systems!)
109 Lsize = calcsize("@L")
110 if len(ts_data)==Lsize:
111 ts_num = unpack("@L", ts_data[:Lsize])[0]
112 else:
113 ts_num = 0 #CurrentTime
114 log.warn("invalid data for 'TIMESTAMP': %s", ([hex(ord(x)) for x in ts_data]))
115 log("selection timestamp(%s)=%s", ts_data, ts_num)
116 # Calculate the X atom for this selection:
117 selection_xatom = get_xatom(self.atom)
118 # Ask X what window we used:
119 self._xwindow = X11WindowBindings().XGetSelectionOwner(self.atom)
121 root = self.clipboard.get_display().get_default_screen().get_root_window()
122 xid = root.get_xid()
123 X11WindowBindings().sendClientMessage(xid, xid, False, StructureNotifyMask,
124 "MANAGER",
125 ts_num, selection_xatom, self._xwindow)
127 if old_owner != XNone and when is self.FORCE:
128 # Block in a recursive mainloop until the previous owner has
129 # cleared out.
130 try:
131 with xsync:
132 window = get_pywindow(self.clipboard, old_owner)
133 window.set_events(window.get_events() | Gdk.EventMask.STRUCTURE_MASK)
134 log("got window")
135 except XError:
136 log("Previous owner is already gone? not blocking", exc_info=True)
137 else:
138 log("Waiting for previous owner to exit...")
139 add_event_receiver(window, self)
140 self.exit_timer = GLib.timeout_add(SELECTION_EXIT_TIMEOUT*1000, self.exit_timeout)
141 Gtk.main()
142 if self.exit_timer:
143 GLib.source_remove(self.exit_timer)
144 self.exit_timer = None
145 log("...they did.")
146 window = get_pywindow(self.clipboard, self._xwindow)
147 window.set_title("Xpra-ManagerSelection-%s" % self.atom)
148 self.clipboard.connect("owner-change", self._owner_change)
150 def exit_timeout(self):
151 self.exit_timer = None
152 log.error("selection timeout")
153 log.error(" the current owner did not exit")
154 sys.exit(EXIT_TIMEOUT)
156 def _owner_change(self, clipboard, event):
157 log("owner_change(%s, %s)", clipboard, event)
158 if str(event.selection)!=self.atom:
159 #log("_owner_change(..) not our selection: %s vs %s", event.selection, self.atom)
160 return
161 if event.owner:
162 owner = event.owner.get_xid()
163 if owner==self._xwindow:
164 log("_owner_change(..) we still own %s", event.selection)
165 return
166 if self._xwindow:
167 self._xwindow = None
168 self.emit("selection-lost")
170 def do_xpra_destroy_event(self, event):
171 remove_event_receiver(event.window, self)
172 Gtk.main_quit()
174 def window(self):
175 if self._xwindow is None:
176 return None
177 return get_pywindow(self.clipboard, self._xwindow)
179GObject.type_register(ManagerSelection)