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

Unified 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: Address Wez's comments Created 8 years, 3 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 side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698