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

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