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