Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(562)

Side by Side Diff: remoting/host/x_server_clipboard.cc

Issue 10909133: Implement clipboard for Chromoting Linux hosts. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Created 8 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « remoting/host/x_server_clipboard.h ('k') | remoting/remoting.gyp » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(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
OLDNEW
« no previous file with comments | « remoting/host/x_server_clipboard.h ('k') | remoting/remoting.gyp » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698