Commit 081f172f authored by Antonio Gomes's avatar Antonio Gomes Committed by Commit Bot

[ozone/wayland] Add clipboard support

This CL makes Ozone/Wayland able to exchange clipboard data with
other applications on the system. It accomplishes the functionality
by making use of wl interfaces like wl_data_source, wl_data_offer,
wl_data_device and wl_data_device_manager.

In order to bridge the communication between Ozone backends (eg wayland)
and the rest of Chromium, CL also introduces a 'delegate' class
class named ClipboardDelegate. In practice, clients of ClipboardDelegate
(eg Mus' ClipboardImpl) delegate clipboard operations to the active
Ozone backend.

FakeServer is also extended to support the various compositor side
functionalities needed so that tests in wayland_data_device_unittest.cc run.
For instance, MockDataDeviceManager, MockDataDevice, MockDataOffer,
MockDataSource classes are added, implementing the respective Wayland
compositor hooks to communicate with Wayland clients (eg Ozone/Wayland).

Last, the CL focuses on the Ozone/Wayland side of the implementation,
leaving plumbing with the rest of Chromium for follow up CLs (eg [1]).
However in order to test the functionality, wayland_data_device_unittest.cc
is introduced and mimics how a client (eg Mus) would use of the functionality.

[1] https://crrev.com/c/981372/

API footprint:

As mentioned, CL adds a new Ozone public API class: ClipboardDelegate. The
API is designed to support "promise scheme" clipboard implementations,
like to Wayland's and MacOS' NSPasteboard.

Flow:

1) Offer clipboard data (writing)
  When offering content to the system wide Clipboard (eg Chromium/Wayland
  user performs a copy or cut operation), WaylandConnection creates a
  WaylandDataSource instance, which installs listeners, including
  ::OnSend for actually exchange the data.
  WaylandDataSource then calls wl_data_source_offer to
  communicate the compositor the mimetypes available for clipboard
  operations, and then wl_data_device_set_selection to flag itself
  as the current active "clipboard owner".

  By the time data is requested from this client (eg another application on
  the system "paste" to an editor), the WaylandDataSource::OnSend callback
  is called with a mime type and a valid file descriptor passed in as
  parameters. Ozone/Wayland then writes it clipboard data into it.
  At this point, the application requesting the data is responsible for
  reading the content from the other end of pipe, and proceed with the
  editing operation on its side, once data is exchanged.

  This is exercised by WaylandDataDeviceManagerTest::WriteToClipboard
  end-to-end, ie client_1 writes data -> compositor -> client_2 reads data.

2) Requesting clibpard data (reading)
  When requesting data from the system wide Clipboard (eg Chromium/Wayland
  user performs a paste operation), WaylandConnection calls wl_data_offer_receive.
  with a valid "WriteIn" file descriptor for the current "clipboard owner"
  write data into. Once written, the data is read by Ozone/Wayland, from
  the other end of the same pipe.

  This is exercised by WaylandDataDeviceManagerTest::ReadFromClibpard
  end-to-end, ie client_2 request data -> compositor -> client_1 writes data.

BUG=578890

