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 "sysemu/sysemu.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. if (type != QEMU_CLIPBOARD_TYPE_TEXT) {
  130. /* unsupported atm */
  131. return;
  132. }
  133. if (dpy->clipboard_proxy) {
  134. if (!qemu_dbus_display1_clipboard_call_request_sync(
  135. dpy->clipboard_proxy,
  136. info->selection,
  137. mimes,
  138. G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) {
  139. error_report("Failed to request clipboard: %s", err->message);
  140. return;
  141. }
  142. if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) {
  143. error_report("Unsupported returned MIME: %s", mime);
  144. return;
  145. }
  146. data = g_variant_get_fixed_array(v_data, &n, 1);
  147. qemu_clipboard_set_data(&dpy->clipboard_peer, info, type,
  148. n, data, true);
  149. }
  150. }
  151. static void
  152. dbus_clipboard_request_cancelled(DBusClipboardRequest *req)
  153. {
  154. if (!req->invocation) {
  155. return;
  156. }
  157. g_dbus_method_invocation_return_error(
  158. req->invocation,
  159. DBUS_DISPLAY_ERROR,
  160. DBUS_DISPLAY_ERROR_FAILED,
  161. "Cancelled clipboard request");
  162. g_clear_object(&req->invocation);
  163. g_source_remove(req->timeout_id);
  164. req->timeout_id = 0;
  165. }
  166. static void
  167. dbus_clipboard_unregister_proxy(DBusDisplay *dpy)
  168. {
  169. const char *name = NULL;
  170. int i;
  171. for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) {
  172. dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]);
  173. }
  174. if (!dpy->clipboard_proxy) {
  175. return;
  176. }
  177. name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
  178. trace_dbus_clipboard_unregister(name);
  179. g_clear_object(&dpy->clipboard_proxy);
  180. }
  181. static void
  182. dbus_on_clipboard_proxy_name_owner_changed(
  183. DBusDisplay *dpy,
  184. GObject *object,
  185. GParamSpec *pspec)
  186. {
  187. dbus_clipboard_unregister_proxy(dpy);
  188. }
  189. static gboolean
  190. dbus_clipboard_register(
  191. DBusDisplay *dpy,
  192. GDBusMethodInvocation *invocation)
  193. {
  194. g_autoptr(GError) err = NULL;
  195. const char *name = NULL;
  196. if (dpy->clipboard_proxy) {
  197. g_dbus_method_invocation_return_error(
  198. invocation,
  199. DBUS_DISPLAY_ERROR,
  200. DBUS_DISPLAY_ERROR_FAILED,
  201. "Clipboard peer already registered!");
  202. return DBUS_METHOD_INVOCATION_HANDLED;
  203. }
  204. dpy->clipboard_proxy =
  205. qemu_dbus_display1_clipboard_proxy_new_sync(
  206. g_dbus_method_invocation_get_connection(invocation),
  207. G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START,
  208. g_dbus_method_invocation_get_sender(invocation),
  209. "/org/qemu/Display1/Clipboard",
  210. NULL,
  211. &err);
  212. if (!dpy->clipboard_proxy) {
  213. g_dbus_method_invocation_return_error(
  214. invocation,
  215. DBUS_DISPLAY_ERROR,
  216. DBUS_DISPLAY_ERROR_FAILED,
  217. "Failed to setup proxy: %s", err->message);
  218. return DBUS_METHOD_INVOCATION_HANDLED;
  219. }
  220. name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy));
  221. trace_dbus_clipboard_register(name);
  222. g_object_connect(dpy->clipboard_proxy,
  223. "swapped-signal::notify::g-name-owner",
  224. dbus_on_clipboard_proxy_name_owner_changed, dpy,
  225. NULL);
  226. qemu_clipboard_reset_serial();
  227. qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation);
  228. return DBUS_METHOD_INVOCATION_HANDLED;
  229. }
  230. static gboolean
  231. dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation)
  232. {
  233. if (!dpy->clipboard_proxy ||
  234. g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)),
  235. g_dbus_method_invocation_get_sender(invocation))) {
  236. g_dbus_method_invocation_return_error(
  237. invocation,
  238. DBUS_DISPLAY_ERROR,
  239. DBUS_DISPLAY_ERROR_FAILED,
  240. "Unregistered caller");
  241. return FALSE;
  242. }
  243. return TRUE;
  244. }
  245. static gboolean
  246. dbus_clipboard_unregister(
  247. DBusDisplay *dpy,
  248. GDBusMethodInvocation *invocation)
  249. {
  250. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  251. return DBUS_METHOD_INVOCATION_HANDLED;
  252. }
  253. dbus_clipboard_unregister_proxy(dpy);
  254. qemu_dbus_display1_clipboard_complete_unregister(
  255. dpy->clipboard, invocation);
  256. return DBUS_METHOD_INVOCATION_HANDLED;
  257. }
  258. static gboolean
  259. dbus_clipboard_grab(
  260. DBusDisplay *dpy,
  261. GDBusMethodInvocation *invocation,
  262. gint arg_selection,
  263. guint arg_serial,
  264. const gchar *const *arg_mimes)
  265. {
  266. QemuClipboardSelection s = arg_selection;
  267. g_autoptr(QemuClipboardInfo) info = NULL;
  268. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  269. return DBUS_METHOD_INVOCATION_HANDLED;
  270. }
  271. if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
  272. g_dbus_method_invocation_return_error(
  273. invocation,
  274. DBUS_DISPLAY_ERROR,
  275. DBUS_DISPLAY_ERROR_FAILED,
  276. "Invalid clipboard selection: %d", arg_selection);
  277. return DBUS_METHOD_INVOCATION_HANDLED;
  278. }
  279. info = qemu_clipboard_info_new(&dpy->clipboard_peer, s);
  280. if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) {
  281. info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true;
  282. }
  283. info->serial = arg_serial;
  284. info->has_serial = true;
  285. if (qemu_clipboard_check_serial(info, true)) {
  286. qemu_clipboard_update(info);
  287. } else {
  288. trace_dbus_clipboard_grab_failed();
  289. }
  290. qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation);
  291. return DBUS_METHOD_INVOCATION_HANDLED;
  292. }
  293. static gboolean
  294. dbus_clipboard_release(
  295. DBusDisplay *dpy,
  296. GDBusMethodInvocation *invocation,
  297. gint arg_selection)
  298. {
  299. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  300. return DBUS_METHOD_INVOCATION_HANDLED;
  301. }
  302. qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection);
  303. qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation);
  304. return DBUS_METHOD_INVOCATION_HANDLED;
  305. }
  306. static gboolean
  307. dbus_clipboard_request_timeout(gpointer user_data)
  308. {
  309. dbus_clipboard_request_cancelled(user_data);
  310. return G_SOURCE_REMOVE;
  311. }
  312. static gboolean
  313. dbus_clipboard_request(
  314. DBusDisplay *dpy,
  315. GDBusMethodInvocation *invocation,
  316. gint arg_selection,
  317. const gchar *const *arg_mimes)
  318. {
  319. QemuClipboardSelection s = arg_selection;
  320. QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT;
  321. QemuClipboardInfo *info = NULL;
  322. if (!dbus_clipboard_check_caller(dpy, invocation)) {
  323. return DBUS_METHOD_INVOCATION_HANDLED;
  324. }
  325. if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) {
  326. g_dbus_method_invocation_return_error(
  327. invocation,
  328. DBUS_DISPLAY_ERROR,
  329. DBUS_DISPLAY_ERROR_FAILED,
  330. "Invalid clipboard selection: %d", arg_selection);
  331. return DBUS_METHOD_INVOCATION_HANDLED;
  332. }
  333. if (dpy->clipboard_request[s].invocation) {
  334. g_dbus_method_invocation_return_error(
  335. invocation,
  336. DBUS_DISPLAY_ERROR,
  337. DBUS_DISPLAY_ERROR_FAILED,
  338. "Pending request");
  339. return DBUS_METHOD_INVOCATION_HANDLED;
  340. }
  341. info = qemu_clipboard_info(s);
  342. if (!info || !info->owner || info->owner == &dpy->clipboard_peer) {
  343. g_dbus_method_invocation_return_error(
  344. invocation,
  345. DBUS_DISPLAY_ERROR,
  346. DBUS_DISPLAY_ERROR_FAILED,
  347. "Empty clipboard");
  348. return DBUS_METHOD_INVOCATION_HANDLED;
  349. }
  350. if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) ||
  351. !info->types[type].available) {
  352. g_dbus_method_invocation_return_error(
  353. invocation,
  354. DBUS_DISPLAY_ERROR,
  355. DBUS_DISPLAY_ERROR_FAILED,
  356. "Unhandled MIME types requested");
  357. return DBUS_METHOD_INVOCATION_HANDLED;
  358. }
  359. if (info->types[type].data) {
  360. dbus_clipboard_complete_request(dpy, invocation, info, type);
  361. } else {
  362. qemu_clipboard_request(info, type);
  363. dpy->clipboard_request[s].invocation = g_object_ref(invocation);
  364. dpy->clipboard_request[s].type = type;
  365. dpy->clipboard_request[s].timeout_id =
  366. g_timeout_add_seconds(5, dbus_clipboard_request_timeout,
  367. &dpy->clipboard_request[s]);
  368. }
  369. return DBUS_METHOD_INVOCATION_HANDLED;
  370. }
  371. void
  372. dbus_clipboard_init(DBusDisplay *dpy)
  373. {
  374. g_autoptr(GDBusObjectSkeleton) clipboard = NULL;
  375. assert(!dpy->clipboard);
  376. clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard");
  377. dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new();
  378. g_object_connect(dpy->clipboard,
  379. "swapped-signal::handle-register",
  380. dbus_clipboard_register, dpy,
  381. "swapped-signal::handle-unregister",
  382. dbus_clipboard_unregister, dpy,
  383. "swapped-signal::handle-grab",
  384. dbus_clipboard_grab, dpy,
  385. "swapped-signal::handle-release",
  386. dbus_clipboard_release, dpy,
  387. "swapped-signal::handle-request",
  388. dbus_clipboard_request, dpy,
  389. NULL);
  390. g_dbus_object_skeleton_add_interface(
  391. G_DBUS_OBJECT_SKELETON(clipboard),
  392. G_DBUS_INTERFACE_SKELETON(dpy->clipboard));
  393. g_dbus_object_manager_server_export(dpy->server, clipboard);
  394. dpy->clipboard_peer.name = "dbus";
  395. dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify;
  396. dpy->clipboard_peer.request = dbus_clipboard_qemu_request;
  397. qemu_clipboard_peer_register(&dpy->clipboard_peer);
  398. }