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

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: Remove timestamp processing, and address 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 remoting {
14
15 XServerClipboard::XServerClipboard()
16 : display_(NULL),
17 clipboard_window_(BadValue),
18 have_xfixes_(false),
19 xfixes_event_base_(-1),
20 xfixes_error_base_(-1),
21 clipboard_atom_(None),
22 large_selection_atom_(None),
23 selection_string_atom_(None),
24 targets_atom_(None),
25 timestamp_atom_(None),
26 utf8_string_atom_(None),
27 large_selection_property_(None) {
28 }
29
30 XServerClipboard::~XServerClipboard() {
31 }
32
33 void XServerClipboard::Init(Display* display,
34 const ClipboardChangedCallback& callback) {
35 display_ = display;
36 callback_ = callback;
37
38 // If any of these X API calls fail, an X Error will be raised, crashing the
39 // process. This is unlikely to occur in practice, and even if it does, it
40 // would mean the X server is in a bad state, so it's not worth trying to
41 // trap such errors here.
42
43 // TODO(lambroslambrou): Consider using ScopedXErrorHandler here, or consider
44 // placing responsibility for handling X Errors outside this class, since
45 // X Error handlers are global to all X connections.
46 clipboard_window_ = XCreateSimpleWindow(display_,
47 DefaultRootWindow(display_),
48 0, 0, 1, 1, // x, y, width, height
49 0, 0, 0);
50
51 // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a
52 // dependency on ui/ or by moving X11AtomCache to base/.
53 const int kNumAtoms = 6;
54 const char* names[kNumAtoms] = {
55 "CLIPBOARD",
56 "INCR",
57 "SELECTION_STRING",
58 "TARGETS",
59 "TIMESTAMP",
60 "UTF8_STRING" };
61 Atom atoms[kNumAtoms];
62 if (XInternAtoms(display_, const_cast<char**>(names), kNumAtoms, False,
63 atoms)) {
64 clipboard_atom_ = atoms[0];
65 large_selection_atom_ = atoms[1];
66 selection_string_atom_ = atoms[2];
67 targets_atom_ = atoms[3];
68 timestamp_atom_ = atoms[4];
69 utf8_string_atom_ = atoms[5];
70 } else {
71 LOG(ERROR) << "XInternAtoms failed";
72 }
73
74 if (XFixesQueryExtension(display_, &xfixes_event_base_,
75 &xfixes_error_base_)) {
76 have_xfixes_ = true;
77 XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY,
78 XFixesSetSelectionOwnerNotifyMask);
79 XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_,
80 XFixesSetSelectionOwnerNotifyMask);
81 } else {
82 LOG(INFO) << "X server does not support XFixes.";
83 }
84 }
85
86 void XServerClipboard::SetClipboard(const std::string& mime_type,
87 const std::string& data) {
88 DCHECK(display_);
89
90 // Currently only UTF-8 is supported.
91 if (mime_type != kMimeTypeTextUtf8) {
92 return;
93 }
94
95 data_ = data;
96
97 AssertSelectionOwnership(XA_PRIMARY);
98 AssertSelectionOwnership(clipboard_atom_);
99 }
100
101 void XServerClipboard::ProcessXEvent(XEvent* event) {
102 if (event->xany.window != clipboard_window_) {
103 return;
104 }
105
106 switch (event->type) {
107 case PropertyNotify:
108 OnPropertyNotify(event);
109 break;
110 case SelectionNotify:
111 OnSelectionNotify(event);
112 break;
113 case SelectionRequest:
114 OnSelectionRequest(event);
115 break;
116 default:
117 break;
118 }
119
120 if (have_xfixes_) {
Wez 2012/09/25 20:29:29 The monitoring code doesn't work if we don't have
Lambros 2012/09/25 22:42:39 Done.
121 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
122 XFixesSelectionNotifyEvent* notify_event =
123 reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
124 OnSetSelectionOwnerNotify(notify_event->selection,
125 notify_event->selection_timestamp);
126 }
127 }
128 }
129
130 void XServerClipboard::OnSetSelectionOwnerNotify(Atom selection,
131 Time timestamp) {
132 // Protect against receiving new XFixes selection notifications whilst we're
133 // in the middle of waiting for information from the current selection owner.
134 // A reasonable timeout allows for misbehaving apps that don't respond
135 // quickly to our requests.
Wez 2012/09/25 20:29:29 We agreed it makes more sense to cancel any pendin
Lambros 2012/09/25 22:42:39 Added TODO.
136 if (!get_selections_time_.is_null() &&
137 (base::TimeTicks::Now() - get_selections_time_) <
138 base::TimeDelta::FromSeconds(5)) {
139 return;
140 }
141
142 if (selection != clipboard_atom_ && selection != XA_PRIMARY) {
Wez 2012/09/25 20:29:29 nit: Add comment "Only process PRIMARY and CLIPBOA
Lambros 2012/09/25 22:42:39 Done.
143 return;
144 }
145
146 // If we own the selection, don't request details for it.
147 if (IsSelectionOwner(selection)) {
148 return;
149 }
150
151 get_selections_time_ = base::TimeTicks::Now();
152
153 // Before getting the value of the chosen selection, request the list of
154 // target formats it supports.
155 RequestSelectionTargets(selection);
156 }
157
158 void XServerClipboard::OnPropertyNotify(XEvent* event) {
159 if (large_selection_property_ != None &&
160 event->xproperty.atom == large_selection_property_ &&
161 event->xproperty.state == PropertyNewValue) {
162 Atom type;
163 int format;
164 unsigned long item_count, after;
165 unsigned char *data;
166 XGetWindowProperty(display_, clipboard_window_, large_selection_property_,
167 0, ~0L, True, AnyPropertyType, &type, &format,
168 &item_count, &after, &data);
169 if (type != None) {
170 // TODO(lambroslambrou): Properly support large transfers -
171 // http://crbug.com/151447.
172 XFree(data);
173
174 // If the property is zero-length then the large transfer is complete.
175 if (item_count == 0) {
176 large_selection_property_ = None;
177 }
178 }
179 }
180 }
181
182 void XServerClipboard::OnSelectionNotify(XEvent* event) {
183 if (event->xselection.property != None) {
184 Atom type;
185 int format;
186 unsigned long item_count, after;
187 unsigned char *data;
188 XGetWindowProperty(display_, clipboard_window_,
189 event->xselection.property, 0, ~0L, True,
190 AnyPropertyType, &type, &format,
191 &item_count, &after, &data);
192 if (type == large_selection_atom_) {
193 // Large selection - just read and ignore these for now.
194 large_selection_property_ = event->xselection.property;
195 } else {
196 // Standard selection - call the selection notifier.
197 large_selection_property_ = None;
198 if (type != None) {
199 DoSelectionNotify(&event->xselection, type, format, item_count, data);
200 XFree(data);
201 return;
202 }
203 }
204 }
205 DoSelectionNotify(&event->xselection, 0, 0, 0, 0);
206 }
207
208 void XServerClipboard::OnSelectionRequest(XEvent* event) {
209 XSelectionEvent selection_event;
210 selection_event.type = SelectionNotify;
211 selection_event.display = event->xselectionrequest.display;
212 selection_event.requestor = event->xselectionrequest.requestor;
213 selection_event.selection = event->xselectionrequest.selection;
214 selection_event.time = event->xselectionrequest.time;
215 selection_event.target = event->xselectionrequest.target;
216 if (event->xselectionrequest.property == None) {
217 event->xselectionrequest.property = event->xselectionrequest.target;
218 }
219 if (!IsSelectionOwner(selection_event.selection)) {
220 selection_event.property = None;
221 } else {
222 selection_event.property = event->xselectionrequest.property;
223 if (selection_event.target == targets_atom_) {
Wez 2012/09/25 20:29:29 nit: Add a comment to this block e.g. "Respond adv
Lambros 2012/09/25 22:42:39 Done.
224 Atom targets[3];
225 targets[0] = timestamp_atom_;
226 targets[1] = utf8_string_atom_;
227 targets[2] = XA_STRING;
228 XChangeProperty(display_, selection_event.requestor,
229 selection_event.property, XA_ATOM, 32, PropModeReplace,
230 reinterpret_cast<unsigned char*>(targets), 3);
231 } else if (selection_event.target == timestamp_atom_) {
232 Time time = selection_own_time_[selection_event.selection];
Wez 2012/09/25 20:29:29 nit: Comment "Respond with the timestamp of our se
Lambros 2012/09/25 22:42:39 Done.
233 XChangeProperty(display_, selection_event.requestor,
234 selection_event.property, XA_INTEGER, 32,
235 PropModeReplace, reinterpret_cast<unsigned char*>(&time),
236 1);
237 } else if (selection_event.target == utf8_string_atom_ ||
238 selection_event.target == XA_STRING) {
239 if (!data_.empty()) {
Wez 2012/09/25 20:29:29 nit: Comment "Return the actual string data. We al
Lambros 2012/09/25 22:42:39 Done.
240 XChangeProperty(display_, selection_event.requestor,
241 selection_event.property, selection_event.target, 8,
242 PropModeReplace,
243 reinterpret_cast<unsigned char*>(
244 const_cast<char*>(data_.data())),
245 data_.size());
246 }
247 }
248 }
249 XSendEvent(display_, selection_event.requestor, False, 0,
250 reinterpret_cast<XEvent*>(&selection_event));
251 }
252
253 void XServerClipboard::DoSelectionNotify(XSelectionEvent* event,
Wez 2012/09/25 20:29:29 nit: Shouldn't this be HandleSelectionNotify, sinc
Lambros 2012/09/25 22:42:39 Done.
254 Atom type,
255 int format,
256 int item_count,
257 void* data) {
258 bool finished = false;
259
260 if (event->target == targets_atom_) {
261 finished = HandleSelectionTargetsEvent(event, format, item_count, data);
262 } else if (event->target == utf8_string_atom_ ||
263 event->target == XA_STRING) {
264 finished = HandleSelectionStringEvent(event, format, item_count, data);
265 }
266
267 if (finished) {
268 get_selections_time_ = base::TimeTicks();
269 }
270 }
271
272 bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event,
273 int format,
274 int item_count,
275 void* data) {
276 if (event->property == targets_atom_) {
277 if (data && format == 32) {
278 // The XGetWindowProperty man-page specifies that the returned
279 // property data will be an array of |long|s in the case where
280 // |format| == 32. Although the items are 32-bit values (as stored and
281 // sent over the X protocol), Xlib presents the data to the client as an
282 // array of |long|s, with zero-padding on a 64-bit system where |long|
283 // is bigger than 32 bits.
284 const long* targets = static_cast<const long*>(data);
285 for (int i = 0; i < item_count; i++) {
286 if (targets[i] == static_cast<long>(utf8_string_atom_)) {
287 RequestSelectionString(event->selection, utf8_string_atom_);
288 return false;
289 }
290 }
291 }
292 }
293 RequestSelectionString(event->selection, XA_STRING);
294 return false;
295 }
296
297 bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event,
298 int format,
299 int item_count,
300 void* data) {
301 if (event->property != selection_string_atom_ || !data || format != 8) {
302 return true;
303 }
304
305 std::string text(static_cast<char*>(data), item_count);
306
307 if (event->target == XA_STRING || event->target == utf8_string_atom_) {
308 NotifyClipboardText(text);
309 }
310 return true;
311 }
312
313 void XServerClipboard::NotifyClipboardText(const std::string& text) {
314 data_ = text;
315 callback_.Run(kMimeTypeTextUtf8, data_);
316 }
317
318 bool XServerClipboard::IsSelectionOwner(Atom selection) {
319 return selections_owned_.find(selection) != selections_owned_.end();
320 }
321
322 void XServerClipboard::RequestSelectionTargets(Atom selection) {
323 XConvertSelection(display_, selection, targets_atom_, targets_atom_,
324 clipboard_window_, CurrentTime);
325 }
326
327 void XServerClipboard::RequestSelectionString(Atom selection, Atom target) {
328 XConvertSelection(display_, selection, target, selection_string_atom_,
329 clipboard_window_, CurrentTime);
330 }
331
332 void XServerClipboard::AssertSelectionOwnership(Atom selection) {
333 // TODO(lambroslambrou): Should use a proper timestamp here instead of
334 // CurrentTime. ICCCM recommends doing a zero-length property append, and
335 // getting a timestamp from the subsequent PropertyNotify event.
336 XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime);
337 if (XGetSelectionOwner(display_, selection) == clipboard_window_) {
338 selections_owned_.insert(selection);
339 selection_own_time_[selection] = CurrentTime;
Wez 2012/09/25 20:29:29 This sets |selection_own_time_[selection]| to zero
Lambros 2012/09/25 22:42:39 Done, and moved TODO to where we actually use Curr
340 } else {
341 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
342 }
343 }
344
345 } // namespace remoting
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698