0

Implement X11 clipboard for ozone x11

Supports only CLIPBOARD selection and not PRIMARY (for now) since
ozone only supports a single clipboard.

Regisers to receive XFixesSetSelectionOwnerNotify events and
prefetches and caches supported mime types whenever ownership
changes.  Also prefetches clipboard contents as 'text/plain'
since that is the most likely type to be requested.  If clients
request a different type, it will be fetched at the time of
requesting.

Converts text/plain[;charset=utf-8] <=> [UTF8_]STRING to
allow interoperability with other applications which do not
use the same mime types as chrome.

This code can be patched into linux-chromeos (chrome os running
on a linux desktop) by modifying the build file:
 sed -i s/is_desktop_linux/is_linux/ ui/base/clipboard/BUILD.gn

Bug: 985187
Change-Id: I4277f903ab8f11e0f5ad14d095eb099cf775f771
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1736619
Reviewed-by: kylechar <kylechar@chromium.org>
Commit-Queue: Joel Hockey <joelhockey@chromium.org>
Cr-Commit-Position: refs/heads/master@{#691892}
This commit is contained in:
Joel Hockey
2019-08-30 01:13:37 +00:00
committed by Commit Bot
parent 52c34f1866
commit 31fc62a978
6 changed files with 484 additions and 107 deletions

@ -20,8 +20,8 @@ source_set("x11") {
"gl_surface_glx_ozone.h",
"ozone_platform_x11.cc",
"ozone_platform_x11.h",
"x11_clipboard.cc",
"x11_clipboard.h",
"x11_clipboard_ozone.cc",
"x11_clipboard_ozone.h",
"x11_cursor_factory_ozone.cc",
"x11_cursor_factory_ozone.h",
"x11_cursor_ozone.cc",
@ -41,6 +41,7 @@ source_set("x11") {
"//gpu/vulkan:buildflags",
"//skia",
"//ui/base",
"//ui/base/clipboard:clipboard_types",
"//ui/base/ime",
"//ui/base/x",
"//ui/display/fake",

@ -15,7 +15,7 @@
#include "ui/events/platform/x11/x11_event_source_default.h"
#include "ui/gfx/x/x11.h"
#include "ui/ozone/common/stub_overlay_manager.h"
#include "ui/ozone/platform/x11/x11_clipboard.h"
#include "ui/ozone/platform/x11/x11_clipboard_ozone.h"
#include "ui/ozone/platform/x11/x11_cursor_factory_ozone.h"
#include "ui/ozone/platform/x11/x11_screen_ozone.h"
#include "ui/ozone/platform/x11/x11_surface_factory.h"
@ -126,7 +126,7 @@ class OzonePlatformX11 : public OzonePlatform {
window_manager_ = std::make_unique<X11WindowManagerOzone>();
overlay_manager_ = std::make_unique<StubOverlayManager>();
input_controller_ = CreateStubInputController();
clipboard_ = std::make_unique<X11Clipboard>();
clipboard_ = std::make_unique<X11ClipboardOzone>();
cursor_factory_ozone_ = std::make_unique<X11CursorFactoryOzone>();
gpu_platform_support_host_.reset(CreateStubGpuPlatformSupportHost());
@ -179,7 +179,7 @@ class OzonePlatformX11 : public OzonePlatform {
std::unique_ptr<X11WindowManagerOzone> window_manager_;
std::unique_ptr<OverlayManagerOzone> overlay_manager_;
std::unique_ptr<InputController> input_controller_;
std::unique_ptr<X11Clipboard> clipboard_;
std::unique_ptr<X11ClipboardOzone> clipboard_;
std::unique_ptr<X11CursorFactoryOzone> cursor_factory_ozone_;
std::unique_ptr<GpuPlatformSupportHost> gpu_platform_support_host_;

@ -1,56 +0,0 @@
// Copyright 2019 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 <utility>
#include "ui/ozone/platform/x11/x11_clipboard.h"
namespace ui {
X11Clipboard::X11Clipboard() = default;
X11Clipboard::~X11Clipboard() = default;
void X11Clipboard::OfferClipboardData(
const PlatformClipboard::DataMap& data_map,
PlatformClipboard::OfferDataClosure callback) {
// TODO(crbug.com/973295): Implement X11Clipboard
NOTIMPLEMENTED_LOG_ONCE();
std::move(callback).Run();
}
void X11Clipboard::RequestClipboardData(
const std::string& mime_type,
PlatformClipboard::DataMap* data_map,
PlatformClipboard::RequestDataClosure callback) {
// TODO(crbug.com/973295): Implement X11Clipboard
NOTIMPLEMENTED_LOG_ONCE();
std::move(callback).Run({});
}
void X11Clipboard::GetAvailableMimeTypes(
PlatformClipboard::GetMimeTypesClosure callback) {
// TODO(crbug.com/973295): Implement X11Clipboard
NOTIMPLEMENTED_LOG_ONCE();
std::move(callback).Run({});
}
bool X11Clipboard::IsSelectionOwner() {
// TODO(crbug.com/973295): Implement X11Clipboard
NOTIMPLEMENTED_LOG_ONCE();
return false;
}
void X11Clipboard::SetSequenceNumberUpdateCb(
PlatformClipboard::SequenceNumberUpdateCb cb) {
DCHECK(update_sequence_cb_.is_null())
<< " The callback can be installed only once.";
update_sequence_cb_ = std::move(cb);
}
void X11Clipboard::UpdateSequenceNumber() {
if (!update_sequence_cb_.is_null())
update_sequence_cb_.Run();
}
} // namespace ui

@ -1,46 +0,0 @@
// Copyright 2019 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.
#ifndef UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_H_
#define UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_H_
#include <string>
#include "base/callback.h"
#include "ui/ozone/public/platform_clipboard.h"
namespace ui {
// Handles clipboard operations for X11 Platform
class X11Clipboard : public PlatformClipboard {
public:
X11Clipboard();
~X11Clipboard() override;
// PlatformClipboard.
void OfferClipboardData(
const PlatformClipboard::DataMap& data_map,
PlatformClipboard::OfferDataClosure callback) override;
void RequestClipboardData(
const std::string& mime_type,
PlatformClipboard::DataMap* data_map,
PlatformClipboard::RequestDataClosure callback) override;
void GetAvailableMimeTypes(
PlatformClipboard::GetMimeTypesClosure callback) override;
bool IsSelectionOwner() override;
void SetSequenceNumberUpdateCb(
PlatformClipboard::SequenceNumberUpdateCb cb) override;
private:
void UpdateSequenceNumber();
// Notifies whenever clipboard sequence number is changed.
PlatformClipboard::SequenceNumberUpdateCb update_sequence_cb_;
DISALLOW_COPY_AND_ASSIGN(X11Clipboard);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_H_

@ -0,0 +1,357 @@
// Copyright 2019 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 "ui/ozone/platform/x11/x11_clipboard_ozone.h"
#include <algorithm>
#include <vector>
#include "base/containers/span.h"
#include "base/stl_util.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/gfx/x/x11_atom_cache.h"
#include "ui/gfx/x/x11_types.h"
using base::Contains;
namespace ui {
namespace {
const char kChromeSelection[] = "CHROME_SELECTION";
const char kClipboard[] = "CLIPBOARD";
const char kMimeTypeUtf8[] = "text/plain;charset=utf-8";
const char kString[] = "STRING";
const char kTargets[] = "TARGETS";
const char kTimestamp[] = "TIMESTAMP";
const char kUtf8String[] = "UTF8_STRING";
// Helps to allow conversions for text/plain[;charset=utf-8] <=> [UTF8_]STRING.
void ExpandTypes(std::vector<std::string>* list) {
bool has_mime_type_text = Contains(*list, ui::kMimeTypeText);
bool has_string = Contains(*list, kString);
bool has_mime_type_utf8 = Contains(*list, kMimeTypeUtf8);
bool has_utf8_string = Contains(*list, kUtf8String);
if (has_mime_type_text && !has_string)
list->push_back(kString);
if (has_string && !has_mime_type_text)
list->push_back(ui::kMimeTypeText);
if (has_mime_type_utf8 && !has_utf8_string)
list->push_back(kUtf8String);
if (has_utf8_string && !has_mime_type_utf8)
list->push_back(kMimeTypeUtf8);
}
XID FindXEventTarget(const XEvent& xev) {
XID target = xev.xany.window;
if (xev.type == GenericEvent)
target = static_cast<XIDeviceEvent*>(xev.xcookie.data)->event;
return target;
}
} // namespace
X11ClipboardOzone::X11ClipboardOzone()
: atom_clipboard_(gfx::GetAtom(kClipboard)),
atom_targets_(gfx::GetAtom(kTargets)),
atom_timestamp_(gfx::GetAtom(kTimestamp)),
x_property_(gfx::GetAtom(kChromeSelection)),
x_display_(gfx::GetXDisplay()),
x_window_(XCreateSimpleWindow(x_display_,
DefaultRootWindow(x_display_),
/*x=*/-100,
/*y=*/-100,
/*width=*/10,
/*height=*/10,
/*border_width=*/0,
/*border=*/0,
/*background=*/0)) {
int ignored; // xfixes_error_base.
if (!XFixesQueryExtension(x_display_, &xfixes_event_base_, &ignored)) {
LOG(ERROR) << "X server does not support XFixes.";
return;
}
using_xfixes_ = true;
// Register to receive standard X11 events.
X11EventSource::GetInstance()->AddXEventDispatcher(this);
// Register to receive XFixes notification when selection owner changes.
XFixesSelectSelectionInput(x_display_, x_window_, atom_clipboard_,
XFixesSetSelectionOwnerNotifyMask);
// Prefetch the current remote clipboard contents.
QueryTargets();
}
X11ClipboardOzone::~X11ClipboardOzone() {
X11EventSource::GetInstance()->RemoveXEventDispatcher(this);
}
bool X11ClipboardOzone::DispatchXEvent(XEvent* xev) {
if (FindXEventTarget(*xev) != x_window_)
return false;
switch (xev->type) {
case SelectionRequest:
return OnSelectionRequest(xev);
case SelectionNotify:
return OnSelectionNotify(xev);
}
if (using_xfixes_ &&
xev->type == xfixes_event_base_ + XFixesSetSelectionOwnerNotify) {
return OnSetSelectionOwnerNotify(xev);
}
return false;
}
// We are the clipboard owner, and a remote peer has requested either:
// TARGETS: List of mime types that we support for the clipboard.
// TIMESTAMP: Time when we took ownership of the clipboard.
// <mime-type>: Mime type to receive clipboard as.
bool X11ClipboardOzone::OnSelectionRequest(XEvent* xev) {
// We only support selection=CLIPBOARD, and property must be set.
if (xev->xselectionrequest.selection != atom_clipboard_ ||
xev->xselectionrequest.property == x11::None) {
return false;
}
XSelectionEvent selection_event;
selection_event.type = SelectionNotify;
selection_event.display = xev->xselectionrequest.display;
selection_event.requestor = xev->xselectionrequest.requestor;
selection_event.selection = xev->xselectionrequest.selection;
selection_event.target = xev->xselectionrequest.target;
selection_event.property = xev->xselectionrequest.property;
selection_event.time = xev->xselectionrequest.time;
selection_event.property = xev->xselectionrequest.property;
// target=TARGETS.
if (selection_event.target == atom_targets_) {
std::vector<std::string> targets;
// Add TIMESTAMP.
targets.push_back(kTimestamp);
for (auto& entry : offer_data_map_) {
targets.push_back(entry.first);
}
// Expand types, then convert from string to atom.
ExpandTypes(&targets);
std::vector<XAtom> atoms;
for (auto& entry : targets) {
atoms.push_back(gfx::GetAtom(entry.c_str()));
}
XChangeProperty(
x_display_, selection_event.requestor, selection_event.property,
XA_ATOM, /*format=*/32, PropModeReplace,
reinterpret_cast<unsigned char*>(atoms.data()), atoms.size());
} else if (selection_event.target == atom_timestamp_) {
// target=TIMESTAMP.
XChangeProperty(
x_display_, selection_event.requestor, selection_event.property,
XA_INTEGER, /*format=*/32, PropModeReplace,
reinterpret_cast<unsigned char*>(&acquired_selection_timestamp_), 1);
} else {
// Send clipboard data.
char* target_name = XGetAtomName(x_display_, selection_event.target);
std::string key = target_name;
// Allow conversions for text/plain[;charset=utf-8] <=> [UTF8_]STRING.
if (key == kUtf8String && !Contains(offer_data_map_, kUtf8String)) {
key = kMimeTypeUtf8;
} else if (key == kString && !Contains(offer_data_map_, kString)) {
key = kMimeTypeText;
}
auto it = offer_data_map_.find(key);
if (it != offer_data_map_.end()) {
XChangeProperty(
x_display_, selection_event.requestor, selection_event.property,
selection_event.target, /*format=*/8, PropModeReplace,
const_cast<unsigned char*>(it->second.data()), it->second.size());
}
XFree(target_name);
}
// Notify remote peer that clipboard has been sent.
XSendEvent(x_display_, selection_event.requestor, /*propagate=*/x11::False,
/*event_mask=*/0, reinterpret_cast<XEvent*>(&selection_event));
return true;
}
// A remote peer owns the clipboard. This event is received in response to
// our request for TARGETS (GetAvailableMimeTypes), or a specific mime type
// (RequestClipboardData).
bool X11ClipboardOzone::OnSelectionNotify(XEvent* xev) {
// GetAvailableMimeTypes.
if (xev->xselection.target == atom_targets_) {
XAtom type;
int format;
unsigned long item_count, after;
unsigned char* data = nullptr;
if (XGetWindowProperty(x_display_, x_window_, x_property_,
/*long_offset=*/0,
/*long_length=*/256 * sizeof(XAtom),
/*delete=*/x11::False, XA_ATOM, &type, &format,
&item_count, &after, &data) != x11::Success) {
return false;
}
mime_types_.clear();
base::span<XAtom> targets(reinterpret_cast<XAtom*>(data), item_count);
for (auto target : targets) {
char* atom_name = XGetAtomName(x_display_, target);
if (atom_name) {
mime_types_.push_back(atom_name);
XFree(atom_name);
}
}
XFree(data);
// If we have a saved callback, invoke it now with expanded types, otherwise
// guess that we will want 'text/plain' and fetch it now.
if (get_available_mime_types_callback_) {
std::vector<std::string> result(mime_types_);
ExpandTypes(&result);
std::move(get_available_mime_types_callback_).Run(std::move(result));
} else {
data_mime_type_ = kMimeTypeText;
ReadRemoteClipboard();
}
return true;
}
// RequestClipboardData.
if (xev->xselection.property == x_property_) {
XAtom type;
int format;
unsigned long item_count, after;
unsigned char* data;
XGetWindowProperty(x_display_, x_window_, x_property_,
/*long_offset=*/0, /*long_length=*/~0L,
/*delete=*/x11::True, AnyPropertyType, &type, &format,
&item_count, &after, &data);
if (type != x11::None && format == 8) {
std::vector<unsigned char> tmp(data, data + item_count);
data_ = tmp;
}
XFree(data);
// If we have a saved callback, invoke it now, otherwise this was a prefetch
// and we have already saved |data_| for the next call to
// |RequestClipboardData|.
if (request_clipboard_data_callback_) {
request_data_map_->emplace(data_mime_type_, data_);
std::move(request_clipboard_data_callback_).Run(data_);
}
return true;
}
return false;
}
bool X11ClipboardOzone::OnSetSelectionOwnerNotify(XEvent* xev) {
// Reset state and fetch remote clipboard if there is a new remote owner.
if (!IsSelectionOwner()) {
mime_types_.clear();
data_mime_type_.clear();
data_.clear();
QueryTargets();
}
return true;
}
void X11ClipboardOzone::QueryTargets() {
mime_types_.clear();
XConvertSelection(x_display_, atom_clipboard_, atom_targets_, x_property_,
x_window_, x11::CurrentTime);
}
void X11ClipboardOzone::ReadRemoteClipboard() {
data_.clear();
// Allow conversions for text/plain[;charset=utf-8] <=> [UTF8_]STRING.
std::string target = data_mime_type_;
if (!Contains(mime_types_, target)) {
if (target == kMimeTypeText) {
target = kString;
} else if (target == kMimeTypeUtf8) {
target = kUtf8String;
}
}
XConvertSelection(x_display_, atom_clipboard_, gfx::GetAtom(target.c_str()),
x_property_, x_window_, x11::CurrentTime);
}
void X11ClipboardOzone::OfferClipboardData(
const PlatformClipboard::DataMap& data_map,
PlatformClipboard::OfferDataClosure callback) {
acquired_selection_timestamp_ = X11EventSource::GetInstance()->GetTimestamp();
offer_data_map_ = data_map;
// Only take ownership if we are using xfixes.
// TODO(joelhockey): Make clipboard work without xfixes.
if (using_xfixes_) {
XSetSelectionOwner(x_display_, atom_clipboard_, x_window_,
acquired_selection_timestamp_);
}
std::move(callback).Run();
}
void X11ClipboardOzone::RequestClipboardData(
const std::string& mime_type,
PlatformClipboard::DataMap* data_map,
PlatformClipboard::RequestDataClosure callback) {
// If we are not using xfixes, return empty data.
// TODO(joelhockey): Make clipboard work without xfixes.
// If we have already prefetched the clipboard for the correct mime type,
// then send it right away, otherwise save the callback and attempt to get the
// requested mime type from the remote clipboard.
if (!using_xfixes_ || (data_mime_type_ == mime_type && !data_.empty())) {
data_map->emplace(mime_type, data_);
std::move(callback).Run(data_);
return;
}
data_mime_type_ = mime_type;
request_data_map_ = data_map;
DCHECK(request_clipboard_data_callback_.is_null());
request_clipboard_data_callback_ = std::move(callback);
ReadRemoteClipboard();
}
void X11ClipboardOzone::GetAvailableMimeTypes(
PlatformClipboard::GetMimeTypesClosure callback) {
// If we are not using xfixes, return empty data.
// TODO(joelhockey): Make clipboard work without xfixes.
// If we already have the list of supported mime types, send the expanded list
// of types right away, otherwise save the callback and get the list of
// TARGETS from the remote clipboard.
if (!using_xfixes_ || !mime_types_.empty()) {
std::vector<std::string> result(mime_types_);
ExpandTypes(&result);
std::move(callback).Run(std::move(result));
return;
}
DCHECK(get_available_mime_types_callback_.is_null());
get_available_mime_types_callback_ = std::move(callback);
QueryTargets();
}
bool X11ClipboardOzone::IsSelectionOwner() {
// If we are not using xfixes, then we are always the owner.
// TODO(joelhockey): Make clipboard work without xfixes.
return !using_xfixes_ ||
XGetSelectionOwner(x_display_, atom_clipboard_) == x_window_;
}
void X11ClipboardOzone::SetSequenceNumberUpdateCb(
PlatformClipboard::SequenceNumberUpdateCb cb) {
update_sequence_cb_ = std::move(cb);
}
} // namespace ui

@ -0,0 +1,121 @@
// Copyright 2019 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.
#ifndef UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_OZONE_H_
#define UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_OZONE_H_
#include <string>
#include <vector>
#include "base/callback.h"
#include "ui/events/platform/x11/x11_event_source.h"
#include "ui/gfx/x/x11.h"
#include "ui/gfx/x/x11_types.h"
#include "ui/ozone/public/platform_clipboard.h"
namespace ui {
// Handles clipboard operations for X11.
// Registers to receive standard X11 events, as well as
// XFixesSetSelectionOwnerNotify. When the remote owner changes, TARGETS and
// text/plain are preemptively fetched. They can then be provided immediately
// to GetAvailableMimeTypes, and RequestClipboardData when mime_type is
// text/plain. Otherwise GetAvailableMimeTypes and RequestClipboardData call
// the appropriate X11 functions and invoke callbacks when the associated events
// are received.
class X11ClipboardOzone : public PlatformClipboard, public XEventDispatcher {
public:
X11ClipboardOzone();
~X11ClipboardOzone() override;
// PlatformClipboard:
void OfferClipboardData(
const PlatformClipboard::DataMap& data_map,
PlatformClipboard::OfferDataClosure callback) override;
void RequestClipboardData(
const std::string& mime_type,
PlatformClipboard::DataMap* data_map,
PlatformClipboard::RequestDataClosure callback) override;
void GetAvailableMimeTypes(
PlatformClipboard::GetMimeTypesClosure callback) override;
bool IsSelectionOwner() override;
void SetSequenceNumberUpdateCb(
PlatformClipboard::SequenceNumberUpdateCb cb) override;
private:
// XEventDispatcher:
bool DispatchXEvent(XEvent* xev) override;
bool OnSelectionRequest(XEvent* xev);
bool OnSelectionNotify(XEvent* xev);
bool OnSetSelectionOwnerNotify(XEvent* xev);
// Queries the current clipboard owner for what mime types are available by
// sending XConvertSelection with target=TARGETS. After sending this, we
// will receive a SelectionNotify event with xselection.target=TARGETS which
// is processed in |OnSelectionNotify|.
void QueryTargets();
// Reads the contents of the remote clipboard by sending XConvertSelection
// with target=<mime-type>. After sending this, we will receive a
// SelectionNotify event with xselection.target=<mime-type> which is processed
// in |OnSelectionNotify|.
void ReadRemoteClipboard();
// Notifies whenever clipboard sequence number is changed.
PlatformClipboard::SequenceNumberUpdateCb update_sequence_cb_;
// DataMap we keep from |OfferClipboardData| to service remote requests for
// the clipboard.
PlatformClipboard::DataMap offer_data_map_;
// DataMap from |RequestClipboardData| that we write remote clipboard contents
// to before calling the completion callback.
PlatformClipboard::DataMap* request_data_map_ = nullptr;
// Mime types supported by remote clipboard.
std::vector<std::string> mime_types_;
// Data most recently read from remote clipboard.
std::vector<unsigned char> data_;
// Mime type of most recently read data from remote clipboard.
std::string data_mime_type_;
// If XFixes is unavailable, this clipboard window will not register to
// receive events and no processing will take place.
// TODO(joelhockey): Make clipboard work without xfixes.
bool using_xfixes_ = false;
// The event base returned by XFixesQueryExtension().
int xfixes_event_base_;
// Callbacks are stored when we haven't already prefetched the remote
// clipboard.
PlatformClipboard::GetMimeTypesClosure get_available_mime_types_callback_;
PlatformClipboard::RequestDataClosure request_clipboard_data_callback_;
// Local cache of atoms.
const XAtom atom_clipboard_;
const XAtom atom_targets_;
const XAtom atom_timestamp_;
// The property on |x_window_| which will receive remote clipboard contents.
const XAtom x_property_;
// Our X11 state.
Display* const x_display_;
// Input-only window used as a selection owner.
const XID x_window_;
// The time that this instance took ownership of the clipboard.
Time acquired_selection_timestamp_;
DISALLOW_COPY_AND_ASSIGN(X11ClipboardOzone);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_X11_X11_CLIPBOARD_OZONE_H_