1 /* 2 * QEMU DBus display console 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/error-report.h" 26 #include "qapi/error.h" 27 #include "ui/input.h" 28 #include "ui/kbd-state.h" 29 #include "trace.h" 30 31 #ifdef G_OS_UNIX 32 #include <gio/gunixfdlist.h> 33 #endif 34 35 #include "dbus.h" 36 37 static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX]; 38 39 struct _DBusDisplayConsole { 40 GDBusObjectSkeleton parent_instance; 41 DisplayChangeListener dcl; 42 43 DBusDisplay *display; 44 GHashTable *listeners; 45 QemuDBusDisplay1Console *iface; 46 47 QemuDBusDisplay1Keyboard *iface_kbd; 48 QKbdState *kbd; 49 50 QemuDBusDisplay1Mouse *iface_mouse; 51 QemuDBusDisplay1MultiTouch *iface_touch; 52 gboolean last_set; 53 guint last_x; 54 guint last_y; 55 Notifier mouse_mode_notifier; 56 }; 57 58 G_DEFINE_TYPE(DBusDisplayConsole, 59 dbus_display_console, 60 G_TYPE_DBUS_OBJECT_SKELETON) 61 62 static void 63 dbus_display_console_set_size(DBusDisplayConsole *ddc, 64 uint32_t width, uint32_t height) 65 { 66 g_object_set(ddc->iface, 67 "width", width, 68 "height", height, 69 NULL); 70 } 71 72 static void 73 dbus_gfx_switch(DisplayChangeListener *dcl, 74 struct DisplaySurface *new_surface) 75 { 76 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 77 78 dbus_display_console_set_size(ddc, 79 surface_width(new_surface), 80 surface_height(new_surface)); 81 } 82 83 static void 84 dbus_gfx_update(DisplayChangeListener *dcl, 85 int x, int y, int w, int h) 86 { 87 } 88 89 static void 90 dbus_gl_scanout_disable(DisplayChangeListener *dcl) 91 { 92 } 93 94 static void 95 dbus_gl_scanout_texture(DisplayChangeListener *dcl, 96 uint32_t tex_id, 97 bool backing_y_0_top, 98 uint32_t backing_width, 99 uint32_t backing_height, 100 uint32_t x, uint32_t y, 101 uint32_t w, uint32_t h, 102 void *d3d_tex2d) 103 { 104 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 105 106 dbus_display_console_set_size(ddc, w, h); 107 } 108 109 static void 110 dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl, 111 QemuDmaBuf *dmabuf) 112 { 113 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 114 115 dbus_display_console_set_size(ddc, 116 dmabuf->width, 117 dmabuf->height); 118 } 119 120 static void 121 dbus_gl_scanout_update(DisplayChangeListener *dcl, 122 uint32_t x, uint32_t y, 123 uint32_t w, uint32_t h) 124 { 125 } 126 127 const DisplayChangeListenerOps dbus_console_dcl_ops = { 128 .dpy_name = "dbus-console", 129 .dpy_gfx_switch = dbus_gfx_switch, 130 .dpy_gfx_update = dbus_gfx_update, 131 .dpy_gl_scanout_disable = dbus_gl_scanout_disable, 132 .dpy_gl_scanout_texture = dbus_gl_scanout_texture, 133 .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf, 134 .dpy_gl_update = dbus_gl_scanout_update, 135 }; 136 137 static void 138 dbus_display_console_init(DBusDisplayConsole *object) 139 { 140 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 141 142 ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal, 143 NULL, g_object_unref); 144 ddc->dcl.ops = &dbus_console_dcl_ops; 145 } 146 147 static void 148 dbus_display_console_dispose(GObject *object) 149 { 150 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 151 152 unregister_displaychangelistener(&ddc->dcl); 153 g_clear_object(&ddc->iface_touch); 154 g_clear_object(&ddc->iface_mouse); 155 g_clear_object(&ddc->iface_kbd); 156 g_clear_object(&ddc->iface); 157 g_clear_pointer(&ddc->listeners, g_hash_table_unref); 158 g_clear_pointer(&ddc->kbd, qkbd_state_free); 159 160 G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object); 161 } 162 163 static void 164 dbus_display_console_class_init(DBusDisplayConsoleClass *klass) 165 { 166 GObjectClass *gobject_class = G_OBJECT_CLASS(klass); 167 168 gobject_class->dispose = dbus_display_console_dispose; 169 } 170 171 static void 172 listener_vanished_cb(DBusDisplayListener *listener) 173 { 174 DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener); 175 const char *name = dbus_display_listener_get_bus_name(listener); 176 177 trace_dbus_listener_vanished(name); 178 179 g_hash_table_remove(ddc->listeners, name); 180 qkbd_state_lift_all_keys(ddc->kbd); 181 } 182 183 static gboolean 184 dbus_console_set_ui_info(DBusDisplayConsole *ddc, 185 GDBusMethodInvocation *invocation, 186 guint16 arg_width_mm, 187 guint16 arg_height_mm, 188 gint arg_xoff, 189 gint arg_yoff, 190 guint arg_width, 191 guint arg_height) 192 { 193 QemuUIInfo info = { 194 .width_mm = arg_width_mm, 195 .height_mm = arg_height_mm, 196 .xoff = arg_xoff, 197 .yoff = arg_yoff, 198 .width = arg_width, 199 .height = arg_height, 200 }; 201 202 if (!dpy_ui_info_supported(ddc->dcl.con)) { 203 g_dbus_method_invocation_return_error(invocation, 204 DBUS_DISPLAY_ERROR, 205 DBUS_DISPLAY_ERROR_UNSUPPORTED, 206 "SetUIInfo is not supported"); 207 return DBUS_METHOD_INVOCATION_HANDLED; 208 } 209 210 dpy_set_ui_info(ddc->dcl.con, &info, false); 211 qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation); 212 return DBUS_METHOD_INVOCATION_HANDLED; 213 } 214 215 #ifdef G_OS_WIN32 216 bool 217 dbus_win32_import_socket(GDBusMethodInvocation *invocation, 218 GVariant *arg_listener, int *socket) 219 { 220 gsize n; 221 WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1); 222 223 if (!info || n != sizeof(*info)) { 224 g_dbus_method_invocation_return_error( 225 invocation, 226 DBUS_DISPLAY_ERROR, 227 DBUS_DISPLAY_ERROR_FAILED, 228 "Failed to get socket infos"); 229 return false; 230 } 231 232 *socket = WSASocketW(FROM_PROTOCOL_INFO, 233 FROM_PROTOCOL_INFO, 234 FROM_PROTOCOL_INFO, 235 info, 0, 0); 236 if (*socket == INVALID_SOCKET) { 237 g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError()); 238 g_dbus_method_invocation_return_error( 239 invocation, 240 DBUS_DISPLAY_ERROR, 241 DBUS_DISPLAY_ERROR_FAILED, 242 "Couldn't create socket: %s", emsg); 243 return false; 244 } 245 246 return true; 247 } 248 #endif 249 250 static gboolean 251 dbus_console_register_listener(DBusDisplayConsole *ddc, 252 GDBusMethodInvocation *invocation, 253 #ifdef G_OS_UNIX 254 GUnixFDList *fd_list, 255 #endif 256 GVariant *arg_listener) 257 { 258 const char *sender = g_dbus_method_invocation_get_sender(invocation); 259 GDBusConnection *listener_conn; 260 g_autoptr(GError) err = NULL; 261 g_autoptr(GSocket) socket = NULL; 262 g_autoptr(GSocketConnection) socket_conn = NULL; 263 g_autofree char *guid = g_dbus_generate_guid(); 264 DBusDisplayListener *listener; 265 int fd; 266 267 if (sender && g_hash_table_contains(ddc->listeners, sender)) { 268 g_dbus_method_invocation_return_error( 269 invocation, 270 DBUS_DISPLAY_ERROR, 271 DBUS_DISPLAY_ERROR_INVALID, 272 "`%s` is already registered!", 273 sender); 274 return DBUS_METHOD_INVOCATION_HANDLED; 275 } 276 277 #ifdef G_OS_WIN32 278 if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) { 279 return DBUS_METHOD_INVOCATION_HANDLED; 280 } 281 #else 282 fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); 283 if (err) { 284 g_dbus_method_invocation_return_error( 285 invocation, 286 DBUS_DISPLAY_ERROR, 287 DBUS_DISPLAY_ERROR_FAILED, 288 "Couldn't get peer fd: %s", err->message); 289 return DBUS_METHOD_INVOCATION_HANDLED; 290 } 291 #endif 292 293 socket = g_socket_new_from_fd(fd, &err); 294 if (err) { 295 g_dbus_method_invocation_return_error( 296 invocation, 297 DBUS_DISPLAY_ERROR, 298 DBUS_DISPLAY_ERROR_FAILED, 299 "Couldn't make a socket: %s", err->message); 300 #ifdef G_OS_WIN32 301 closesocket(fd); 302 #else 303 close(fd); 304 #endif 305 return DBUS_METHOD_INVOCATION_HANDLED; 306 } 307 socket_conn = g_socket_connection_factory_create_connection(socket); 308 309 qemu_dbus_display1_console_complete_register_listener( 310 ddc->iface, invocation 311 #ifdef G_OS_UNIX 312 , NULL 313 #endif 314 ); 315 316 listener_conn = g_dbus_connection_new_sync( 317 G_IO_STREAM(socket_conn), 318 guid, 319 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, 320 NULL, NULL, &err); 321 if (err) { 322 error_report("Failed to setup peer connection: %s", err->message); 323 return DBUS_METHOD_INVOCATION_HANDLED; 324 } 325 326 listener = dbus_display_listener_new(sender, listener_conn, ddc); 327 if (!listener) { 328 return DBUS_METHOD_INVOCATION_HANDLED; 329 } 330 331 g_hash_table_insert(ddc->listeners, 332 (gpointer)dbus_display_listener_get_bus_name(listener), 333 listener); 334 g_object_connect(listener_conn, 335 "swapped-signal::closed", listener_vanished_cb, listener, 336 NULL); 337 338 trace_dbus_registered_listener(sender); 339 return DBUS_METHOD_INVOCATION_HANDLED; 340 } 341 342 static gboolean 343 dbus_kbd_press(DBusDisplayConsole *ddc, 344 GDBusMethodInvocation *invocation, 345 guint arg_keycode) 346 { 347 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 348 349 trace_dbus_kbd_press(arg_keycode); 350 351 qkbd_state_key_event(ddc->kbd, qcode, true); 352 353 qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation); 354 355 return DBUS_METHOD_INVOCATION_HANDLED; 356 } 357 358 static gboolean 359 dbus_kbd_release(DBusDisplayConsole *ddc, 360 GDBusMethodInvocation *invocation, 361 guint arg_keycode) 362 { 363 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 364 365 trace_dbus_kbd_release(arg_keycode); 366 367 qkbd_state_key_event(ddc->kbd, qcode, false); 368 369 qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation); 370 371 return DBUS_METHOD_INVOCATION_HANDLED; 372 } 373 374 static void 375 dbus_kbd_qemu_leds_updated(void *data, int ledstate) 376 { 377 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data); 378 379 qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate); 380 } 381 382 static gboolean 383 dbus_mouse_rel_motion(DBusDisplayConsole *ddc, 384 GDBusMethodInvocation *invocation, 385 int dx, int dy) 386 { 387 trace_dbus_mouse_rel_motion(dx, dy); 388 389 if (qemu_input_is_absolute(ddc->dcl.con)) { 390 g_dbus_method_invocation_return_error( 391 invocation, DBUS_DISPLAY_ERROR, 392 DBUS_DISPLAY_ERROR_INVALID, 393 "Mouse is not relative"); 394 return DBUS_METHOD_INVOCATION_HANDLED; 395 } 396 397 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx); 398 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy); 399 qemu_input_event_sync(); 400 401 qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse, 402 invocation); 403 404 return DBUS_METHOD_INVOCATION_HANDLED; 405 } 406 407 static gboolean 408 dbus_touch_send_event(DBusDisplayConsole *ddc, 409 GDBusMethodInvocation *invocation, 410 guint kind, uint64_t num_slot, 411 double x, double y) 412 { 413 Error *error = NULL; 414 int width, height; 415 trace_dbus_touch_send_event(kind, num_slot, x, y); 416 417 if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN && 418 kind != INPUT_MULTI_TOUCH_TYPE_UPDATE && 419 kind != INPUT_MULTI_TOUCH_TYPE_CANCEL && 420 kind != INPUT_MULTI_TOUCH_TYPE_END) 421 { 422 g_dbus_method_invocation_return_error( 423 invocation, DBUS_DISPLAY_ERROR, 424 DBUS_DISPLAY_ERROR_INVALID, 425 "Invalid touch event kind"); 426 return DBUS_METHOD_INVOCATION_HANDLED; 427 } 428 width = qemu_console_get_width(ddc->dcl.con, 0); 429 height = qemu_console_get_height(ddc->dcl.con, 0); 430 431 console_handle_touch_event(ddc->dcl.con, touch_slots, 432 num_slot, width, height, 433 x, y, kind, &error); 434 if (error != NULL) { 435 g_dbus_method_invocation_return_error( 436 invocation, DBUS_DISPLAY_ERROR, 437 DBUS_DISPLAY_ERROR_INVALID, 438 error_get_pretty(error), NULL); 439 error_free(error); 440 } else { 441 qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch, 442 invocation); 443 } 444 return DBUS_METHOD_INVOCATION_HANDLED; 445 } 446 447 static gboolean 448 dbus_mouse_set_pos(DBusDisplayConsole *ddc, 449 GDBusMethodInvocation *invocation, 450 guint x, guint y) 451 { 452 int width, height; 453 454 trace_dbus_mouse_set_pos(x, y); 455 456 if (!qemu_input_is_absolute(ddc->dcl.con)) { 457 g_dbus_method_invocation_return_error( 458 invocation, DBUS_DISPLAY_ERROR, 459 DBUS_DISPLAY_ERROR_INVALID, 460 "Mouse is not absolute"); 461 return DBUS_METHOD_INVOCATION_HANDLED; 462 } 463 464 width = qemu_console_get_width(ddc->dcl.con, 0); 465 height = qemu_console_get_height(ddc->dcl.con, 0); 466 if (x >= width || y >= height) { 467 g_dbus_method_invocation_return_error( 468 invocation, DBUS_DISPLAY_ERROR, 469 DBUS_DISPLAY_ERROR_INVALID, 470 "Invalid mouse position"); 471 return DBUS_METHOD_INVOCATION_HANDLED; 472 } 473 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width); 474 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height); 475 qemu_input_event_sync(); 476 477 qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse, 478 invocation); 479 480 return DBUS_METHOD_INVOCATION_HANDLED; 481 } 482 483 static gboolean 484 dbus_mouse_press(DBusDisplayConsole *ddc, 485 GDBusMethodInvocation *invocation, 486 guint button) 487 { 488 trace_dbus_mouse_press(button); 489 490 qemu_input_queue_btn(ddc->dcl.con, button, true); 491 qemu_input_event_sync(); 492 493 qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation); 494 495 return DBUS_METHOD_INVOCATION_HANDLED; 496 } 497 498 static gboolean 499 dbus_mouse_release(DBusDisplayConsole *ddc, 500 GDBusMethodInvocation *invocation, 501 guint button) 502 { 503 trace_dbus_mouse_release(button); 504 505 qemu_input_queue_btn(ddc->dcl.con, button, false); 506 qemu_input_event_sync(); 507 508 qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation); 509 510 return DBUS_METHOD_INVOCATION_HANDLED; 511 } 512 513 static void 514 dbus_mouse_update_is_absolute(DBusDisplayConsole *ddc) 515 { 516 g_object_set(ddc->iface_mouse, 517 "is-absolute", qemu_input_is_absolute(ddc->dcl.con), 518 NULL); 519 } 520 521 static void 522 dbus_mouse_mode_change(Notifier *notify, void *data) 523 { 524 DBusDisplayConsole *ddc = 525 container_of(notify, DBusDisplayConsole, mouse_mode_notifier); 526 527 dbus_mouse_update_is_absolute(ddc); 528 } 529 530 int dbus_display_console_get_index(DBusDisplayConsole *ddc) 531 { 532 return qemu_console_get_index(ddc->dcl.con); 533 } 534 535 DBusDisplayConsole * 536 dbus_display_console_new(DBusDisplay *display, QemuConsole *con) 537 { 538 g_autofree char *path = NULL; 539 g_autofree char *label = NULL; 540 char device_addr[256] = ""; 541 DBusDisplayConsole *ddc; 542 int idx, i; 543 const char *interfaces[] = { 544 "org.qemu.Display1.Keyboard", 545 "org.qemu.Display1.Mouse", 546 "org.qemu.Display1.MultiTouch", 547 NULL 548 }; 549 550 assert(display); 551 assert(con); 552 553 label = qemu_console_get_label(con); 554 idx = qemu_console_get_index(con); 555 path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx); 556 ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE, 557 "g-object-path", path, 558 NULL); 559 ddc->display = display; 560 ddc->dcl.con = con; 561 /* handle errors, and skip non graphics? */ 562 qemu_console_fill_device_address( 563 con, device_addr, sizeof(device_addr), NULL); 564 565 ddc->iface = qemu_dbus_display1_console_skeleton_new(); 566 g_object_set(ddc->iface, 567 "label", label, 568 "type", qemu_console_is_graphic(con) ? "Graphic" : "Text", 569 "head", qemu_console_get_head(con), 570 "width", qemu_console_get_width(con, 0), 571 "height", qemu_console_get_height(con, 0), 572 "device-address", device_addr, 573 "interfaces", interfaces, 574 NULL); 575 g_object_connect(ddc->iface, 576 "swapped-signal::handle-register-listener", 577 dbus_console_register_listener, ddc, 578 "swapped-signal::handle-set-uiinfo", 579 dbus_console_set_ui_info, ddc, 580 NULL); 581 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 582 G_DBUS_INTERFACE_SKELETON(ddc->iface)); 583 584 ddc->kbd = qkbd_state_init(con); 585 ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new(); 586 qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc); 587 g_object_connect(ddc->iface_kbd, 588 "swapped-signal::handle-press", dbus_kbd_press, ddc, 589 "swapped-signal::handle-release", dbus_kbd_release, ddc, 590 NULL); 591 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 592 G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd)); 593 594 ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new(); 595 g_object_connect(ddc->iface_mouse, 596 "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc, 597 "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc, 598 "swapped-signal::handle-press", dbus_mouse_press, ddc, 599 "swapped-signal::handle-release", dbus_mouse_release, ddc, 600 NULL); 601 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 602 G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); 603 604 ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new(); 605 g_object_connect(ddc->iface_touch, 606 "swapped-signal::handle-send-event", dbus_touch_send_event, ddc, 607 NULL); 608 qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch, 609 INPUT_EVENT_SLOTS_MAX); 610 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 611 G_DBUS_INTERFACE_SKELETON(ddc->iface_touch)); 612 613 for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { 614 struct touch_slot *slot = &touch_slots[i]; 615 slot->tracking_id = -1; 616 } 617 618 register_displaychangelistener(&ddc->dcl); 619 ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; 620 qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); 621 dbus_mouse_update_is_absolute(ddc); 622 623 return ddc; 624 } 625