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 void 208 dbus_on_clipboard_proxy_name_owner_changed( 209 DBusDisplay *dpy, 210 GObject *object, 211 GParamSpec *pspec) 212 { 213 dbus_clipboard_unregister_proxy(dpy); 214 } 215 216 static gboolean 217 dbus_clipboard_register( 218 DBusDisplay *dpy, 219 GDBusMethodInvocation *invocation) 220 { 221 g_autoptr(GError) err = NULL; 222 const char *name = NULL; 223 224 if (dpy->clipboard_proxy) { 225 g_dbus_method_invocation_return_error( 226 invocation, 227 DBUS_DISPLAY_ERROR, 228 DBUS_DISPLAY_ERROR_FAILED, 229 "Clipboard peer already registered!"); 230 return DBUS_METHOD_INVOCATION_HANDLED; 231 } 232 233 dpy->clipboard_proxy = 234 qemu_dbus_display1_clipboard_proxy_new_sync( 235 g_dbus_method_invocation_get_connection(invocation), 236 G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, 237 g_dbus_method_invocation_get_sender(invocation), 238 "/org/qemu/Display1/Clipboard", 239 NULL, 240 &err); 241 if (!dpy->clipboard_proxy) { 242 g_dbus_method_invocation_return_error( 243 invocation, 244 DBUS_DISPLAY_ERROR, 245 DBUS_DISPLAY_ERROR_FAILED, 246 "Failed to setup proxy: %s", err->message); 247 return DBUS_METHOD_INVOCATION_HANDLED; 248 } 249 250 name = g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)); 251 trace_dbus_clipboard_register(name); 252 253 g_object_connect(dpy->clipboard_proxy, 254 "swapped-signal::notify::g-name-owner", 255 dbus_on_clipboard_proxy_name_owner_changed, dpy, 256 NULL); 257 qemu_clipboard_reset_serial(); 258 259 qemu_dbus_display1_clipboard_complete_register(dpy->clipboard, invocation); 260 return DBUS_METHOD_INVOCATION_HANDLED; 261 } 262 263 static gboolean 264 dbus_clipboard_check_caller(DBusDisplay *dpy, GDBusMethodInvocation *invocation) 265 { 266 if (!dpy->clipboard_proxy || 267 g_strcmp0(g_dbus_proxy_get_name(G_DBUS_PROXY(dpy->clipboard_proxy)), 268 g_dbus_method_invocation_get_sender(invocation))) { 269 g_dbus_method_invocation_return_error( 270 invocation, 271 DBUS_DISPLAY_ERROR, 272 DBUS_DISPLAY_ERROR_FAILED, 273 "Unregistered caller"); 274 return FALSE; 275 } 276 277 return TRUE; 278 } 279 280 static gboolean 281 dbus_clipboard_unregister( 282 DBusDisplay *dpy, 283 GDBusMethodInvocation *invocation) 284 { 285 if (!dbus_clipboard_check_caller(dpy, invocation)) { 286 return DBUS_METHOD_INVOCATION_HANDLED; 287 } 288 289 dbus_clipboard_unregister_proxy(dpy); 290 291 qemu_dbus_display1_clipboard_complete_unregister( 292 dpy->clipboard, invocation); 293 294 return DBUS_METHOD_INVOCATION_HANDLED; 295 } 296 297 static gboolean 298 dbus_clipboard_grab( 299 DBusDisplay *dpy, 300 GDBusMethodInvocation *invocation, 301 gint arg_selection, 302 guint arg_serial, 303 const gchar *const *arg_mimes) 304 { 305 QemuClipboardSelection s = arg_selection; 306 g_autoptr(QemuClipboardInfo) info = NULL; 307 308 if (!dbus_clipboard_check_caller(dpy, invocation)) { 309 return DBUS_METHOD_INVOCATION_HANDLED; 310 } 311 312 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 313 g_dbus_method_invocation_return_error( 314 invocation, 315 DBUS_DISPLAY_ERROR, 316 DBUS_DISPLAY_ERROR_FAILED, 317 "Invalid clipboard selection: %d", arg_selection); 318 return DBUS_METHOD_INVOCATION_HANDLED; 319 } 320 321 info = qemu_clipboard_info_new(&dpy->clipboard_peer, s); 322 if (g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8)) { 323 info->types[QEMU_CLIPBOARD_TYPE_TEXT].available = true; 324 } 325 info->serial = arg_serial; 326 info->has_serial = true; 327 if (qemu_clipboard_check_serial(info, true)) { 328 qemu_clipboard_update(info); 329 } else { 330 trace_dbus_clipboard_grab_failed(); 331 } 332 333 qemu_dbus_display1_clipboard_complete_grab(dpy->clipboard, invocation); 334 return DBUS_METHOD_INVOCATION_HANDLED; 335 } 336 337 static gboolean 338 dbus_clipboard_release( 339 DBusDisplay *dpy, 340 GDBusMethodInvocation *invocation, 341 gint arg_selection) 342 { 343 if (!dbus_clipboard_check_caller(dpy, invocation)) { 344 return DBUS_METHOD_INVOCATION_HANDLED; 345 } 346 347 qemu_clipboard_peer_release(&dpy->clipboard_peer, arg_selection); 348 349 qemu_dbus_display1_clipboard_complete_release(dpy->clipboard, invocation); 350 return DBUS_METHOD_INVOCATION_HANDLED; 351 } 352 353 static gboolean 354 dbus_clipboard_request_timeout(gpointer user_data) 355 { 356 dbus_clipboard_request_cancelled(user_data); 357 return G_SOURCE_REMOVE; 358 } 359 360 static gboolean 361 dbus_clipboard_request( 362 DBusDisplay *dpy, 363 GDBusMethodInvocation *invocation, 364 gint arg_selection, 365 const gchar *const *arg_mimes) 366 { 367 QemuClipboardSelection s = arg_selection; 368 QemuClipboardType type = QEMU_CLIPBOARD_TYPE_TEXT; 369 QemuClipboardInfo *info = NULL; 370 371 if (!dbus_clipboard_check_caller(dpy, invocation)) { 372 return DBUS_METHOD_INVOCATION_HANDLED; 373 } 374 375 if (s >= QEMU_CLIPBOARD_SELECTION__COUNT) { 376 g_dbus_method_invocation_return_error( 377 invocation, 378 DBUS_DISPLAY_ERROR, 379 DBUS_DISPLAY_ERROR_FAILED, 380 "Invalid clipboard selection: %d", arg_selection); 381 return DBUS_METHOD_INVOCATION_HANDLED; 382 } 383 384 if (dpy->clipboard_request[s].invocation) { 385 g_dbus_method_invocation_return_error( 386 invocation, 387 DBUS_DISPLAY_ERROR, 388 DBUS_DISPLAY_ERROR_FAILED, 389 "Pending request"); 390 return DBUS_METHOD_INVOCATION_HANDLED; 391 } 392 393 info = qemu_clipboard_info(s); 394 if (!info || !info->owner || info->owner == &dpy->clipboard_peer) { 395 g_dbus_method_invocation_return_error( 396 invocation, 397 DBUS_DISPLAY_ERROR, 398 DBUS_DISPLAY_ERROR_FAILED, 399 "Empty clipboard"); 400 return DBUS_METHOD_INVOCATION_HANDLED; 401 } 402 403 if (!g_strv_contains(arg_mimes, MIME_TEXT_PLAIN_UTF8) || 404 !info->types[type].available) { 405 g_dbus_method_invocation_return_error( 406 invocation, 407 DBUS_DISPLAY_ERROR, 408 DBUS_DISPLAY_ERROR_FAILED, 409 "Unhandled MIME types requested"); 410 return DBUS_METHOD_INVOCATION_HANDLED; 411 } 412 413 if (info->types[type].data) { 414 dbus_clipboard_complete_request(dpy, invocation, info, type); 415 } else { 416 qemu_clipboard_request(info, type); 417 418 dpy->clipboard_request[s].invocation = g_object_ref(invocation); 419 dpy->clipboard_request[s].type = type; 420 dpy->clipboard_request[s].timeout_id = 421 g_timeout_add_seconds(5, dbus_clipboard_request_timeout, 422 &dpy->clipboard_request[s]); 423 } 424 425 return DBUS_METHOD_INVOCATION_HANDLED; 426 } 427 428 void 429 dbus_clipboard_init(DBusDisplay *dpy) 430 { 431 g_autoptr(GDBusObjectSkeleton) clipboard = NULL; 432 433 assert(!dpy->clipboard); 434 435 clipboard = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Clipboard"); 436 dpy->clipboard = qemu_dbus_display1_clipboard_skeleton_new(); 437 g_object_connect(dpy->clipboard, 438 "swapped-signal::handle-register", 439 dbus_clipboard_register, dpy, 440 "swapped-signal::handle-unregister", 441 dbus_clipboard_unregister, dpy, 442 "swapped-signal::handle-grab", 443 dbus_clipboard_grab, dpy, 444 "swapped-signal::handle-release", 445 dbus_clipboard_release, dpy, 446 "swapped-signal::handle-request", 447 dbus_clipboard_request, dpy, 448 NULL); 449 450 g_dbus_object_skeleton_add_interface( 451 G_DBUS_OBJECT_SKELETON(clipboard), 452 G_DBUS_INTERFACE_SKELETON(dpy->clipboard)); 453 g_dbus_object_manager_server_export(dpy->server, clipboard); 454 dpy->clipboard_peer.name = "dbus"; 455 dpy->clipboard_peer.notifier.notify = dbus_clipboard_notify; 456 dpy->clipboard_peer.request = dbus_clipboard_qemu_request; 457 qemu_clipboard_peer_register(&dpy->clipboard_peer); 458 } 459