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

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: I'm an idiot! (Fix BadWindow crash) 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) {
Wez 2012/09/26 00:30:39 You need to check |clipboard_window_| here as well
Lambros 2012/09/26 01:37:10 Done.
87 DCHECK(display_);
88
89 // Currently only UTF-8 is supported.
90 if (mime_type != kMimeTypeTextUtf8) {
91 return;
92 }
93
94 data_ = data;
95
96 AssertSelectionOwnership(XA_PRIMARY);
97 AssertSelectionOwnership(clipboard_atom_);
98 }
99
100 void XServerClipboard::ProcessXEvent(XEvent* event) {
101 if (clipboard_window_ == BadValue) {
Wez 2012/09/26 00:30:39 Is it possible for event->xany.window to be BadVal
Lambros 2012/09/26 01:37:10 I don't know if it's possible, so I've merged the
102 return;
103 }
104
105 if (event->xany.window != clipboard_window_) {
106 return;
107 }
108
109 switch (event->type) {
110 case PropertyNotify:
111 OnPropertyNotify(event);
112 break;
113 case SelectionNotify:
114 OnSelectionNotify(event);
115 break;
116 case SelectionRequest:
117 OnSelectionRequest(event);
118 break;
119 default:
120 break;
121 }
122
123 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
124 XFixesSelectionNotifyEvent* notify_event =
125 reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
126 OnSetSelectionOwnerNotify(notify_event->selection,
127 notify_event->selection_timestamp);
128 }
129 }
130
131 void XServerClipboard::OnSetSelectionOwnerNotify(Atom selection,
132 Time timestamp) {
133 // Protect against receiving new XFixes selection notifications whilst we're
134 // in the middle of waiting for information from the current selection owner.
135 // A reasonable timeout allows for misbehaving apps that don't respond
136 // quickly to our requests.
137 if (!get_selections_time_.is_null() &&
138 (base::TimeTicks::Now() - get_selections_time_) <
139 base::TimeDelta::FromSeconds(5)) {
140 // TODO(lambroslambrou): Instead of ignoring this notification, cancel any
141 // pending request operations and ignore the resulting events, before
142 // dispatching new requests here.
143 return;
144 }
145
146 if (selection != clipboard_atom_ && selection != XA_PRIMARY) {
147 // Only process PRIMARY and CLIPBOARD selections.
148 return;
149 }
150
151 // If we own the selection, don't request details for it.
152 if (IsSelectionOwner(selection)) {
153 return;
154 }
155
156 get_selections_time_ = base::TimeTicks::Now();
157
158 // Before getting the value of the chosen selection, request the list of
159 // target formats it supports.
160 RequestSelectionTargets(selection);
161 }
162
163 void XServerClipboard::OnPropertyNotify(XEvent* event) {
164 if (large_selection_property_ != None &&
165 event->xproperty.atom == large_selection_property_ &&
166 event->xproperty.state == PropertyNewValue) {
167 Atom type;
168 int format;
169 unsigned long item_count, after;
170 unsigned char *data;
171 XGetWindowProperty(display_, clipboard_window_, large_selection_property_,
172 0, ~0L, True, AnyPropertyType, &type, &format,
173 &item_count, &after, &data);
174 if (type != None) {
175 // TODO(lambroslambrou): Properly support large transfers -
176 // http://crbug.com/151447.
177 XFree(data);
178
179 // If the property is zero-length then the large transfer is complete.
180 if (item_count == 0) {
181 large_selection_property_ = None;
182 }
183 }
184 }
185 }
186
187 void XServerClipboard::OnSelectionNotify(XEvent* event) {
188 if (event->xselection.property != None) {
189 Atom type;
190 int format;
191 unsigned long item_count, after;
192 unsigned char *data;
193 XGetWindowProperty(display_, clipboard_window_,
194 event->xselection.property, 0, ~0L, True,
195 AnyPropertyType, &type, &format,
196 &item_count, &after, &data);
197 if (type == large_selection_atom_) {
198 // Large selection - just read and ignore these for now.
199 large_selection_property_ = event->xselection.property;
200 } else {
201 // Standard selection - call the selection notifier.
202 large_selection_property_ = None;
203 if (type != None) {
204 HandleSelectionNotify(&event->xselection, type, format, item_count,
205 data);
206 XFree(data);
207 return;
208 }
209 }
210 }
211 HandleSelectionNotify(&event->xselection, 0, 0, 0, 0);
212 }
213
214 void XServerClipboard::OnSelectionRequest(XEvent* event) {
215 XSelectionEvent selection_event;
216 selection_event.type = SelectionNotify;
217 selection_event.display = event->xselectionrequest.display;
218 selection_event.requestor = event->xselectionrequest.requestor;
219 selection_event.selection = event->xselectionrequest.selection;
220 selection_event.time = event->xselectionrequest.time;
221 selection_event.target = event->xselectionrequest.target;
222 if (event->xselectionrequest.property == None) {
223 event->xselectionrequest.property = event->xselectionrequest.target;
224 }
225 if (!IsSelectionOwner(selection_event.selection)) {
226 selection_event.property = None;
227 } else {
228 selection_event.property = event->xselectionrequest.property;
229 if (selection_event.target == targets_atom_) {
230 // Respond advertising XA_STRING, UTF8_STRING and TIMESTAMP data for the
231 // selection.
232 Atom targets[3];
233 targets[0] = timestamp_atom_;
234 targets[1] = utf8_string_atom_;
235 targets[2] = XA_STRING;
236 XChangeProperty(display_, selection_event.requestor,
237 selection_event.property, XA_ATOM, 32, PropModeReplace,
238 reinterpret_cast<unsigned char*>(targets), 3);
239 } else if (selection_event.target == timestamp_atom_) {
240 // Respond with the timestamp of our selection; we always return
241 // CurrentTime since our selections are set by remote clients, so there
242 // is no associated local X event.
243
244 // TODO(lambroslambrou): Should use a proper timestamp here instead of
245 // CurrentTime. ICCCM recommends doing a zero-length property append,
246 // and getting a timestamp from the subsequent PropertyNotify event.
247 Time time = CurrentTime;
248 XChangeProperty(display_, selection_event.requestor,
249 selection_event.property, XA_INTEGER, 32,
250 PropModeReplace, reinterpret_cast<unsigned char*>(&time),
251 1);
252 } else if (selection_event.target == utf8_string_atom_ ||
253 selection_event.target == XA_STRING) {
254 if (!data_.empty()) {
255 // Return the actual string data; we always return UTF8, regardless of
256 // the configured locale.
257 XChangeProperty(display_, selection_event.requestor,
258 selection_event.property, selection_event.target, 8,
259 PropModeReplace,
260 reinterpret_cast<unsigned char*>(
261 const_cast<char*>(data_.data())),
262 data_.size());
263 }
264 }
265 }
266 XSendEvent(display_, selection_event.requestor, False, 0,
267 reinterpret_cast<XEvent*>(&selection_event));
268 }
269
270 void XServerClipboard::HandleSelectionNotify(XSelectionEvent* event,
271 Atom type,
272 int format,
273 int item_count,
274 void* data) {
275 bool finished = false;
276
277 if (event->target == targets_atom_) {
278 finished = HandleSelectionTargetsEvent(event, format, item_count, data);
279 } else if (event->target == utf8_string_atom_ ||
280 event->target == XA_STRING) {
281 finished = HandleSelectionStringEvent(event, format, item_count, data);
282 }
283
284 if (finished) {
285 get_selections_time_ = base::TimeTicks();
286 }
287 }
288
289 bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event,
290 int format,
291 int item_count,
292 void* data) {
293 if (event->property == targets_atom_) {
294 if (data && format == 32) {
295 // The XGetWindowProperty man-page specifies that the returned
296 // property data will be an array of |long|s in the case where
297 // |format| == 32. Although the items are 32-bit values (as stored and
298 // sent over the X protocol), Xlib presents the data to the client as an
299 // array of |long|s, with zero-padding on a 64-bit system where |long|
300 // is bigger than 32 bits.
301 const long* targets = static_cast<const long*>(data);
302 for (int i = 0; i < item_count; i++) {
303 if (targets[i] == static_cast<long>(utf8_string_atom_)) {
304 RequestSelectionString(event->selection, utf8_string_atom_);
305 return false;
306 }
307 }
308 }
309 }
310 RequestSelectionString(event->selection, XA_STRING);
311 return false;
312 }
313
314 bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event,
315 int format,
316 int item_count,
317 void* data) {
318 if (event->property != selection_string_atom_ || !data || format != 8) {
319 return true;
320 }
321
322 std::string text(static_cast<char*>(data), item_count);
323
324 if (event->target == XA_STRING || event->target == utf8_string_atom_) {
325 NotifyClipboardText(text);
326 }
327 return true;
328 }
329
330 void XServerClipboard::NotifyClipboardText(const std::string& text) {
331 data_ = text;
332 callback_.Run(kMimeTypeTextUtf8, data_);
333 }
334
335 void XServerClipboard::RequestSelectionTargets(Atom selection) {
336 XConvertSelection(display_, selection, targets_atom_, targets_atom_,
337 clipboard_window_, CurrentTime);
338 }
339
340 void XServerClipboard::RequestSelectionString(Atom selection, Atom target) {
341 XConvertSelection(display_, selection, target, selection_string_atom_,
342 clipboard_window_, CurrentTime);
343 }
344
345 void XServerClipboard::AssertSelectionOwnership(Atom selection) {
346 XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime);
347 if (XGetSelectionOwner(display_, selection) == clipboard_window_) {
348 selections_owned_.insert(selection);
349 } else {
350 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
351 }
352 }
353
354 bool XServerClipboard::IsSelectionOwner(Atom selection) {
355 return selections_owned_.find(selection) != selections_owned_.end();
356 }
357
358 } // 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