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

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: Address Wez's comments 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).
18 bool IsTimeLaterThan(Time a, Time b) {
19 if (a == 0) {
20 return false;
21 }
22 if (b == 0) {
23 return true;
24 }
25
26 // Deal with possible wrap-around: if |b| is close to the largest possible
27 // value, and |a| is close to 0, consider |a| to be later than |b| even
28 // though |a| is smaller numerically.
Wez 2012/09/20 23:37:46 nit: Suggest: 32-bit X timestamps will wrap around
Lambros 2012/09/21 17:05:57 Removed code.
29 return static_cast<long>(a - b) > 0;
30 }
31
32 } // namespace
33
34 namespace remoting {
35
36 XServerClipboard::XServerClipboard()
37 : display_(NULL),
38 clipboard_window_(BadValue),
39 have_xfixes_(false),
40 xfixes_event_base_(-1),
41 xfixes_error_base_(-1),
42 clipboard_atom_(None),
43 large_selection_atom_(None),
44 selection_string_atom_(None),
45 targets_atom_(None),
46 timestamp_atom_(None),
47 utf8_string_atom_(None),
48 large_selection_property_(None),
49 current_selection_time_(0),
50 new_selection_(None),
51 getting_initial_selection_(true),
52 new_cut_text_(false),
53 had_valid_timestamp_(false) {
54 }
55
56 XServerClipboard::~XServerClipboard() {
57 }
58
59 void XServerClipboard::Init(Display* display,
60 const ClipboardChangedCallback& callback) {
61 display_ = display;
62 callback_ = callback;
63
64 // If any of these X API calls fail, an X Error will be raised, crashing the
65 // process. This is unlikely to occur in practice, and even if it does, it
66 // would mean the X server is in a bad state, so it's not worth trying to
67 // trap such errors here.
68
69 // TODO(lambroslambrou): Consider using ScopedXErrorHandler here, or consider
70 // placing responsibility for handling X Errors outside this class, since
71 // X Error handlers are global to all X connections.
72 clipboard_window_ = XCreateSimpleWindow(display_,
73 DefaultRootWindow(display_),
74 0, 0, 1, 1, // x, y, width, height
75 0, 0, 0);
76
77 // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a
78 // dependency on ui/ or by moving X11AtomCache to base/.
79 const int kNumAtoms = 6;
80 const char* names[kNumAtoms] = {
81 "CLIPBOARD",
82 "INCR",
83 "SELECTION_STRING",
84 "TARGETS",
85 "TIMESTAMP",
86 "UTF8_STRING" };
87 Atom atoms[kNumAtoms];
88 if (XInternAtoms(display_, const_cast<char**>(names), kNumAtoms, False,
89 atoms)) {
90 clipboard_atom_ = atoms[0];
91 large_selection_atom_ = atoms[1];
92 selection_string_atom_ = atoms[2];
93 targets_atom_ = atoms[3];
94 timestamp_atom_ = atoms[4];
95 utf8_string_atom_ = atoms[5];
96 } else {
97 LOG(ERROR) << "XInternAtoms failed";
98 }
99
100 if (XFixesQueryExtension(display_, &xfixes_event_base_,
101 &xfixes_error_base_)) {
102 have_xfixes_ = true;
103 XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY,
104 XFixesSetSelectionOwnerNotifyMask);
105 XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_,
106 XFixesSetSelectionOwnerNotifyMask);
107 } else {
108 LOG(INFO) << "X server does not support XFixes.";
109 }
110
111 GetSelections(None, 0);
112 }
113
114 void XServerClipboard::SetClipboard(const std::string& mime_type,
115 const std::string& data) {
116 DCHECK(display_);
117
118 // Currently only UTF-8 is supported.
119 if (mime_type != kMimeTypeTextUtf8) {
120 return;
121 }
122
123 data_ = data;
124
125 AssertSelectionOwnership(XA_PRIMARY);
126 AssertSelectionOwnership(clipboard_atom_);
127 current_selection_time_ = CurrentTime;
128 }
129
130 void XServerClipboard::ProcessXEvent(XEvent* event) {
131 if (event->xany.window != clipboard_window_) {
132 return;
133 }
134
135 switch (event->type) {
136 case PropertyNotify:
137 OnPropertyNotify(event);
138 break;
139 case SelectionNotify:
140 OnSelectionNotify(event);
141 break;
142 case SelectionRequest:
143 OnSelectionRequest(event);
144 break;
145 default:
146 break;
147 }
148
149 if (have_xfixes_) {
150 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
151 XFixesSelectionNotifyEvent* notify_event =
152 reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
153 GetSelections(notify_event->selection, notify_event->selection_timestamp);
154 }
155 }
156 }
157
158 void XServerClipboard::GetSelections(Atom selection, Time timestamp) {
159 // This is called to check the state of the X selections. It is called
160 // during initialization (with |selection| and |timestamp| equal to zero),
Wez 2012/09/20 23:37:46 If it's called during initialization with |selecti
Lambros 2012/09/21 17:05:57 No loner called during init. This is now just an
161 // and whenever XFixesSelectionNotifyEvent is received (with |selection| and
162 // |timestamp| taken from the notification event). This requests information
163 // from the selection owner(s) about the timestamp, the target list and the
164 // string value of the selection.
165
166 // We start out trying to establish the timestamp(s) on the selections to
167 // decide which one to retrieve. If |selection| and |timestamp| are non-zero
168 // then we retrieve that selection. Otherwise we request the timestamp(s)
169 // from the selection owner. If we get a valid timestamp and one of the
170 // selections is newer than |current_selection_time_|, then we use that
Wez 2012/09/20 23:37:46 nit for discussion off-CL: Do we really need these
Lambros 2012/09/21 17:05:57 Done. PTAL at the simplified version.
171 // selection.
172
173 // Protect against receiving new XFixes selection notifications whilst we're
174 // in the middle of waiting for information from the current selection owner.
175 // A reasonable timeout allows for misbehaving apps that don't respond
176 // quickly to our requests.
177 if (!get_selections_time_.is_null() &&
178 (base::TimeTicks::Now() - get_selections_time_) <
179 base::TimeDelta::FromSeconds(5)) {
180 return;
181 }
182
183 if (selection != None) {
184 if (selection != clipboard_atom_ && selection != XA_PRIMARY) {
185 return;
186 }
187 } else {
188 selection = XA_PRIMARY;
189 }
190
191 // If the selection is actually owned by us, there's no need to request the
192 // details for it.
Wez 2012/09/20 23:37:46 nit: Suggest: If we own the selection, don't reque
Lambros 2012/09/21 17:05:57 Done.
193 if (IsSelectionOwner(selection)) {
194 FinishGetSelections();
195 }
196
197 get_selections_time_ = base::TimeTicks::Now();
198
199 if (timestamp) {
200 current_selection_time_ = timestamp;
201
202 // Before getting the value of the chosen selection, request the list of
203 // targets supported for that selection (currently only UTF8_STRING and
Wez 2012/09/20 23:37:46 nit: ... the list of target formats it supports.
Lambros 2012/09/21 17:05:57 Done.
204 // STRING are supported).
Wez 2012/09/20 23:37:46 nit: No need to clarify the targets we support her
Lambros 2012/09/21 17:05:57 Done.
205 RequestSelectionTargets(selection);
206 } else {
207 new_selection_ = 0;
208 had_valid_timestamp_ = false;
209 RequestSelectionTimestamp(selection, CurrentTime);
210 }
211 }
212
213 void XServerClipboard::FinishGetSelections() {
214 getting_initial_selection_ = false;
215 get_selections_time_ = base::TimeTicks();
216 }
217
218 void XServerClipboard::OnPropertyNotify(XEvent* event) {
219 if (large_selection_property_ != None &&
220 event->xproperty.atom == large_selection_property_ &&
221 event->xproperty.state == PropertyNewValue) {
222 Atom type;
223 int format;
224 unsigned long item_count, after;
225 unsigned char *data;
226 XGetWindowProperty(display_, clipboard_window_, large_selection_property_,
227 0, ~0L, True, AnyPropertyType, &type, &format,
228 &item_count, &after, &data);
229 if (type != None) {
230 // TODO(lambroslambrou): Properly support large transfers.
Wez 2012/09/20 23:37:46 nit: Add a bug for that and refer to it here.
Lambros 2012/09/21 17:05:57 Done.
231 XFree(data);
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 return;
261 }
262 }
263 }
264 DoSelectionNotify(&event->xselection, 0, 0, 0, 0);
265 }
266
267 void XServerClipboard::OnSelectionRequest(XEvent* event) {
268 XSelectionEvent selection_event;
269 selection_event.type = SelectionNotify;
270 selection_event.display = event->xselectionrequest.display;
271 selection_event.requestor = event->xselectionrequest.requestor;
272 selection_event.selection = event->xselectionrequest.selection;
273 selection_event.time = event->xselectionrequest.time;
274 selection_event.target = event->xselectionrequest.target;
275 if (event->xselectionrequest.property == None) {
276 event->xselectionrequest.property = event->xselectionrequest.target;
277 }
278 if (!IsSelectionOwner(selection_event.selection)) {
279 selection_event.property = None;
280 } else {
281 selection_event.property = event->xselectionrequest.property;
282 if (selection_event.target == targets_atom_) {
283 Atom targets[3];
284 targets[0] = timestamp_atom_;
285 targets[1] = utf8_string_atom_;
286 targets[2] = XA_STRING;
287 XChangeProperty(display_, selection_event.requestor,
288 selection_event.property, XA_ATOM, 32, PropModeReplace,
289 reinterpret_cast<unsigned char*>(targets), 3);
290 } else if (selection_event.target == timestamp_atom_) {
291 Time time = selection_own_time_[selection_event.selection];
292 XChangeProperty(display_, selection_event.requestor,
293 selection_event.property, XA_INTEGER, 32,
294 PropModeReplace, reinterpret_cast<unsigned char*>(&time),
295 1);
296 } else if (selection_event.target == utf8_string_atom_ ||
297 selection_event.target == XA_STRING) {
298 if (!data_.empty()) {
299 XChangeProperty(display_, selection_event.requestor,
300 selection_event.property, selection_event.target, 8,
301 PropModeReplace,
302 reinterpret_cast<unsigned char*>(
303 const_cast<char*>(data_.data())),
304 data_.size());
305 }
306 }
307 }
308 XSendEvent(display_, selection_event.requestor, False, 0,
309 reinterpret_cast<XEvent*>(&selection_event));
310 }
311
312 void XServerClipboard::DoSelectionNotify(XSelectionEvent* event,
313 Atom type,
314 int format,
315 int item_count,
316 void* data) {
317 bool finished = false;
318
319 if (event->target == timestamp_atom_) {
320 finished = HandleSelectionTimestampEvent(event, format, item_count, data);
321 } else if (event->target == targets_atom_) {
322 finished = HandleSelectionTargetsEvent(event, format, item_count, data);
323 } else if (event->target == utf8_string_atom_ ||
324 event->target == XA_STRING) {
325 finished = HandleSelectionStringEvent(event, format, item_count, data);
326 }
327
328 if (finished) {
329 FinishGetSelections();
330 }
331 }
332
333 bool XServerClipboard::HandleSelectionTimestampEvent(XSelectionEvent* event,
334 int format,
335 int item_count,
336 void* data) {
337 if (event->property == timestamp_atom_) {
338 // The selection owner properly responded to the timestamp request. Test
339 // the timestamp for validity.
340 if (data && format == 32 && item_count == 1) {
341 Time selection_time = *static_cast<Time*>(data);
342 if (selection_time != 0) {
343 had_valid_timestamp_ = true;
344 }
345 if (IsTimeLaterThan(selection_time, current_selection_time_)) {
346 current_selection_time_ = selection_time;
347 new_selection_ = event->selection;
348 }
349 }
350 }
351
352 if (event->selection == XA_PRIMARY) {
353 if (!IsSelectionOwner(clipboard_atom_)) {
354 RequestSelectionTimestamp(clipboard_atom_, event->time);
355 return false;
356 }
357 } else if (event->selection != clipboard_atom_) {
358 return false;
359 }
360
361 if (getting_initial_selection_ && had_valid_timestamp_) {
362 return true;
363 }
364
365 if (new_selection_) {
366 RequestSelectionTargets(new_selection_);
367 } else if (!had_valid_timestamp_) {
368 // If we didn't get a valid timestamp (for example an app like Mozilla
369 // which sets a zero timestamp, or an app like VMWare which doesn't support
370 // the timestamp target), we can't use the timestamp optimisation so we
371 // have to get the actual value of the selection each time.
Wez 2012/09/20 23:37:46 But this will only be triggered by an XFixes notif
Lambros 2012/09/21 17:05:57 Removed code. We no longer request the timestamp
372 RequestSelectionTargets(XA_PRIMARY);
373 } else {
374 return true;
375 }
376 return false;
377 }
378
379 bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event,
380 int format,
381 int item_count,
382 void* data) {
383 if (event->property == targets_atom_) {
384 if (data && format == 32) {
385 // The XGetWindowProperty man-page specifies that the returned
386 // property data will be an array of |long|s in the case where
387 // |format| == 32. Although the items are 32-bit values (as stored and
388 // sent over the X protocol), Xlib presents the data to the client as an
389 // array of |long|s, with zero-padding on a 64-bit system where |long|
390 // is bigger than 32 bits.
391 const long* targets = static_cast<const long*>(data);
392 for (int i = 0; i < item_count; i++) {
393 if (targets[i] == static_cast<long>(utf8_string_atom_)) {
394 RequestSelectionString(event->selection, utf8_string_atom_);
395 return false;
396 }
397 }
398 }
399 }
400 RequestSelectionString(event->selection, XA_STRING);
401 return false;
402 }
403
404 bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event,
405 int format,
406 int item_count,
407 void* data) {
408 if (event->property != selection_string_atom_ || !data || format != 8) {
409 return true;
410 }
411
412 std::string text(static_cast<char*>(data), item_count);
413
414 if (event->target == XA_STRING || event->target == utf8_string_atom_) {
415 NotifyClipboardText(text);
416 }
417 return true;
418 }
419
420 void XServerClipboard::NotifyClipboardText(const std::string& text) {
421 data_ = text;
422 new_cut_text_ = false;
423 if (!getting_initial_selection_) {
424 callback_.Run(kMimeTypeTextUtf8, data_);
425 }
426 }
427
428 bool XServerClipboard::IsSelectionOwner(Atom selection) {
429 return selections_owned_.find(selection) != selections_owned_.end();
430 }
431
432 void XServerClipboard::RequestSelectionTimestamp(Atom selection, Time time) {
433 XConvertSelection(display_, selection, timestamp_atom_, timestamp_atom_,
434 clipboard_window_, time);
435 }
436
437 void XServerClipboard::RequestSelectionTargets(Atom selection) {
438 XConvertSelection(display_, selection, targets_atom_, targets_atom_,
439 clipboard_window_, CurrentTime);
440 }
441
442 void XServerClipboard::RequestSelectionString(Atom selection, Atom target) {
443 XConvertSelection(display_, selection, target, selection_string_atom_,
444 clipboard_window_, CurrentTime);
445 }
446
447 void XServerClipboard::AssertSelectionOwnership(Atom selection) {
448 XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime);
449 if (XGetSelectionOwner(display_, selection) == clipboard_window_) {
450 selections_owned_.insert(selection);
451 selection_own_time_[selection] = CurrentTime;
452 } else {
453 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
454 }
455 }
456
457 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698