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# -*- coding: utf-8 -*- 

2# This file is part of Xpra. 

3# Copyright (C) 2010-2020 Antoine Martin <antoine@xpra.org> 

4# Copyright (C) 2008 Nathaniel Smith <njs@pobox.com> 

5# Xpra is released under the terms of the GNU GPL v2, or, at your option, any 

6# later version. See the file COPYING for details. 

7 

8import os.path 

9 

10from xpra.platform.features import CLIPBOARDS, CLIPBOARD_PREFERRED_TARGETS 

11from xpra.util import csv 

12from xpra.scripts.config import FALSE_OPTIONS 

13from xpra.server.mixins.stub_server_mixin import StubServerMixin 

14from xpra.log import Logger 

15 

16log = Logger("clipboard") 

17 

18 

19""" 

20Mixin for servers that handle clipboard synchronization. 

21""" 

22class ClipboardServer(StubServerMixin): 

23 

24 def __init__(self): 

25 self.clipboard = False 

26 self.clipboard_direction = "none" 

27 self.clipboard_filter_file = None 

28 self._clipboard_helper = None 

29 

30 def init(self, opts): 

31 self.clipboard = not (opts.clipboard or "").lower() in FALSE_OPTIONS 

32 self.clipboard_direction = opts.clipboard_direction 

33 self.clipboard_filter_file = opts.clipboard_filter_file 

34 

35 def setup(self): 

36 self.init_clipboard() 

37 

38 def cleanup(self): 

39 ch = self._clipboard_helper 

40 if ch: 

41 self._clipboard_helper = None 

42 ch.cleanup() 

43 

44 def cleanup_protocol(self, protocol): 

45 ch = self._clipboard_helper 

46 if ch and self._clipboard_client and self._clipboard_client.protocol==protocol: 

47 self._clipboard_client = None 

48 ch.client_reset() 

49 

50 

51 def parse_hello(self, ss, _caps, send_ui): 

52 if send_ui and self.clipboard: 

53 self.parse_hello_ui_clipboard(ss) 

54 

55 

56 def get_info(self, _proto) -> dict: 

57 if self._clipboard_helper is None: 

58 return {} 

59 ci = self._clipboard_helper.get_info() 

60 cc = self._clipboard_client 

61 if cc: 

62 ci["client"] = cc.uuid 

63 return {"clipboard" : ci} 

64 

65 

66 def get_server_features(self, server_source=None) -> dict: 

67 clipboard = self._clipboard_helper is not None 

68 log("clipboard_helper=%s, clipboard_client=%s, source=%s, clipboard=%s", 

69 self._clipboard_helper, self._clipboard_client, server_source, clipboard) 

70 if not clipboard: 

71 return {} 

72 f = { 

73 "clipboards" : self._clipboards, 

74 "clipboard-direction" : self.clipboard_direction, 

75 "clipboard" : { 

76 "" : True, 

77 "enable-selections" : True, #client check removed in v4 

78 "contents-slice-fix" : True, #fixed in v2.4 

79 "preferred-targets" : CLIPBOARD_PREFERRED_TARGETS, 

80 }, 

81 } 

82 if self._clipboard_helper: 

83 f["clipboard.loop-uuids"] = self._clipboard_helper.get_loop_uuids() 

84 return f 

85 

86 def init_clipboard(self): 

87 log("init_clipboard() enabled=%s, filter file=%s", self.clipboard, self.clipboard_filter_file) 

88 ### Clipboard handling: 

89 self._clipboard_helper = None 

90 self._clipboard_client = None 

91 self._clipboards = [] 

92 if not self.clipboard: 

93 return 

94 clipboard_filter_res = [] 

95 if self.clipboard_filter_file: 

96 if not os.path.exists(self.clipboard_filter_file): 

97 log.error("invalid clipboard filter file: '%s' does not exist - clipboard disabled!", 

98 self.clipboard_filter_file) 

99 return 

100 try: 

101 with open(self.clipboard_filter_file, "r" ) as f: 

102 for line in f: 

103 clipboard_filter_res.append(line.strip()) 

104 log("loaded %s regular expressions from clipboard filter file %s", 

105 len(clipboard_filter_res), self.clipboard_filter_file) 

106 except OSError: 

107 log.error("Error: reading clipboard filter file %s - clipboard disabled!", 

108 self.clipboard_filter_file, exc_info=True) 

109 return 

110 try: 

111 from xpra.platform.gui import get_clipboard_native_class 

112 clipboard_class = get_clipboard_native_class() 

113 assert clipboard_class, "no native clipboard support" 

114 parts = clipboard_class.split(".") 

115 mod = ".".join(parts[:-1]) 

116 module = __import__(mod, {}, {}, [parts[-1]]) 

117 ClipboardClass = getattr(module, parts[-1]) 

118 log("ClipboardClass for %s: %s", clipboard_class, ClipboardClass) 

119 kwargs = { 

120 "filters" : clipboard_filter_res, 

121 "can-send" : self.clipboard_direction in ("to-client", "both"), 

122 "can-receive" : self.clipboard_direction in ("to-server", "both"), 

123 } 

124 self._clipboard_helper = ClipboardClass(self.send_clipboard_packet, 

125 self.clipboard_progress, **kwargs) 

