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"
17 XServerClipboard::XServerClipboard()
19 clipboard_window_(BadValue
),
20 xfixes_event_base_(-1),
21 clipboard_atom_(None
),
22 large_selection_atom_(None
),
23 selection_string_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
) {
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.";
53 clipboard_window_
= XCreateSimpleWindow(display_
,
54 DefaultRootWindow(display_
),
55 0, 0, 1, 1, // x, y, width, height
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
[] = {
68 static const int kNumAtomNames
= arraysize(kAtomNames
);
70 Atom atoms
[kNumAtomNames
];
71 if (XInternAtoms(display_
, const_cast<char**>(kAtomNames
), kNumAtomNames
,
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");
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
) {
92 if (clipboard_window_
== BadValue
)
95 // Currently only UTF-8 is supported.
96 if (mime_type
!= kMimeTypeTextUtf8
)
98 if (!StringIsUtf8(data
.c_str(), data
.length())) {
99 LOG(ERROR
) << "ClipboardEvent: data is not UTF-8 encoded.";
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_
) {
115 switch (event
->type
) {
117 OnPropertyNotify(event
);
119 case SelectionNotify
:
120 OnSelectionNotify(event
);
122 case SelectionRequest
:
123 OnSelectionRequest(event
);
126 OnSelectionClear(event
);
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
,
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.
155 // Only process CLIPBOARD selections.
156 if (selection
!= clipboard_atom_
)
159 // If we own the selection, don't request details for it.
160 if (IsSelectionOwner(selection
))
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
) {
176 unsigned long item_count
, after
;
178 XGetWindowProperty(display_
, clipboard_window_
, large_selection_property_
,
179 0, ~0L, True
, AnyPropertyType
, &type
, &format
,
180 &item_count
, &after
, &data
);
182 // TODO(lambroslambrou): Properly support large transfers -
183 // http://crbug.com/151447.
186 // If the property is zero-length then the large transfer is complete.
188 large_selection_property_
= None
;
193 void XServerClipboard::OnSelectionNotify(XEvent
* event
) {
194 if (event
->xselection
.property
!= None
) {
197 unsigned long item_count
, after
;
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
;
207 // Standard selection - call the selection notifier.
208 large_selection_property_
= None
;
210 HandleSelectionNotify(&event
->xselection
, type
, format
, item_count
,
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
;
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
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
,
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())),
289 void XServerClipboard::HandleSelectionNotify(XSelectionEvent
* event
,
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
);
304 get_selections_time_
= base::TimeTicks();
307 bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent
* event
,
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_
);
328 RequestSelectionString(event
->selection
, XA_STRING
);
332 bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent
* event
,
336 if (event
->property
!= selection_string_atom_
|| !data
|| format
!= 8)
339 std::string
text(static_cast<char*>(data
), item_count
);
341 if (event
->target
== XA_STRING
|| event
->target
== utf8_string_atom_
)
342 NotifyClipboardText(text
);
347 void XServerClipboard::NotifyClipboardText(const std::string
& 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
);
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