dbus-clipboard.c 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458
  1. /*
  2. * QEMU DBus display
  3. *
  4. * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
  5. *
  6. * Permission is hereby granted, free of charge, to any person obtaining a copy
  7. * of this software and associated documentation files (the "Software"), to deal
  8. * in the Software without restriction, including without limitation the rights
  9. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. * copies of the Software, and to permit persons to whom the Software is
  11. * furnished to do so, subject to the following conditions:
  12. *
  13. * The above copyright notice and this permission notice shall be included in
  14. * all copies or substantial portions of the Software.
  15. *
  16. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
  19. * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  22. * THE SOFTWARE.
  23. */
  24. #include "qemu/osdep.h"
  25. #include "qemu/dbus.h"
  26. #include "qemu/error-report.h"
  27. #include "qemu/main-loop.h"
  28. #include "qom/object_interfaces.h"
  29. #include "system/system.h"
  30. #include "qapi/error.h"
  31. #include "trace.h"
  32. #include "dbus.h"
  33. #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8"
  34. static void
  35. dbus_clipboard_complete_request(
  36. DBusDisplay *dpy,
  37. GDBusMethodInvocation *invocation,
  38. QemuClipboardInfo *info,
  39. QemuClipboardType type)
  40. {
  41. GVariant *v_data = g_variant_new_from_data(
  42. G_VARIANT_TYPE("ay"),
  43. info->types[type].data,
  44. info->types[type].size,
  45. TRUE,
  46. (GDestroyNotify)qemu_clipboard_info_unref,
  47. qemu_clipboard_info_ref(info));
  48. qemu_dbus_display1_clipboard_complete_request(
  49. dpy->clipboard, invocation,
  50. MIME_TEXT_PLAIN_UTF8, v_data);
  51. }
  52. static void
  53. dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info)
  54. {
  55. bool self_update = info->owner == &dpy->clipboard_peer;
  56. const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, };
  57. DBusClipboardRequest *req;
  58. int i = 0;
  59. if (info->owner == NULL) {
  60. if (dpy->clipboard_proxy) {
  61. qemu_dbus_display1_clipboard_call_release(
  62. dpy->clipboard_proxy,
  63. info->selection,
  64. G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
  65. }
  66. return;
  67. }
  68. if (self_update || !info->has_serial) {
  69. return;
  70. }
  71. req = &dpy->clipboard_request[info->selection];
  72. if (req->invocation && info->types[req->type].data) {
  73. dbus_clipboard_complete_request(dpy, req->invocation, info, req->type);
  74. g_clear_object(&req->invocation);
  75. g_source_remove(req->timeout_id);
  76. req->timeout_id = 0;
  77. return;
  78. }
  79. if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) {
  80. mime[i++] = MIME_TEXT_PLAIN_UTF8;
  81. }
  82. if (i > 0) {
  83. if (dpy->clipboard_proxy) {
  84. qemu_dbus_display1_clipboard_call_grab(
  85. dpy->clipboard_proxy,
  86. info->selection,
  87. info->serial,
  88. mime,
  89. G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL);
  90. }
  91. }
  92. }
  93. static void
  94. dbus_clipboard_reset_serial(DBusDisplay *dpy)
  95. {
  96. if (dpy->clipboard_proxy) {
  97. qemu_dbus_display1_clipboard_call_register(
  98. dpy->clipboard_proxy,
  99. G_DBUS_CALL_FLAGS_NONE,
  100. -1, NULL, NULL, NULL);
  101. }
  102. }
  103. static void
  104. dbus_clipboard_notify(Notifier *notifier, void *data)
  105. {
  106. DBusDisplay *dpy =
  107. container_of(notifier, DBusDisplay, clipboard_peer.notifier);
  108. QemuClipboardNotify *notify = data;
  109. switch (notify->type) {
  110. case QEMU_CLIPBOARD_UPDATE_INFO:
  111. dbus_clipboard_update_info(dpy, notify->info);
  112. return;
  113. case QEMU_CLIPBOARD_RESET_SERIAL:
  114. dbus_clipboard_reset_serial(dpy);
  115. return;
  116. }
  117. }
  118. static void
  119. dbus_clipboard_qemu_request(QemuClipboardInfo *info,
  120. QemuClipboardType type)
  121. {
  122. DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer);
  123. g_autofree char *mime = NULL;
  124. g_autoptr(GVariant) v_data = NULL;
  125. g_autoptr(GError) err = NULL;
  126. const char *data = NULL;
  127. const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL };
  128. size_t n;
  129. trace_dbus_clipboard_qemu_request(type);
  130. if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
  131. /* unsupported atm */
  132. return;
  133. }
  134. if (dpy->clipboard_proxy) {
  135. if (!qemu_dbus_display1_clipboard_call_request_sync(
  136. dpy->clipboard_proxy,
  137. info->selection,
  138. mimes,
  139. G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
  140. error_report("Failed to request clipboard: %s", err->message);
  141. return;
  142. }
  143. if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
  144. error_report("Unsupported returned MIME: %s", mime);
  145. return;
  146. }
  147. data = g_variant_get_fixed_array(v_data, &n, 1);
  148. qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
  149. n, data, true);
  150. }
  151. }
  152. static void
  153. dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
  154. {
  155. if (!req->invocation) {
  156. return;
  157. }
  158. g_dbus_method_invocation_return_error(
  159. req->invocation,
  160. DBUS_DISPLAY_ERROR,
  161. DBUS_DISPLAY_ERROR_FAILED,
  162. "Cancelled clipboard request");
  163. g_clear_object(&req->invocation);
  164. g_source_remove(req->timeout_id);
  165. req->timeout_id = 0;
  166. }
  167. static void
  168. dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
  169. {
  170. const char *name = NULL;
  171. int i;
  172. for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
  173. dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
  174. }
  175. if (!dpy->clipboard_proxy) {
  176. return;
  177. }
  178. name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
  179. trace_dbus_clipboard_unregister(name);
  180. g_clear_object(&dpy->clipboard_proxy);
  181. }
  182. static gboolean
  183. dbus_clipboard_register(
  184. DBusDisplay *dpy,
  185. GDBusMethodInvocation *invocation)
  186. {
  187. g_autoptr(GError) err = NULL;
  188. const char *name = NULL;
  189. GDBusConnection *connection = g_dbus_method_invocation_get_connection(invocation);
  190. if (dpy->clipboard_proxy) {
  191. g_dbus_method_invocation_return_error(
  192. invocation,
  193. DBUS_DISPLAY_ERROR,
  194. DBUS_DISPLAY_ERROR_FAILED,
  195. "Clipboard peer already registered!");
  196. return DBUS_METHOD_INVOCATION_HANDLED;
  197. }
  198. dpy->clipboard_proxy =
  199. qemu_dbus_display1_clipboard_proxy_new_sync(
  200. connection,
  201. G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
  202. g_dbus_method_invocation_get_sender(invocation),
  203. "/org/qemu/Display1/Clipboard",
  204. NULL,
  205. &err);
  206. if (!dpy->clipboard_proxy) {
  207. g_dbus_method_invocation_return_error(
  208. invocation,
  209. DBUS_DISPLAY_ERROR,
  210. DBUS_DISPLAY_ERROR_FAILED,
  211. "Failed to setup proxy: %s", err->message);
  212. return DBUS_METHOD_INVOCATION_HANDLED;
  213. }
  214. name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
  215. trace_dbus_clipboard_register(name);
  216. g_object_connect(dpy->clipboard_proxy,
  217. "swapped-signal::notify::g-name-owner",
  218. dbus_clipboard_unregister_proxy, dpy,
  219. NULL);
  220. g_object_connect(connection,
  221. "swapped-signal::closed",
  222. dbus_clipboard_unregister_proxy, dpy,
  223. NULL);
  224. qemu_clipboard_reset_serial();
  225. qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
  226. return DBUS_METHOD_INVOCATION_HANDLED;
  227. }
  228. static gboolean
  229. dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
  230. {
  231. if (!dpy->clipboard_proxy ||
  232. g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
  233. g_dbus_method_invocation_get_sender(invocation))) {
  234. g_dbus_method_invocation_return_error(
  235. invocation,
  236. DBUS_DISPLAY_ERROR,
  237. DBUS_DISPLAY_ERROR_FAILED,
  238. "Unregistered caller");
  239. return FALSE;
  240. }
  241. return TRUE;
  242. }
  243. static gboolean
  244. dbus_clipboard_unregister(
  245. DBusDisplay *dpy,
  246. GDBusMethodInvocation *invocation)
  247. {
  248. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  249. return DBUS_METHOD_INVOCATION_HANDLED;
  250. }
  251. dbus_clipboard_unregister_proxy(dpy);
  252. qemu_dbus_display1_clipboard_complete_unregister(
  253. dpy->clipboard, invocation);
  254. return DBUS_METHOD_INVOCATION_HANDLED;
  255. }
  256. static gboolean
  257. dbus_clipboard_grab(
  258. DBusDisplay *dpy,
  259. GDBusMethodInvocation *invocation,
  260. gint arg_selection,
  261. guint arg_serial,
  262. const gchar *const *arg_mimes)
  263. {
  264. QemuClipboardSelection s = arg_selection;
  265. g_autoptr(QemuClipboardInfo) info = NULL;
  266. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  267. return DBUS_METHOD_INVOCATION_HANDLED;
  268. }
  269. trace_dbus_clipboard_grab(arg_selection, arg_serial);
  270. if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
  271. g_dbus_method_invocation_return_error(
  272. invocation,
  273. DBUS_DISPLAY_ERROR,
  274. DBUS_DISPLAY_ERROR_FAILED,
  275. "Invalid clipboard selection: %d", arg_selection);
  276. return DBUS_METHOD_INVOCATION_HANDLED;
  277. }
  278. info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
  279. if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
  280. info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
  281. }
  282. info->serial = arg_serial;
  283. info->has_serial = true;
  284. if (qemu_clipboard_check_serial(info, true)) {
  285. qemu_clipboard_update(info);
  286. } else {
  287. trace_dbus_clipboard_grab_failed();
  288. }
  289. qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
  290. return DBUS_METHOD_INVOCATION_HANDLED;
  291. }
  292. static gboolean
  293. dbus_clipboard_release(
  294. DBusDisplay *dpy,
  295. GDBusMethodInvocation *invocation,
  296. gint arg_selection)
  297. {
  298. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  299. return DBUS_METHOD_INVOCATION_HANDLED;
  300. }
  301. qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
  302. qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
  303. return DBUS_METHOD_INVOCATION_HANDLED;
  304. }
  305. static gboolean
  306. dbus_clipboard_request_timeout(gpointer user_data)
  307. {
  308. dbus_clipboard_request_cancelled(user_data);
  309. return G_SOURCE_REMOVE;
  310. }
  311. static gboolean
  312. dbus_clipboard_request(
  313. DBusDisplay *dpy,
  314. GDBusMethodInvocation *invocation,
  315. gint arg_selection,
  316. const gchar *const *arg_mimes)
  317. {
  318. QemuClipboardSelection s = arg_selection;
  319. QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
  320. QemuClipboardInfo *info = NULL;
  321. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  322. return DBUS_METHOD_INVOCATION_HANDLED;
  323. }
  324. if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
  325. g_dbus_method_invocation_return_error(
  326. invocation,
  327. DBUS_DISPLAY_ERROR,
  328. DBUS_DISPLAY_ERROR_FAILED,
  329. "Invalid clipboard selection: %d", arg_selection);
  330. return DBUS_METHOD_INVOCATION_HANDLED;
  331. }
  332. if (dpy->clipboard_request[s].invocation) {
  333. g_dbus_method_invocation_return_error(
  334. invocation,
  335. DBUS_DISPLAY_ERROR,
  336. DBUS_DISPLAY_ERROR_FAILED,
  337. "Pending request");
  338. return DBUS_METHOD_INVOCATION_HANDLED;
  339. }
  340. info = qemu_clipboard_info(s);
  341. if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
  342. g_dbus_method_invocation_return_error(
  343. invocation,
  344. DBUS_DISPLAY_ERROR,
  345. DBUS_DISPLAY_ERROR_FAILED,
  346. "Empty clipboard");
  347. return DBUS_METHOD_INVOCATION_HANDLED;
  348. }
  349. if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
  350. !info->types[type].available) {
  351. g_dbus_method_invocation_return_error(
  352. invocation,
  353. DBUS_DISPLAY_ERROR,
  354. DBUS_DISPLAY_ERROR_FAILED,
  355. "Unhandled MIME types requested");
  356. return DBUS_METHOD_INVOCATION_HANDLED;
  357. }
  358. if (info->types[type].data) {
  359. dbus_clipboard_complete_request(dpy, invocation, info, type);
  360. } else {
  361. qemu_clipboard_request(info, type);
  362. dpy->clipboard_request[s].invocation = g_object_ref(invocation);
  363. dpy->clipboard_request[s].type = type;
  364. dpy->clipboard_request[s].timeout_id =
  365. g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
  366. &dpy->clipboard_request[s]);
  367. }
  368. return DBUS_METHOD_INVOCATION_HANDLED;
  369. }
  370. void
  371. dbus_clipboard_init(DBusDisplay *dpy)
  372. {
  373. g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
  374. assert(!dpy->clipboard);
  375. clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
  376. dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
  377. g_object_connect(dpy->clipboard,
  378. "swapped-signal::handle-register",
  379. dbus_clipboard_register, dpy,
  380. "swapped-signal::handle-unregister",
  381. dbus_clipboard_unregister, dpy,
  382. "swapped-signal::handle-grab",
  383. dbus_clipboard_grab, dpy,
  384. "swapped-signal::handle-release",
  385. dbus_clipboard_release, dpy,
  386. "swapped-signal::handle-request",
  387. dbus_clipboard_request, dpy,
  388. NULL);
  389. g_dbus_object_skeleton_add_interface(
  390. G_DBUS_OBJECT_SKELETON(clipboard),
  391. G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
  392. g_dbus_object_manager_server_export(dpy->server, clipboard);
  393. dpy->clipboard_peer.name = "dbus";
  394. dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
  395. dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
  396. qemu_clipboard_peer_register(&dpy->clipboard_peer);
  397. }