Coverage for /home/antoine/projects/xpra-git/dist/python3/lib64/python/xpra/server/mixins/clipboard_server.py : 47%
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.
8import os.path
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
16log = Logger("clipboard")
19"""
20Mixin for servers that handle clipboard synchronization.
21"""
22class ClipboardServer(StubServerMixin):
24 def __init__(self):
25 self.clipboard = False
26 self.clipboard_direction = "none"
27 self.clipboard_filter_file = None
28 self._clipboard_helper = None
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
35 def setup(self):
36 self.init_clipboard()
38 def cleanup(self):
39 ch = self._clipboard_helper
40 if ch:
41 self._clipboard_helper = None
42 ch.cleanup()
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()
51 def parse_hello(self, ss, _caps, send_ui):
52 if send_ui and self.clipboard:
53 self.parse_hello_ui_clipboard(ss)
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}
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
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
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)
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([])
174 def last_client_exited(self):
175 ch = self._clipboard_helper
176 if ch:
177 ch.client_reset()
180 def set_session_driver(self, source):
181 self.set_clipboard_source(source)
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)
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)
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([])
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)
237 def send_clipboard_packet(self, *parts):
238 assert self.clipboard
239 if self._clipboard_client:
240 self._clipboard_client.send_clipboard(parts)
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)