Change-Id: Ibfb8c26fe817e7639f32ac4bdc2f106d4b8bb513
Reviewed-on: https://chromium-review.googlesource.com/976461
Commit-Queue: Antonio Gomes <tonikitoo@igalia.com>
Reviewed-by: default avatarRobert Kroeger <rjkroege@chromium.org>
Reviewed-by: default avatarMaksim Sisov <msisov@igalia.com>
Cr-Commit-Position: refs/heads/master@{#553326}
parent f3010716
......@@ -61,6 +61,7 @@ constructor_list_cc_file = "$target_gen_dir/constructor_list.cc"
jumbo_component("ozone_base") {
sources = [
"ozone_base_export.h",
"public/clipboard_delegate.h",
"public/cursor_factory_ozone.cc",
"public/cursor_factory_ozone.h",
"public/gl_ozone.h",
......
......@@ -22,6 +22,12 @@ source_set("wayland") {
"wayland_connection.h",
"wayland_cursor.cc",
"wayland_cursor.h",
"wayland_data_device.cc",
"wayland_data_device.h",
"wayland_data_offer.cc",
"wayland_data_offer.h",
"wayland_data_source.cc",
"wayland_data_source.h",
"wayland_keyboard.cc",
"wayland_keyboard.h",
"wayland_object.cc",
......@@ -88,6 +94,7 @@ source_set("wayland_unittests") {
"fake_server.cc",
"fake_server.h",
"wayland_connection_unittest.cc",
"wayland_data_device_unittest.cc",
"wayland_keyboard_unittest.cc",
"wayland_pointer_unittest.cc",
"wayland_surface_factory_unittest.cc",
......
......@@ -10,16 +10,20 @@
#include <xdg-shell-unstable-v6-server-protocol.h>
#include "base/bind.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/memory/ptr_util.h"
#include "base/posix/eintr_wrapper.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "base/task_runner_util.h"
namespace wl {
namespace {
const uint32_t kCompositorVersion = 4;
const uint32_t kOutputVersion = 2;
const uint32_t kDataDeviceManagerVersion = 3;
const uint32_t kSeatVersion = 4;
const uint32_t kXdgShellVersion = 1;
......@@ -32,6 +36,37 @@ void DestroyResource(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
void WriteDataOnWorkerThread(base::ScopedFD fd, const std::string& utf8_text) {
if (!base::WriteFileDescriptor(fd.get(), utf8_text.data(), utf8_text.size()))
LOG(ERROR) << "Failed to write selection data to clipboard.";
}
std::vector<uint8_t> ReadDataOnWorkerThread(base::ScopedFD fd) {
constexpr size_t kChunkSize = 1024;
std::vector<uint8_t> bytes;
while (true) {
uint8_t chunk[kChunkSize];
ssize_t bytes_read = HANDLE_EINTR(read(fd.get(), chunk, kChunkSize));
if (bytes_read > 0) {
bytes.insert(bytes.end(), chunk, chunk + bytes_read);
continue;
}
if (!bytes_read)
return bytes;
if (bytes_read < 0) {
LOG(ERROR) << "Failed to read selection data from clipboard.";
return std::vector<uint8_t>();
}
}
}
void CreatePipe(base::ScopedFD* read_pipe, base::ScopedFD* write_pipe) {
int raw_pipe[2];
PCHECK(0 == pipe(raw_pipe));
read_pipe->reset(raw_pipe[0]);
write_pipe->reset(raw_pipe[1]);
}
// wl_compositor
void CreateSurface(wl_client* client, wl_resource* resource, uint32_t id) {
......@@ -129,6 +164,97 @@ const struct zxdg_shell_v6_interface zxdg_shell_v6_impl = {
&Pong, // pong
};
// wl_data_device
void DataDeviceSetSelection(wl_client* client,
wl_resource* resource,
wl_resource* data_source,
uint32_t serial) {
GetUserDataAs<MockDataDevice>(resource)->SetSelection(
data_source ? GetUserDataAs<MockDataSource>(data_source) : nullptr,
serial);
}
void DataDeviceRelease(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
const struct wl_data_device_interface data_device_impl = {
nullptr /*data_device_start_drag*/, &DataDeviceSetSelection,
&DataDeviceRelease};
// wl_data_device_manager
void CreateDataSource(wl_client* client, wl_resource* resource, uint32_t id) {
wl_resource* data_source_resource = wl_resource_create(
client, &wl_data_source_interface, wl_resource_get_version(resource), id);
if (!data_source_resource) {
wl_client_post_no_memory(client);
return;
}
std::unique_ptr<MockDataSource> data_source(
new MockDataSource(data_source_resource));
auto* data_device_manager = GetUserDataAs<MockDataDeviceManager>(resource);
data_device_manager->set_data_source(std::move(data_source));
}
void GetDataDevice(wl_client* client,
wl_resource* resource,
uint32_t id,
wl_resource* seat_resource) {
wl_resource* data_device_resource = wl_resource_create(
client, &wl_data_device_interface, wl_resource_get_version(resource), id);
if (!data_device_resource) {
wl_client_post_no_memory(client);
return;
}
std::unique_ptr<MockDataDevice> data_device(
new MockDataDevice(client, data_device_resource));
auto* data_device_manager = GetUserDataAs<MockDataDeviceManager>(resource);
data_device_manager->set_data_device(std::move(data_device));
}
const struct wl_data_device_manager_interface data_device_manager_impl = {
&CreateDataSource, &GetDataDevice};
// wl_data_offer
void DataOfferReceive(wl_client* client,
wl_resource* resource,
const char* mime_type,
int fd) {
GetUserDataAs<MockDataOffer>(resource)->Receive(mime_type,
base::ScopedFD(fd));
}
void DataOfferDestroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
const struct wl_data_offer_interface data_offer_impl = {
nullptr /* data_offer_accept*/, DataOfferReceive,
nullptr /*data_offer_finish*/, DataOfferDestroy,
nullptr /*data_offer_set_actions*/};
// wl_data_source
void DataSourceOffer(wl_client* client,
wl_resource* resource,
const char* mime_type) {
GetUserDataAs<MockDataSource>(resource)->Offer(mime_type);
}
void DataSourceDestroy(wl_client* client, wl_resource* resource) {
wl_resource_destroy(resource);
}
const struct wl_data_source_interface data_source_impl = {
DataSourceOffer, DataSourceDestroy, nullptr /*data_source_set_actions*/};
// wl_seat
void GetPointer(wl_client* client, wl_resource* resource, uint32_t id) {
......@@ -413,6 +539,98 @@ MockTouch::MockTouch(wl_resource* resource) : ServerObject(resource) {
MockTouch::~MockTouch() {}
MockDataOffer::MockDataOffer(wl_resource* resource)
: ServerObject(resource),
io_thread_("Worker thread"),
write_data_weak_ptr_factory_(this) {
SetImplementation(resource, &data_offer_impl, this);
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
}
MockDataOffer::~MockDataOffer() {}
void MockDataOffer::Receive(const std::string& mime_type, base::ScopedFD fd) {
DCHECK(fd.is_valid());
std::string text_utf8(kSampleClipboardText);
io_thread_.task_runner()->PostTask(
FROM_HERE,
base::BindOnce(&WriteDataOnWorkerThread, std::move(fd), text_utf8));
}
void MockDataOffer::OnOffer(const std::string& mime_type) {
wl_data_offer_send_offer(resource(), mime_type.c_str());
}
MockDataDevice::MockDataDevice(wl_client* client, wl_resource* resource)
: ServerObject(resource), client_(client) {
SetImplementation(resource, &data_device_impl, this);
}
MockDataDevice::~MockDataDevice() {}
void MockDataDevice::SetSelection(MockDataSource* data_source,
uint32_t serial) {
NOTIMPLEMENTED();
}
MockDataOffer* MockDataDevice::OnDataOffer() {
wl_resource* data_offer_resource =
wl_resource_create(client_, &wl_data_offer_interface,
wl_resource_get_version(resource()), 0);
data_offer_.reset(new MockDataOffer(data_offer_resource));
wl_data_device_send_data_offer(resource(), data_offer_resource);
return GetUserDataAs<MockDataOffer>(data_offer_resource);
}
void MockDataDevice::OnSelection(MockDataOffer& data_offer) {
wl_data_device_send_selection(resource(), data_offer.resource());
}
MockDataSource::MockDataSource(wl_resource* resource)
: ServerObject(resource),
io_thread_("Worker thread"),
read_data_weak_ptr_factory_(this) {
SetImplementation(resource, &data_source_impl, this);
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
io_thread_.StartWithOptions(options);
}
MockDataSource::~MockDataSource() {}
void MockDataSource::Offer(const std::string& mime_type) {
NOTIMPLEMENTED();
}
void MockDataSource::ReadData(ReadDataCallback callback) {
base::ScopedFD read_fd;
base::ScopedFD write_fd;
CreatePipe(&read_fd, &write_fd);
wl_data_source_send_send(resource(), kTextMimeTypeUtf8, write_fd.get());
base::PostTaskAndReplyWithResult(
io_thread_.task_runner().get(), FROM_HERE,
base::BindOnce(&ReadDataOnWorkerThread, std::move(read_fd)),
base::BindOnce(&MockDataSource::DataReadCb,
read_data_weak_ptr_factory_.GetWeakPtr(),
std::move(callback)));
}
void MockDataSource::DataReadCb(ReadDataCallback callback,
const std::vector<uint8_t>& data) {
std::move(callback).Run(data);
}
void MockDataSource::OnCancelled() {
wl_data_source_send_cancelled(resource());
}
void GlobalDeleter::operator()(wl_global* global) {
wl_global_destroy(global);
}
......@@ -465,6 +683,13 @@ void MockCompositor::AddSurface(std::unique_ptr<MockSurface> surface) {
surfaces_.push_back(std::move(surface));
}
MockDataDeviceManager::MockDataDeviceManager()
: Global(&wl_data_device_manager_interface,
&data_device_manager_impl,
kDataDeviceManagerVersion) {}
MockDataDeviceManager::~MockDataDeviceManager() {}
MockOutput::MockOutput()
: Global(&wl_output_interface, nullptr, kOutputVersion) {}
......@@ -529,6 +754,8 @@ bool FakeServer::Start(uint32_t shell_version) {
return false;
if (!output_.Initialize(display_.get()))
return false;
if (!data_device_manager_.Initialize(display_.get()))
return false;
if (!seat_.Initialize(display_.get()))
return false;
if (shell_version == 5) {
......
......@@ -8,6 +8,7 @@
#include <wayland-server-core.h>
#include "base/bind.h"
#include "base/files/scoped_file.h"
#include "base/message_loop/message_pump_libevent.h"
#include "base/synchronization/waitable_event.h"
#include "base/threading/thread.h"
......@@ -22,6 +23,9 @@ struct wl_resource;
namespace wl {
constexpr char kTextMimeTypeUtf8[] = "text/plain;charset=utf-8";
constexpr char kSampleClipboardText[] = "This is a sample text for clipboard.";
// Base class for managing the life cycle of server objects.
class ServerObject {
public:
......@@ -138,6 +142,58 @@ class MockTouch : public ServerObject {
DISALLOW_COPY_AND_ASSIGN(MockTouch);
};
class MockDataOffer : public ServerObject {
public:
explicit MockDataOffer(wl_resource* resource);
~MockDataOffer() override;
void Receive(const std::string& mime_type, base::ScopedFD fd);
void OnOffer(const std::string& mime_type);
private:
base::Thread io_thread_;
base::WeakPtrFactory<MockDataOffer> write_data_weak_ptr_factory_;
};
class MockDataSource : public ServerObject {
public:
explicit MockDataSource(wl_resource* resource);
~MockDataSource() override;
void Offer(const std::string& mime_type);
using ReadDataCallback =
base::OnceCallback<void(const std::vector<uint8_t>&)>;
void ReadData(ReadDataCallback);
void OnCancelled();
private:
void DataReadCb(ReadDataCallback callback, const std::vector<uint8_t>& data);
base::Thread io_thread_;
base::WeakPtrFactory<MockDataSource> read_data_weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(MockDataSource);
};
class MockDataDevice : public ServerObject {
public:
MockDataDevice(wl_client* client, wl_resource* resource);
~MockDataDevice() override;
void SetSelection(MockDataSource* data_source, uint32_t serial);
MockDataOffer* OnDataOffer();
void OnSelection(MockDataOffer& data_offer);
private:
std::unique_ptr<MockDataOffer> data_offer_;
wl_client* client_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockDataDevice);
};
struct GlobalDeleter {
void operator()(wl_global* global);
};
......@@ -192,6 +248,29 @@ class MockCompositor : public Global {
DISALLOW_COPY_AND_ASSIGN(MockCompositor);
};
// Manage wl_data_device_manager object.
class MockDataDeviceManager : public Global {
public:
MockDataDeviceManager();
~MockDataDeviceManager() override;
MockDataDevice* data_device() { return data_device_.get(); }
void set_data_device(std::unique_ptr<MockDataDevice> data_device) {
data_device_ = std::move(data_device);
}
MockDataSource* data_source() { return data_source_.get(); }
void set_data_source(std::unique_ptr<MockDataSource> data_source) {
data_source_ = std::move(data_source);
}
private:
std::unique_ptr<MockDataDevice> data_device_;
std::unique_ptr<MockDataSource> data_source_;
DISALLOW_COPY_AND_ASSIGN(MockDataDeviceManager);
};
// Handle wl_output object.
class MockOutput : public Global {
public:
......@@ -294,6 +373,7 @@ class FakeServer : public base::Thread, base::MessagePumpLibevent::FdWatcher {
return resource ? T::FromResource(resource) : nullptr;
}
MockDataDeviceManager* data_device_manager() { return &data_device_manager_; }
MockSeat* seat() { return &seat_; }
MockXdgShell* xdg_shell() { return &xdg_shell_; }
MockOutput* output() { return &output_; }
......@@ -316,6 +396,7 @@ class FakeServer : public base::Thread, base::MessagePumpLibevent::FdWatcher {
// Represent Wayland global objects
MockCompositor compositor_;
MockDataDeviceManager data_device_manager_;
MockOutput output_;
MockSeat seat_;
MockXdgShell xdg_shell_;
......
......@@ -134,6 +134,65 @@ int WaylandConnection::GetKeyboardModifiers() {
return modifiers;
}
ClipboardDelegate* WaylandConnection::GetClipboardDelegate() {
return this;
}
void WaylandConnection::OfferClipboardData(
const ClipboardDelegate::DataMap& data_map,
ClipboardDelegate::OfferDataClosure callback) {
if (!data_source_) {
wl_data_source* data_source =
wl_data_device_manager_create_data_source(data_device_manager_.get());
data_source_.reset(new WaylandDataSource(data_source));
data_source_->set_connection(this);
data_source_->WriteToClipboard(data_map);
}
data_source_->UpdataDataMap(data_map);
std::move(callback).Run();
}
void WaylandConnection::RequestClipboardData(
const std::string& mime_type,
ClipboardDelegate::DataMap* data_map,
ClipboardDelegate::RequestDataClosure callback) {
read_clipboard_closure_ = std::move(callback);
DCHECK(data_map);
data_map_ = data_map;
data_device_->RequestSelectionData(mime_type);
}
bool WaylandConnection::IsSelectionOwner() {
return !!data_source_;
}
void WaylandConnection::GetAvailableMimeTypes(
ClipboardDelegate::GetMimeTypesClosure callback) {
std::move(callback).Run(data_device_->GetAvailableMimeTypes());
}
void WaylandConnection::DataSourceCancelled() {
SetClipboardData(std::string(), std::string());
data_source_.reset();
}
void WaylandConnection::SetClipboardData(const std::string& contents,
const std::string& mime_type) {
if (!data_map_)
return;
(*data_map_)[mime_type] =
std::vector<uint8_t>(contents.begin(), contents.end());
if (!read_clipboard_closure_.is_null()) {
auto it = data_map_->find(mime_type);
DCHECK(it != data_map_->end());
std::move(read_clipboard_closure_).Run(it->second);
}
data_map_ = nullptr;
}
void WaylandConnection::OnDispatcherListChanged() {
StartProcessingEvents();
}
......@@ -195,6 +254,20 @@ void WaylandConnection::Global(void* data,
return;
}
wl_seat_add_listener(connection->seat_.get(), &seat_listener, connection);
// TODO(tonikitoo,msisov): The connection passed to WaylandInputDevice must
// have a valid data device manager. We should ideally be robust to the
// compositor advertising a wl_seat first. No known compositor does this,
// fortunately.
if (!connection->data_device_manager_) {
LOG(ERROR)
<< "No data device manager. Clipboard won't be fully functional";
return;
}
wl_data_device* data_device = wl_data_device_manager_get_data_device(
connection->data_device_manager_.get(), connection->seat_.get());
connection->data_device_.reset(
new WaylandDataDevice(connection, data_device));
} else if (!connection->shell_v6_ &&
strcmp(interface, "zxdg_shell_v6") == 0) {
// Check for zxdg_shell_v6 first.
......@@ -230,6 +303,10 @@ void WaylandConnection::Global(void* data,
connection->output_list_.push_back(
base::WrapUnique(new WaylandOutput(output.release())));
} else if (!connection->data_device_manager_ &&
strcmp(interface, "wl_data_device_manager") == 0) {
connection->data_device_manager_ =
wl::Bind<wl_data_device_manager>(registry, name, 1);
}
connection->ScheduleFlush();
......
......@@ -10,17 +10,21 @@
#include "base/message_loop/message_pump_libevent.h"
#include "ui/events/platform/platform_event_source.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/ozone/platform/wayland/wayland_data_device.h"
#include "ui/ozone/platform/wayland/wayland_data_source.h"
#include "ui/ozone/platform/wayland/wayland_keyboard.h"
#include "ui/ozone/platform/wayland/wayland_object.h"
#include "ui/ozone/platform/wayland/wayland_output.h"
#include "ui/ozone/platform/wayland/wayland_pointer.h"
#include "ui/ozone/platform/wayland/wayland_touch.h"
#include "ui/ozone/public/clipboard_delegate.h"
namespace ui {
class WaylandWindow;
class WaylandConnection : public PlatformEventSource,
public ClipboardDelegate,
public base::MessagePumpLibevent::FdWatcher {
public:
WaylandConnection();
......@@ -37,6 +41,7 @@ class WaylandConnection : public PlatformEventSource,
wl_shm* shm() { return shm_.get(); }
xdg_shell* shell() { return shell_.get(); }
zxdg_shell_v6* shell_v6() { return shell_v6_.get(); }
wl_data_device* data_device() { return data_device_->data_device(); }
WaylandWindow* GetWindow(gfx::AcceleratedWidget widget);
void AddWindow(gfx::AcceleratedWidget widget, WaylandWindow* window);
......@@ -56,6 +61,24 @@ class WaylandConnection : public PlatformEventSource,
// Returns the current pointer, which may be null.
WaylandPointer* pointer() { return pointer_.get(); }
// Clipboard implementation.
ClipboardDelegate* GetClipboardDelegate();
void DataSourceCancelled();
void SetClipboardData(const std::string& contents,
const std::string& mime_type);
// ClipboardDelegate.
void OfferClipboardData(
const ClipboardDelegate::DataMap& data_map,
ClipboardDelegate::OfferDataClosure callback) override;
void RequestClipboardData(
const std::string& mime_type,
ClipboardDelegate::DataMap* data_map,
ClipboardDelegate::RequestDataClosure callback) override;
void GetAvailableMimeTypes(
ClipboardDelegate::GetMimeTypesClosure callback) override;
bool IsSelectionOwner() override;
private:
void Flush();
void DispatchUiEvent(Event* event);
......@@ -88,6 +111,7 @@ class WaylandConnection : public PlatformEventSource,
std::map<gfx::AcceleratedWidget, WaylandWindow*> window_map_;
wl::Object<wl_display> display_;
wl::Object<wl_data_device_manager> data_device_manager_;
wl::Object<wl_registry> registry_;
wl::Object<wl_compositor> compositor_;
wl::Object<wl_seat> seat_;
......@@ -95,6 +119,8 @@ class WaylandConnection : public PlatformEventSource,
wl::Object<xdg_shell> shell_;
wl::Object<zxdg_shell_v6> shell_v6_;
std::unique_ptr<WaylandDataDevice> data_device_;
std::unique_ptr<WaylandDataSource> data_source_;
std::unique_ptr<WaylandPointer> pointer_;
std::unique_ptr<WaylandKeyboard> keyboard_;
std::unique_ptr<WaylandTouch> touch_;
......@@ -107,6 +133,13 @@ class WaylandConnection : public PlatformEventSource,
std::vector<std::unique_ptr<WaylandOutput>> output_list_;
// Holds a temporary instance of the client's clipboard content
// so that we can asynchronously write to it.
ClipboardDelegate::DataMap* data_map_ = nullptr;
// Stores the callback to be invoked upon data reading from clipboard.
RequestDataClosure read_clipboard_closure_;
DISALLOW_COPY_AND_ASSIGN(WaylandConnection);
};
......
// Copyright 2018 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/wayland/wayland_data_device.h"
#include "base/bind.h"
#include "ui/ozone/platform/wayland/wayland_connection.h"
namespace ui {
// static
const wl_callback_listener WaylandDataDevice::callback_listener_ = {
WaylandDataDevice::SyncCallback,
};
WaylandDataDevice::WaylandDataDevice(WaylandConnection* connection,
wl_data_device* data_device)
: data_device_(data_device), connection_(connection) {
static const struct wl_data_device_listener kDataDeviceListener = {
WaylandDataDevice::OnDataOffer,
nullptr /*OnEnter*/,
nullptr /*OnLeave*/,
nullptr /*OnMotion*/,
nullptr /*OnDrop*/,
WaylandDataDevice::OnSelection};
wl_data_device_add_listener(data_device_.get(), &kDataDeviceListener, this);
}
WaylandDataDevice::~WaylandDataDevice() {}
void WaylandDataDevice::RequestSelectionData(const std::string& mime_type) {
base::ScopedFD fd = selection_offer_->Receive(mime_type);
if (!fd.is_valid()) {
LOG(ERROR) << "Failed to open file descriptor.";
return;
}
// Ensure there is not pending operation to be performed by the compositor,
// otherwise read(..) can block awaiting data to be sent to pipe.
read_from_fd_closure_ =
base::BindOnce(&WaylandDataDevice::ReadClipboardDataFromFD,
base::Unretained(this), std::move(fd), mime_type);
sync_callback_.reset(wl_display_sync(connection_->display()));
wl_callback_add_listener(sync_callback_.get(), &callback_listener_, this);
wl_display_flush(connection_->display());
}
void WaylandDataDevice::ReadClipboardDataFromFD(base::ScopedFD fd,
const std::string& mime_type) {
std::string contents;
char buffer[1 << 10]; // 1 kB in bytes.
ssize_t length;
while ((length = read(fd.get(), buffer, sizeof(buffer))) > 0)
contents.append(buffer, length);
connection_->SetClipboardData(contents, mime_type);
}
std::vector<std::string> WaylandDataDevice::GetAvailableMimeTypes() {
if (selection_offer_)
return selection_offer_->GetAvailableMimeTypes();
return std::vector<std::string>();
}
// static
void WaylandDataDevice::OnDataOffer(void* data,
wl_data_device* data_device,
wl_data_offer* offer) {
auto* self = static_cast<WaylandDataDevice*>(data);
DCHECK(!self->new_offer_);
self->new_offer_.reset(new WaylandDataOffer(offer));
}
// static
void WaylandDataDevice::OnSelection(void* data,
wl_data_device* data_device,
wl_data_offer* offer) {
auto* self = static_cast<WaylandDataDevice*>(data);
DCHECK(self);
// 'offer' will be null to indicate that the selection is no longer valid,
// i.e. there is no longer clipboard data available to paste.
if (!offer) {
self->selection_offer_.reset();
// Clear Clipboard cache.
self->connection_->SetClipboardData(std::string(), std::string());
return;
}
DCHECK(self->new_offer_);
self->selection_offer_ = std::move(self->new_offer_);
self->selection_offer_->EnsureTextMimeTypeIfNeeded();
}
void WaylandDataDevice::SyncCallback(void* data,
struct wl_callback* cb,
uint32_t time) {
WaylandDataDevice* data_device = static_cast<WaylandDataDevice*>(data);
DCHECK(data_device);
std::move(data_device->read_from_fd_closure_).Run();
DCHECK(data_device->read_from_fd_closure_.is_null());
data_device->sync_callback_.reset();
}
} // namespace ui
// Copyright 2018 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_WAYLAND_WAYLAND_DATA_DEVICE_H_
#define UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_DEVICE_H_
#include <wayland-client.h>
#include <string>
#include "base/callback.h"
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "ui/ozone/platform/wayland/wayland_data_offer.h"
#include "ui/ozone/platform/wayland/wayland_object.h"
namespace ui {
class WaylandDataOffer;
class WaylandConnection;
// This class provides access to inter-client data transfer mechanisms
// such as copy-and-paste and drag-and-drop mechanisms.
//
// TODO(tonikitoo,msisov): Add drag&drop support.
class WaylandDataDevice {
public:
WaylandDataDevice(WaylandConnection* connection, wl_data_device* data_device);
~WaylandDataDevice();
void RequestSelectionData(const std::string& mime_type);
wl_data_device* data_device() { return data_device_.get(); }
std::vector<std::string> GetAvailableMimeTypes();
private:
void ReadClipboardDataFromFD(base::ScopedFD fd, const std::string& mime_type);
// wl_data_device_listener callbacks
static void OnDataOffer(void* data,
wl_data_device* data_device,
wl_data_offer* id);
// Called by the compositor when the window gets pointer or keyboard focus,
// or clipboard content changes behind the scenes.
//
// https://wayland.freedesktop.org/docs/html/apa.html#protocol-spec-wl_data_device
static void OnSelection(void* data,
wl_data_device* data_device,
wl_data_offer* id);
static void SyncCallback(void* data, struct wl_callback* cb, uint32_t time);
// The wl_data_device wrapped by this WaylandDataDevice.
wl::Object<wl_data_device> data_device_;
// Used to call out to WaylandConnection once clipboard data
// has been successfully read.
WaylandConnection* connection_ = nullptr;
// There are two separate data offers at a time, the drag offer and the
// selection offer, each with independent lifetimes. When we receive a new
// offer, it is not immediately possible to know whether the new offer is the
// drag offer or the selection offer. This variable is used to store ownership
// of new data offers temporarily until its identity becomes known.
std::unique_ptr<WaylandDataOffer> new_offer_;
// Offer that holds the most-recent clipboard selection, or null if no
// clipboard data is available.
std::unique_ptr<WaylandDataOffer> selection_offer_;
// Make sure server has written data on the pipe, before block on read().
static const wl_callback_listener callback_listener_;
base::OnceClosure read_from_fd_closure_;
wl::Object<wl_callback> sync_callback_;
DISALLOW_COPY_AND_ASSIGN(WaylandDataDevice);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_DEVICE_H_
// Copyright 2018 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 <wayland-server.h>
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/ozone/platform/wayland/fake_server.h"
#include "ui/ozone/platform/wayland/wayland_test.h"
#include "ui/ozone/public/clipboard_delegate.h"
namespace ui {
// This class mocks how a real clipboard/ozone client would
// hook to ClipboardDelegate, with one difference: real clients
// have no access to the WaylandConnection instance like this
// MockClipboardClient impl does. Instead, clients and ozone gets
// plumbbed up by calling the appropriated Ozone API,
// OzonePlatform::GetClipboardDelegate.
class MockClipboardClient {
public:
MockClipboardClient(WaylandConnection* connection) {
DCHECK(connection);
// See comment above for reasoning to access the WaylandConnection
// directly from here.
delegate_ = connection->GetClipboardDelegate();
DCHECK(delegate_);
}
~MockClipboardClient() = default;
// Fill the clipboard backing store with sample data.
void SetData(const std::string& utf8_text,
const std::string& mime_type,
ClipboardDelegate::OfferDataClosure callback) {
// This mimics how Mus' ClipboardImpl writes data to the DataMap.
std::vector<char> object_map(utf8_text.begin(), utf8_text.end());
char* object_data = &object_map.front();
data_types_[mime_type] =
std::vector<uint8_t>(object_data, object_data + object_map.size());
delegate_->OfferClipboardData(data_types_, std::move(callback));
}
void ReadData(const std::string& mime_type,
ClipboardDelegate::RequestDataClosure callback) {
delegate_->RequestClipboardData(mime_type, &data_types_,
std::move(callback));
}
bool IsSelectionOwner() { return delegate_->IsSelectionOwner(); }
private:
ClipboardDelegate* delegate_ = nullptr;
ClipboardDelegate::DataMap data_types_;
DISALLOW_COPY_AND_ASSIGN(MockClipboardClient);
};
class WaylandDataDeviceManagerTest : public WaylandTest {
public:
WaylandDataDeviceManagerTest() {}
void SetUp() override {
WaylandTest::SetUp();
Sync();
data_device_manager_ = server_.data_device_manager();
DCHECK(data_device_manager_);
clipboard_client_.reset(new MockClipboardClient(connection_.get()));
}
protected:
wl::MockDataDeviceManager* data_device_manager_;
std::unique_ptr<MockClipboardClient> clipboard_client_;
DISALLOW_COPY_AND_ASSIGN(WaylandDataDeviceManagerTest);
};
TEST_P(WaylandDataDeviceManagerTest, WriteToClipboard) {
// The client writes data to the clipboard ...
auto callback = base::BindOnce([]() {});
clipboard_client_->SetData(wl::kSampleClipboardText, wl::kTextMimeTypeUtf8,
std::move(callback));
Sync();
// ... and the server reads it.
data_device_manager_->data_source()->ReadData(
base::BindOnce([](const std::vector<uint8_t>& data) {
std::string string_data(data.begin(), data.end());
EXPECT_EQ(wl::kSampleClipboardText, string_data);
}));
Sync();
}
TEST_P(WaylandDataDeviceManagerTest, ReadFromClibpard) {
// TODO: implement this in terms of an actual wl_surface that gets
// focused and compositor sends data_device data to it.
auto* data_offer = data_device_manager_->data_device()->OnDataOffer();
data_offer->OnOffer(wl::kTextMimeTypeUtf8);
data_device_manager_->data_device()->OnSelection(*data_offer);
Sync();
// The client requests to reading clipboard data from the server.
// The Server writes in some sample data, and we check it matches
// expectation.
auto callback =
base::BindOnce([](const base::Optional<std::vector<uint8_t>>& data) {
std::string string_data = std::string(data->begin(), data->end());
EXPECT_EQ(wl::kSampleClipboardText, string_data);
});
clipboard_client_->ReadData(wl::kTextMimeTypeUtf8, std::move(callback));
Sync();
}
TEST_P(WaylandDataDeviceManagerTest, IsSelectionOwner) {
auto callback = base::BindOnce([]() {});
clipboard_client_->SetData(wl::kSampleClipboardText, wl::kTextMimeTypeUtf8,
std::move(callback));
Sync();
ASSERT_TRUE(clipboard_client_->IsSelectionOwner());
// The compositor sends OnCancelled whenever another application
// on the system sets a new selection. It means we are not the application
// that owns the current selection data.
data_device_manager_->data_source()->OnCancelled();
Sync();
ASSERT_FALSE(clipboard_client_->IsSelectionOwner());
}
INSTANTIATE_TEST_CASE_P(XdgVersionV5Test,
WaylandDataDeviceManagerTest,
::testing::Values(kXdgShellV5));
INSTANTIATE_TEST_CASE_P(XdgVersionV6Test,
WaylandDataDeviceManagerTest,
::testing::Values(kXdgShellV6));
} // namespace ui
// Copyright 2018 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/wayland/wayland_data_offer.h"
#include <fcntl.h>
#include <algorithm>
#include "base/logging.h"
namespace ui {
namespace {
const char kString[] = "STRING";
const char kText[] = "TEXT";
const char kTextPlain[] = "text/plain";
const char kTextPlainUtf8[] = "text/plain;charset=utf-8";
const char kUtf8String[] = "UTF8_STRING";
void CreatePipe(base::ScopedFD* read_pipe, base::ScopedFD* write_pipe) {
int raw_pipe[2];
PCHECK(0 == pipe(raw_pipe));
read_pipe->reset(raw_pipe[0]);
write_pipe->reset(raw_pipe[1]);
}
} // namespace
WaylandDataOffer::WaylandDataOffer(wl_data_offer* data_offer)
: data_offer_(data_offer) {
static const struct wl_data_offer_listener kDataOfferListener = {
WaylandDataOffer::OnOffer};
wl_data_offer_add_listener(data_offer, &kDataOfferListener, this);
}
WaylandDataOffer::~WaylandDataOffer() {
data_offer_.reset();
}
void WaylandDataOffer::EnsureTextMimeTypeIfNeeded() {
if (std::find(mime_types_.begin(), mime_types_.end(), kTextPlain) !=
mime_types_.end())
return;
if (std::any_of(mime_types_.begin(), mime_types_.end(),
[](const std::string& mime_type) {
return mime_type == kString || mime_type == kText ||
mime_type == kTextPlainUtf8 ||
mime_type == kUtf8String;
})) {
mime_types_.push_back(kTextPlain);
text_plain_mime_type_inserted_ = true;
}
}
base::ScopedFD WaylandDataOffer::Receive(const std::string& mime_type) {
if (std::find(mime_types_.begin(), mime_types_.end(), mime_type) ==
mime_types_.end())
return base::ScopedFD();
base::ScopedFD read_fd;
base::ScopedFD write_fd;
CreatePipe(&read_fd, &write_fd);
// If we needed to forcibly write "text/plain" as an available
// mimetype, then it is safer to "read" the clipboard data with
// a mimetype mime_type known to be available.
std::string effective_mime_type = mime_type;
if (mime_type == kTextPlain && text_plain_mime_type_inserted_) {
effective_mime_type = kTextPlainUtf8;
}
wl_data_offer_receive(data_offer_.get(), effective_mime_type.data(),
write_fd.get());
return read_fd;
}
// static
void WaylandDataOffer::OnOffer(void* data,
wl_data_offer* data_offer,
const char* mime_type) {
auto* self = static_cast<WaylandDataOffer*>(data);
self->mime_types_.push_back(mime_type);
}
} // namespace ui
// Copyright 2018 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_WAYLAND_WAYLAND_DATA_OFFER_H_
#define UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_OFFER_H_
#include <wayland-client.h>
#include <string>
#include <vector>
#include "base/files/scoped_file.h"
#include "base/macros.h"
#include "ui/ozone/platform/wayland/wayland_object.h"
namespace ui {
// This class represents a piece of data offered for transfer by another
// client, the source client (see WaylandDataSource for more).
// It is used by the copy-and-paste and drag-and-drop mechanisms.
//
// The offer describes the different mime types that the data can be
// converted to and provides the mechanism for transferring the data
// directly from the source client.
//
// TODO(tonikitoo,msisov): Add drag&drop support.
class WaylandDataOffer {
public:
// Takes ownership of data_offer.
explicit WaylandDataOffer(wl_data_offer* data_offer);
~WaylandDataOffer();
const std::vector<std::string>& GetAvailableMimeTypes() const {
return mime_types_;
}
// Some X11 applications on Gnome/Wayland (running through XWayland)
// do not send the "text/plain" mime type that Chrome relies on, but
// instead they send mime types like "text/plain;charset=utf-8".
// When it happens, this method forcibly injects "text/plain" to the
// list of provided mime types so that Chrome clipboard's machinery
// works fine.
void EnsureTextMimeTypeIfNeeded();
// Creates a pipe (read & write FDs), passing the write-end of to pipe
// to the compositor (via wl_data_offer_receive) and returning the
// read-end to the pipe.
base::ScopedFD Receive(const std::string& mime_type);
private:
// wl_data_offer_listener callbacks.
static void OnOffer(void* data,
wl_data_offer* data_offer,
const char* mime_type);
wl::Object<wl_data_offer> data_offer_;
std::vector<std::string> mime_types_;
bool text_plain_mime_type_inserted_ = false;
DISALLOW_COPY_AND_ASSIGN(WaylandDataOffer);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_OFFER_H_
// Copyright 2018 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/wayland/wayland_data_source.h"
#include "base/files/file_util.h"
#include "ui/ozone/platform/wayland/wayland_connection.h"
namespace ui {
constexpr char kTextMimeTypeUtf8[] = "text/plain;charset=utf-8";
WaylandDataSource::WaylandDataSource(wl_data_source* data_source)
: data_source_(data_source) {
static const struct wl_data_source_listener kDataSourceListener = {
WaylandDataSource::OnTarget, WaylandDataSource::OnSend,
WaylandDataSource::OnCancel};
wl_data_source_add_listener(data_source, &kDataSourceListener, this);
}
WaylandDataSource::~WaylandDataSource() = default;
void WaylandDataSource::WriteToClipboard(
const ClipboardDelegate::DataMap& data_map) {
for (const auto& data : data_map) {
wl_data_source_offer(data_source_.get(), data.first.c_str());
if (strcmp(data.first.c_str(), "text/plain") == 0)
wl_data_source_offer(data_source_.get(), kTextMimeTypeUtf8);
}
wl_data_device_set_selection(connection_->data_device(), data_source_.get(),
connection_->serial());
wl_display_flush(connection_->display());
}
void WaylandDataSource::UpdataDataMap(
const ClipboardDelegate::DataMap& data_map) {
data_map_ = data_map;
}
// static
void WaylandDataSource::OnTarget(void* data,
wl_data_source* source,
const char* mime_type) {
NOTIMPLEMENTED();
}
// static
void WaylandDataSource::OnSend(void* data,
wl_data_source* source,
const char* mime_type,
int32_t fd) {
WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
base::Optional<std::vector<uint8_t>> mime_data;
self->GetClipboardData(mime_type, &mime_data);
if (!mime_data.has_value() && strcmp(mime_type, kTextMimeTypeUtf8) == 0)
self->GetClipboardData("text/plain", &mime_data);
std::string contents(mime_data->begin(), mime_data->end());
DCHECK(base::WriteFileDescriptor(fd, contents.data(), contents.length()));
close(fd);
}
// static
void WaylandDataSource::OnCancel(void* data, wl_data_source* source) {
WaylandDataSource* self = static_cast<WaylandDataSource*>(data);
self->connection_->DataSourceCancelled();
}
void WaylandDataSource::GetClipboardData(
const std::string& mime_type,
base::Optional<std::vector<uint8_t>>* data) {
auto it = data_map_.find(mime_type);
if (it != data_map_.end()) {
data->emplace(it->second);
// TODO: return here?
return;
}
}
} // namespace ui
// Copyright 2018 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_WAYLAND_WAYLAND_DATA_SOURCE_H_
#define UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_SOURCE_H_
#include <wayland-client.h>
#include <string>
#include <unordered_map>
#include <vector>
#include "base/logging.h"
#include "base/macros.h"
#include "base/optional.h"
#include "ui/ozone/platform/wayland/wayland_object.h"
#include "ui/ozone/public/clipboard_delegate.h"
namespace ui {
class WaylandConnection;
// The WaylandDataSource object represents the source side of a
// WaylandDataOffer. It is created by the source client in a data
// transfer and provides a way to describe the offered data
// (wl_data_source_offer) // and a way to respond to requests to
// transfer the data (OnSend listener).
class WaylandDataSource {
public:
// Takes ownership of data_source.
explicit WaylandDataSource(wl_data_source* data_source);
~WaylandDataSource();
void set_connection(WaylandConnection* connection) {
DCHECK(connection);
connection_ = connection;
}
void WriteToClipboard(const ClipboardDelegate::DataMap& data_map);
void UpdataDataMap(const ClipboardDelegate::DataMap& data_map);
private:
static void OnTarget(void* data,
wl_data_source* source,
const char* mime_type);
static void OnSend(void* data,
wl_data_source* source,
const char* mime_type,
int32_t fd);
static void OnCancel(void* data, wl_data_source* source);
void GetClipboardData(const std::string& mime_type,
base::Optional<std::vector<uint8_t>>* data);
wl::Object<wl_data_source> data_source_;
WaylandConnection* connection_ = nullptr;
ClipboardDelegate::DataMap data_map_;
DISALLOW_COPY_AND_ASSIGN(WaylandDataSource);
};
} // namespace ui
#endif // UI_OZONE_PLATFORM_WAYLAND_WAYLAND_DATA_SOURCE_H_
......@@ -53,6 +53,26 @@ const wl_interface* ObjectTraits<wl_compositor>::interface =
void (*ObjectTraits<wl_compositor>::deleter)(wl_compositor*) =
&wl_compositor_destroy;
const wl_interface* ObjectTraits<wl_data_device_manager>::interface =
&wl_data_device_manager_interface;
void (*ObjectTraits<wl_data_device_manager>::deleter)(wl_data_device_manager*) =
&wl_data_device_manager_destroy;
const wl_interface* ObjectTraits<wl_data_device>::interface =
&wl_data_device_interface;
void (*ObjectTraits<wl_data_device>::deleter)(wl_data_device*) =
&wl_data_device_destroy;
const wl_interface* ObjectTraits<wl_data_offer>::interface =
&wl_data_offer_interface;
void (*ObjectTraits<wl_data_offer>::deleter)(wl_data_offer*) =
&wl_data_offer_destroy;
const wl_interface* ObjectTraits<wl_data_source>::interface =
&wl_data_source_interface;
void (*ObjectTraits<wl_data_source>::deleter)(wl_data_source*) =
&wl_data_source_destroy;
const wl_interface* ObjectTraits<wl_display>::interface = &wl_display_interface;
void (*ObjectTraits<wl_display>::deleter)(wl_display*) = &wl_display_disconnect;
......
......@@ -12,6 +12,10 @@
struct wl_buffer;
struct wl_callback;
struct wl_compositor;
struct wl_data_device_manager;
struct wl_data_device;
struct wl_data_offer;
struct wl_data_source;
struct wl_keyboard;
struct wl_output;
struct wl_pointer;
......@@ -50,6 +54,30 @@ struct ObjectTraits<wl_compositor> {
static void (*deleter)(wl_compositor*);
};
template <>
struct ObjectTraits<wl_data_device_manager> {
static const wl_interface* interface;
static void (*deleter)(wl_data_device_manager*);
};
template <>
struct ObjectTraits<wl_data_device> {
static const wl_interface* interface;
static void (*deleter)(wl_data_device*);
};
template <>
struct ObjectTraits<wl_data_offer> {
static const wl_interface* interface;
static void (*deleter)(wl_data_offer*);
};
template <>
struct ObjectTraits<wl_data_source> {
static const wl_interface* interface;
static void (*deleter)(wl_data_source*);
};
template <>
struct ObjectTraits<wl_display> {
static const wl_interface* interface;
......
// Copyright 2018 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_PUBLIC_CLIPBOARD_DELEGATE_H_
#define UI_OZONE_PUBLIC_CLIPBOARD_DELEGATE_H_
#include <string>
#include <unordered_map>
#include <vector>
#include "base/macros.h"
#include "base/optional.h"
#include "ui/ozone/ozone_base_export.h"
namespace ui {
// ClipboardDelegate is an interface that allows Ozone backends to exchange
// data with other applications on the host system. The most familiar use for
// it is handling copy and paste operations.
//
class OZONE_BASE_EXPORT ClipboardDelegate {
public:
// DataMap is a map from "mime type" to associated data, whereas
// the data can be organized differently for each mime type.
using Data = std::vector<uint8_t>;
using DataMap = std::unordered_map<std::string, Data>;
// Offers a given clipboard data 'data_map' to the host system clipboard.
//
// It is common that host clipboard implementations simply get offered
// the set of mime types available for the data being shared. In such cases,
// the actual clipboard data is only 'transferred' to the consuming
// application asynchronously, upon an explicit request for data given a
// specific mime type. This is the case of Wayland compositors and MacOS
// (NSPasteboard), for example.
//
// The invoker assumes the Ozone implementation will not free |DataMap|
// before |OfferDataClosure| is called.
//
// OfferDataClosure should be invoked when the host clipboard implementation
// acknowledges that the "offer to clipboard" operation is performed.
using OfferDataClosure = base::OnceCallback<void()>;
virtual void OfferClipboardData(const DataMap& data_map,
OfferDataClosure callback) = 0;
// Reads data from host system clipboard given mime type. The data is
// stored in 'data_map'.
//
// RequestDataClosure is invoked to acknowledge that the requested clipboard
// data has been read and stored into 'data_map'.
using RequestDataClosure =
base::OnceCallback<void(const base::Optional<std::vector<uint8_t>>&)>;
virtual void RequestClipboardData(const std::string& mime_type,
DataMap* data_map,
RequestDataClosure callback) = 0;
// Gets the mime types of the data available for clipboard operations
// in the host system clipboard.
//
// GetMimeTypesClosure is invoked when the mime types available for clipboard
// operations are known.
using GetMimeTypesClosure =
base::OnceCallback<void(const std::vector<std::string>&)>;
virtual void GetAvailableMimeTypes(GetMimeTypesClosure callback) = 0;
// Returns true if the current application writing data to the host clipboard
// data is this one; false otherwise.
//
// It can be relevant to know this information in case the client wants to
// caches the clipboard data, and wants to know if it is possible to use
// the cached data in order to reply faster to read-clipboard operations.
virtual bool IsSelectionOwner() = 0;
};
} // namespace ui
#endif // UI_OZONE_PUBLIC_CLIPBOARD_DELEGATE_H_
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment