| 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..86bea19d89d74067c98f48c3beb10399b27c56bf
|
| --- /dev/null
|
| +++ b/remoting/host/linux/x_server_clipboard.cc
|
| @@ -0,0 +1,359 @@
|
| +// 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 remoting {
|
| +
|
| +XServerClipboard::XServerClipboard()
|
| + : display_(NULL),
|
| + clipboard_window_(BadValue),
|
| + 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) {
|
| +}
|
| +
|
| +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.
|
| + if (!XFixesQueryExtension(display_, &xfixes_event_base_,
|
| + &xfixes_error_base_)) {
|
| + LOG(INFO) << "X server does not support XFixes.";
|
| + return;
|
| + }
|
| +
|
| + clipboard_window_ = XCreateSimpleWindow(display_,
|
| + DefaultRootWindow(display_),
|
| + 0, 0, 1, 1, // x, y, width, height
|
| + 0, 0, 0);
|
| +
|
| + XFixesSelectSelectionInput(display_, clipboard_window_, XA_PRIMARY,
|
| + XFixesSetSelectionOwnerNotifyMask);
|
| + XFixesSelectSelectionInput(display_, clipboard_window_, clipboard_atom_,
|
| + XFixesSetSelectionOwnerNotifyMask);
|
| +
|
| + // 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";
|
| + }
|
| +}
|
| +
|
| +void XServerClipboard::SetClipboard(const std::string& mime_type,
|
| + const std::string& data) {
|
| + DCHECK(display_);
|
| +
|
| + if (clipboard_window_ == BadValue) {
|
| + return;
|
| + }
|
| +
|
| + // Currently only UTF-8 is supported.
|
| + if (mime_type != kMimeTypeTextUtf8) {
|
| + return;
|
| + }
|
| +
|
| + data_ = data;
|
| +
|
| + AssertSelectionOwnership(XA_PRIMARY);
|
| + AssertSelectionOwnership(clipboard_atom_);
|
| +}
|
| +
|
| +void XServerClipboard::ProcessXEvent(XEvent* event) {
|
| + if (clipboard_window_ == BadValue ||
|
| + 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 (event->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
|
| + XFixesSelectionNotifyEvent* notify_event =
|
| + reinterpret_cast<XFixesSelectionNotifyEvent*>(event);
|
| + OnSetSelectionOwnerNotify(notify_event->selection,
|
| + notify_event->selection_timestamp);
|
| + }
|
| +}
|
| +
|
| +void XServerClipboard::OnSetSelectionOwnerNotify(Atom selection,
|
| + Time timestamp) {
|
| + // 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)) {
|
| + // TODO(lambroslambrou): Instead of ignoring this notification, cancel any
|
| + // pending request operations and ignore the resulting events, before
|
| + // dispatching new requests here.
|
| + return;
|
| + }
|
| +
|
| + if (selection != clipboard_atom_ && selection != XA_PRIMARY) {
|
| + // Only process PRIMARY and CLIPBOARD selections.
|
| + return;
|
| + }
|
| +
|
| + // If we own the selection, don't request details for it.
|
| + if (IsSelectionOwner(selection)) {
|
| + return;
|
| + }
|
| +
|
| + get_selections_time_ = base::TimeTicks::Now();
|
| +
|
| + // Before getting the value of the chosen selection, request the list of
|
| + // target formats it supports.
|
| + RequestSelectionTargets(selection);
|
| +}
|
| +
|
| +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 -
|
| + // http://crbug.com/151447.
|
| + 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) {
|
| + HandleSelectionNotify(&event->xselection, type, format, item_count,
|
| + data);
|
| + XFree(data);
|
| + return;
|
| + }
|
| + }
|
| + }
|
| + HandleSelectionNotify(&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_) {
|
| + // Respond advertising XA_STRING, UTF8_STRING and TIMESTAMP data for the
|
| + // selection.
|
| + 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_) {
|
| + // Respond with the timestamp of our selection; we always return
|
| + // CurrentTime since our selections are set by remote clients, so there
|
| + // is no associated local X event.
|
| +
|
| + // TODO(lambroslambrou): Should use a proper timestamp here instead of
|
| + // CurrentTime. ICCCM recommends doing a zero-length property append,
|
| + // and getting a timestamp from the subsequent PropertyNotify event.
|
| + Time time = CurrentTime;
|
| + 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()) {
|
| + // Return the actual string data; we always return UTF8, regardless of
|
| + // the configured locale.
|
| + 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::HandleSelectionNotify(XSelectionEvent* event,
|
| + Atom type,
|
| + int format,
|
| + int item_count,
|
| + void* data) {
|
| + bool finished = false;
|
| +
|
| + 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) {
|
| + get_selections_time_ = base::TimeTicks();
|
| + }
|
| +}
|
| +
|
| +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;
|
| + callback_.Run(kMimeTypeTextUtf8, data_);
|
| +}
|
| +
|
| +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);
|
| + } else {
|
| + LOG(ERROR) << "XSetSelectionOwner failed for selection " << selection;
|
| + }
|
| +}
|
| +
|
| +bool XServerClipboard::IsSelectionOwner(Atom selection) {
|
| + return selections_owned_.find(selection) != selections_owned_.end();
|
| +}
|
| +
|
| +} // namespace remoting
|
|
|