Index: remoting/host/linux/x_server_clipboard.cc |
diff --git a/remoting/host/linux/x_server_clipboard.cc b/remoting/host/linux/x_server_clipboard.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..049c78c081da6b203ded47017072c558b81d8ece |
--- /dev/null |
+++ b/remoting/host/linux/x_server_clipboard.cc |
@@ -0,0 +1,457 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "remoting/host/linux/x_server_clipboard.h" |
+ |
+#include <X11/extensions/Xfixes.h> |
+ |
+#include "base/callback.h" |
+#include "base/logging.h" |
+#include "remoting/base/constants.h" |
+ |
+namespace { |
+ |
+// Returns true if |a| is later than |b|, treating 0 as being a long time ago. |
+// This is different from the usual meaning of CurrentTime (which is defined |
+// to be 0). |
+bool IsTimeLaterThan(Time a, Time b) { |
+ if (a == 0) { |
+ return false; |
+ } |
+ if (b == 0) { |
+ return true; |
+ } |
+ |
+ // Deal with possible wrap-around: if |b| is close to the largest possible |
+ // value, and |a| is close to 0, consider |a| to be later than |b| even |
+ // though |a| is smaller numerically. |
Wez
2012/09/20 23:37:46
nit: Suggest: 32-bit X timestamps will wrap around
Lambros
2012/09/21 17:05:57
Removed code.
|
+ return static_cast<long>(a - b) > 0; |
+} |
+ |
+} // namespace |
+ |
+namespace remoting { |
+ |
+XServerClipboard::XServerClipboard() |
+ : display_(NULL), |
+ clipboard_window_(BadValue), |
+ have_xfixes_(false), |
+ xfixes_event_base_(-1), |
+ xfixes_error_base_(-1), |
+ clipboard_atom_(None), |
+ large_selection_atom_(None), |
+ selection_string_atom_(None), |
+ targets_atom_(None), |
+ timestamp_atom_(None), |
+ utf8_string_atom_(None), |
+ large_selection_property_(None), |
+ current_selection_time_(0), |
+ new_selection_(None), |
+ getting_initial_selection_(true), |
+ new_cut_text_(false), |
+ had_valid_timestamp_(false) { |
+} |
+ |
+XServerClipboard::~XServerClipboard() { |
+} |
+ |
+void XServerClipboard::Init(Display* display, |
+ const ClipboardChangedCallback& callback) { |
+ display_ = display; |
+ callback_ = callback; |
+ |
+ // If any of these X API calls fail, an X Error will be raised, crashing the |
+ // process. This is unlikely to occur in practice, and even if it does, it |
+ // would mean the X server is in a bad state, so it's not worth trying to |
+ // trap such errors here. |
+ |
+ // TODO(lambroslambrou): Consider using ScopedXErrorHandler here, or consider |
+ // placing responsibility for handling X Errors outside this class, since |
+ // X Error handlers are global to all X connections. |
+ clipboard_window_ = XCreateSimpleWindow(display_, |
+ DefaultRootWindow(display_), |
+ 0, 0, 1, 1, // x, y, width, height |
+ 0, 0, 0); |
+ |
+ // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a |
+ // dependency on ui/ or by moving X11AtomCache to base/. |
+ const int kNumAtoms = 6; |
+ const char* names[kNumAtoms] = { |
+ "CLIPBOARD", |
+ "INCR", |
+ "SELECTION_STRING", |
+ "TARGETS", |
+ "TIMESTAMP", |
+ "UTF8_STRING" }; |
+ Atom atoms[kNumAtoms]; |
+ if (XInternAtoms(display_, const_cast<char**>(names), kNumAtoms, False, |
+ atoms)) { |
+ clipboard_atom_ = atoms[0]; |
+ large_selection_atom_ = atoms[1]; |
+ selection_string_atom_ = atoms[2]; |
+ targets_atom_ = atoms[3]; |
+ timestamp_atom_ = atoms[4]; |
+ utf8_string_atom_ = atoms[5]; |
+ } else { |
+ LOG(ERROR) << "XInternAtoms failed"; |
+ } |
+ |
+ if (XFixesQueryExtension(display_, &xfixes_event_base_, |
+ &xfixes_error_base_)) { |
+ have_xfixes_ = true; |
+ XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY, |
+ XFixesSetSelectionOwnerNotifyMask); |
+ XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_, |
+ XFixesSetSelectionOwnerNotifyMask); |
+ } else { |
+ LOG(INFO) << "X server does not support XFixes."; |
+ } |
+ |
+ GetSelections(None, 0); |
+} |
+ |
+void XServerClipboard::SetClipboard(const std::string& mime_type, |
+ const std::string& data) { |
+ DCHECK(display_); |
+ |
+ // Currently only UTF-8 is supported. |
+ if (mime_type != kMimeTypeTextUtf8) { |
+ return; |
+ } |
+ |
+ data_ = data; |
+ |
+ AssertSelectionOwnership(XA_PRIMARY); |
+ AssertSelectionOwnership(clipboard_atom_); |
+ current_selection_time_ = CurrentTime; |
+} |
+ |
+void XServerClipboard::ProcessXEvent(XEvent* event) { |
+ if (event->xany.window != clipboard_window_) { |
+ return; |
+ } |
+ |
+ switch (event->type) { |
+ case PropertyNotify: |
+ OnPropertyNotify(event); |
+ break; |
+ case SelectionNotify: |
+ OnSelectionNotify(event); |
+ break; |
+ case SelectionRequest: |
+ OnSelectionRequest(event); |
+ break; |
+ default: |
+ break; |
+ } |
+ |
+ if (have_xfixes_) { |
+ if (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) { |
+ XFixesSelectionNotifyEvent* notify_event = |
+ reinterpret_cast<XFixesSelectionNotifyEvent*>(event); |
+ GetSelections(notify_event->selection, notify_event->selection_timestamp); |
+ } |
+ } |
+} |
+ |
+void XServerClipboard::GetSelections(Atom selection, Time timestamp) { |
+ // This is called to check the state of the X selections. It is called |
+ // during initialization (with |selection| and |timestamp| equal to zero), |
Wez
2012/09/20 23:37:46
If it's called during initialization with |selecti
Lambros
2012/09/21 17:05:57
No loner called during init. This is now just an
|
+ // and whenever XFixesSelectionNotifyEvent is received (with |selection| and |
+ // |timestamp| taken from the notification event). This requests information |
+ // from the selection owner(s) about the timestamp, the target list and the |
+ // string value of the selection. |
+ |
+ // We start out trying to establish the timestamp(s) on the selections to |
+ // decide which one to retrieve. If |selection| and |timestamp| are non-zero |
+ // then we retrieve that selection. Otherwise we request the timestamp(s) |
+ // from the selection owner. If we get a valid timestamp and one of the |
+ // selections is newer than |current_selection_time_|, then we use that |
Wez
2012/09/20 23:37:46
nit for discussion off-CL: Do we really need these
Lambros
2012/09/21 17:05:57
Done. PTAL at the simplified version.
|
+ // selection. |
+ |
+ // Protect against receiving new XFixes selection notifications whilst we're |
+ // in the middle of waiting for information from the current selection owner. |
+ // A reasonable timeout allows for misbehaving apps that don't respond |
+ // quickly to our requests. |
+ if (!get_selections_time_.is_null() && |
+ (base::TimeTicks::Now() - get_selections_time_) < |
+ base::TimeDelta::FromSeconds(5)) { |
+ return; |
+ } |
+ |
+ if (selection != None) { |
+ if (selection != clipboard_atom_ && selection != XA_PRIMARY) { |
+ return; |
+ } |
+ } else { |
+ selection = XA_PRIMARY; |
+ } |
+ |
+ // If the selection is actually owned by us, there's no need to request the |
+ // details for it. |
Wez
2012/09/20 23:37:46
nit: Suggest:
If we own the selection, don't reque
Lambros
2012/09/21 17:05:57
Done.
|
+ if (IsSelectionOwner(selection)) { |
+ FinishGetSelections(); |
+ } |
+ |
+ get_selections_time_ = base::TimeTicks::Now(); |
+ |
+ if (timestamp) { |
+ current_selection_time_ = timestamp; |
+ |
+ // Before getting the value of the chosen selection, request the list of |
+ // targets supported for that selection (currently only UTF8_STRING and |
Wez
2012/09/20 23:37:46
nit: ... the list of target formats it supports.
Lambros
2012/09/21 17:05:57
Done.
|
+ // STRING are supported). |
Wez
2012/09/20 23:37:46
nit: No need to clarify the targets we support her
Lambros
2012/09/21 17:05:57
Done.
|
+ RequestSelectionTargets(selection); |
+ } else { |
+ new_selection_ = 0; |
+ had_valid_timestamp_ = false; |
+ RequestSelectionTimestamp(selection, CurrentTime); |
+ } |
+} |
+ |
+void XServerClipboard::FinishGetSelections() { |
+ getting_initial_selection_ = false; |
+ get_selections_time_ = base::TimeTicks(); |
+} |
+ |
+void XServerClipboard::OnPropertyNotify(XEvent* event) { |
+ if (large_selection_property_ != None && |
+ event->xproperty.atom == large_selection_property_ && |
+ event->xproperty.state == PropertyNewValue) { |
+ Atom type; |
+ int format; |
+ unsigned long item_count, after; |
+ unsigned char *data; |
+ XGetWindowProperty(display_, clipboard_window_, large_selection_property_, |
+ 0, ~0L, True, AnyPropertyType, &type, &format, |
+ &item_count, &after, &data); |
+ if (type != None) { |
+ // TODO(lambroslambrou): Properly support large transfers. |
Wez
2012/09/20 23:37:46
nit: Add a bug for that and refer to it here.
Lambros
2012/09/21 17:05:57
Done.
|
+ XFree(data); |
+ |
+ // If the property is zero-length then the large transfer is complete. |
+ if (item_count == 0) { |
+ large_selection_property_ = None; |
+ } |
+ } |
+ } |
+} |
+ |
+void XServerClipboard::OnSelectionNotify(XEvent* event) { |
+ if (event->xselection.property != None) { |
+ Atom type; |
+ int format; |
+ unsigned long item_count, after; |
+ unsigned char *data; |
+ XGetWindowProperty(display_, clipboard_window_, |
+ event->xselection.property, 0, ~0L, True, |
+ AnyPropertyType, &type, &format, |
+ &item_count, &after, &data); |
+ if (type == large_selection_atom_) { |
+ // Large selection - just read and ignore these for now. |
+ large_selection_property_ = event->xselection.property; |
+ } else { |
+ // Standard selection - call the selection notifier. |
+ large_selection_property_ = None; |
+ if (type != None) { |
+ DoSelectionNotify(&event->xselection, type, format, item_count, data); |
+ XFree(data); |
+ return; |
+ } |
+ } |
+ } |
+ DoSelectionNotify(&event->xselection, 0, 0, 0, 0); |
+} |
+ |
+void XServerClipboard::OnSelectionRequest(XEvent* event) { |
+ XSelectionEvent selection_event; |
+ selection_event.type = SelectionNotify; |
+ selection_event.display = event->xselectionrequest.display; |
+ selection_event.requestor = event->xselectionrequest.requestor; |
+ selection_event.selection = event->xselectionrequest.selection; |
+ selection_event.time = event->xselectionrequest.time; |
+ selection_event.target = event->xselectionrequest.target; |
+ if (event->xselectionrequest.property == None) { |
+ event->xselectionrequest.property = event->xselectionrequest.target; |
+ } |
+ if (!IsSelectionOwner(selection_event.selection)) { |
+ selection_event.property = None; |
+ } else { |
+ selection_event.property = event->xselectionrequest.property; |
+ if (selection_event.target == targets_atom_) { |
+ Atom targets[3]; |
+ targets[0] = timestamp_atom_; |
+ targets[1] = utf8_string_atom_; |
+ targets[2] = XA_STRING; |
+ XChangeProperty(display_, selection_event.requestor, |
+ selection_event.property, XA_ATOM, 32, PropModeReplace, |
+ reinterpret_cast<unsigned char*>(targets), 3); |
+ } else if (selection_event.target == timestamp_atom_) { |
+ Time time = selection_own_time_[selection_event.selection]; |
+ XChangeProperty(display_, selection_event.requestor, |
+ selection_event.property, XA_INTEGER, 32, |
+ PropModeReplace, reinterpret_cast<unsigned char*>(&time), |
+ 1); |
+ } else if (selection_event.target == utf8_string_atom_ || |
+ selection_event.target == XA_STRING) { |
+ if (!data_.empty()) { |
+ XChangeProperty(display_, selection_event.requestor, |
+ selection_event.property, selection_event.target, 8, |
+ PropModeReplace, |
+ reinterpret_cast<unsigned char*>( |
+ const_cast<char*>(data_.data())), |
+ data_.size()); |
+ } |
+ } |
+ } |
+ XSendEvent(display_, selection_event.requestor, False, 0, |
+ reinterpret_cast<XEvent*>(&selection_event)); |
+} |
+ |
+void XServerClipboard::DoSelectionNotify(XSelectionEvent* event, |
+ Atom type, |
+ int format, |
+ int item_count, |
+ void* data) { |
+ bool finished = false; |
+ |
+ if (event->target == timestamp_atom_) { |
+ finished = HandleSelectionTimestampEvent(event, format, item_count, data); |
+ } else if (event->target == targets_atom_) { |
+ finished = HandleSelectionTargetsEvent(event, format, item_count, data); |
+ } else if (event->target == utf8_string_atom_ || |
+ event->target == XA_STRING) { |
+ finished = HandleSelectionStringEvent(event, format, item_count, data); |
+ } |
+ |
+ if (finished) { |
+ FinishGetSelections(); |
+ } |
+} |
+ |
+bool XServerClipboard::HandleSelectionTimestampEvent(XSelectionEvent* event, |
+ int format, |
+ int item_count, |
+ void* data) { |
+ if (event->property == timestamp_atom_) { |
+ // The selection owner properly responded to the timestamp request. Test |
+ // the timestamp for validity. |
+ if (data && format == 32 && item_count == 1) { |
+ Time selection_time = *static_cast<Time*>(data); |
+ if (selection_time != 0) { |
+ had_valid_timestamp_ = true; |
+ } |
+ if (IsTimeLaterThan(selection_time, current_selection_time_)) { |
+ current_selection_time_ = selection_time; |
+ new_selection_ = event->selection; |
+ } |
+ } |
+ } |
+ |
+ if (event->selection == XA_PRIMARY) { |
+ if (!IsSelectionOwner(clipboard_atom_)) { |
+ RequestSelectionTimestamp(clipboard_atom_, event->time); |
+ return false; |
+ } |
+ } else if (event->selection != clipboard_atom_) { |
+ return false; |
+ } |
+ |
+ if (getting_initial_selection_ && had_valid_timestamp_) { |
+ return true; |
+ } |
+ |
+ if (new_selection_) { |
+ RequestSelectionTargets(new_selection_); |
+ } else if (!had_valid_timestamp_) { |
+ // If we didn't get a valid timestamp (for example an app like Mozilla |
+ // which sets a zero timestamp, or an app like VMWare which doesn't support |
+ // the timestamp target), we can't use the timestamp optimisation so we |
+ // have to get the actual value of the selection each time. |
Wez
2012/09/20 23:37:46
But this will only be triggered by an XFixes notif
Lambros
2012/09/21 17:05:57
Removed code. We no longer request the timestamp
|
+ RequestSelectionTargets(XA_PRIMARY); |
+ } else { |
+ return true; |
+ } |
+ return false; |
+} |
+ |
+bool XServerClipboard::HandleSelectionTargetsEvent(XSelectionEvent* event, |
+ int format, |
+ int item_count, |
+ void* data) { |
+ if (event->property == targets_atom_) { |
+ if (data && format == 32) { |
+ // The XGetWindowProperty man-page specifies that the returned |
+ // property data will be an array of |long|s in the case where |
+ // |format| == 32. Although the items are 32-bit values (as stored and |
+ // sent over the X protocol), Xlib presents the data to the client as an |
+ // array of |long|s, with zero-padding on a 64-bit system where |long| |
+ // is bigger than 32 bits. |
+ const long* targets = static_cast<const long*>(data); |
+ for (int i = 0; i < item_count; i++) { |
+ if (targets[i] == static_cast<long>(utf8_string_atom_)) { |
+ RequestSelectionString(event->selection, utf8_string_atom_); |
+ return false; |
+ } |
+ } |
+ } |
+ } |
+ RequestSelectionString(event->selection, XA_STRING); |
+ return false; |
+} |
+ |
+bool XServerClipboard::HandleSelectionStringEvent(XSelectionEvent* event, |
+ int format, |
+ int item_count, |
+ void* data) { |
+ if (event->property != selection_string_atom_ || !data || format != 8) { |
+ return true; |
+ } |
+ |
+ std::string text(static_cast<char*>(data), item_count); |
+ |
+ if (event->target == XA_STRING || event->target == utf8_string_atom_) { |
+ NotifyClipboardText(text); |
+ } |
+ return true; |
+} |
+ |
+void XServerClipboard::NotifyClipboardText(const std::string& text) { |
+ data_ = text; |
+ new_cut_text_ = false; |
+ if (!getting_initial_selection_) { |
+ callback_.Run(kMimeTypeTextUtf8, data_); |
+ } |
+} |
+ |
+bool XServerClipboard::IsSelectionOwner(Atom selection) { |
+ return selections_owned_.find(selection) != selections_owned_.end(); |
+} |
+ |
+void XServerClipboard::RequestSelectionTimestamp(Atom selection, Time time) { |
+ XConvertSelection(display_, selection, timestamp_atom_, timestamp_atom_, |
+ clipboard_window_, time); |
+} |
+ |
+void XServerClipboard::RequestSelectionTargets(Atom selection) { |
+ XConvertSelection(display_, selection, targets_atom_, targets_atom_, |
+ clipboard_window_, CurrentTime); |
+} |
+ |
+void XServerClipboard::RequestSelectionString(Atom selection, Atom target) { |
+ XConvertSelection(display_, selection, target, selection_string_atom_, |
+ clipboard_window_, CurrentTime); |
+} |
+ |
+void XServerClipboard::AssertSelectionOwnership(Atom selection) { |
+ XSetSelectionOwner(display_, selection, clipboard_window_, CurrentTime); |
+ if (XGetSelectionOwner(display_, selection) == clipboard_window_) { |
+ selections_owned_.insert(selection); |
+ selection_own_time_[selection] = CurrentTime; |
+ } else { |
+ LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection; |
+ } |
+} |
+ |
+} // namespace remoting |