123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422 |
- // Copyright (c) 2021 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/base/x/x11_clipboard_helper.h"
- #include <string>
- #include <vector>
- #include "base/containers/contains.h"
- #include "base/feature_list.h"
- #include "base/memory/ref_counted_memory.h"
- #include "base/memory/singleton.h"
- #include "base/metrics/histogram_macros.h"
- #include "base/time/time.h"
- #include "ui/base/clipboard/clipboard_buffer.h"
- #include "ui/base/clipboard/clipboard_constants.h"
- #include "ui/base/ui_base_features.h"
- #include "ui/base/x/selection_owner.h"
- #include "ui/base/x/selection_requestor.h"
- #include "ui/base/x/selection_utils.h"
- #include "ui/base/x/x11_util.h"
- #include "ui/gfx/x/connection.h"
- #include "ui/gfx/x/event.h"
- #include "ui/gfx/x/x11_atom_cache.h"
- #include "ui/gfx/x/x11_window_event_manager.h"
- #include "ui/gfx/x/xfixes.h"
- #include "ui/gfx/x/xproto.h"
- #include "ui/gfx/x/xproto_util.h"
- namespace ui {
- namespace {
- const char kClipboard[] = "CLIPBOARD";
- const char kClipboardManager[] = "CLIPBOARD_MANAGER";
- // Uses the XFixes API to notify about selection changes.
- class SelectionChangeObserver : public x11::EventObserver {
- public:
- using SelectionChangeCallback = XClipboardHelper::SelectionChangeCallback;
- SelectionChangeObserver(const SelectionChangeObserver&) = delete;
- SelectionChangeObserver& operator=(const SelectionChangeObserver&) = delete;
- static SelectionChangeObserver* Get();
- void set_callback(SelectionChangeCallback callback) {
- callback_ = std::move(callback);
- }
- private:
- friend struct base::DefaultSingletonTraits<SelectionChangeObserver>;
- SelectionChangeObserver();
- ~SelectionChangeObserver() override = default;
- // x11::EventObserver:
- void OnEvent(const x11::Event& xev) override;
- const x11::Atom clipboard_atom_{};
- SelectionChangeCallback callback_;
- };
- SelectionChangeObserver::SelectionChangeObserver()
- : clipboard_atom_(x11::GetAtom(kClipboard)) {
- auto* connection = x11::Connection::Get();
- auto& xfixes = connection->xfixes();
- // Let the server know the client version. No need to sync since we don't
- // care what version is running on the server.
- xfixes.QueryVersion({x11::XFixes::major_version, x11::XFixes::minor_version});
- if (!xfixes.present())
- return;
- auto mask = x11::XFixes::SelectionEventMask::SetSelectionOwner |
- x11::XFixes::SelectionEventMask::SelectionWindowDestroy |
- x11::XFixes::SelectionEventMask::SelectionClientClose;
- xfixes.SelectSelectionInput({GetX11RootWindow(), clipboard_atom_, mask});
- // This seems to be semi-optional. For some reason, registering for any
- // selection notify events seems to subscribe us to events for both the
- // primary and the clipboard buffers. Register anyway just to be safe.
- xfixes.SelectSelectionInput({GetX11RootWindow(), x11::Atom::PRIMARY, mask});
- connection->AddEventObserver(this);
- }
- SelectionChangeObserver* SelectionChangeObserver::Get() {
- return base::Singleton<SelectionChangeObserver>::get();
- }
- void SelectionChangeObserver::OnEvent(const x11::Event& xev) {
- if (auto* ev = xev.As<x11::XFixes::SelectionNotifyEvent>()) {
- DCHECK(ev->selection == x11::Atom::PRIMARY ||
- ev->selection == clipboard_atom_)
- << "Unexpected selection atom: "
- << static_cast<uint32_t>(ev->selection);
- if (callback_) {
- callback_.Run(ev->selection == x11::Atom::PRIMARY
- ? ClipboardBuffer::kSelection
- : ClipboardBuffer::kCopyPaste);
- }
- }
- }
- x11::Window GetSelectionOwner(x11::Atom selection) {
- auto response = x11::Connection::Get()->GetSelectionOwner({selection}).Sync();
- return response ? response->owner : x11::Window::None;
- }
- } // namespace
- class XClipboardHelper::TargetList {
- public:
- explicit TargetList(const std::vector<x11::Atom>& target_list)
- : target_list_(target_list) {}
- TargetList(const TargetList&) = default;
- TargetList& operator=(const TargetList&) = default;
- ~TargetList() = default;
- const std::vector<x11::Atom>& target_list() const { return target_list_; }
- bool ContainsText() const {
- for (const auto& atom : GetTextAtomsFrom()) {
- if (base::Contains(target_list_, atom))
- return true;
- }
- return false;
- }
- bool ContainsFormat(const ClipboardFormatType& format_type) const {
- x11::Atom atom = x11::GetAtom(format_type.GetName().c_str());
- return base::Contains(target_list_, atom);
- }
- private:
- std::vector<x11::Atom> target_list_;
- };
- XClipboardHelper::XClipboardHelper(
- SelectionChangeCallback selection_change_callback)
- : connection_(x11::Connection::Get()),
- x_root_window_(ui::GetX11RootWindow()),
- x_window_(x11::CreateDummyWindow("Chromium Clipboard Window")),
- selection_requestor_(
- std::make_unique<SelectionRequestor>(x_window_, this)),
- clipboard_owner_(connection_, x_window_, x11::GetAtom(kClipboard)),
- primary_owner_(connection_, x_window_, x11::Atom::PRIMARY) {
- DCHECK(selection_requestor_);
- x11::SetStringProperty(x_window_, x11::Atom::WM_NAME, x11::Atom::STRING,
- "Chromium clipboard");
- x_window_events_ = std::make_unique<x11::XScopedEventSelector>(
- x_window_, x11::EventMask::PropertyChange);
- connection_->AddEventObserver(this);
- SelectionChangeObserver::Get()->set_callback(
- std::move(selection_change_callback));
- }
- XClipboardHelper::~XClipboardHelper() {
- connection_->RemoveEventObserver(this);
- connection_->DestroyWindow({x_window_});
- SelectionChangeObserver::Get()->set_callback(SelectionChangeCallback());
- }
- void XClipboardHelper::CreateNewClipboardData() {
- clipboard_data_ = SelectionFormatMap();
- }
- void XClipboardHelper::InsertMapping(
- const std::string& key,
- const scoped_refptr<base::RefCountedMemory>& memory) {
- x11::Atom atom_key = x11::GetAtom(key.c_str());
- clipboard_data_.Insert(atom_key, memory);
- }
- void XClipboardHelper::TakeOwnershipOfSelection(ClipboardBuffer buffer) {
- if (buffer == ClipboardBuffer::kCopyPaste)
- return clipboard_owner_.TakeOwnershipOfSelection(clipboard_data_);
- else
- return primary_owner_.TakeOwnershipOfSelection(clipboard_data_);
- }
- SelectionData XClipboardHelper::Read(ClipboardBuffer buffer,
- const std::vector<x11::Atom>& types) {
- x11::Atom selection_name = LookupSelectionForClipboardBuffer(buffer);
- if (GetSelectionOwner(selection_name) == x_window_) {
- // We can local fastpath instead of playing the nested run loop game
- // with the X server.
- const SelectionFormatMap& format_map = LookupStorageForAtom(selection_name);
- for (const auto& type : types) {
- auto format_map_it = format_map.find(type);
- if (format_map_it != format_map.end())
- return SelectionData(format_map_it->first, format_map_it->second);
- }
- return SelectionData();
- }
- auto targets = GetTargetList(buffer);
- std::vector<x11::Atom> intersection;
- GetAtomIntersection(types, targets.target_list(), &intersection);
- return selection_requestor_->RequestAndWaitForTypes(selection_name,
- intersection);
- }
- std::vector<std::string> XClipboardHelper::GetAvailableTypes(
- ClipboardBuffer buffer) {
- std::vector<std::string> available_types;
- auto target_list = GetTargetList(buffer);
- if (target_list.ContainsText())
- available_types.push_back(kMimeTypeText);
- if (target_list.ContainsFormat(ClipboardFormatType::HtmlType()))
- available_types.push_back(kMimeTypeHTML);
- if (target_list.ContainsFormat(ClipboardFormatType::SvgType()))
- available_types.push_back(kMimeTypeSvg);
- if (target_list.ContainsFormat(ClipboardFormatType::RtfType()))
- available_types.push_back(kMimeTypeRTF);
- if (target_list.ContainsFormat(ClipboardFormatType::PngType()))
- available_types.push_back(kMimeTypePNG);
- if (target_list.ContainsFormat(ClipboardFormatType::FilenamesType()))
- available_types.push_back(kMimeTypeURIList);
- if (target_list.ContainsFormat(ClipboardFormatType::WebCustomDataType()))
- available_types.push_back(kMimeTypeWebCustomData);
- return available_types;
- }
- std::vector<std::string> XClipboardHelper::GetAvailableAtomNames(
- ClipboardBuffer buffer) {
- auto target_list = GetTargetList(buffer).target_list();
- if (target_list.empty())
- return {};
- auto* connection = x11::Connection::Get();
- std::vector<x11::Future<x11::GetAtomNameReply>> futures;
- for (x11::Atom target : target_list)
- futures.push_back(connection->GetAtomName({target}));
- std::vector<std::string> atom_names;
- atom_names.reserve(target_list.size());
- for (auto& future : futures) {
- if (auto response = future.Sync())
- atom_names.push_back(response->name);
- else
- atom_names.emplace_back();
- }
- return atom_names;
- }
- bool XClipboardHelper::IsFormatAvailable(ClipboardBuffer buffer,
- const ClipboardFormatType& format) {
- auto target_list = GetTargetList(buffer);
- if (format == ClipboardFormatType::PlainTextType() ||
- format == ClipboardFormatType::UrlType()) {
- return target_list.ContainsText();
- }
- return target_list.ContainsFormat(format);
- }
- bool XClipboardHelper::IsSelectionOwner(ClipboardBuffer buffer) const {
- x11::Atom selection = LookupSelectionForClipboardBuffer(buffer);
- return GetSelectionOwner(selection) == x_window_;
- }
- std::vector<x11::Atom> XClipboardHelper::GetTextAtoms() const {
- return GetTextAtomsFrom();
- }
- std::vector<x11::Atom> XClipboardHelper::GetAtomsForFormat(
- const ClipboardFormatType& format) {
- return {x11::GetAtom(format.GetName().c_str())};
- }
- void XClipboardHelper::Clear(ClipboardBuffer buffer) {
- if (buffer == ClipboardBuffer::kCopyPaste)
- clipboard_owner_.ClearSelectionOwner();
- else
- primary_owner_.ClearSelectionOwner();
- }
- void XClipboardHelper::StoreCopyPasteDataAndWait() {
- x11::Atom selection = GetCopyPasteSelection();
- if (GetSelectionOwner(selection) != x_window_)
- return;
- x11::Atom clipboard_manager_atom = x11::GetAtom(kClipboardManager);
- if (GetSelectionOwner(clipboard_manager_atom) == x11::Window::None)
- return;
- const SelectionFormatMap& format_map = LookupStorageForAtom(selection);
- if (format_map.size() == 0)
- return;
- std::vector<x11::Atom> targets = format_map.GetTypes();
- base::TimeTicks start = base::TimeTicks::Now();
- selection_requestor_->PerformBlockingConvertSelectionWithParameter(
- x11::GetAtom(kClipboardManager), x11::GetAtom(kSaveTargets), targets);
- UMA_HISTOGRAM_TIMES("Clipboard.X11StoreCopyPasteDuration",
- base::TimeTicks::Now() - start);
- }
- XClipboardHelper::TargetList XClipboardHelper::GetTargetList(
- ClipboardBuffer buffer) {
- x11::Atom selection_name = LookupSelectionForClipboardBuffer(buffer);
- std::vector<x11::Atom> out;
- if (GetSelectionOwner(selection_name) == x_window_) {
- // We can local fastpath and return the list of local targets.
- const SelectionFormatMap& format_map = LookupStorageForAtom(selection_name);
- for (const auto& format : format_map)
- out.push_back(format.first);
- } else {
- std::vector<uint8_t> data;
- x11::Atom out_type = x11::Atom::None;
- if (selection_requestor_->PerformBlockingConvertSelection(
- selection_name, x11::GetAtom(kTargets), &data, &out_type)) {
- // Some apps return an |out_type| of "TARGETS". (crbug.com/377893)
- if (out_type == x11::Atom::ATOM || out_type == x11::GetAtom(kTargets)) {
- const x11::Atom* atom_array =
- reinterpret_cast<const x11::Atom*>(data.data());
- for (size_t i = 0; i < data.size() / sizeof(x11::Atom); ++i)
- out.push_back(atom_array[i]);
- }
- } else {
- // There was no target list. Most Java apps doesn't offer a TARGETS list,
- // even though they AWT to. They will offer individual text types if you
- // ask. If this is the case we attempt to make sense of the contents as
- // text. This is pretty unfortunate since it means we have to actually
- // copy the data to see if it is available, but at least this path
- // shouldn't be hit for conforming programs.
- std::vector<x11::Atom> types = GetTextAtoms();
- for (const auto& text_atom : types) {
- x11::Atom type = x11::Atom::None;
- if (selection_requestor_->PerformBlockingConvertSelection(
- selection_name, text_atom, nullptr, &type) &&
- type == text_atom) {
- out.push_back(text_atom);
- }
- }
- }
- }
- return XClipboardHelper::TargetList(out);
- }
- bool XClipboardHelper::DispatchEvent(const x11::Event& xev) {
- if (auto* request = xev.As<x11::SelectionRequestEvent>()) {
- if (request->owner != x_window_)
- return false;
- if (request->selection == x11::Atom::PRIMARY) {
- primary_owner_.OnSelectionRequest(*request);
- } else {
- // We should not get requests for the CLIPBOARD_MANAGER selection
- // because we never take ownership of it.
- DCHECK_EQ(GetCopyPasteSelection(), request->selection);
- clipboard_owner_.OnSelectionRequest(*request);
- }
- } else if (auto* notify = xev.As<x11::SelectionNotifyEvent>()) {
- if (notify->requestor == x_window_)
- selection_requestor_->OnSelectionNotify(*notify);
- else
- return false;
- } else if (auto* clear = xev.As<x11::SelectionClearEvent>()) {
- if (clear->owner != x_window_)
- return false;
- if (clear->selection == x11::Atom::PRIMARY) {
- primary_owner_.OnSelectionClear(*clear);
- } else {
- // We should not get requests for the CLIPBOARD_MANAGER selection
- // because we never take ownership of it.
- DCHECK_EQ(GetCopyPasteSelection(), clear->selection);
- clipboard_owner_.OnSelectionClear(*clear);
- }
- } else if (auto* prop = xev.As<x11::PropertyNotifyEvent>()) {
- if (primary_owner_.CanDispatchPropertyEvent(*prop))
- primary_owner_.OnPropertyEvent(*prop);
- else if (clipboard_owner_.CanDispatchPropertyEvent(*prop))
- clipboard_owner_.OnPropertyEvent(*prop);
- else if (selection_requestor_->CanDispatchPropertyEvent(*prop))
- selection_requestor_->OnPropertyEvent(*prop);
- else
- return false;
- } else {
- return false;
- }
- return true;
- }
- SelectionRequestor* XClipboardHelper::GetSelectionRequestorForTest() {
- return selection_requestor_.get();
- }
- void XClipboardHelper::OnEvent(const x11::Event& xev) {
- DispatchEvent(xev);
- }
- x11::Atom XClipboardHelper::LookupSelectionForClipboardBuffer(
- ClipboardBuffer buffer) const {
- if (buffer == ClipboardBuffer::kCopyPaste)
- return GetCopyPasteSelection();
- return x11::Atom::PRIMARY;
- }
- x11::Atom XClipboardHelper::GetCopyPasteSelection() const {
- return x11::GetAtom(kClipboard);
- }
- const SelectionFormatMap& XClipboardHelper::LookupStorageForAtom(
- x11::Atom atom) {
- if (atom == x11::Atom::PRIMARY)
- return primary_owner_.selection_format_map();
- DCHECK_EQ(GetCopyPasteSelection(), atom);
- return clipboard_owner_.selection_format_map();
- }
- } // namespace ui
|