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 #include <gio/gunixfdlist.h> 32 33 #include "dbus.h" 34 35 struct _DBusDisplayConsole { 36 GDBusObjectSkeleton parent_instance; 37 DisplayChangeListener dcl; 38 39 DBusDisplay *display; 40 GHashTable *listeners; 41 QemuDBusDisplay1Console *iface; 42 43 QemuDBusDisplay1Keyboard *iface_kbd; 44 QKbdState *kbd; 45 46 QemuDBusDisplay1Mouse *iface_mouse; 47 gboolean last_set; 48 guint last_x; 49 guint last_y; 50 Notifier mouse_mode_notifier; 51 }; 52 53 G_DEFINE_TYPE(DBusDisplayConsole, 54 dbus_display_console, 55 G_TYPE_DBUS_OBJECT_SKELETON) 56 57 static void 58 dbus_display_console_set_size(DBusDisplayConsole *ddc, 59 uint32_t width, uint32_t height) 60 { 61 g_object_set(ddc->iface, 62 "width", width, 63 "height", height, 64 NULL); 65 } 66 67 static void 68 dbus_gfx_switch(DisplayChangeListener *dcl, 69 struct DisplaySurface *new_surface) 70 { 71 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 72 73 dbus_display_console_set_size(ddc, 74 surface_width(new_surface), 75 surface_height(new_surface)); 76 } 77 78 static void 79 dbus_gfx_update(DisplayChangeListener *dcl, 80 int x, int y, int w, int h) 81 { 82 } 83 84 static void 85 dbus_gl_scanout_disable(DisplayChangeListener *dcl) 86 { 87 } 88 89 static void 90 dbus_gl_scanout_texture(DisplayChangeListener *dcl, 91 uint32_t tex_id, 92 bool backing_y_0_top, 93 uint32_t backing_width, 94 uint32_t backing_height, 95 uint32_t x, uint32_t y, 96 uint32_t w, uint32_t h) 97 { 98 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 99 100 dbus_display_console_set_size(ddc, w, h); 101 } 102 103 static void 104 dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl, 105 QemuDmaBuf *dmabuf) 106 { 107 DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); 108 109 dbus_display_console_set_size(ddc, 110 dmabuf->width, 111 dmabuf->height); 112 } 113 114 static void 115 dbus_gl_scanout_update(DisplayChangeListener *dcl, 116 uint32_t x, uint32_t y, 117 uint32_t w, uint32_t h) 118 { 119 } 120 121 const DisplayChangeListenerOps dbus_console_dcl_ops = { 122 .dpy_name = "dbus-console", 123 .dpy_gfx_switch = dbus_gfx_switch, 124 .dpy_gfx_update = dbus_gfx_update, 125 .dpy_gl_scanout_disable = dbus_gl_scanout_disable, 126 .dpy_gl_scanout_texture = dbus_gl_scanout_texture, 127 .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf, 128 .dpy_gl_update = dbus_gl_scanout_update, 129 }; 130 131 static void 132 dbus_display_console_init(DBusDisplayConsole *object) 133 { 134 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 135 136 ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal, 137 NULL, g_object_unref); 138 ddc->dcl.ops = &dbus_console_dcl_ops; 139 } 140 141 static void 142 dbus_display_console_dispose(GObject *object) 143 { 144 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); 145 146 unregister_displaychangelistener(&ddc->dcl); 147 g_clear_object(&ddc->iface_kbd); 148 g_clear_object(&ddc->iface); 149 g_clear_pointer(&ddc->listeners, g_hash_table_unref); 150 g_clear_pointer(&ddc->kbd, qkbd_state_free); 151 152 G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object); 153 } 154 155 static void 156 dbus_display_console_class_init(DBusDisplayConsoleClass *klass) 157 { 158 GObjectClass *gobject_class = G_OBJECT_CLASS(klass); 159 160 gobject_class->dispose = dbus_display_console_dispose; 161 } 162 163 static void 164 listener_vanished_cb(DBusDisplayListener *listener) 165 { 166 DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener); 167 const char *name = dbus_display_listener_get_bus_name(listener); 168 169 trace_dbus_listener_vanished(name); 170 171 g_hash_table_remove(ddc->listeners, name); 172 qkbd_state_lift_all_keys(ddc->kbd); 173 } 174 175 static gboolean 176 dbus_console_set_ui_info(DBusDisplayConsole *ddc, 177 GDBusMethodInvocation *invocation, 178 guint16 arg_width_mm, 179 guint16 arg_height_mm, 180 gint arg_xoff, 181 gint arg_yoff, 182 guint arg_width, 183 guint arg_height) 184 { 185 QemuUIInfo info = { 186 .width_mm = arg_width_mm, 187 .height_mm = arg_height_mm, 188 .xoff = arg_xoff, 189 .yoff = arg_yoff, 190 .width = arg_width, 191 .height = arg_height, 192 }; 193 194 if (!dpy_ui_info_supported(ddc->dcl.con)) { 195 g_dbus_method_invocation_return_error(invocation, 196 DBUS_DISPLAY_ERROR, 197 DBUS_DISPLAY_ERROR_UNSUPPORTED, 198 "SetUIInfo is not supported"); 199 return DBUS_METHOD_INVOCATION_HANDLED; 200 } 201 202 dpy_set_ui_info(ddc->dcl.con, &info, false); 203 qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation); 204 return DBUS_METHOD_INVOCATION_HANDLED; 205 } 206 207 static gboolean 208 dbus_console_register_listener(DBusDisplayConsole *ddc, 209 GDBusMethodInvocation *invocation, 210 GUnixFDList *fd_list, 211 GVariant *arg_listener) 212 { 213 const char *sender = g_dbus_method_invocation_get_sender(invocation); 214 GDBusConnection *listener_conn; 215 g_autoptr(GError) err = NULL; 216 g_autoptr(GSocket) socket = NULL; 217 g_autoptr(GSocketConnection) socket_conn = NULL; 218 g_autofree char *guid = g_dbus_generate_guid(); 219 DBusDisplayListener *listener; 220 int fd; 221 222 if (sender && g_hash_table_contains(ddc->listeners, sender)) { 223 g_dbus_method_invocation_return_error( 224 invocation, 225 DBUS_DISPLAY_ERROR, 226 DBUS_DISPLAY_ERROR_INVALID, 227 "`%s` is already registered!", 228 sender); 229 return DBUS_METHOD_INVOCATION_HANDLED; 230 } 231 232 fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); 233 if (err) { 234 g_dbus_method_invocation_return_error( 235 invocation, 236 DBUS_DISPLAY_ERROR, 237 DBUS_DISPLAY_ERROR_FAILED, 238 "Couldn't get peer fd: %s", err->message); 239 return DBUS_METHOD_INVOCATION_HANDLED; 240 } 241 242 socket = g_socket_new_from_fd(fd, &err); 243 if (err) { 244 g_dbus_method_invocation_return_error( 245 invocation, 246 DBUS_DISPLAY_ERROR, 247 DBUS_DISPLAY_ERROR_FAILED, 248 "Couldn't make a socket: %s", err->message); 249 close(fd); 250 return DBUS_METHOD_INVOCATION_HANDLED; 251 } 252 socket_conn = g_socket_connection_factory_create_connection(socket); 253 254 qemu_dbus_display1_console_complete_register_listener( 255 ddc->iface, invocation, NULL); 256 257 listener_conn = g_dbus_connection_new_sync( 258 G_IO_STREAM(socket_conn), 259 guid, 260 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, 261 NULL, NULL, &err); 262 if (err) { 263 error_report("Failed to setup peer connection: %s", err->message); 264 return DBUS_METHOD_INVOCATION_HANDLED; 265 } 266 267 listener = dbus_display_listener_new(sender, listener_conn, ddc); 268 if (!listener) { 269 return DBUS_METHOD_INVOCATION_HANDLED; 270 } 271 272 g_hash_table_insert(ddc->listeners, 273 (gpointer)dbus_display_listener_get_bus_name(listener), 274 listener); 275 g_object_connect(listener_conn, 276 "swapped-signal::closed", listener_vanished_cb, listener, 277 NULL); 278 279 trace_dbus_registered_listener(sender); 280 return DBUS_METHOD_INVOCATION_HANDLED; 281 } 282 283 static gboolean 284 dbus_kbd_press(DBusDisplayConsole *ddc, 285 GDBusMethodInvocation *invocation, 286 guint arg_keycode) 287 { 288 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 289 290 trace_dbus_kbd_press(arg_keycode); 291 292 qkbd_state_key_event(ddc->kbd, qcode, true); 293 294 qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation); 295 296 return DBUS_METHOD_INVOCATION_HANDLED; 297 } 298 299 static gboolean 300 dbus_kbd_release(DBusDisplayConsole *ddc, 301 GDBusMethodInvocation *invocation, 302 guint arg_keycode) 303 { 304 QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); 305 306 trace_dbus_kbd_release(arg_keycode); 307 308 qkbd_state_key_event(ddc->kbd, qcode, false); 309 310 qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation); 311 312 return DBUS_METHOD_INVOCATION_HANDLED; 313 } 314 315 static void 316 dbus_kbd_qemu_leds_updated(void *data, int ledstate) 317 { 318 DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data); 319 320 qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate); 321 } 322 323 static gboolean 324 dbus_mouse_rel_motion(DBusDisplayConsole *ddc, 325 GDBusMethodInvocation *invocation, 326 int dx, int dy) 327 { 328 trace_dbus_mouse_rel_motion(dx, dy); 329 330 if (qemu_input_is_absolute()) { 331 g_dbus_method_invocation_return_error( 332 invocation, DBUS_DISPLAY_ERROR, 333 DBUS_DISPLAY_ERROR_INVALID, 334 "Mouse is not relative"); 335 return DBUS_METHOD_INVOCATION_HANDLED; 336 } 337 338 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx); 339 qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy); 340 qemu_input_event_sync(); 341 342 qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse, 343 invocation); 344 345 return DBUS_METHOD_INVOCATION_HANDLED; 346 } 347 348 static gboolean 349 dbus_mouse_set_pos(DBusDisplayConsole *ddc, 350 GDBusMethodInvocation *invocation, 351 guint x, guint y) 352 { 353 int width, height; 354 355 trace_dbus_mouse_set_pos(x, y); 356 357 if (!qemu_input_is_absolute()) { 358 g_dbus_method_invocation_return_error( 359 invocation, DBUS_DISPLAY_ERROR, 360 DBUS_DISPLAY_ERROR_INVALID, 361 "Mouse is not absolute"); 362 return DBUS_METHOD_INVOCATION_HANDLED; 363 } 364 365 width = qemu_console_get_width(ddc->dcl.con, 0); 366 height = qemu_console_get_height(ddc->dcl.con, 0); 367 if (x >= width || y >= height) { 368 g_dbus_method_invocation_return_error( 369 invocation, DBUS_DISPLAY_ERROR, 370 DBUS_DISPLAY_ERROR_INVALID, 371 "Invalid mouse position"); 372 return DBUS_METHOD_INVOCATION_HANDLED; 373 } 374 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width); 375 qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height); 376 qemu_input_event_sync(); 377 378 qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse, 379 invocation); 380 381 return DBUS_METHOD_INVOCATION_HANDLED; 382 } 383 384 static gboolean 385 dbus_mouse_press(DBusDisplayConsole *ddc, 386 GDBusMethodInvocation *invocation, 387 guint button) 388 { 389 trace_dbus_mouse_press(button); 390 391 qemu_input_queue_btn(ddc->dcl.con, button, true); 392 qemu_input_event_sync(); 393 394 qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation); 395 396 return DBUS_METHOD_INVOCATION_HANDLED; 397 } 398 399 static gboolean 400 dbus_mouse_release(DBusDisplayConsole *ddc, 401 GDBusMethodInvocation *invocation, 402 guint button) 403 { 404 trace_dbus_mouse_release(button); 405 406 qemu_input_queue_btn(ddc->dcl.con, button, false); 407 qemu_input_event_sync(); 408 409 qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation); 410 411 return DBUS_METHOD_INVOCATION_HANDLED; 412 } 413 414 static void 415 dbus_mouse_mode_change(Notifier *notify, void *data) 416 { 417 DBusDisplayConsole *ddc = 418 container_of(notify, DBusDisplayConsole, mouse_mode_notifier); 419 420 g_object_set(ddc->iface_mouse, 421 "is-absolute", qemu_input_is_absolute(), 422 NULL); 423 } 424 425 int dbus_display_console_get_index(DBusDisplayConsole *ddc) 426 { 427 return qemu_console_get_index(ddc->dcl.con); 428 } 429 430 DBusDisplayConsole * 431 dbus_display_console_new(DBusDisplay *display, QemuConsole *con) 432 { 433 g_autofree char *path = NULL; 434 g_autofree char *label = NULL; 435 char device_addr[256] = ""; 436 DBusDisplayConsole *ddc; 437 int idx; 438 439 assert(display); 440 assert(con); 441 442 label = qemu_console_get_label(con); 443 idx = qemu_console_get_index(con); 444 path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx); 445 ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE, 446 "g-object-path", path, 447 NULL); 448 ddc->display = display; 449 ddc->dcl.con = con; 450 /* handle errors, and skip non graphics? */ 451 qemu_console_fill_device_address( 452 con, device_addr, sizeof(device_addr), NULL); 453 454 ddc->iface = qemu_dbus_display1_console_skeleton_new(); 455 g_object_set(ddc->iface, 456 "label", label, 457 "type", qemu_console_is_graphic(con) ? "Graphic" : "Text", 458 "head", qemu_console_get_head(con), 459 "width", qemu_console_get_width(con, 0), 460 "height", qemu_console_get_height(con, 0), 461 "device-address", device_addr, 462 NULL); 463 g_object_connect(ddc->iface, 464 "swapped-signal::handle-register-listener", 465 dbus_console_register_listener, ddc, 466 "swapped-signal::handle-set-uiinfo", 467 dbus_console_set_ui_info, ddc, 468 NULL); 469 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 470 G_DBUS_INTERFACE_SKELETON(ddc->iface)); 471 472 ddc->kbd = qkbd_state_init(con); 473 ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new(); 474 qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc); 475 g_object_connect(ddc->iface_kbd, 476 "swapped-signal::handle-press", dbus_kbd_press, ddc, 477 "swapped-signal::handle-release", dbus_kbd_release, ddc, 478 NULL); 479 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 480 G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd)); 481 482 ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new(); 483 g_object_connect(ddc->iface_mouse, 484 "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc, 485 "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc, 486 "swapped-signal::handle-press", dbus_mouse_press, ddc, 487 "swapped-signal::handle-release", dbus_mouse_release, ddc, 488 NULL); 489 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), 490 G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); 491 492 register_displaychangelistener(&ddc->dcl); 493 ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; 494 qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); 495 496 return ddc; 497 } 498