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 33 #include "dbus.h" 34 35 #define MIME_TEXT_PLAIN_UTF8 "text/plain;charset=utf-8" 36 37 static void 38 dbus_clipboard_complete_request( 39 DBusDisplay *dpy, 40 GDBusMethodInvocation *invocation, 41 QemuClipboardInfo *info, 42 QemuClipboardType type) 43 { 44 GVariant *v_data = g_variant_new_from_data( 45 G_VARIANT_TYPE("ay"), 46 info->types[type].data, 47 info->types[type].size, 48 TRUE, 49 (GDestroyNotify)qemu_clipboard_info_unref, 50 qemu_clipboard_info_ref(info)); 51 52 qemu_dbus_display1_clipboard_complete_request( 53 dpy->clipboard, invocation, 54 MIME_TEXT_PLAIN_UTF8, v_data); 55 } 56 57 static void 58 dbus_clipboard_update_info(DBusDisplay *dpy, QemuClipboardInfo *info) 59 { 60 bool self_update = info->owner == &dpy->clipboard_peer; 61 const char *mime[QEMU_CLIPBOARD_TYPE__COUNT + 1] = { 0, }; 62 DBusClipboardRequest *req; 63 int i = 0; 64 65 if (info->owner == NULL) { 66 if (dpy->clipboard_proxy) { 67 qemu_dbus_display1_clipboard_call_release( 68 dpy->clipboard_proxy, 69 info->selection, 70 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); 71 } 72 return; 73 } 74 75 if (self_update || !info->has_serial) { 76 return; 77 } 78 79 req = &dpy->clipboard_request[info->selection]; 80 if (req->invocation && info->types[req->type].data) { 81 dbus_clipboard_complete_request(dpy, req->invocation, info, req->type); 82 g_clear_object(&req->invocation); 83 g_source_remove(req->timeout_id); 84 req->timeout_id = 0; 85 return; 86 } 87 88 if (info->types[QEMU_CLIPBOARD_TYPE_TEXT].available) { 89 mime[i++] = MIME_TEXT_PLAIN_UTF8; 90 } 91 92 if (i > 0) { 93 if (dpy->clipboard_proxy) { 94 qemu_dbus_display1_clipboard_call_grab( 95 dpy->clipboard_proxy, 96 info->selection, 97 info->serial, 98 mime, 99 G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); 100 } 101 } 102 } 103 104 static void 105 dbus_clipboard_reset_serial(DBusDisplay *dpy) 106 { 107 if (dpy->clipboard_proxy) { 108 qemu_dbus_display1_clipboard_call_register( 109 dpy->clipboard_proxy, 110 G_DBUS_CALL_FLAGS_NONE, 111 -1, NULL, NULL, NULL); 112 } 113 } 114 115 static void 116 dbus_clipboard_notify(Notifier *notifier, void *data) 117 { 118 DBusDisplay *dpy = 119 container_of(notifier, DBusDisplay, clipboard_peer.notifier); 120 QemuClipboardNotify *notify = data; 121 122 switch (notify->type) { 123 case QEMU_CLIPBOARD_UPDATE_INFO: 124 dbus_clipboard_update_info(dpy, notify->info); 125 return; 126 case QEMU_CLIPBOARD_RESET_SERIAL: 127 dbus_clipboard_reset_serial(dpy); 128 return; 129 } 130 } 131 132 static void 133 dbus_clipboard_qemu_request(QemuClipboardInfo *info, 134 QemuClipboardType type) 135 { 136 DBusDisplay *dpy = container_of(info->owner, DBusDisplay, clipboard_peer); 137 g_autofree char *mime = NULL; 138 g_autoptr(GVariant) v_data = NULL; 139 g_autoptr(GError) err = NULL; 140 const char *data = NULL; 141 const char *mimes[] = { MIME_TEXT_PLAIN_UTF8, NULL }; 142 size_t n; 143 144 if (type != QEMU_CLIPBOARD_TYPE_TEXT) { 145 /* unsupported atm */ 146 return; 147 } 148 149 if (dpy->clipboard_proxy) { 150 if (!qemu_dbus_display1_clipboard_call_request_sync( 151 dpy->clipboard_proxy, 152 info->selection, 153 mimes, 154 G_DBUS_CALL_FLAGS_NONE, -1, &mime, &v_data, NULL, &err)) { 155 error_report("Failed to request clipboard: %s", err->message); 156 return; 157 } 158 159 if (g_strcmp0(mime, MIME_TEXT_PLAIN_UTF8)) { 160 error_report("Unsupported returned MIME: %s", mime); 161 return; 162 } 163 164 data = g_variant_get_fixed_array(v_data, &n, 1); 165 qemu_clipboard_set_data(&dpy->clipboard_peer, info, type, 166 n, data, true); 167 } 168 } 169 170 static void 171 dbus_clipboard_request_cancelled(DBusClipboardRequest *req) 172 { 173 if (!req->invocation) { 174 return; 175 } 176 177 g_dbus_method_invocation_return_error( 178 req->invocation, 179 DBUS_DISPLAY_ERROR, 180 DBUS_DISPLAY_ERROR_FAILED, 181 "Cancelled clipboard request"); 182 183 g_clear_object(&req->invocation); 184 g_source_remove(req->timeout_id); 185 req->timeout_id = 0; 186 } 187 188 static void 189 dbus_clipboard_unregister_proxy(DBusDisplay *dpy) 190 { 191 const char *name = NULL; 192 int i; 193 194 for (i = 0; i < G_N_ELEMENTS(dpy->clipboard_request); ++i) { 195 dbus_clipboard_request_cancelled(&dpy->clipboard_request[i]); 196 } 197 198 if (!dpy->clipboard_proxy) { 199 return; 200 } 201 202 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); 203 trace_dbus_clipboard_unregister(name); 204 g_clear_object(&dpy->clipboard_proxy); 205 } 206 207 static gboolean 208 dbus_clipboard_register( 209 DBusDisplay *dpy, 210 GDBusMethodInvocation *invocation) 211 { 212 g_autoptr(GError) err = NULL; 213 const char *name = NULL; 214 GDBusConnection *connection = g_dbus_method_invocation_get_connection(invocation); 215 216 if (dpy->clipboard_proxy) { 217 g_dbus_method_invocation_return_error( 218 invocation, 219 DBUS_DISPLAY_ERROR, 220 DBUS_DISPLAY_ERROR_FAILED, 221 "Clipboard peer already registered!"); 222 return DBUS_METHOD_INVOCATION_HANDLED; 223 } 224 225 dpy->clipboard_proxy = 226 qemu_dbus_display1_clipboard_proxy_new_sync( 227 connection, 228 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, 229 g_dbus_method_invocation_get_sender(invocation), 230 "/org/qemu/Display1/Clipboard", 231 NULL, 232 &err); 233 if (!dpy->clipboard_proxy) { 234 g_dbus_method_invocation_return_error( 235 invocation, 236 DBUS_DISPLAY_ERROR, 237 DBUS_DISPLAY_ERROR_FAILED, 238 "Failed to setup proxy: %s", err->message); 239 return DBUS_METHOD_INVOCATION_HANDLED; 240 } 241 242 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); 243 trace_dbus_clipboard_register(name); 244 245 g_object_connect(dpy->clipboard_proxy, 246 "swapped-signal::notify::g-name-owner", 247 dbus_clipboard_unregister_proxy, dpy, 248 NULL); 249 g_object_connect(connection, 250 "swapped-signal::closed", 251 dbus_clipboard_unregister_proxy, dpy, 252 NULL); 253 qemu_clipboard_reset_serial(); 254 255 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation); 256 return DBUS_METHOD_INVOCATION_HANDLED; 257 } 258 259 static gboolean 260 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation) 261 { 262 if (!dpy->clipboard_proxy || 263 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)), 264 g_dbus_method_invocation_get_sender(invocation))) { 265 g_dbus_method_invocation_return_error( 266 invocation, 267 DBUS_DISPLAY_ERROR, 268 DBUS_DISPLAY_ERROR_FAILED, 269 "Unregistered caller"); 270 return FALSE; 271 } 272 273 return TRUE; 274 } 275 276 static gboolean 277 dbus_clipboard_unregister( 278 DBusDisplay *dpy, 279 GDBusMethodInvocation *invocation) 280 { 281 if (!dbus_clipboard_check_caller(dpy, invocation)) { 282 return DBUS_METHOD_INVOCATION_HANDLED; 283 } 284 285 dbus_clipboard_unregister_proxy(dpy); 286 287 qemu_dbus_display1_clipboard_complete_unregister( 288 dpy->clipboard, invocation); 289 290 return DBUS_METHOD_INVOCATION_HANDLED; 291 } 292 293 static gboolean 294 dbus_clipboard_grab( 295 DBusDisplay *dpy, 296 GDBusMethodInvocation *invocation, 297 gint arg_selection, 298 guint arg_serial, 299 const gchar *const *arg_mimes) 300 { 301 QemuClipboardSelection s = arg_selection; 302 g_autoptr(QemuClipboardInfo) info = NULL; 303 304 if (!dbus_clipboard_check_caller(dpy, invocation)) { 305 return DBUS_METHOD_INVOCATION_HANDLED; 306 } 307 308 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 309 g_dbus_method_invocation_return_error( 310 invocation, 311 DBUS_DISPLAY_ERROR, 312 DBUS_DISPLAY_ERROR_FAILED, 313 "Invalid clipboard selection: %d", arg_selection); 314 return DBUS_METHOD_INVOCATION_HANDLED; 315 } 316 317 info = qemu_clipboard_info_new(&dpy->clipboard_peer, s); 318 if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) { 319 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; 320 } 321 info->serial = arg_serial; 322 info->has_serial = true; 323 if (qemu_clipboard_check_serial(info, true)) { 324 qemu_clipboard_update(info); 325 } else { 326 trace_dbus_clipboard_grab_failed(); 327 } 328 329 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation); 330 return DBUS_METHOD_INVOCATION_HANDLED; 331 } 332 333 static gboolean 334 dbus_clipboard_release( 335 DBusDisplay *dpy, 336 GDBusMethodInvocation *invocation, 337 gint arg_selection) 338 { 339 if (!dbus_clipboard_check_caller(dpy, invocation)) { 340 return DBUS_METHOD_INVOCATION_HANDLED; 341 } 342 343 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection); 344 345 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation); 346 return DBUS_METHOD_INVOCATION_HANDLED; 347 } 348 349 static gboolean 350 dbus_clipboard_request_timeout(gpointer user_data) 351 { 352 dbus_clipboard_request_cancelled(user_data); 353 return G_SOURCE_REMOVE; 354 } 355 356 static gboolean 357 dbus_clipboard_request( 358 DBusDisplay *dpy, 359 GDBusMethodInvocation *invocation, 360 gint arg_selection, 361 const gchar *const *arg_mimes) 362 { 363 QemuClipboardSelection s = arg_selection; 364 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT; 365 QemuClipboardInfo *info = NULL; 366 367 if (!dbus_clipboard_check_caller(dpy, invocation)) { 368 return DBUS_METHOD_INVOCATION_HANDLED; 369 } 370 371 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 372 g_dbus_method_invocation_return_error( 373 invocation, 374 DBUS_DISPLAY_ERROR, 375 DBUS_DISPLAY_ERROR_FAILED, 376 "Invalid clipboard selection: %d", arg_selection); 377 return DBUS_METHOD_INVOCATION_HANDLED; 378 } 379 380 if (dpy->clipboard_request[s].invocation) { 381 g_dbus_method_invocation_return_error( 382 invocation, 383 DBUS_DISPLAY_ERROR, 384 DBUS_DISPLAY_ERROR_FAILED, 385 "Pending request"); 386 return DBUS_METHOD_INVOCATION_HANDLED; 387 } 388 389 info = qemu_clipboard_info(s); 390 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) { 391 g_dbus_method_invocation_return_error( 392 invocation, 393 DBUS_DISPLAY_ERROR, 394 DBUS_DISPLAY_ERROR_FAILED, 395 "Empty clipboard"); 396 return DBUS_METHOD_INVOCATION_HANDLED; 397 } 398 399 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) || 400 !info->types[type].available) { 401 g_dbus_method_invocation_return_error( 402 invocation, 403 DBUS_DISPLAY_ERROR, 404 DBUS_DISPLAY_ERROR_FAILED, 405 "Unhandled MIME types requested"); 406 return DBUS_METHOD_INVOCATION_HANDLED; 407 } 408 409 if (info->types[type].data) { 410 dbus_clipboard_complete_request(dpy, invocation, info, type); 411 } else { 412 qemu_clipboard_request(info, type); 413 414 dpy->clipboard_request[s].invocation = g_object_ref(invocation); 415 dpy->clipboard_request[s].type = type; 416 dpy->clipboard_request[s].timeout_id = 417 g_timeout_add_seconds(5, dbus_clipboard_request_timeout, 418 &dpy->clipboard_request[s]); 419 } 420 421 return DBUS_METHOD_INVOCATION_HANDLED; 422 } 423 424 void 425 dbus_clipboard_init(DBusDisplay *dpy) 426 { 427 g_autoptr(GDBusObjectSkeleton) clipboard = NULL; 428 429 assert(!dpy->clipboard); 430 431 clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard"); 432 dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new(); 433 g_object_connect(dpy->clipboard, 434 "swapped-signal::handle-register", 435 dbus_clipboard_register, dpy, 436 "swapped-signal::handle-unregister", 437 dbus_clipboard_unregister, dpy, 438 "swapped-signal::handle-grab", 439 dbus_clipboard_grab, dpy, 440 "swapped-signal::handle-release", 441 dbus_clipboard_release, dpy, 442 "swapped-signal::handle-request", 443 dbus_clipboard_request, dpy, 444 NULL); 445 446 g_dbus_object_skeleton_add_interface( 447 G_DBUS_OBJECT_SKELETON(clipboard), 448 G_DBUS_INTERFACE_SKELETON(dpy->clipboard)); 449 g_dbus_object_manager_server_export(dpy->server, clipboard); 450 dpy->clipboard_peer.name = "dbus"; 451 dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify; 452 dpy->clipboard_peer.request = dbus_clipboard_qemu_request; 453 qemu_clipboard_peer_register(&dpy->clipboard_peer); 454 } 455