blob: ff3e07b57c941f41869380892030088b732e5edc [file] [log] [blame]
// 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/wayland/host/wayland_clipboard.h"
#include <memory>
#include <string>
#include "base/check.h"
#include "base/containers/contains.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/base/clipboard/clipboard_buffer.h"
#include "ui/base/clipboard/clipboard_constants.h"
#include "ui/ozone/platform/wayland/common/wayland_object.h"
#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device.h"
#include "ui/ozone/platform/wayland/host/gtk_primary_selection_device_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_connection.h"
#include "ui/ozone/platform/wayland/host/wayland_data_device.h"
#include "ui/ozone/platform/wayland/host/wayland_data_device_base.h"
#include "ui/ozone/platform/wayland/host/wayland_data_device_manager.h"
#include "ui/ozone/platform/wayland/host/wayland_data_offer_base.h"
#include "ui/ozone/platform/wayland/host/wayland_data_source.h"
#include "ui/ozone/platform/wayland/host/zwp_primary_selection_device.h"
#include "ui/ozone/platform/wayland/host/zwp_primary_selection_device_manager.h"
#include "ui/ozone/public/platform_clipboard.h"
namespace wl {
// Internal Wayland Clipboard interface. A wl::Clipboard implementation handles
// a single ui::ClipboardBuffer. With this common interface it is possible to
// seamlessly support different clipboard buffers backed by different underlying
// Wayland protocol objects.
class Clipboard {
public:
using ClipboardDataChangedCallback =
ui::PlatformClipboard::ClipboardDataChangedCallback;
virtual ~Clipboard() = default;
// Synchronously retrieves the mime types list currently available to be read.
virtual std::vector<std::string> ReadMimeTypes() = 0;
// Asynchronously reads clipboard content with |mime_type| format. The result
// data is expected to arrive through OnSelectionDataReceived() callback.
virtual bool Read(const std::string& mime_type,
ui::PlatformClipboard::RequestDataClosure callback) = 0;
// Synchronously stores and announces |data| as available from this clipboard.
virtual void Write(const ui::PlatformClipboard::DataMap* data) = 0;
// Tells if this clipboard instance is the current selection owner.
virtual bool IsSelectionOwner() const = 0;
// Sets the callback to be executed when clipboard data changes.
virtual void SetClipboardDataChangedCallback(
ClipboardDataChangedCallback callback) = 0;
};
// Templated wl::Clipboard implementation. Whereas DataSource is the data source
// class capable of creating data offers upon clipboard writes and communicates
// events through DataSource::Delegate, and DataDevice is its device counterpart
// providing read and write access to the underlying data selection-related
// protocol objects. See *_data_{source,device}.h for more details.
template <typename Manager,
typename DataSource = typename Manager::DataSource,
typename DataDevice = typename Manager::DataDevice>
class ClipboardImpl final : public Clipboard,
public DataSource::Delegate,
public DataDevice::SelectionDelegate {
public:
explicit ClipboardImpl(Manager* manager, ui::ClipboardBuffer buffer)
: manager_(manager), buffer_(buffer) {
GetDevice()->set_selection_delegate(this);
}
~ClipboardImpl() final { GetDevice()->set_selection_delegate(nullptr); }
ClipboardImpl(const ClipboardImpl&) = delete;
ClipboardImpl& operator=(const ClipboardImpl&) = delete;
// TODO(crbug.com/1165466): Support nested clipboard requests.
bool Read(const std::string& mime_type,
ui::PlatformClipboard::RequestDataClosure callback) final {
requested_mime_type_ = mime_type;
read_clipboard_closure_ = std::move(callback);
if (GetDevice()->RequestSelectionData(GetMimeTypeForRequest(mime_type)))
return true;
DeliverData(nullptr, mime_type);
return false;
}
std::vector<std::string> ReadMimeTypes() final {
return GetDevice()->GetAvailableMimeTypes();
}
// Once this client sends wl_data_source::offer, it is responsible for holding
// onto its clipboard contents. At future points in time, the wayland server
// may send a wl_data_source::send event, in which case this client is
// responsible for writing the clipboard contents into the supplied fd. This
// client can only drop the clipboard contents when it receives a
// wl_data_source::cancelled event.
void Write(const ui::PlatformClipboard::DataMap* data) final {
if (!data || data->empty()) {
offered_data_.clear();
source_.reset();
} else {
offered_data_ = *data;
source_ = manager_->CreateSource(this);
source_->Offer(GetOfferedMimeTypes());
}
GetDevice()->SetSelectionSource(source_.get());
if (!clipboard_changed_callback_.is_null())
clipboard_changed_callback_.Run(buffer_);
}
bool IsSelectionOwner() const final { return !!source_; }
void SetClipboardDataChangedCallback(
ClipboardDataChangedCallback callback) final {
clipboard_changed_callback_ = std::move(callback);
}
private:
DataDevice* GetDevice() { return manager_->GetDevice(); }
std::vector<std::string> GetOfferedMimeTypes() {
std::vector<std::string> mime_types;
for (const auto& data : offered_data_) {
mime_types.push_back(data.first);
if (data.first == ui::kMimeTypeText)
mime_types.push_back(ui::kMimeTypeTextUtf8);
}
return mime_types;
}
std::string GetMimeTypeForRequest(const std::string& mime_type) {
if (mime_type != ui::kMimeTypeText)
return mime_type;
// Prioritize unicode for text data.
for (const auto& t : GetDevice()->GetAvailableMimeTypes()) {
if (t == ui::kMimeTypeTextUtf8 || t == ui::kMimeTypeLinuxString ||
t == ui::kMimeTypeLinuxUtf8String || t == ui::kMimeTypeLinuxText) {
return t;
}
}
return mime_type;
}
void DeliverData(ui::PlatformClipboard::Data contents,
const std::string& mime_type) {
CHECK_EQ(GetMimeTypeForRequest(requested_mime_type_), mime_type);
if (!read_clipboard_closure_.is_null())
std::move(read_clipboard_closure_).Run(contents);
requested_mime_type_.clear();
}
// WaylandDataDeviceBase::SelectionDelegate:
void OnSelectionOffer(ui::WaylandDataOfferBase* offer) final {
if (IsSelectionOwner())
return;
if (!clipboard_changed_callback_.is_null())
clipboard_changed_callback_.Run(buffer_);
}
void OnSelectionDataReceived(const std::string& mime_type,
ui::PlatformClipboard::Data contents) final {
DeliverData(contents, mime_type);
}
// WaylandDataSource::Delegate:
void OnDataSourceFinish(bool completed) override {
if (!completed)
Write(nullptr);
}
void OnDataSourceSend(const std::string& mime_type,
std::string* contents) override {
DCHECK(contents);
auto it = offered_data_.find(mime_type);
if (it == offered_data_.end() && mime_type == ui::kMimeTypeTextUtf8)
it = offered_data_.find(ui::kMimeTypeText);
if (it != offered_data_.end())
contents->assign(it->second->data().begin(), it->second->data().end());
}
// The device manager used to access data device and create data sources.
Manager* const manager_;
// The clipboard buffer managed by this |this|.
const ui::ClipboardBuffer buffer_;
// The current data source used to offer clipboard data.
std::unique_ptr<DataSource> source_;
// The data currently stored in a given clipboard buffer.
ui::PlatformClipboard::DataMap offered_data_;
// Stores the callback to be invoked upon data reading from clipboard.
ui::PlatformClipboard::RequestDataClosure read_clipboard_closure_;
// Last mime type requested to be read from the clipboard.
std::string requested_mime_type_;
// Notifies when clipboard data changes. Can be empty if not set.
ClipboardDataChangedCallback clipboard_changed_callback_;
};
} // namespace wl
namespace ui {
WaylandClipboard::WaylandClipboard(WaylandConnection* connection,
WaylandDataDeviceManager* manager)
: connection_(connection),
copypaste_clipboard_(
std::make_unique<wl::ClipboardImpl<WaylandDataDeviceManager>>(
manager,
ClipboardBuffer::kCopyPaste)) {
DCHECK(manager);
DCHECK(connection_);
DCHECK(copypaste_clipboard_);
}
WaylandClipboard::~WaylandClipboard() = default;
void WaylandClipboard::OfferClipboardData(
ClipboardBuffer buffer,
const PlatformClipboard::DataMap& data_map,
PlatformClipboard::OfferDataClosure callback) {
if (auto* clipboard = GetClipboard(buffer))
clipboard->Write(&data_map);
std::move(callback).Run();
}
// TODO(crbug.com/1165466): Support nested clipboard requests.
void WaylandClipboard::RequestClipboardData(
ClipboardBuffer buffer,
const std::string& mime_type,
PlatformClipboard::RequestDataClosure callback) {
if (auto* clipboard = GetClipboard(buffer))
clipboard->Read(mime_type, std::move(callback));
else
std::move(callback).Run(nullptr);
}
bool WaylandClipboard::IsSelectionOwner(ClipboardBuffer buffer) {
if (auto* clipboard = GetClipboard(buffer))
return clipboard->IsSelectionOwner();
return false;
}
void WaylandClipboard::SetClipboardDataChangedCallback(
ClipboardDataChangedCallback data_changed_callback) {
copypaste_clipboard_->SetClipboardDataChangedCallback(data_changed_callback);
if (auto* selection_clipboard = GetClipboard(ClipboardBuffer::kSelection))
selection_clipboard->SetClipboardDataChangedCallback(data_changed_callback);
}
void WaylandClipboard::GetAvailableMimeTypes(
ClipboardBuffer buffer,
PlatformClipboard::GetMimeTypesClosure callback) {
std::vector<std::string> mime_types;
if (auto* clipboard = GetClipboard(buffer))
mime_types = clipboard->ReadMimeTypes();
std::move(callback).Run(mime_types);
}
bool WaylandClipboard::IsSelectionBufferAvailable() const {
return (connection_->zwp_primary_selection_device_manager() != nullptr) ||
(connection_->gtk_primary_selection_device_manager() != nullptr);
}
wl::Clipboard* WaylandClipboard::GetClipboard(ClipboardBuffer buffer) {
if (buffer == ClipboardBuffer::kCopyPaste)
return copypaste_clipboard_.get();
if (buffer == ClipboardBuffer::kSelection) {
if (auto* manager = connection_->zwp_primary_selection_device_manager()) {
if (!primary_selection_clipboard_) {
primary_selection_clipboard_ = std::make_unique<
wl::ClipboardImpl<ZwpPrimarySelectionDeviceManager>>(
manager, ClipboardBuffer::kSelection);
}
return primary_selection_clipboard_.get();
} else if (auto* manager =
connection_->gtk_primary_selection_device_manager()) {
if (!primary_selection_clipboard_) {
primary_selection_clipboard_ = std::make_unique<
wl::ClipboardImpl<GtkPrimarySelectionDeviceManager>>(
manager, ClipboardBuffer::kSelection);
}
return primary_selection_clipboard_.get();
}
// Primary selection extension not available.
return nullptr;
}
NOTREACHED() << "Unsupported clipboard buffer: " << static_cast<int>(buffer);
return nullptr;
}
} // namespace ui