x_server_clipboard.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. // Copyright (c) 2012 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 "remoting/host/linux/x_server_clipboard.h"
  5. #include <limits>
  6. #include "base/callback.h"
  7. #include "base/memory/ref_counted_memory.h"
  8. #include "base/memory/scoped_refptr.h"
  9. #include "base/strings/string_util.h"
  10. #include "remoting/base/constants.h"
  11. #include "remoting/base/logging.h"
  12. #include "remoting/base/util.h"
  13. #include "ui/gfx/x/extension_manager.h"
  14. #include "ui/gfx/x/future.h"
  15. #include "ui/gfx/x/xproto.h"
  16. #include "ui/gfx/x/xproto_util.h"
  17. namespace remoting {
  18. XServerClipboard::XServerClipboard() = default;
  19. XServerClipboard::~XServerClipboard() = default;
  20. void XServerClipboard::Init(x11::Connection* connection,
  21. const ClipboardChangedCallback& callback) {
  22. connection_ = connection;
  23. callback_ = callback;
  24. if (!connection_->xfixes().present()) {
  25. HOST_LOG << "X server does not support XFixes.";
  26. return;
  27. }
  28. // Let the server know the client version.
  29. connection_->xfixes().QueryVersion(
  30. {x11::XFixes::major_version, x11::XFixes::minor_version});
  31. clipboard_window_ = connection_->GenerateId<x11::Window>();
  32. connection_->CreateWindow({
  33. .wid = clipboard_window_,
  34. .parent = connection_->default_root(),
  35. .width = 1,
  36. .height = 1,
  37. .override_redirect = x11::Bool32(true),
  38. });
  39. // TODO(lambroslambrou): Use ui::X11AtomCache for this, either by adding a
  40. // dependency on ui/ or by moving X11AtomCache to base/.
  41. static const char* const kAtomNames[] = {"CLIPBOARD", "INCR",
  42. "SELECTION_STRING", "TARGETS",
  43. "TIMESTAMP", "UTF8_STRING"};
  44. static const int kNumAtomNames = std::size(kAtomNames);
  45. x11::Future<x11::InternAtomReply> futures[kNumAtomNames];
  46. for (size_t i = 0; i < kNumAtomNames; i++)
  47. futures[i] = connection_->InternAtom({false, kAtomNames[i]});
  48. connection_->Flush();
  49. x11::Atom atoms[kNumAtomNames];
  50. memset(atoms, 0, sizeof(atoms));
  51. for (size_t i = 0; i < kNumAtomNames; i++) {
  52. if (auto reply = futures[i].Sync()) {
  53. atoms[i] = reply->atom;
  54. } else {
  55. LOG(ERROR) << "Failed to intern atom(s)";
  56. break;
  57. }
  58. }
  59. clipboard_atom_ = atoms[0];
  60. large_selection_atom_ = atoms[1];
  61. selection_string_atom_ = atoms[2];
  62. targets_atom_ = atoms[3];
  63. timestamp_atom_ = atoms[4];
  64. utf8_string_atom_ = atoms[5];
  65. static_assert(kNumAtomNames >= 6, "kAtomNames is too small");
  66. connection_->xfixes().SelectSelectionInput(
  67. {static_cast<x11::Window>(clipboard_window_),
  68. static_cast<x11::Atom>(clipboard_atom_),
  69. x11::XFixes::SelectionEventMask::SetSelectionOwner});
  70. connection_->Flush();
  71. }
  72. void XServerClipboard::SetClipboard(const std::string& mime_type,
  73. const std::string& data) {
  74. DCHECK(connection_->Ready());
  75. if (clipboard_window_ == x11::Window::None)
  76. return;
  77. // Currently only UTF-8 is supported.
  78. if (mime_type != kMimeTypeTextUtf8)
  79. return;
  80. if (!base::IsStringUTF8AllowingNoncharacters(data)) {
  81. LOG(ERROR) << "ClipboardEvent: data is not UTF-8 encoded.";
  82. return;
  83. }
  84. data_ = data;
  85. AssertSelectionOwnership(x11::Atom::PRIMARY);
  86. AssertSelectionOwnership(clipboard_atom_);
  87. }
  88. void XServerClipboard::ProcessXEvent(const x11::Event& event) {
  89. if (clipboard_window_ == x11::Window::None ||
  90. event.window() != clipboard_window_) {
  91. return;
  92. }
  93. if (auto* property_notify = event.As<x11::PropertyNotifyEvent>())
  94. OnPropertyNotify(*property_notify);
  95. else if (auto* selection_notify = event.As<x11::SelectionNotifyEvent>())
  96. OnSelectionNotify(*selection_notify);
  97. else if (auto* selection_request = event.As<x11::SelectionRequestEvent>())
  98. OnSelectionRequest(*selection_request);
  99. else if (auto* selection_clear = event.As<x11::SelectionClearEvent>())
  100. OnSelectionClear(*selection_clear);
  101. if (auto* xfixes_selection_notify =
  102. event.As<x11::XFixes::SelectionNotifyEvent>()) {
  103. OnSetSelectionOwnerNotify(xfixes_selection_notify->selection,
  104. xfixes_selection_notify->selection_timestamp);
  105. }
  106. }
  107. void XServerClipboard::OnSetSelectionOwnerNotify(x11::Atom selection,
  108. x11::Time timestamp) {
  109. // Protect against receiving new XFixes selection notifications whilst we're
  110. // in the middle of waiting for information from the current selection owner.
  111. // A reasonable timeout allows for misbehaving apps that don't respond
  112. // quickly to our requests.
  113. if (!get_selections_time_.is_null() &&
  114. (base::TimeTicks::Now() - get_selections_time_) < base::Seconds(5)) {
  115. // TODO(lambroslambrou): Instead of ignoring this notification, cancel any
  116. // pending request operations and ignore the resulting events, before
  117. // dispatching new requests here.
  118. return;
  119. }
  120. // Only process CLIPBOARD selections.
  121. if (selection != clipboard_atom_)
  122. return;
  123. // If we own the selection, don't request details for it.
  124. if (IsSelectionOwner(selection))
  125. return;
  126. get_selections_time_ = base::TimeTicks::Now();
  127. // Before getting the value of the chosen selection, request the list of
  128. // target formats it supports.
  129. RequestSelectionTargets(selection);
  130. }
  131. void XServerClipboard::OnPropertyNotify(const x11::PropertyNotifyEvent& event) {
  132. if (large_selection_property_ != x11::Atom::None &&
  133. event.atom == large_selection_property_ &&
  134. event.state == x11::Property::NewValue) {
  135. auto req = connection_->GetProperty({
  136. .c_delete = true,
  137. .window = clipboard_window_,
  138. .property = large_selection_property_,
  139. .type = x11::Atom::Any,
  140. .long_length = std::numeric_limits<uint32_t>::max(),
  141. });
  142. if (auto reply = req.Sync()) {
  143. if (reply->type != x11::Atom::None) {
  144. // TODO(lambroslambrou): Properly support large transfers -
  145. // http://crbug.com/151447.
  146. // If the property is zero-length then the large transfer is complete.
  147. if (reply->value_len == 0)
  148. large_selection_property_ = x11::Atom::None;
  149. }
  150. }
  151. }
  152. }
  153. void XServerClipboard::OnSelectionNotify(
  154. const x11::SelectionNotifyEvent& event) {
  155. if (event.property != x11::Atom::None) {
  156. auto req = connection_->GetProperty({
  157. .c_delete = true,
  158. .window = clipboard_window_,
  159. .property = event.property,
  160. .type = x11::Atom::Any,
  161. .long_length = std::numeric_limits<uint32_t>::max(),
  162. });
  163. if (auto reply = req.Sync()) {
  164. if (reply->type == large_selection_atom_) {
  165. // Large selection - just read and ignore these for now.
  166. large_selection_property_ = event.property;
  167. } else {
  168. // Standard selection - call the selection notifier.
  169. large_selection_property_ = x11::Atom::None;
  170. if (reply->type != x11::Atom::None) {
  171. HandleSelectionNotify(event, reply->type, reply->format,
  172. reply->value_len, reply->value->data());
  173. return;
  174. }
  175. }
  176. }
  177. }
  178. HandleSelectionNotify(event, x11::Atom::None, 0, 0, nullptr);
  179. }
  180. void XServerClipboard::OnSelectionRequest(
  181. const x11::SelectionRequestEvent& event) {
  182. x11::SelectionNotifyEvent selection_event;
  183. selection_event.requestor = event.requestor;
  184. selection_event.selection = event.selection;
  185. selection_event.time = event.time;
  186. selection_event.target = event.target;
  187. auto property =
  188. event.property == x11::Atom::None ? event.target : event.property;
  189. if (!IsSelectionOwner(selection_event.selection)) {
  190. selection_event.property = x11::Atom::None;
  191. } else {
  192. selection_event.property = property;
  193. if (selection_event.target == static_cast<x11::Atom>(targets_atom_)) {
  194. SendTargetsResponse(selection_event.requestor, selection_event.property);
  195. } else if (selection_event.target ==
  196. static_cast<x11::Atom>(timestamp_atom_)) {
  197. SendTimestampResponse(selection_event.requestor,
  198. selection_event.property);
  199. } else if (selection_event.target ==
  200. static_cast<x11::Atom>(utf8_string_atom_) ||
  201. selection_event.target == x11::Atom::STRING) {
  202. SendStringResponse(selection_event.requestor, selection_event.property,
  203. selection_event.target);
  204. }
  205. }
  206. x11::SendEvent(selection_event, selection_event.requestor,
  207. x11::EventMask::NoEvent, connection_);
  208. }
  209. void XServerClipboard::OnSelectionClear(const x11::SelectionClearEvent& event) {
  210. selections_owned_.erase(event.selection);
  211. }
  212. void XServerClipboard::SendTargetsResponse(x11::Window requestor,
  213. x11::Atom property) {
  214. // Respond advertising x11::Atom::STRING, UTF8_STRING and TIMESTAMP data for
  215. // the selection.
  216. x11::Atom targets[3] = {
  217. timestamp_atom_,
  218. utf8_string_atom_,
  219. x11::Atom::STRING,
  220. };
  221. connection_->ChangeProperty({
  222. .mode = x11::PropMode::Replace,
  223. .window = requestor,
  224. .property = property,
  225. .type = x11::Atom::ATOM,
  226. .format = CHAR_BIT * sizeof(x11::Atom),
  227. .data_len = std::size(targets),
  228. .data = base::MakeRefCounted<base::RefCountedStaticMemory>(
  229. &targets[0], sizeof(targets)),
  230. });
  231. connection_->Flush();
  232. }
  233. void XServerClipboard::SendTimestampResponse(x11::Window requestor,
  234. x11::Atom property) {
  235. // Respond with the timestamp of our selection; we always return
  236. // CurrentTime since our selections are set by remote clients, so there
  237. // is no associated local X event.
  238. // TODO(lambroslambrou): Should use a proper timestamp here instead of
  239. // CurrentTime. ICCCM recommends doing a zero-length property append,
  240. // and getting a timestamp from the subsequent PropertyNotify event.
  241. x11::Time time = x11::Time::CurrentTime;
  242. connection_->ChangeProperty({
  243. .mode = x11::PropMode::Replace,
  244. .window = requestor,
  245. .property = property,
  246. .type = x11::Atom::INTEGER,
  247. .format = CHAR_BIT * sizeof(x11::Time),
  248. .data_len = 1,
  249. .data = base::MakeRefCounted<base::RefCountedStaticMemory>(&time,
  250. sizeof(time)),
  251. });
  252. connection_->Flush();
  253. }
  254. void XServerClipboard::SendStringResponse(x11::Window requestor,
  255. x11::Atom property,
  256. x11::Atom target) {
  257. if (!data_.empty()) {
  258. // Return the actual string data; we always return UTF8, regardless of
  259. // the configured locale.
  260. connection_->ChangeProperty({
  261. .mode = x11::PropMode::Replace,
  262. .window = requestor,
  263. .property = property,
  264. .type = target,
  265. .format = 8,
  266. .data_len = static_cast<uint32_t>(data_.size()),
  267. .data = base::MakeRefCounted<base::RefCountedStaticMemory>(
  268. data_.data(), data_.size()),
  269. });
  270. connection_->Flush();
  271. }
  272. }
  273. void XServerClipboard::HandleSelectionNotify(
  274. const x11::SelectionNotifyEvent& event,
  275. x11::Atom type,
  276. int format,
  277. int item_count,
  278. const void* data) {
  279. bool finished = false;
  280. auto target = event.target;
  281. if (target == targets_atom_)
  282. finished = HandleSelectionTargetsEvent(event, format, item_count, data);
  283. else if (target == utf8_string_atom_ || target == x11::Atom::STRING)
  284. finished = HandleSelectionStringEvent(event, format, item_count, data);
  285. if (finished)
  286. get_selections_time_ = base::TimeTicks();
  287. }
  288. bool XServerClipboard::HandleSelectionTargetsEvent(
  289. const x11::SelectionNotifyEvent& event,
  290. int format,
  291. int item_count,
  292. const void* data) {
  293. auto selection = event.selection;
  294. if (event.property == targets_atom_) {
  295. if (data && format == 32) {
  296. const uint32_t* targets = static_cast<const uint32_t*>(data);
  297. for (int i = 0; i < item_count; i++) {
  298. if (targets[i] == static_cast<uint32_t>(utf8_string_atom_)) {
  299. RequestSelectionString(selection, utf8_string_atom_);
  300. return false;
  301. }
  302. }
  303. }
  304. }
  305. RequestSelectionString(selection, x11::Atom::STRING);
  306. return false;
  307. }
  308. bool XServerClipboard::HandleSelectionStringEvent(
  309. const x11::SelectionNotifyEvent& event,
  310. int format,
  311. int item_count,
  312. const void* data) {
  313. auto property = event.property;
  314. auto target = event.target;
  315. if (property != selection_string_atom_ || !data || format != 8)
  316. return true;
  317. std::string text(static_cast<const char*>(data), item_count);
  318. if (target == x11::Atom::STRING || target == utf8_string_atom_)
  319. NotifyClipboardText(text);
  320. return true;
  321. }
  322. void XServerClipboard::NotifyClipboardText(const std::string& text) {
  323. data_ = text;
  324. callback_.Run(kMimeTypeTextUtf8, data_);
  325. }
  326. void XServerClipboard::RequestSelectionTargets(x11::Atom selection) {
  327. connection_->ConvertSelection({clipboard_window_, selection, targets_atom_,
  328. targets_atom_, x11::Time::CurrentTime});
  329. }
  330. void XServerClipboard::RequestSelectionString(x11::Atom selection,
  331. x11::Atom target) {
  332. connection_->ConvertSelection({clipboard_window_, selection, target,
  333. selection_string_atom_,
  334. x11::Time::CurrentTime});
  335. }
  336. void XServerClipboard::AssertSelectionOwnership(x11::Atom selection) {
  337. connection_->SetSelectionOwner(
  338. {clipboard_window_, selection, x11::Time::CurrentTime});
  339. auto reply = connection_->GetSelectionOwner({selection}).Sync();
  340. auto owner = reply ? reply->owner : x11::Window::None;
  341. if (owner == clipboard_window_) {
  342. selections_owned_.insert(selection);
  343. } else {
  344. LOG(ERROR) << "XSetSelectionOwner failed for selection "
  345. << static_cast<uint32_t>(selection);
  346. }
  347. }
  348. bool XServerClipboard::IsSelectionOwner(x11::Atom selection) {
  349. return selections_owned_.find(selection) != selections_owned_.end();
  350. }
  351. } // namespace remoting