x11_clipboard_helper.cc 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. // Copyright (c) 2021 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ui/base/x/x11_clipboard_helper.h"
  5. #include <string>
  6. #include <vector>
  7. #include "base/containers/contains.h"
  8. #include "base/feature_list.h"
  9. #include "base/memory/ref_counted_memory.h"
  10. #include "base/memory/singleton.h"
  11. #include "base/metrics/histogram_macros.h"
  12. #include "base/time/time.h"
  13. #include "ui/base/clipboard/clipboard_buffer.h"
  14. #include "ui/base/clipboard/clipboard_constants.h"
  15. #include "ui/base/ui_base_features.h"
  16. #include "ui/base/x/selection_owner.h"
  17. #include "ui/base/x/selection_requestor.h"
  18. #include "ui/base/x/selection_utils.h"
  19. #include "ui/base/x/x11_util.h"
  20. #include "ui/gfx/x/connection.h"
  21. #include "ui/gfx/x/event.h"
  22. #include "ui/gfx/x/x11_atom_cache.h"
  23. #include "ui/gfx/x/x11_window_event_manager.h"
  24. #include "ui/gfx/x/xfixes.h"
  25. #include "ui/gfx/x/xproto.h"
  26. #include "ui/gfx/x/xproto_util.h"
  27. namespace ui {
  28. namespace {
  29. const char kClipboard[] = "CLIPBOARD";
  30. const char kClipboardManager[] = "CLIPBOARD_MANAGER";
  31. // Uses the XFixes API to notify about selection changes.
  32. class SelectionChangeObserver : public x11::EventObserver {
  33. public:
  34. using SelectionChangeCallback = XClipboardHelper::SelectionChangeCallback;
  35. SelectionChangeObserver(const SelectionChangeObserver&) = delete;
  36. SelectionChangeObserver& operator=(const SelectionChangeObserver&) = delete;
  37. static SelectionChangeObserver* Get();
  38. void set_callback(SelectionChangeCallback callback) {
  39. callback_ = std::move(callback);
  40. }
  41. private:
  42. friend struct base::DefaultSingletonTraits<SelectionChangeObserver>;
  43. SelectionChangeObserver();
  44. ~SelectionChangeObserver() override = default;
  45. // x11::EventObserver:
  46. void OnEvent(const x11::Event& xev) override;
  47. const x11::Atom clipboard_atom_{};
  48. SelectionChangeCallback callback_;
  49. };
  50. SelectionChangeObserver::SelectionChangeObserver()
  51. : clipboard_atom_(x11::GetAtom(kClipboard)) {
  52. auto* connection = x11::Connection::Get();
  53. auto& xfixes = connection->xfixes();
  54. // Let the server know the client version. No need to sync since we don't
  55. // care what version is running on the server.
  56. xfixes.QueryVersion({x11::XFixes::major_version, x11::XFixes::minor_version});
  57. if (!xfixes.present())
  58. return;
  59. auto mask = x11::XFixes::SelectionEventMask::SetSelectionOwner |
  60. x11::XFixes::SelectionEventMask::SelectionWindowDestroy |
  61. x11::XFixes::SelectionEventMask::SelectionClientClose;
  62. xfixes.SelectSelectionInput({GetX11RootWindow(), clipboard_atom_, mask});
  63. // This seems to be semi-optional. For some reason, registering for any
  64. // selection notify events seems to subscribe us to events for both the
  65. // primary and the clipboard buffers. Register anyway just to be safe.
  66. xfixes.SelectSelectionInput({GetX11RootWindow(), x11::Atom::PRIMARY, mask});
  67. connection->AddEventObserver(this);
  68. }
  69. SelectionChangeObserver* SelectionChangeObserver::Get() {
  70. return base::Singleton<SelectionChangeObserver>::get();
  71. }
  72. void SelectionChangeObserver::OnEvent(const x11::Event& xev) {
  73. if (auto* ev = xev.As<x11::XFixes::SelectionNotifyEvent>()) {
  74. DCHECK(ev->selection == x11::Atom::PRIMARY ||
  75. ev->selection == clipboard_atom_)
  76. << "Unexpected selection atom: "
  77. << static_cast<uint32_t>(ev->selection);
  78. if (callback_) {
  79. callback_.Run(ev->selection == x11::Atom::PRIMARY
  80. ? ClipboardBuffer::kSelection
  81. : ClipboardBuffer::kCopyPaste);
  82. }
  83. }
  84. }
  85. x11::Window GetSelectionOwner(x11::Atom selection) {
  86. auto response = x11::Connection::Get()->GetSelectionOwner({selection}).Sync();
  87. return response ? response->owner : x11::Window::None;
  88. }
  89. } // namespace
  90. class XClipboardHelper::TargetList {
  91. public:
  92. explicit TargetList(const std::vector<x11::Atom>& target_list)
  93. : target_list_(target_list) {}
  94. TargetList(const TargetList&) = default;
  95. TargetList& operator=(const TargetList&) = default;
  96. ~TargetList() = default;
  97. const std::vector<x11::Atom>& target_list() const { return target_list_; }
  98. bool ContainsText() const {
  99. for (const auto& atom : GetTextAtomsFrom()) {
  100. if (base::Contains(target_list_, atom))
  101. return true;
  102. }
  103. return false;
  104. }
  105. bool ContainsFormat(const ClipboardFormatType& format_type) const {
  106. x11::Atom atom = x11::GetAtom(format_type.GetName().c_str());
  107. return base::Contains(target_list_, atom);
  108. }
  109. private:
  110. std::vector<x11::Atom> target_list_;
  111. };
  112. XClipboardHelper::XClipboardHelper(
  113. SelectionChangeCallback selection_change_callback)
  114. : connection_(x11::Connection::Get()),
  115. x_root_window_(ui::GetX11RootWindow()),
  116. x_window_(x11::CreateDummyWindow("Chromium Clipboard Window")),
  117. selection_requestor_(
  118. std::make_unique<SelectionRequestor>(x_window_, this)),
  119. clipboard_owner_(connection_, x_window_, x11::GetAtom(kClipboard)),
  120. primary_owner_(connection_, x_window_, x11::Atom::PRIMARY) {
  121. DCHECK(selection_requestor_);
  122. x11::SetStringProperty(x_window_, x11::Atom::WM_NAME, x11::Atom::STRING,
  123. "Chromium clipboard");
  124. x_window_events_ = std::make_unique<x11::XScopedEventSelector>(
  125. x_window_, x11::EventMask::PropertyChange);
  126. connection_->AddEventObserver(this);
  127. SelectionChangeObserver::Get()->set_callback(
  128. std::move(selection_change_callback));
  129. }
  130. XClipboardHelper::~XClipboardHelper() {
  131. connection_->RemoveEventObserver(this);
  132. connection_->DestroyWindow({x_window_});
  133. SelectionChangeObserver::Get()->set_callback(SelectionChangeCallback());
  134. }
  135. void XClipboardHelper::CreateNewClipboardData() {
  136. clipboard_data_ = SelectionFormatMap();
  137. }
  138. void XClipboardHelper::InsertMapping(
  139. const std::string& key,
  140. const scoped_refptr<base::RefCountedMemory>& memory) {
  141. x11::Atom atom_key = x11::GetAtom(key.c_str());
  142. clipboard_data_.Insert(atom_key, memory);
  143. }
  144. void XClipboardHelper::TakeOwnershipOfSelection(ClipboardBuffer buffer) {
  145. if (buffer == ClipboardBuffer::kCopyPaste)
  146. return clipboard_owner_.TakeOwnershipOfSelection(clipboard_data_);
  147. else
  148. return primary_owner_.TakeOwnershipOfSelection(clipboard_data_);
  149. }
  150. SelectionData XClipboardHelper::Read(ClipboardBuffer buffer,
  151. const std::vector<x11::Atom>& types) {
  152. x11::Atom selection_name = LookupSelectionForClipboardBuffer(buffer);
  153. if (GetSelectionOwner(selection_name) == x_window_) {
  154. // We can local fastpath instead of playing the nested run loop game
  155. // with the X server.
  156. const SelectionFormatMap& format_map = LookupStorageForAtom(selection_name);
  157. for (const auto& type : types) {
  158. auto format_map_it = format_map.find(type);
  159. if (format_map_it != format_map.end())
  160. return SelectionData(format_map_it->first, format_map_it->second);
  161. }
  162. return SelectionData();
  163. }
  164. auto targets = GetTargetList(buffer);
  165. std::vector<x11::Atom> intersection;
  166. GetAtomIntersection(types, targets.target_list(), &intersection);
  167. return selection_requestor_->RequestAndWaitForTypes(selection_name,
  168. intersection);
  169. }
  170. std::vector<std::string> XClipboardHelper::GetAvailableTypes(
  171. ClipboardBuffer buffer) {
  172. std::vector<std::string> available_types;
  173. auto target_list = GetTargetList(buffer);
  174. if (target_list.ContainsText())
  175. available_types.push_back(kMimeTypeText);
  176. if (target_list.ContainsFormat(ClipboardFormatType::HtmlType()))
  177. available_types.push_back(kMimeTypeHTML);
  178. if (target_list.ContainsFormat(ClipboardFormatType::SvgType()))
  179. available_types.push_back(kMimeTypeSvg);
  180. if (target_list.ContainsFormat(ClipboardFormatType::RtfType()))
  181. available_types.push_back(kMimeTypeRTF);
  182. if (target_list.ContainsFormat(ClipboardFormatType::PngType()))
  183. available_types.push_back(kMimeTypePNG);
  184. if (target_list.ContainsFormat(ClipboardFormatType::FilenamesType()))
  185. available_types.push_back(kMimeTypeURIList);
  186. if (target_list.ContainsFormat(ClipboardFormatType::WebCustomDataType()))
  187. available_types.push_back(kMimeTypeWebCustomData);
  188. return available_types;
  189. }
  190. std::vector<std::string> XClipboardHelper::GetAvailableAtomNames(
  191. ClipboardBuffer buffer) {
  192. auto target_list = GetTargetList(buffer).target_list();
  193. if (target_list.empty())
  194. return {};
  195. auto* connection = x11::Connection::Get();
  196. std::vector<x11::Future<x11::GetAtomNameReply>> futures;
  197. for (x11::Atom target : target_list)
  198. futures.push_back(connection->GetAtomName({target}));
  199. std::vector<std::string> atom_names;
  200. atom_names.reserve(target_list.size());
  201. for (auto& future : futures) {
  202. if (auto response = future.Sync())
  203. atom_names.push_back(response->name);
  204. else
  205. atom_names.emplace_back();
  206. }
  207. return atom_names;
  208. }
  209. bool XClipboardHelper::IsFormatAvailable(ClipboardBuffer buffer,
  210. const ClipboardFormatType& format) {
  211. auto target_list = GetTargetList(buffer);
  212. if (format == ClipboardFormatType::PlainTextType() ||
  213. format == ClipboardFormatType::UrlType()) {
  214. return target_list.ContainsText();
  215. }
  216. return target_list.ContainsFormat(format);
  217. }
  218. bool XClipboardHelper::IsSelectionOwner(ClipboardBuffer buffer) const {
  219. x11::Atom selection = LookupSelectionForClipboardBuffer(buffer);
  220. return GetSelectionOwner(selection) == x_window_;
  221. }
  222. std::vector<x11::Atom> XClipboardHelper::GetTextAtoms() const {
  223. return GetTextAtomsFrom();
  224. }
  225. std::vector<x11::Atom> XClipboardHelper::GetAtomsForFormat(
  226. const ClipboardFormatType& format) {
  227. return {x11::GetAtom(format.GetName().c_str())};
  228. }
  229. void XClipboardHelper::Clear(ClipboardBuffer buffer) {
  230. if (buffer == ClipboardBuffer::kCopyPaste)
  231. clipboard_owner_.ClearSelectionOwner();
  232. else
  233. primary_owner_.ClearSelectionOwner();
  234. }
  235. void XClipboardHelper::StoreCopyPasteDataAndWait() {
  236. x11::Atom selection = GetCopyPasteSelection();
  237. if (GetSelectionOwner(selection) != x_window_)
  238. return;
  239. x11::Atom clipboard_manager_atom = x11::GetAtom(kClipboardManager);
  240. if (GetSelectionOwner(clipboard_manager_atom) == x11::Window::None)
  241. return;
  242. const SelectionFormatMap& format_map = LookupStorageForAtom(selection);
  243. if (format_map.size() == 0)
  244. return;
  245. std::vector<x11::Atom> targets = format_map.GetTypes();
  246. base::TimeTicks start = base::TimeTicks::Now();
  247. selection_requestor_->PerformBlockingConvertSelectionWithParameter(
  248. x11::GetAtom(kClipboardManager), x11::GetAtom(kSaveTargets), targets);
  249. UMA_HISTOGRAM_TIMES("Clipboard.X11StoreCopyPasteDuration",
  250. base::TimeTicks::Now() - start);
  251. }
  252. XClipboardHelper::TargetList XClipboardHelper::GetTargetList(
  253. ClipboardBuffer buffer) {
  254. x11::Atom selection_name = LookupSelectionForClipboardBuffer(buffer);
  255. std::vector<x11::Atom> out;
  256. if (GetSelectionOwner(selection_name) == x_window_) {
  257. // We can local fastpath and return the list of local targets.
  258. const SelectionFormatMap& format_map = LookupStorageForAtom(selection_name);
  259. for (const auto& format : format_map)
  260. out.push_back(format.first);
  261. } else {
  262. std::vector<uint8_t> data;
  263. x11::Atom out_type = x11::Atom::None;
  264. if (selection_requestor_->PerformBlockingConvertSelection(
  265. selection_name, x11::GetAtom(kTargets), &data, &out_type)) {
  266. // Some apps return an |out_type| of "TARGETS". (crbug.com/377893)
  267. if (out_type == x11::Atom::ATOM || out_type == x11::GetAtom(kTargets)) {
  268. const x11::Atom* atom_array =
  269. reinterpret_cast<const x11::Atom*>(data.data());
  270. for (size_t i = 0; i < data.size() / sizeof(x11::Atom); ++i)
  271. out.push_back(atom_array[i]);
  272. }
  273. } else {
  274. // There was no target list. Most Java apps doesn't offer a TARGETS list,
  275. // even though they AWT to. They will offer individual text types if you
  276. // ask. If this is the case we attempt to make sense of the contents as
  277. // text. This is pretty unfortunate since it means we have to actually
  278. // copy the data to see if it is available, but at least this path
  279. // shouldn't be hit for conforming programs.
  280. std::vector<x11::Atom> types = GetTextAtoms();
  281. for (const auto& text_atom : types) {
  282. x11::Atom type = x11::Atom::None;
  283. if (selection_requestor_->PerformBlockingConvertSelection(
  284. selection_name, text_atom, nullptr, &type) &&
  285. type == text_atom) {
  286. out.push_back(text_atom);
  287. }
  288. }
  289. }
  290. }
  291. return XClipboardHelper::TargetList(out);
  292. }
  293. bool XClipboardHelper::DispatchEvent(const x11::Event& xev) {
  294. if (auto* request = xev.As<x11::SelectionRequestEvent>()) {
  295. if (request->owner != x_window_)
  296. return false;
  297. if (request->selection == x11::Atom::PRIMARY) {
  298. primary_owner_.OnSelectionRequest(*request);
  299. } else {
  300. // We should not get requests for the CLIPBOARD_MANAGER selection
  301. // because we never take ownership of it.
  302. DCHECK_EQ(GetCopyPasteSelection(), request->selection);
  303. clipboard_owner_.OnSelectionRequest(*request);
  304. }
  305. } else if (auto* notify = xev.As<x11::SelectionNotifyEvent>()) {
  306. if (notify->requestor == x_window_)
  307. selection_requestor_->OnSelectionNotify(*notify);
  308. else
  309. return false;
  310. } else if (auto* clear = xev.As<x11::SelectionClearEvent>()) {
  311. if (clear->owner != x_window_)
  312. return false;
  313. if (clear->selection == x11::Atom::PRIMARY) {
  314. primary_owner_.OnSelectionClear(*clear);
  315. } else {
  316. // We should not get requests for the CLIPBOARD_MANAGER selection
  317. // because we never take ownership of it.
  318. DCHECK_EQ(GetCopyPasteSelection(), clear->selection);
  319. clipboard_owner_.OnSelectionClear(*clear);
  320. }
  321. } else if (auto* prop = xev.As<x11::PropertyNotifyEvent>()) {
  322. if (primary_owner_.CanDispatchPropertyEvent(*prop))
  323. primary_owner_.OnPropertyEvent(*prop);
  324. else if (clipboard_owner_.CanDispatchPropertyEvent(*prop))
  325. clipboard_owner_.OnPropertyEvent(*prop);
  326. else if (selection_requestor_->CanDispatchPropertyEvent(*prop))
  327. selection_requestor_->OnPropertyEvent(*prop);
  328. else
  329. return false;
  330. } else {
  331. return false;
  332. }
  333. return true;
  334. }
  335. SelectionRequestor* XClipboardHelper::GetSelectionRequestorForTest() {
  336. return selection_requestor_.get();
  337. }
  338. void XClipboardHelper::OnEvent(const x11::Event& xev) {
  339. DispatchEvent(xev);
  340. }
  341. x11::Atom XClipboardHelper::LookupSelectionForClipboardBuffer(
  342. ClipboardBuffer buffer) const {
  343. if (buffer == ClipboardBuffer::kCopyPaste)
  344. return GetCopyPasteSelection();
  345. return x11::Atom::PRIMARY;
  346. }
  347. x11::Atom XClipboardHelper::GetCopyPasteSelection() const {
  348. return x11::GetAtom(kClipboard);
  349. }
  350. const SelectionFormatMap& XClipboardHelper::LookupStorageForAtom(
  351. x11::Atom atom) {
  352. if (atom == x11::Atom::PRIMARY)
  353. return primary_owner_.selection_format_map();
  354. DCHECK_EQ(GetCopyPasteSelection(), atom);
  355. return clipboard_owner_.selection_format_map();
  356. }
  357. } // namespace ui