126 self._clipboard_helper.init_proxies_uuid() 

127 self._clipboards = CLIPBOARDS 

128 except Exception: 

129 #log("gdk clipboard helper failure", exc_info=True) 

130 log.error("Error: failed to setup clipboard helper", exc_info=True) 

131 self.clipboard = False 

132 

133 def parse_hello_ui_clipboard(self, ss): 

134 #take the clipboard if no-one else has it yet: 

135 if not getattr(ss, "clipboard_enabled", False): 

136 log("client does not support clipboard") 

137 return 

138 if not self._clipboard_helper: 

139 log("server does not support clipboard") 

140 return 

141 cc = self._clipboard_client 

142 if cc and not cc.is_closed(): 

143 log("another client already owns the clipboard") 

144 return 

145 self.set_clipboard_source(ss) 

146 

147 def set_clipboard_source(self, ss): 

148 if not getattr(ss, "clipboard_enabled", False): 

149 #don't use this client as clipboard source! 

150 #(its clipboard is disabled) 

151 return 

152 if self._clipboard_client==ss: 

153 return 

154 self._clipboard_client = ss 

155 ch = self._clipboard_helper 

156 log("client %s is the clipboard peer, helper=%s", ss, ch) 

157 if not ch: 

158 return 

159 if ss: 

160 log(" greedy=%s", ss.clipboard_greedy) 

161 log(" want targets=%s", ss.clipboard_want_targets) 

162 log(" server has selections: %s", csv(self._clipboards)) 

163 log(" client initial selections: %s", csv(ss.clipboard_client_selections)) 

164 ch.set_greedy_client(ss.clipboard_greedy) 

165 ch.set_want_targets_client(ss.clipboard_want_targets) 

166 ch.enable_selections(ss.clipboard_client_selections) 

167 ch.set_clipboard_contents_slice_fix(ss.clipboard_contents_slice_fix) 

168 ch.set_preferred_targets(ss.clipboard_preferred_targets) 

169 ch.send_tokens(ss.clipboard_client_selections) 

170 else: 

171 ch.enable_selections([]) 

172 

173 

174 def last_client_exited(self): 

175 ch = self._clipboard_helper 

176 if ch: 

177 ch.client_reset() 

178 

179 

180 def set_session_driver(self, source): 

181 self.set_clipboard_source(source) 

182 

183 

184 def _process_clipboard_packet(self, proto, packet): 

185 assert self.clipboard 

186 if self.readonly: 

187 return 

188 ss = self.get_server_source(proto) 

189 if not ss: 

190 #protocol has been dropped! 

191 return 

192 if self._clipboard_client!=ss: 

193 log("the clipboard packet '%s' does not come from the clipboard owner!", packet[0]) 

194 return 

195 if not ss.clipboard_enabled: 

196 #this can happen when we disable clipboard in the middle of transfers 

197 #(especially when there is a clipboard loop) 

198 log.warn("Warning: unexpected clipboard packet") 

199 log.warn(" clipboard is disabled for %r", ss.uuid) 

200 return 

201 ch = self._clipboard_helper 

202 assert ch, "received a clipboard packet but clipboard sharing is disabled" 

203 self.idle_add(ch.process_clipboard_packet, packet) 

204 

205 def _process_clipboard_enabled_status(self, proto, packet): 

206 assert self.clipboard 

207 if self.readonly: 

208 return 

209 clipboard_enabled = packet[1] 

210 ss = self.get_server_source(proto) 

211 if ss: 

212 self.set_clipboard_enabled_status(ss, clipboard_enabled) 

213 

214 def set_clipboard_enabled_status(self, ss, clipboard_enabled): 

215 ch = self._clipboard_helper 

216 if not ch: 

217 log.warn("Warning: client try to toggle clipboard-enabled status,") 

218 log.warn(" but we do not support clipboard at all! Ignoring it.") 

219 return 

220 cc = self._clipboard_client 

221 cc.clipboard_enabled = clipboard_enabled 

222 log("toggled clipboard to %s for %s", clipboard_enabled, ss.protocol) 

223 if cc!=ss or ss is None: 

224 log("received a request to change the clipboard status,") 

225 log(" but it does not come from the clipboard owner! Ignoring it.") 

226 log(" from %s", cc) 

227 log(" owner is %s", self._clipboard_client) 

228 return 

229 if not clipboard_enabled: 

230 ch.enable_selections([]) 

231 

232 def clipboard_progress(self, local_requests, _remote_requests): 

233 assert self.clipboard 

234 if self._clipboard_client: 

235 self._clipboard_client.send_clipboard_progress(local_requests) 

236 

237 def send_clipboard_packet(self, *parts): 

238 assert self.clipboard 

239 if self._clipboard_client: 

240 self._clipboard_client.send_clipboard(parts) 

241 

242 

243 def init_packet_handlers(self): 

244 if self.clipboard: 

245 self.add_packet_handler("set-clipboard-enabled", self._process_clipboard_enabled_status) 

246 for x in ( 

247 "token", "request", "contents", "contents-none", 

248 "pending-requests", "enable-selections", "loop-uuids", 

249 ): 

250 self.add_packet_handler("clipboard-%s" % x, self._process_clipboard_packet)