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

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

Powered by Google App Engine
This is Rietveld 408576698