Hide keyboard shortcuts

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. 

6 

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. 

11 

12import sys 

13from struct import unpack, calcsize 

14from gi.repository import GObject, Gtk, Gdk, GLib 

15 

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 

28 

29log = Logger("x11", "util") 

30 

31SELECTION_EXIT_TIMEOUT = envint("XPRA_SELECTION_EXIT_TIMEOUT", 20) 

32 

33StructureNotifyMask = constants["StructureNotifyMask"] 

34XNone = constants["XNone"] 

35 

36 

37class AlreadyOwned(Exception): 

38 pass 

39 

40class ManagerSelection(GObject.GObject): 

41 __gsignals__ = { 

42 "selection-lost": no_arg_signal, 

43 

44 "xpra-destroy-event": one_arg_signal, 

45 } 

46 

47 def __str__(self): 

48 return "ManagerSelection(%s)" % self.atom 

49 

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 

57 

58 def _owner(self): 

59 return X11WindowBindings().XGetSelectionOwner(self.atom) 

60 

61 def owned(self): 

62 "Returns True if someone owns the given selection." 

63 return self._owner() != XNone 

64 

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 

78 

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)) 

85 

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. 

92 

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. 

99 

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) 

106 

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) 

120 

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) 

126 

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) 

149 

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) 

155 

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") 

169 

170 def do_xpra_destroy_event(self, event): 

171 remove_event_receiver(event.window, self) 

172 Gtk.main_quit() 

173 

174 def window(self): 

175 if self._xwindow is None: 

176 return None 

177 return get_pywindow(self.clipboard, self._xwindow) 

178 

179GObject.type_register(ManagerSelection)