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

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

Issue 10909133: Implement clipboard for Chromoting Linux hosts. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Stomped another MessagePumpLibevent 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
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/linux/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).
Wez 2012/09/17 22:13:29 As you comment, 0 is normally CurrentTime; isn't i
Lambros 2012/09/19 23:58:19 This is only called from one place, where 0 means
Wez 2012/09/20 23:37:46 And are X timestamps guaranteed never to wrap thro
18 bool IsTimeLater(Time a, Time b) {
Wez 2012/09/17 22:13:29 nit: IsTimeLaterThan But... this function is just
Lambros 2012/09/19 23:58:19 Done
19 if (a == 0) {
20 return false;
21 }
22 if (b == 0) {
Wez 2012/09/17 22:13:29 nit: You don't need this clause, since at this poi
Lambros 2012/09/19 23:58:19 I meant to test (a - b) > 0, to deal with wrap-aro
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 XServerClipboard::~XServerClipboard() {
53 }
54
55 void XServerClipboard::Init(Display* display,
56 const ClipboardChangedCallback& callback) {
57 display_ = display;
58 callback_ = callback;
59
60 // If any of these X API calls fail, an X Error will be raised, crashing the
61 // process. This is unlikely to occur in practice, and even if it does, it
62 // would mean the X server is in a bad state, so it's not worth trying to
63 // trap such errors here.
Wez 2012/09/17 22:13:29 Jamie's XRANDR CL adds ScopedXErrorHandler; use th
Lambros 2012/09/19 23:58:19 Added TODO. X Error handlers are tricky, since (I
Wez 2012/09/20 23:37:46 That's decidedly unhelpful! Would we be better off
64 clipboard_window_ = XCreateSimpleWindow(display_,
65 DefaultRootWindow(display_),
66 0, 0, 1, 1, // x, y, width, height
67 0, 0, 0);
68
69 // TODO(lambroslambrou): Use ui::X11AtomCache for this.
Wez 2012/09/17 22:13:29 nit: Why are you not already using it in this CL?
Lambros 2012/09/19 23:58:19 Clarified reason for not using it right now.
70 const int kNumAtoms = 6;
71 const char* names[kNumAtoms] = {
72 "CLIPBOARD",
73 "INCR",
74 "SELECTION_STRING",
75 "TARGETS",
76 "TIMESTAMP",
77 "UTF8_STRING" };
78 Atom atoms[kNumAtoms];
79 if (XInternAtoms(display_, const_cast<char**>(names), kNumAtoms, False,
80 atoms)) {
81 clipboard_atom_ = atoms[0];
82 large_selection_atom_ = atoms[1];
83 selection_string_atom_ = atoms[2];
84 targets_atom_ = atoms[3];
85 timestamp_atom_ = atoms[4];
86 utf8_string_atom_ = atoms[5];
87 } else {
88 LOG(ERROR) << "XInternAtoms failed";
89 }
90
91 if (XFixesQueryExtension(display_, &xfixes_event_base_,
92 &xfixes_error_base_)) {
93 have_xfixes_ = true;
94 XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY,
95 XFixesSetSelectionOwnerNotifyMask);
96 XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_,
97 XFixesSetSelectionOwnerNotifyMask);
98 } else {
99 LOG(INFO) << "X server does not support XFixes.";
100 }
101
102 GetSelections(None, 0);
103 }
104
105 void XServerClipboard::SetClipboard(const std::string& mime_type,
106 const std::string& data) {
107 if (!display_) {
Wez 2012/09/17 22:13:29 Under what circumstances is it ever valid to call
Lambros 2012/09/19 23:58:19 Done.
108 return;
109 }
110
111 // Currently only UTF-8 is supported.
112 if (mime_type != kMimeTypeTextUtf8) {
113 return;
114 }
115
116 data_ = data;
117
118 OwnSelection(XA_PRIMARY, CurrentTime);
Wez 2012/09/17 22:13:29 You're calling XSetSelectionOwner() with CurrentTi
Lambros 2012/09/19 23:58:19 I don't think so - the man-page and this web-site
119 OwnSelection(clipboard_atom_, CurrentTime);
120 current_selection_time_ = CurrentTime;
Wez 2012/09/17 22:13:29 CurrentTime == 0, so this doesn't do what it looks
Lambros 2012/09/19 23:58:19 You're right, this will cause any other timestamp
Wez 2012/09/20 23:37:46 Leaving the most-recently-seen time is probably fi
121 }
122
123 void XServerClipboard::ProcessXEvent(XEvent* event) {
124 if (event->xany.window != clipboard_window_) {
125 return;
126 }
127
128 switch (event->type) {
129 case PropertyNotify:
130 OnPropertyNotify(event);
131 break;
132 case SelectionNotify:
133 OnSelectionNotify(event);
134 break;
135 case SelectionRequest:
136 OnSelectionRequest(event);
137 break;
138 default:
139 break;
140 }
141
142 if (have_xfixes_) {
143 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
144 XFixesSelectionNotifyEvent* notify_event =
145 reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
146 GetSelections(notify_event->selection, notify_event->selection_timestamp);
Wez 2012/09/17 22:13:29 |selection_timestamp| is the timestamp used to gen
Lambros 2012/09/19 23:58:19 I think the X Server is supposed to substitute the
147 }
148 }
149 }
150
151 void XServerClipboard::GetSelections(Atom selection, Time timestamp) {
152 // There are several different "targets" for which we request information:
153 // the timestamp, the target list and the string value of the selection.
Wez 2012/09/17 22:13:29 I don't understand this sentence; are you saying t
Lambros 2012/09/19 23:58:19 Clarified.
154 //
155 // The process takes some time since there are several round-trips to the X
156 // server and the selection-owning app(s). We need to protect against
157 // restarting the process when we are in the middle of it since we store
158 // state between the various selectionNotify() callbacks we will get. We set
Wez 2012/09/17 22:13:29 Where are we storing this problem state?
159 // a timer to flag when it is happening. Where the selection owner(s) are
160 // behaving themselves we cancel the timer in FinishGetSelections(). If they
161 // don't respond in reasonable time then we assume something went wrong and
162 // will allow a retry after the timer expires.
Wez 2012/09/17 22:13:29 How does this resolve the issue of leaving stale s
Lambros 2012/09/19 23:58:19 I've rephrased this, ptal.
163 //
164 // We start out trying to establish the timestamp(s) on the selections to
165 // decide which one to retrieve. If a selection and timestamp are passed in
Wez 2012/09/17 22:13:29 A timestamp is always passed it; is there some spe
Lambros 2012/09/19 23:58:19 Clarified.
166 // then we always use that selection and treat it as new. Otherwise we
Wez 2012/09/17 22:13:29 You haven't introduced a concept of "new" up to th
167 // request the timestamp(s) from the selection owner. If we get a valid
168 // timestamp and one of the selections is new, then we use that selection.
Wez 2012/09/17 22:13:29 What does it mean for a selection to be "new"?
Lambros 2012/09/19 23:58:19 Newer than |current_selection_time_|
169 // If we get a valid timestamp but the selections are not new, then we just
170 // stop processing there.
171 //
172 // If we don't get a valid timestamp (for example an app like Mozilla which
173 // sets a zero timestamp, or an app like VMWare which doesn't support the
174 // timestamp target), then we can't use the timestamp optimisation so we have
175 // to get the actual value of the selection each time. We use either the
176 // PRIMARY or CLIPBOARD selection as appropriate.
177 //
178 // Before getting the value of the chosen selection, we request the list of
179 // targets supported for that selection. If UTF8_STRING is supported then we
180 // use that, otherwise we fall back to using STRING.
Wez 2012/09/17 22:13:29 As with the other parts of this big block of comme
Lambros 2012/09/19 23:58:19 Done.
Lambros 2012/09/19 23:58:19 Done.
181 //
182 // As a final minor complication, when one of the selections is actually
183 // owned by us, we don't request the details for it.
184 if (!get_selections_time_.is_null() &&
Wez 2012/09/17 22:13:29 Add a comment, separate from this big block commen
Lambros 2012/09/19 23:58:19 Done.
185 (base::TimeTicks::Now() - get_selections_time_) <
186 base::TimeDelta::FromSeconds(5)) {
187 return;
188 }
189
190 if (selection != None) {
191 if (selection != clipboard_atom_ && selection != XA_PRIMARY) {
192 return;
193 }
194 } else {
195 selection = XA_PRIMARY;
196 }
197
198 if (IsSelectionOwner(selection)) {
Wez 2012/09/17 22:13:29 Add a comment to this to indicate why we ignore se
Lambros 2012/09/19 23:58:19 Done.
199 FinishGetSelections();
200 }
201
202 get_selections_time_ = base::TimeTicks::Now();
203
204 if (timestamp) {
205 current_selection_time_ = timestamp;
206 GetSelectionTargets(selection);
207 } else {
208 new_selection_ = 0;
209 had_valid_timestamp_ = false;
210 GetSelectionTimestamp(selection, CurrentTime);
211 }
212 }
213
214 void XServerClipboard::FinishGetSelections() {
215 getting_initial_selection_ = false;
216 get_selections_time_ = base::TimeTicks();
217 }
218
219 void XServerClipboard::OnPropertyNotify(XEvent* event) {
220 if (large_selection_property_ != None &&
221 event->xproperty.atom == large_selection_property_ &&
222 event->xproperty.state == PropertyNewValue) {
223 Atom type;
224 int format;
225 unsigned long item_count, after;
226 unsigned char *data;
227 XGetWindowProperty(display_, clipboard_window_, large_selection_property_,
228 0, ~0L, True, AnyPropertyType, &type, &format,
229 &item_count, &after, &data);
230 if (type != None) {
231 XFree(data);
Wez 2012/09/17 22:13:29 Should you actually be doing something with this d
Lambros 2012/09/19 23:58:19 Added TODO for large transfers.
232
233 // If the property is zero-length then the large transfer is complete.
234 if (item_count == 0) {
235 large_selection_property_ = None;
236 }
237 }
238 }
239 }
240
241 void XServerClipboard::OnSelectionNotify(XEvent* event) {
242 if (event->xselection.property != None) {
243 Atom type;
244 int format;
245 unsigned long item_count, after;
246 unsigned char *data;
247 XGetWindowProperty(display_, clipboard_window_,
248 event->xselection.property, 0, ~0L, True,
249 AnyPropertyType, &type, &format,
250 &item_count, &after, &data);
251 if (type == large_selection_atom_) {
252 // Large selection - just read and ignore these for now.
253 large_selection_property_ = event->xselection.property;
254 } else {
255 // Standard selection - call the selection notifier.
256 large_selection_property_ = None;
257 if (type != None) {
258 DoSelectionNotify(&event->xselection, type, format, item_count, data);
259 XFree(data);
260 }
261 }
262 }
263 DoSelectionNotify(&event->xselection, 0, 0, 0, 0);
Wez 2012/09/17 22:13:29 Are you missing a closing } after this?
Wez 2012/09/17 22:13:29 What does this DoSelectionNotify() call mean? Shou
Lambros 2012/09/19 23:58:19 No, this is an indentation fail! Fixed.
Lambros 2012/09/19 23:58:19 Good catch! Added missing return.
264 }
265
266 void XServerClipboard::OnSelectionRequest(XEvent* event) {
267 XSelectionEvent selection_event;
268 selection_event.type = SelectionNotify;
269 selection_event.display = event->xselectionrequest.display;
270 selection_event.requestor = event->xselectionrequest.requestor;
271 selection_event.selection = event->xselectionrequest.selection;
272 selection_event.time = event->xselectionrequest.time;
273 selection_event.target = event->xselectionrequest.target;
274 if (event->xselectionrequest.property == None) {
275 event->xselectionrequest.property = event->xselectionrequest.target;
276 }
277 if (!IsSelectionOwner(selection_event.selection)) {
278 selection_event.property = None;
279 } else {
280 selection_event.property = event->xselectionrequest.property;
281 if (selection_event.target == targets_atom_) {
282 Atom targets[3];
283 targets[0] = timestamp_atom_;
284 targets[1] = utf8_string_atom_;
285 targets[2] = XA_STRING;
286 XChangeProperty(display_, selection_event.requestor,
287 selection_event.property, XA_ATOM, 32, PropModeReplace,
288 reinterpret_cast<unsigned char*>(targets), 3);
289 } else if (selection_event.target == timestamp_atom_) {
290 Time time = selection_own_time_[selection_event.selection];
291 XChangeProperty(display_, selection_event.requestor,
292 selection_event.property, XA_INTEGER, 32,
293 PropModeReplace, reinterpret_cast<unsigned char*>(&time),
294 1);
295 } else if (selection_event.target == utf8_string_atom_ ||
296 selection_event.target == XA_STRING) {
297 if (!data_.empty()) {
298 XChangeProperty(display_, selection_event.requestor,
299 selection_event.property, selection_event.target, 8,
300 PropModeReplace,
301 reinterpret_cast<unsigned char*>(
302 const_cast<char*>(data_.data())),
303 data_.size());
304 }
305 }
306 }
307 XSendEvent(display_, selection_event.requestor, False, 0,
308 reinterpret_cast<XEvent*>(&selection_event));
309 }
310
311 void XServerClipboard::DoSelectionNotify(XSelectionEvent* event,
312 Atom type,
313 int format,
314 int item_count,
315 void* data) {
316 bool finished = false;
317
318 if (event->target == timestamp_atom_) {
319 finished = GotSelectionTimestamp(event, format, item_count, data);
320 } else if (event->target == targets_atom_) {
321 finished = GotSelectionTargets(event, format, item_count, data);
322 } else if (event->target == utf8_string_atom_ ||
323 event->target == XA_STRING) {
324 finished = GotSelectionString(event, format, item_count, data);
325 }
326
327 if (finished) {
328 FinishGetSelections();
329 }
330 }
331
332 bool XServerClipboard::GotSelectionTimestamp(XSelectionEvent* event,
333 int format,
334 int item_count,
335 void* data) {
336 if (event->property == timestamp_atom_) {
Wez 2012/09/17 22:13:29 What does it mean for property not to be TIMESTAMP
Lambros 2012/09/19 23:58:19 Added comments (some copied from elsewhere as you
337 if (data && format == 32 && item_count == 1) {
338 Time selection_time = *static_cast<Time*>(data);
339 if (selection_time != 0) {
340 had_valid_timestamp_ = true;
341 }
342 if (IsTimeLater(selection_time, current_selection_time_)) {
343 current_selection_time_ = selection_time;
344 new_selection_ = event->selection;
345 }
346 }
347 }
348
349 if (event->selection == XA_PRIMARY) {
350 if (!IsSelectionOwner(clipboard_atom_)) {
351 GetSelectionTimestamp(clipboard_atom_, event->time);
352 return false;
353 }
354 } else if (event->selection != clipboard_atom_) {
355 return false;
356 }
357
358 if (getting_initial_selection_ && had_valid_timestamp_) {
359 return true;
360 }
361
362 if (new_selection_) {
363 GetSelectionTargets(new_selection_);
364 } else if (!had_valid_timestamp_) {
365 GetSelectionTargets(XA_PRIMARY);
366 } else {
367 return true;
368 }
369 return false;
370 }
371
372 bool XServerClipboard::GotSelectionTargets(XSelectionEvent* event,
Wez 2012/09/17 22:13:29 nit: HandleSelectionTargetsEvent?
Lambros 2012/09/19 23:58:19 Done.
373 int format,
374 int item_count,
375 void* data) {
376 if (event->property == targets_atom_) {
377 if (data && format == 32) {
378 // The XGetWindowProperty man-page specifies that the returned
379 // property data will be an array of |long|s in the case where
380 // |format| == 32. On a 64-bit system, each item gets padded to
381 // 8 bytes, the size of a |long|.
Wez 2012/09/17 22:13:29 nit: This is an Xlib-ism; the protocol uses CARD32
Lambros 2012/09/19 23:58:19 Clarified that it's Xlib that does the padding.
382 const long* targets = static_cast<const long*>(data);
383 for (int i = 0; i < item_count; i++) {
384 if (targets[i] == static_cast<long>(utf8_string_atom_)) {
385 GetSelectionString(event->selection, utf8_string_atom_);
386 return false;
387 }
388 }
389 }
390 }
391 GetSelectionString(event->selection, XA_STRING);
392 return false;
393 }
394
395 bool XServerClipboard::GotSelectionString(XSelectionEvent* event,
Wez 2012/09/17 22:13:29 nit: HandleSelectionStringEvent?
Lambros 2012/09/19 23:58:19 Done.
396 int format,
397 int item_count,
398 void* data) {
399 if (event->property != selection_string_atom_ || !data || format != 8) {
400 return true;
401 }
402
403 std::string text(static_cast<char*>(data), item_count);
404
405 if (event->target == XA_STRING || event->target == utf8_string_atom_) {
406 GotCutTextUtf8(text);
407 }
408 return true;
409 }
410
411 void XServerClipboard::GotCutTextUtf8(const std::string& text) {
Wez 2012/09/17 22:13:29 nit: NotifyClipboardText()?
Lambros 2012/09/19 23:58:19 Done.
412 data_ = text;
413 new_cut_text_ = false;
414 if (!getting_initial_selection_) {
415 callback_.Run(kMimeTypeTextUtf8, data_);
416 }
417 }
418
419 bool XServerClipboard::IsSelectionOwner(Atom selection) {
420 return selections_owned_.find(selection) != selections_owned_.end();
421 }
422
423 void XServerClipboard::GetSelectionTimestamp(Atom selection, Time time) {
Wez 2012/09/17 22:13:29 nit: RequestSelectionTimestamp, since it's async?
Lambros 2012/09/19 23:58:19 Done.
424 XConvertSelection(display_, selection, timestamp_atom_, timestamp_atom_,
425 clipboard_window_, time);
426 }
427
428 void XServerClipboard::GetSelectionTargets(Atom selection) {
Wez 2012/09/17 22:13:29 As above
Lambros 2012/09/19 23:58:19 Done.
429 XConvertSelection(display_, selection, targets_atom_, targets_atom_,
430 clipboard_window_, CurrentTime);
431 }
432
433 void XServerClipboard::GetSelectionString(Atom selection, Atom target) {
Wez 2012/09/17 22:13:29 As above
Lambros 2012/09/19 23:58:19 Done.
434 XConvertSelection(display_, selection, target, selection_string_atom_,
435 clipboard_window_, CurrentTime);
436 }
437
438 void XServerClipboard::OwnSelection(Atom selection, Time time) {
Wez 2012/09/17 22:13:29 nit: AssertSelectionOwnership()
Lambros 2012/09/19 23:58:19 Done.
439 XSetSelectionOwner(display_, selection, clipboard_window_, time);
440 if (XGetSelectionOwner(display_, selection) == clipboard_window_) {
441 selections_owned_.insert(selection);
442 selection_own_time_[selection] = time;
443 } else {
444 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
445 }
446 }
447
448 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698