OLD | NEW |
---|---|
(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 | |
OLD | NEW |