Copy Smart Lock user preferences to local state so we can access them on the sign...
[chromium-blink-merge.git] / remoting / host / linux / x_server_clipboard.cc
blobb587e9a4c02b2d96ef80ec76f95208d66424ce04
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.
5 #include "remoting/host/linux/x_server_clipboard.h"
7 #include <X11/extensions/Xfixes.h>
9 #include "base/basictypes.h"
10 #include "base/callback.h"
11 #include "remoting/base/constants.h"
12 #include "remoting/base/logging.h"
13 #include "remoting/base/util.h"
15 namespace remoting {
17 XServerClipboard::XServerClipboard()
18 : display_(nullptr),
19 clipboard_window_(BadValue),
20 xfixes_event_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) {
30 XServerClipboard::~XServerClipboard() {
33 void XServerClipboard::Init(Display* display,
34 const ClipboardChangedCallback& callback) {
35 display_ = display;
36 callback_ = callback;
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.
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 int xfixes_error_base;
47 if (!XFixesQueryExtension(display_, &xfixes_event_base_,
48 &xfixes_error_base)) {
49 HOST_LOG << "X server does not support XFixes.";
50 return;
53 clipboard_window_ = XCreateSimpleWindow(display_,
54 DefaultRootWindow(display_),
55 0, 0, 1, 1, // x, y, width, height
56 0, 0, 0);
58 // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a
59 // dependency on ui/ or by moving X11AtomCache to base/.
60 static const char* const kAtomNames[] = {
61 "CLIPBOARD",
62 "INCR",
63 "SELECTION_STRING",
64 "TARGETS",
65 "TIMESTAMP",
66 "UTF8_STRING"
68 static const int kNumAtomNames = arraysize(kAtomNames);
70 Atom atoms[kNumAtomNames];
71 if (XInternAtoms(display_, const_cast<char**>(kAtomNames), kNumAtomNames,
72 False, atoms)) {
73 clipboard_atom_ = atoms[0];
74 large_selection_atom_ = atoms[1];
75 selection_string_atom_ = atoms[2];
76 targets_atom_ = atoms[3];
77 timestamp_atom_ = atoms[4];
78 utf8_string_atom_ = atoms[5];
79 static_assert(kNumAtomNames >= 6, "kAtomNames is too small");
80 } else {
81 LOG(ERROR) << "XInternAtoms failed";
84 XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_,
85 XFixesSetSelectionOwnerNotifyMask);
88 void XServerClipboard::SetClipboard(const std::string& mime_type,
89 const std::string& data) {
90 DCHECK(display_);
92 if (clipboard_window_ == BadValue)
93 return;
95 // Currently only UTF-8 is supported.
96 if (mime_type != kMimeTypeTextUtf8)
97 return;
98 if (!StringIsUtf8(data.c_str(), data.length())) {
99 LOG(ERROR) << "ClipboardEvent: data is not UTF-8 encoded.";
100 return;
103 data_ = data;
105 AssertSelectionOwnership(XA_PRIMARY);
106 AssertSelectionOwnership(clipboard_atom_);
109 void XServerClipboard::ProcessXEvent(XEvent* event) {
110 if (clipboard_window_ == BadValue ||
111 event->xany.window != clipboard_window_) {
112 return;
115 switch (event->type) {
116 case PropertyNotify:
117 OnPropertyNotify(event);
118 break;
119 case SelectionNotify:
120 OnSelectionNotify(event);
121 break;
122 case SelectionRequest:
123 OnSelectionRequest(event);
124 break;
125 case SelectionClear:
126 OnSelectionClear(event);
127 break;
128 default:
129 break;
132 if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
133 XFixesSelectionNotifyEvent* notify_event =
134 reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
135 OnSetSelectionOwnerNotify(notify_event->selection,
136 notify_event->selection_timestamp);
140 void XServerClipboard::OnSetSelectionOwnerNotify(Atom selection,
141 Time timestamp) {
142 // Protect against receiving new XFixes selection notifications whilst we're
143 // in the middle of waiting for information from the current selection owner.
144 // A reasonable timeout allows for misbehaving apps that don't respond
145 // quickly to our requests.
146 if (!get_selections_time_.is_null() &&
147 (base::TimeTicks::Now() - get_selections_time_) <
148 base::TimeDelta::FromSeconds(5)) {
149 // TODO(lambroslambrou): Instead of ignoring this notification, cancel any
150 // pending request operations and ignore the resulting events, before
151 // dispatching new requests here.
152 return;
155 // Only process CLIPBOARD selections.
156 if (selection != clipboard_atom_)
157 return;
159 // If we own the selection, don't request details for it.
160 if (IsSelectionOwner(selection))
161 return;
163 get_selections_time_ = base::TimeTicks::Now();
165 // Before getting the value of the chosen selection, request the list of
166 // target formats it supports.
167 RequestSelectionTargets(selection);
170 void XServerClipboard::OnPropertyNotify(XEvent* event) {
171 if (large_selection_property_ != None &&
172 event->xproperty.atom == large_selection_property_ &&
173 event->xproperty.state == PropertyNewValue) {
174 Atom type;
175 int format;
176 unsigned long item_count, after;
177 unsigned char *data;
178 XGetWindowProperty(display_, clipboard_window_, large_selection_property_,
179 0, ~0L, True, AnyPropertyType, &type, &format,
180 &item_count, &after, &data);
181 if (type != None) {
182 // TODO(lambroslambrou): Properly support large transfers -
183 // http://crbug.com/151447.
184 XFree(data);
186 // If the property is zero-length then the large transfer is complete.
187 if (item_count == 0)
188 large_selection_property_ = None;
193 void XServerClipboard::OnSelectionNotify(XEvent* event) {
194 if (event->xselection.property != None) {
195 Atom type;
196 int format;
197 unsigned long item_count, after;
198 unsigned char *data;
199 XGetWindowProperty(display_, clipboard_window_,
200 event->xselection.property, 0, ~0L, True,
201 AnyPropertyType, &type, &format,
202 &item_count, &after, &data);
203 if (type == large_selection_atom_) {
204 // Large selection - just read and ignore these for now.
205 large_selection_property_ = event->xselection.property;
206 } else {
207 // Standard selection - call the selection notifier.
208 large_selection_property_ = None;
209 if (type != None) {
210 HandleSelectionNotify(&event->xselection, type, format, item_count,
211 data);
212 XFree(data);
213 return;
217 HandleSelectionNotify(&event->xselection, 0, 0, 0, 0);
220 void XServerClipboard::OnSelectionRequest(XEvent* event) {
221 XSelectionEvent selection_event;
222 selection_event.type = SelectionNotify;
223 selection_event.display = event->xselectionrequest.display;
224 selection_event.requestor = event->xselectionrequest.requestor;
225 selection_event.selection = event->xselectionrequest.selection;
226 selection_event.time = event->xselectionrequest.time;
227 selection_event.target = event->xselectionrequest.target;
228 if (event->xselectionrequest.property == None)
229 event->xselectionrequest.property = event->xselectionrequest.target;
230 if (!IsSelectionOwner(selection_event.selection)) {
231 selection_event.property = None;
232 } else {
233 selection_event.property = event->xselectionrequest.property;
234 if (selection_event.target == targets_atom_) {
235 SendTargetsResponse(selection_event.requestor, selection_event.property);
236 } else if (selection_event.target == timestamp_atom_) {
237 SendTimestampResponse(selection_event.requestor,
238 selection_event.property);
239 } else if (selection_event.target == utf8_string_atom_ ||
240 selection_event.target == XA_STRING) {
241 SendStringResponse(selection_event.requestor, selection_event.property,
242 selection_event.target);
245 XSendEvent(display_, selection_event.requestor, False, 0,
246 reinterpret_cast<XEvent*>(&selection_event));
249 void XServerClipboard::OnSelectionClear(XEvent* event) {
250 selections_owned_.erase(event->xselectionclear.selection);
253 void XServerClipboard::SendTargetsResponse(Window requestor, Atom property) {
254 // Respond advertising XA_STRING, UTF8_STRING and TIMESTAMP data for the
255 // selection.
256 Atom targets[3];
257 targets[0] = timestamp_atom_;
258 targets[1] = utf8_string_atom_;
259 targets[2] = XA_STRING;
260 XChangeProperty(display_, requestor, property, XA_ATOM, 32, PropModeReplace,
261 reinterpret_cast<unsigned char*>(targets), 3);
264 void XServerClipboard::SendTimestampResponse(Window requestor, Atom property) {
265 // Respond with the timestamp of our selection; we always return
266 // CurrentTime since our selections are set by remote clients, so there
267 // is no associated local X event.
269 // TODO(lambroslambrou): Should use a proper timestamp here instead of
270 // CurrentTime. ICCCM recommends doing a zero-length property append,
271 // and getting a timestamp from the subsequent PropertyNotify event.
272 Time time = CurrentTime;
273 XChangeProperty(display_, requestor, property, XA_INTEGER, 32,
274 PropModeReplace, reinterpret_cast<unsigned char*>(&time), 1);
277 void XServerClipboard::SendStringResponse(Window requestor, Atom property,
278 Atom target) {
279 if (!data_.empty()) {
280 // Return the actual string data; we always return UTF8, regardless of
281 // the configured locale.
282 XChangeProperty(display_, requestor, property, target, 8, PropModeReplace,
283 reinterpret_cast<unsigned char*>(
284 const_cast<char*>(data_.data())),
285 data_.size());
289 void XServerClipboard::HandleSelectionNotify(XSelectionEvent* event,
290 Atom type,
291 int format,
292 int item_count,
293 void* data) {
294 bool finished = false;
296 if (event->target == targets_atom_) {
297 finished = HandleSelectionTargetsEvent(event, format, item_count, data);
298 } else if (event->target == utf8_string_atom_ ||
299 event->target == XA_STRING) {
300 finished = HandleSelectionStringEvent(event, format, item_count, data);
303 if (finished)
304 get_selections_time_ = base::TimeTicks();
307 bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event,
308 int format,
309 int item_count,
310 void* data) {
311 if (event->property == targets_atom_) {
312 if (data && format == 32) {
313 // The XGetWindowProperty man-page specifies that the returned
314 // property data will be an array of |long|s in the case where
315 // |format| == 32. Although the items are 32-bit values (as stored and
316 // sent over the X protocol), Xlib presents the data to the client as an
317 // array of |long|s, with zero-padding on a 64-bit system where |long|
318 // is bigger than 32 bits.
319 const long* targets = static_cast<const long*>(data);
320 for (int i = 0; i < item_count; i++) {
321 if (targets[i] == static_cast<long>(utf8_string_atom_)) {
322 RequestSelectionString(event->selection, utf8_string_atom_);
323 return false;
328 RequestSelectionString(event->selection, XA_STRING);
329 return false;
332 bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event,
333 int format,
334 int item_count,
335 void* data) {
336 if (event->property != selection_string_atom_ || !data || format != 8)
337 return true;
339 std::string text(static_cast<char*>(data), item_count);
341 if (event->target == XA_STRING || event->target == utf8_string_atom_)
342 NotifyClipboardText(text);
344 return true;
347 void XServerClipboard::NotifyClipboardText(const std::string& text) {
348 data_ = text;
349 callback_.Run(kMimeTypeTextUtf8, data_);
352 void XServerClipboard::RequestSelectionTargets(Atom selection) {
353 XConvertSelection(display_, selection, targets_atom_, targets_atom_,
354 clipboard_window_, CurrentTime);
357 void XServerClipboard::RequestSelectionString(Atom selection, Atom target) {
358 XConvertSelection(display_, selection, target, selection_string_atom_,
359 clipboard_window_, CurrentTime);
362 void XServerClipboard::AssertSelectionOwnership(Atom selection) {
363 XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime);
364 if (XGetSelectionOwner(display_, selection) == clipboard_window_) {
365 selections_owned_.insert(selection);
366 } else {
367 LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
371 bool XServerClipboard::IsSelectionOwner(Atom selection) {
372 return selections_owned_.find(selection) != selections_owned_.end();
375 } // namespace remoting