123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401 |
- // 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 <limits>
- #include "base/callback.h"
- #include "base/memory/ref_counted_memory.h"
- #include "base/memory/scoped_refptr.h"
- #include "base/strings/string_util.h"
- #include "remoting/base/constants.h"
- #include "remoting/base/logging.h"
- #include "remoting/base/util.h"
- #include "ui/gfx/x/extension_manager.h"
- #include "ui/gfx/x/future.h"
- #include "ui/gfx/x/xproto.h"
- #include "ui/gfx/x/xproto_util.h"
- namespace remoting {
- XServerClipboard::XServerClipboard() = default;
- XServerClipboard::~XServerClipboard() = default;
- void XServerClipboard::Init(x11::Connection* connection,
- const ClipboardChangedCallback& callback) {
- connection_ = connection;
- callback_ = callback;
- if (!connection_->xfixes().present()) {
- HOST_LOG << "X server does not support XFixes.";
- return;
- }
- // Let the server know the client version.
- connection_->xfixes().QueryVersion(
- {x11::XFixes::major_version, x11::XFixes::minor_version});
- clipboard_window_ = connection_->GenerateId<x11::Window>();
- connection_->CreateWindow({
- .wid = clipboard_window_,
- .parent = connection_->default_root(),
- .width = 1,
- .height = 1,
- .override_redirect = x11::Bool32(true),
- });
- // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a
- // dependency on ui/ or by moving X11AtomCache to base/.
- static const char* const kAtomNames[] = {"CLIPBOARD", "INCR",
- "SELECTION_STRING", "TARGETS",
- "TIMESTAMP", "UTF8_STRING"};
- static const int kNumAtomNames = std::size(kAtomNames);
- x11::Future<x11::InternAtomReply> futures[kNumAtomNames];
- for (size_t i = 0; i < kNumAtomNames; i++)
- futures[i] = connection_->InternAtom({false, kAtomNames[i]});
- connection_->Flush();
- x11::Atom atoms[kNumAtomNames];
- memset(atoms, 0, sizeof(atoms));
- for (size_t i = 0; i < kNumAtomNames; i++) {
- if (auto reply = futures[i].Sync()) {
- atoms[i] = reply->atom;
- } else {
- LOG(ERROR) << "Failed to intern atom(s)";
- break;
- }
- }
- 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];
- static_assert(kNumAtomNames >= 6, "kAtomNames is too small");
- connection_->xfixes().SelectSelectionInput(
- {static_cast<x11::Window>(clipboard_window_),
- static_cast<x11::Atom>(clipboard_atom_),
- x11::XFixes::SelectionEventMask::SetSelectionOwner});
- connection_->Flush();
- }
- void XServerClipboard::SetClipboard(const std::string& mime_type,
- const std::string& data) {
- DCHECK(connection_->Ready());
- if (clipboard_window_ == x11::Window::None)
- return;
- // Currently only UTF-8 is supported.
- if (mime_type != kMimeTypeTextUtf8)
- return;
- if (!base::IsStringUTF8AllowingNoncharacters(data)) {
- LOG(ERROR) << "ClipboardEvent: data is not UTF-8 encoded.";
- return;
- }
- data_ = data;
- AssertSelectionOwnership(x11::Atom::PRIMARY);
- AssertSelectionOwnership(clipboard_atom_);
- }
- void XServerClipboard::ProcessXEvent(const x11::Event& event) {
- if (clipboard_window_ == x11::Window::None ||
- event.window() != clipboard_window_) {
- return;
- }
- if (auto* property_notify = event.As<x11::PropertyNotifyEvent>())
- OnPropertyNotify(*property_notify);
- else if (auto* selection_notify = event.As<x11::SelectionNotifyEvent>())
- OnSelectionNotify(*selection_notify);
- else if (auto* selection_request = event.As<x11::SelectionRequestEvent>())
- OnSelectionRequest(*selection_request);
- else if (auto* selection_clear = event.As<x11::SelectionClearEvent>())
- OnSelectionClear(*selection_clear);
- if (auto* xfixes_selection_notify =
- event.As<x11::XFixes::SelectionNotifyEvent>()) {
- OnSetSelectionOwnerNotify(xfixes_selection_notify->selection,
- xfixes_selection_notify->selection_timestamp);
- }
- }
- void XServerClipboard::OnSetSelectionOwnerNotify(x11::Atom selection,
- x11::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::Seconds(5)) {
- // TODO(lambroslambrou): Instead of ignoring this notification, cancel any
- // pending request operations and ignore the resulting events, before
- // dispatching new requests here.
- return;
- }
- // Only process CLIPBOARD selections.
- if (selection != clipboard_atom_)
- 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(const x11::PropertyNotifyEvent& event) {
- if (large_selection_property_ != x11::Atom::None &&
- event.atom == large_selection_property_ &&
- event.state == x11::Property::NewValue) {
- auto req = connection_->GetProperty({
- .c_delete = true,
- .window = clipboard_window_,
- .property = large_selection_property_,
- .type = x11::Atom::Any,
- .long_length = std::numeric_limits<uint32_t>::max(),
- });
- if (auto reply = req.Sync()) {
- if (reply->type != x11::Atom::None) {
- // TODO(lambroslambrou): Properly support large transfers -
- // http://crbug.com/151447.
- // If the property is zero-length then the large transfer is complete.
- if (reply->value_len == 0)
- large_selection_property_ = x11::Atom::None;
- }
- }
- }
- }
- void XServerClipboard::OnSelectionNotify(
- const x11::SelectionNotifyEvent& event) {
- if (event.property != x11::Atom::None) {
- auto req = connection_->GetProperty({
- .c_delete = true,
- .window = clipboard_window_,
- .property = event.property,
- .type = x11::Atom::Any,
- .long_length = std::numeric_limits<uint32_t>::max(),
- });
- if (auto reply = req.Sync()) {
- if (reply->type == large_selection_atom_) {
- // Large selection - just read and ignore these for now.
- large_selection_property_ = event.property;
- } else {
- // Standard selection - call the selection notifier.
- large_selection_property_ = x11::Atom::None;
- if (reply->type != x11::Atom::None) {
- HandleSelectionNotify(event, reply->type, reply->format,
- reply->value_len, reply->value->data());
- return;
- }
- }
- }
- }
- HandleSelectionNotify(event, x11::Atom::None, 0, 0, nullptr);
- }
- void XServerClipboard::OnSelectionRequest(
- const x11::SelectionRequestEvent& event) {
- x11::SelectionNotifyEvent selection_event;
- selection_event.requestor = event.requestor;
- selection_event.selection = event.selection;
- selection_event.time = event.time;
- selection_event.target = event.target;
- auto property =
- event.property == x11::Atom::None ? event.target : event.property;
- if (!IsSelectionOwner(selection_event.selection)) {
- selection_event.property = x11::Atom::None;
- } else {
- selection_event.property = property;
- if (selection_event.target == static_cast<x11::Atom>(targets_atom_)) {
- SendTargetsResponse(selection_event.requestor, selection_event.property);
- } else if (selection_event.target ==
- static_cast<x11::Atom>(timestamp_atom_)) {
- SendTimestampResponse(selection_event.requestor,
- selection_event.property);
- } else if (selection_event.target ==
- static_cast<x11::Atom>(utf8_string_atom_) ||
- selection_event.target == x11::Atom::STRING) {
- SendStringResponse(selection_event.requestor, selection_event.property,
- selection_event.target);
- }
- }
- x11::SendEvent(selection_event, selection_event.requestor,
- x11::EventMask::NoEvent, connection_);
- }
- void XServerClipboard::OnSelectionClear(const x11::SelectionClearEvent& event) {
- selections_owned_.erase(event.selection);
- }
- void XServerClipboard::SendTargetsResponse(x11::Window requestor,
- x11::Atom property) {
- // Respond advertising x11::Atom::STRING, UTF8_STRING and TIMESTAMP data for
- // the selection.
- x11::Atom targets[3] = {
- timestamp_atom_,
- utf8_string_atom_,
- x11::Atom::STRING,
- };
- connection_->ChangeProperty({
- .mode = x11::PropMode::Replace,
- .window = requestor,
- .property = property,
- .type = x11::Atom::ATOM,
- .format = CHAR_BIT * sizeof(x11::Atom),
- .data_len = std::size(targets),
- .data = base::MakeRefCounted<base::RefCountedStaticMemory>(
- &targets[0], sizeof(targets)),
- });
- connection_->Flush();
- }
- void XServerClipboard::SendTimestampResponse(x11::Window requestor,
- x11::Atom property) {
- // 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.
- x11::Time time = x11::Time::CurrentTime;
- connection_->ChangeProperty({
- .mode = x11::PropMode::Replace,
- .window = requestor,
- .property = property,
- .type = x11::Atom::INTEGER,
- .format = CHAR_BIT * sizeof(x11::Time),
- .data_len = 1,
- .data = base::MakeRefCounted<base::RefCountedStaticMemory>(&time,
- sizeof(time)),
- });
- connection_->Flush();
- }
- void XServerClipboard::SendStringResponse(x11::Window requestor,
- x11::Atom property,
- x11::Atom target) {
- if (!data_.empty()) {
- // Return the actual string data; we always return UTF8, regardless of
- // the configured locale.
- connection_->ChangeProperty({
- .mode = x11::PropMode::Replace,
- .window = requestor,
- .property = property,
- .type = target,
- .format = 8,
- .data_len = static_cast<uint32_t>(data_.size()),
- .data = base::MakeRefCounted<base::RefCountedStaticMemory>(
- data_.data(), data_.size()),
- });
- connection_->Flush();
- }
- }
- void XServerClipboard::HandleSelectionNotify(
- const x11::SelectionNotifyEvent& event,
- x11::Atom type,
- int format,
- int item_count,
- const void* data) {
- bool finished = false;
- auto target = event.target;
- if (target == targets_atom_)
- finished = HandleSelectionTargetsEvent(event, format, item_count, data);
- else if (target == utf8_string_atom_ || target == x11::Atom::STRING)
- finished = HandleSelectionStringEvent(event, format, item_count, data);
- if (finished)
- get_selections_time_ = base::TimeTicks();
- }
- bool XServerClipboard::HandleSelectionTargetsEvent(
- const x11::SelectionNotifyEvent& event,
- int format,
- int item_count,
- const void* data) {
- auto selection = event.selection;
- if (event.property == targets_atom_) {
- if (data && format == 32) {
- const uint32_t* targets = static_cast<const uint32_t*>(data);
- for (int i = 0; i < item_count; i++) {
- if (targets[i] == static_cast<uint32_t>(utf8_string_atom_)) {
- RequestSelectionString(selection, utf8_string_atom_);
- return false;
- }
- }
- }
- }
- RequestSelectionString(selection, x11::Atom::STRING);
- return false;
- }
- bool XServerClipboard::HandleSelectionStringEvent(
- const x11::SelectionNotifyEvent& event,
- int format,
- int item_count,
- const void* data) {
- auto property = event.property;
- auto target = event.target;
- if (property != selection_string_atom_ || !data || format != 8)
- return true;
- std::string text(static_cast<const char*>(data), item_count);
- if (target == x11::Atom::STRING || target == utf8_string_atom_)
- NotifyClipboardText(text);
- return true;
- }
- void XServerClipboard::NotifyClipboardText(const std::string& text) {
- data_ = text;
- callback_.Run(kMimeTypeTextUtf8, data_);
- }
- void XServerClipboard::RequestSelectionTargets(x11::Atom selection) {
- connection_->ConvertSelection({clipboard_window_, selection, targets_atom_,
- targets_atom_, x11::Time::CurrentTime});
- }
- void XServerClipboard::RequestSelectionString(x11::Atom selection,
- x11::Atom target) {
- connection_->ConvertSelection({clipboard_window_, selection, target,
- selection_string_atom_,
- x11::Time::CurrentTime});
- }
- void XServerClipboard::AssertSelectionOwnership(x11::Atom selection) {
- connection_->SetSelectionOwner(
- {clipboard_window_, selection, x11::Time::CurrentTime});
- auto reply = connection_->GetSelectionOwner({selection}).Sync();
- auto owner = reply ? reply->owner : x11::Window::None;
- if (owner == clipboard_window_) {
- selections_owned_.insert(selection);
- } else {
- LOG(ERROR) << "XSetSelectionOwner failed for selection "
- << static_cast<uint32_t>(selection);
- }
- }
- bool XServerClipboard::IsSelectionOwner(x11::Atom selection) {
- return selections_owned_.find(selection) != selections_owned_.end();
- }
- } // namespace remoting
|