OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "remoting/host/x_server_clipboard.h" | |
6 | |
7 #include <X11/extensions/Xfixes.h> | |
8 | |
9 #include "base/callback.h" | |
10 #include "base/logging.h" | |
11 #include "remoting/base/constants.h" | |
12 | |
13 namespace { | |
14 | |
15 // Returns true if |a| is later than |b|, treating 0 as being a long time ago. | |
16 // This is different from the usual meaning of CurrentTime (which is defined | |
17 // to be 0). | |
18 bool IsTimeLater(Time a, Time b) { | |
19 if (a == 0) { | |
20 return false; | |
21 } | |
22 if (b == 0) { | |
23 return true; | |
24 } | |
25 return a > b; | |
26 } | |
27 | |
28 } // namespace | |
29 | |
30 namespace remoting { | |
31 | |
32 XServerClipboard::XServerClipboard() | |
33 : display_(NULL), | |
34 clipboard_window_(BadValue), | |
35 have_xfixes_(false), | |
36 xfixes_event_base_(-1), | |
37 xfixes_error_base_(-1), | |
38 clipboard_atom_(None), | |
39 large_selection_atom_(None), | |
40 selection_string_atom_(None), | |
41 targets_atom_(None), | |
42 timestamp_atom_(None), | |
43 utf8_string_atom_(None), | |
44 large_selection_property_(None), | |
45 current_selection_time_(0), | |
46 new_selection_(None), | |
47 getting_initial_selection_(true), | |
48 new_cut_text_(false), | |
49 had_valid_timestamp_(false) | |
50 { | |
51 } | |
52 | |
53 XServerClipboard::~XServerClipboard() { | |
54 } | |
55 | |
56 void XServerClipboard::Init(Display* display, | |
dcheng
2012/09/07 23:11:37
Nit: indent.
Lambros
2012/09/12 23:42:32
Done.
| |
57 const ClipboardChangedCallback& callback) { | |
58 display_ = display; | |
59 callback_ = callback; | |
60 | |
61 // If any of these X API calls fail, an X Error will be raised, crashing the | |
62 // process. This is unlikely to occur in practice, and even if it does, it | |
63 // would mean the X server is in a bad state, so it's not worth trying to | |
64 // trap such errors here. | |
65 clipboard_window_ = XCreateSimpleWindow(display_, | |
66 DefaultRootWindow(display_), | |
67 0, 0, 1, 1, // x, y, width, height | |
68 0, 0, 0); | |
69 clipboard_atom_ = XInternAtom(display_, "CLIPBOARD", False); | |
dcheng
2012/09/07 23:11:37
It might be worth it to use X11AtomCache here. erg
Lambros
2012/09/12 23:42:32
That's in ui/, which the Remoting Host currently d
| |
70 large_selection_atom_ = XInternAtom(display_, "INCR", False); | |
71 selection_string_atom_ = XInternAtom(display_, "SELECTION_STRING", False); | |
72 targets_atom_ = XInternAtom(display_, "TARGETS", False); | |
73 timestamp_atom_ = XInternAtom(display_, "TIMESTAMP", False); | |
74 utf8_string_atom_ = XInternAtom(display_, "UTF8_STRING", False); | |
75 | |
76 if (XFixesQueryExtension(display_, &xfixes_event_base_, | |
77 &xfixes_error_base_)) { | |
78 have_xfixes_ = true; | |
79 XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY, | |
80 XFixesSetSelectionOwnerNotifyMask); | |
81 XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_, | |
82 XFixesSetSelectionOwnerNotifyMask); | |
83 } else { | |
84 LOG(INFO) << "X server does not support XFixes."; | |
85 } | |
86 | |
87 GetSelections(None, 0); | |
88 } | |
89 | |
90 void XServerClipboard::SetClipboard(const std::string& mime_type, | |
91 const std::string& data) { | |
92 if (!display_) { | |
93 return; | |
94 } | |
95 | |
96 // Currently only UTF-8 is supported. | |
97 if (mime_type != kMimeTypeTextUtf8) { | |
98 return; | |
99 } | |
100 | |
101 data_ = data; | |
102 | |
103 OwnSelection(XA_PRIMARY, CurrentTime); | |
104 OwnSelection(clipboard_atom_, CurrentTime); | |
105 current_selection_time_ = CurrentTime; | |
106 } | |
107 | |
108 void XServerClipboard::ProcessXEvent(XEvent* event) { | |
109 if (event->xany.window != clipboard_window_) { | |
110 return; | |
111 } | |
112 | |
113 switch (event->type) { | |
114 case PropertyNotify: | |
115 OnPropertyNotify(event); | |
116 break; | |
117 case SelectionNotify: | |
118 OnSelectionNotify(event); | |
119 break; | |
120 case SelectionRequest: | |
121 OnSelectionRequest(event); | |
122 break; | |
123 default: | |
124 break; | |
125 } | |
126 | |
127 if (have_xfixes_) { | |
128 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) { | |
129 XFixesSelectionNotifyEvent* notify_event = | |
130 reinterpret_cast<XFixesSelectionNotifyEvent*>(event); | |
131 GetSelections(notify_event->selection, notify_event->selection_timestamp); | |
132 } | |
133 } | |
134 } | |
135 | |
136 void XServerClipboard::GetSelections(Atom selection, Time timestamp) { | |
137 // There are several different "targets" for which we request information: | |
138 // the timestamp, the target list and the string value of the selection. | |
139 // | |
140 // The process takes some time since there are several round-trips to the X | |
141 // server and the selection-owning app(s). We need to protect against | |
142 // restarting the process when we are in the middle of it since we store | |
143 // state between the various selectionNotify() callbacks we will get. We set | |
144 // a timer to flag when it is happening. Where the selection owner(s) are | |
145 // behaving themselves we cancel the timer in FinishGetSelections(). If they | |
146 // don't respond in reasonable time then we assume something went wrong and | |
147 // will allow a retry after the timer expires. | |
148 // | |
149 // We start out trying to establish the timestamp(s) on the selections to | |
150 // decide which one to retrieve. If a selection and timestamp are passed in | |
151 // then we always use that selection and treat it as new. Otherwise we | |
152 // request the timestamp(s) from the selection owner. If we get a valid | |
153 // timestamp and one of the selections is new, then we use that selection. | |
154 // If we get a valid timestamp but the selections are not new, then we just | |
155 // stop processing there. | |
156 // | |
157 // If we don't get a valid timestamp (for example an app like Mozilla which | |
158 // sets a zero timestamp, or an app like VMWare which doesn't support the | |
159 // timestamp target), then we can't use the timestamp optimisation so we have | |
160 // to get the actual value of the selection each time. We use either the | |
161 // PRIMARY or CLIPBOARD selection as appropriate. | |
162 // | |
163 // Before getting the value of the chosen selection, we request the list of | |
164 // targets supported for that selection. If UTF8_STRING is supported then we | |
165 // use that, otherwise we fall back to using STRING. | |
166 // | |
167 // As a final minor complication, when one of the selections is actually | |
168 // owned by us, we don't request the details for it. | |
169 if (!get_selections_time_.is_null() && | |
170 (base::TimeTicks::Now() - get_selections_time_) < | |
171 base::TimeDelta::FromSeconds(5)) { | |
172 return; | |
173 } | |
174 | |
175 if (selection != None) { | |
176 if (selection != clipboard_atom_ && selection != XA_PRIMARY) { | |
177 return; | |
178 } | |
179 } else { | |
180 selection = XA_PRIMARY; | |
181 } | |
182 | |
183 if (IsSelectionOwner(selection)) { | |
184 FinishGetSelections(); | |
185 } | |
186 | |
187 get_selections_time_ = base::TimeTicks::Now(); | |
188 | |
189 if (timestamp) { | |
190 current_selection_time_ = timestamp; | |
191 GetSelectionTargets(selection); | |
192 } else { | |
193 new_selection_ = 0; | |
194 had_valid_timestamp_ = false; | |
195 GetSelectionTimestamp(selection, CurrentTime); | |
196 } | |
197 } | |
198 | |
199 void XServerClipboard::FinishGetSelections() { | |
200 getting_initial_selection_ = false; | |
201 get_selections_time_ = base::TimeTicks(); | |
202 } | |
203 | |
204 void XServerClipboard::OnPropertyNotify(XEvent* event) { | |
205 if (large_selection_property_ != None && | |
206 event->xproperty.atom == large_selection_property_ && | |
207 event->xproperty.state == PropertyNewValue) { | |
208 Atom type; | |
209 int format; | |
210 unsigned long item_count, after; | |
211 unsigned char *data; | |
212 XGetWindowProperty(display_, clipboard_window_, large_selection_property_, | |
213 0, ~0L, True, AnyPropertyType, &type, &format, | |
214 &item_count, &after, &data); | |
215 if (type != None) { | |
216 XFree(data); | |
217 | |
218 // If the property is zero-length then the large transfer is complete. | |
219 if (item_count == 0) { | |
220 large_selection_property_ = None; | |
221 } | |
222 } | |
223 } | |
224 } | |
225 | |
226 void XServerClipboard::OnSelectionNotify(XEvent* event) { | |
227 if (event->xselection.property != None) { | |
228 Atom type; | |
229 int format; | |
230 unsigned long item_count, after; | |
231 unsigned char *data; | |
232 XGetWindowProperty(display_, clipboard_window_, | |
233 event->xselection.property, 0, ~0L, True, | |
234 AnyPropertyType, &type, &format, | |
235 &item_count, &after, &data); | |
236 if (type == large_selection_atom_) { | |
237 // Large selection - just read and ignore these for now. | |
238 large_selection_property_ = event->xselection.property; | |
239 } else { | |
240 // Standard selection - call the selection notifier. | |
241 large_selection_property_ = None; | |
242 if (type != None) { | |
243 DoSelectionNotify(&event->xselection, type, format, item_count, data); | |
244 XFree(data); | |
245 } | |
246 } | |
247 } | |
248 DoSelectionNotify(&event->xselection, 0, 0, 0, 0); | |
249 } | |
250 | |
251 void XServerClipboard::OnSelectionRequest(XEvent* event) { | |
252 XSelectionEvent selection_event; | |
253 selection_event.type = SelectionNotify; | |
254 selection_event.display = event->xselectionrequest.display; | |
255 selection_event.requestor = event->xselectionrequest.requestor; | |
256 selection_event.selection = event->xselectionrequest.selection; | |
257 selection_event.time = event->xselectionrequest.time; | |
258 selection_event.target = event->xselectionrequest.target; | |
259 if (event->xselectionrequest.property == None) { | |
260 event->xselectionrequest.property = event->xselectionrequest.target; | |
261 } | |
262 if (!IsSelectionOwner(selection_event.selection)) { | |
263 selection_event.property = None; | |
264 } else { | |
265 selection_event.property = event->xselectionrequest.property; | |
266 if (selection_event.target == targets_atom_) { | |
267 Atom targets[3]; | |
268 targets[0] = timestamp_atom_; | |
269 targets[1] = utf8_string_atom_; | |
270 targets[2] = XA_STRING; | |
271 XChangeProperty(display_, selection_event.requestor, | |
272 selection_event.property, XA_ATOM, 32, PropModeReplace, | |
273 reinterpret_cast<unsigned char*>(targets), 3); | |
274 } else if (selection_event.target == timestamp_atom_) { | |
275 Time time = selection_own_time_[selection_event.selection]; | |
276 XChangeProperty(display_, selection_event.requestor, | |
277 selection_event.property, XA_INTEGER, 32, | |
278 PropModeReplace, reinterpret_cast<unsigned char*>(&time), | |
279 1); | |
280 } else if (selection_event.target == utf8_string_atom_ || | |
281 selection_event.target == XA_STRING) { | |
282 if (!data_.empty()) { | |
283 XChangeProperty(display_, selection_event.requestor, | |
284 selection_event.property, selection_event.target, 8, | |
285 PropModeReplace, | |
286 reinterpret_cast<unsigned char*>( | |
287 const_cast<char*>(data_.data())), | |
288 data_.size()); | |
289 } | |
290 } | |
291 } | |
292 XSendEvent(display_, selection_event.requestor, False, 0, | |
293 reinterpret_cast<XEvent*>(&selection_event)); | |
294 } | |
295 | |
296 void XServerClipboard::DoSelectionNotify(XSelectionEvent* event, | |
297 Atom type, | |
298 int format, | |
299 int item_count, | |
300 void* data) { | |
301 bool finished = false; | |
302 | |
303 if (event->target == timestamp_atom_) { | |
304 finished = GotSelectionTimestamp(event, format, item_count, data); | |
305 } else if (event->target == targets_atom_) { | |
306 finished = GotSelectionTargets(event, format, item_count, data); | |
307 } else if (event->target == utf8_string_atom_ || | |
308 event->target == XA_STRING) { | |
309 finished = GotSelectionString(event, format, item_count, data); | |
310 } | |
311 | |
312 if (finished) { | |
313 FinishGetSelections(); | |
314 } | |
315 } | |
316 | |
317 bool XServerClipboard::GotSelectionTimestamp(XSelectionEvent* event, | |
318 int format, | |
319 int item_count, | |
320 void* data) { | |
321 if (event->property == timestamp_atom_) { | |
322 if (data && format == 32 && item_count == 1) { | |
323 Time selection_time = *static_cast<Time*>(data); | |
324 if (selection_time != 0) { | |
325 had_valid_timestamp_ = true; | |
326 } | |
327 if (IsTimeLater(selection_time, current_selection_time_)) { | |
328 current_selection_time_ = selection_time; | |
329 new_selection_ = event->selection; | |
330 } | |
331 } | |
332 } | |
333 | |
334 if (event->selection == XA_PRIMARY) { | |
335 if (!IsSelectionOwner(clipboard_atom_)) { | |
336 GetSelectionTimestamp(clipboard_atom_, event->time); | |
337 return false; | |
338 } | |
339 } else if (event->selection != clipboard_atom_) { | |
340 return false; | |
341 } | |
342 | |
343 if (getting_initial_selection_ && had_valid_timestamp_) { | |
344 return true; | |
345 } | |
346 | |
347 if (new_selection_) { | |
348 GetSelectionTargets(new_selection_); | |
349 } else if (!had_valid_timestamp_) { | |
350 GetSelectionTargets(XA_PRIMARY); | |
351 } else { | |
352 return true; | |
353 } | |
354 return false; | |
355 } | |
356 | |
357 bool XServerClipboard::GotSelectionTargets(XSelectionEvent* event, | |
358 int format, | |
359 int item_count, | |
360 void* data) { | |
361 if (event->property == targets_atom_) { | |
362 if (data && format == 32) { | |
363 // The XGetWindowProperty man-page specifies that the returned | |
364 // property data will be an array of |long|s in the case where | |
365 // |format| == 32. On a 64-bit system, each item gets padded to | |
366 // 8 bytes, the size of a |long|. | |
367 const long* targets = static_cast<const long*>(data); | |
368 for (int i = 0; i < item_count; i++) { | |
369 if (targets[i] == static_cast<long>(utf8_string_atom_)) { | |
370 GetSelectionString(event->selection, utf8_string_atom_); | |
371 return false; | |
372 } | |
373 } | |
374 } | |
375 } | |
376 GetSelectionString(event->selection, XA_STRING); | |
377 return false; | |
378 } | |
379 | |
380 bool XServerClipboard::GotSelectionString(XSelectionEvent* event, | |
381 int format, | |
382 int item_count, | |
383 void* data) { | |
384 if (event->property != selection_string_atom_ || !data || format != 8) { | |
385 return true; | |
386 } | |
387 | |
388 std::string text(static_cast<char*>(data), item_count); | |
389 | |
390 if (event->target == XA_STRING || event->target == utf8_string_atom_) { | |
391 GotCutTextUtf8(text); | |
392 } | |
393 return true; | |
394 } | |
395 | |
396 void XServerClipboard::GotCutTextUtf8(const std::string& text) { | |
397 data_ = text; | |
398 new_cut_text_ = false; | |
399 if (!getting_initial_selection_) { | |
400 callback_.Run(kMimeTypeTextUtf8, data_); | |
401 } | |
402 } | |
403 | |
404 bool XServerClipboard::IsSelectionOwner(Atom selection) { | |
405 return selections_owned_.find(selection) != selections_owned_.end(); | |
406 } | |
407 | |
408 void XServerClipboard::GetSelectionTimestamp(Atom selection, Time time) { | |
409 XConvertSelection(display_, selection, timestamp_atom_, timestamp_atom_, | |
410 clipboard_window_, time); | |
411 } | |
412 | |
413 void XServerClipboard::GetSelectionTargets(Atom selection) { | |
414 XConvertSelection(display_, selection, targets_atom_, targets_atom_, | |
415 clipboard_window_, CurrentTime); | |
416 } | |
417 | |
418 void XServerClipboard::GetSelectionString(Atom selection, Atom target) { | |
419 XConvertSelection(display_, selection, target, selection_string_atom_, | |
420 clipboard_window_, CurrentTime); | |
421 } | |
422 | |
423 void XServerClipboard::OwnSelection(Atom selection, Time time) { | |
424 XSetSelectionOwner(display_, selection, clipboard_window_, time); | |
425 if (XGetSelectionOwner(display_, selection) == clipboard_window_) { | |
426 selections_owned_.insert(selection); | |
427 selection_own_time_[selection] = time; | |
428 } else { | |
429 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection; | |
430 } | |
431 } | |
432 | |
433 } // namespace remoting | |
OLD | NEW |