/* * QEMU DBus display console * * Copyright (c) 2021 Marc-André Lureau * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "qemu/osdep.h" #include "qemu/error-report.h" #include "qapi/error.h" #include "ui/input.h" #include "ui/kbd-state.h" #include "trace.h" #ifdef G_OS_UNIX #include #endif #include "dbus.h" static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX]; struct _DBusDisplayConsole { GDBusObjectSkeleton parent_instance; DisplayChangeListener dcl; DBusDisplay *display; GHashTable *listeners; QemuDBusDisplay1Console *iface; QemuDBusDisplay1Keyboard *iface_kbd; QKbdState *kbd; QemuDBusDisplay1Mouse *iface_mouse; QemuDBusDisplay1MultiTouch *iface_touch; gboolean last_set; guint last_x; guint last_y; Notifier mouse_mode_notifier; }; G_DEFINE_TYPE(DBusDisplayConsole, dbus_display_console, G_TYPE_DBUS_OBJECT_SKELETON) static void dbus_display_console_set_size(DBusDisplayConsole *ddc, uint32_t width, uint32_t height) { g_object_set(ddc->iface, "width", width, "height", height, NULL); } static void dbus_gfx_switch(DisplayChangeListener *dcl, struct DisplaySurface *new_surface) { DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); dbus_display_console_set_size(ddc, surface_width(new_surface), surface_height(new_surface)); } static void dbus_gfx_update(DisplayChangeListener *dcl, int x, int y, int w, int h) { } static void dbus_gl_scanout_disable(DisplayChangeListener *dcl) { } static void dbus_gl_scanout_texture(DisplayChangeListener *dcl, uint32_t tex_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, uint32_t w, uint32_t h, void *d3d_tex2d) { DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); dbus_display_console_set_size(ddc, w, h); } static void dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf) { DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); dbus_display_console_set_size(ddc, dmabuf->width, dmabuf->height); } static void dbus_gl_scanout_update(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { } const DisplayChangeListenerOps dbus_console_dcl_ops = { .dpy_name = "dbus-console", .dpy_gfx_switch = dbus_gfx_switch, .dpy_gfx_update = dbus_gfx_update, .dpy_gl_scanout_disable = dbus_gl_scanout_disable, .dpy_gl_scanout_texture = dbus_gl_scanout_texture, .dpy_gl_scanout_dmabuf = dbus_gl_scanout_dmabuf, .dpy_gl_update = dbus_gl_scanout_update, }; static void dbus_display_console_init(DBusDisplayConsole *object) { DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, g_object_unref); ddc->dcl.ops = &dbus_console_dcl_ops; } static void dbus_display_console_dispose(GObject *object) { DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object); unregister_displaychangelistener(&ddc->dcl); g_clear_object(&ddc->iface_touch); g_clear_object(&ddc->iface_mouse); g_clear_object(&ddc->iface_kbd); g_clear_object(&ddc->iface); g_clear_pointer(&ddc->listeners, g_hash_table_unref); g_clear_pointer(&ddc->kbd, qkbd_state_free); G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object); } static void dbus_display_console_class_init(DBusDisplayConsoleClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS(klass); gobject_class->dispose = dbus_display_console_dispose; } static void listener_vanished_cb(DBusDisplayListener *listener) { DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener); const char *name = dbus_display_listener_get_bus_name(listener); trace_dbus_listener_vanished(name); g_hash_table_remove(ddc->listeners, name); qkbd_state_lift_all_keys(ddc->kbd); } static gboolean dbus_console_set_ui_info(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint16 arg_width_mm, guint16 arg_height_mm, gint arg_xoff, gint arg_yoff, guint arg_width, guint arg_height) { QemuUIInfo info = { .width_mm = arg_width_mm, .height_mm = arg_height_mm, .xoff = arg_xoff, .yoff = arg_yoff, .width = arg_width, .height = arg_height, }; if (!dpy_ui_info_supported(ddc->dcl.con)) { g_dbus_method_invocation_return_error(invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_UNSUPPORTED, "SetUIInfo is not supported"); return DBUS_METHOD_INVOCATION_HANDLED; } dpy_set_ui_info(ddc->dcl.con, &info, false); qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } #ifdef G_OS_WIN32 bool dbus_win32_import_socket(GDBusMethodInvocation *invocation, GVariant *arg_listener, int *socket) { gsize n; WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1); if (!info || n != sizeof(*info)) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Failed to get socket infos"); return false; } *socket = WSASocketW(FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, FROM_PROTOCOL_INFO, info, 0, 0); if (*socket == INVALID_SOCKET) { g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError()); g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Couldn't create socket: %s", emsg); return false; } return true; } #endif static gboolean dbus_console_register_listener(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, #ifdef G_OS_UNIX GUnixFDList *fd_list, #endif GVariant *arg_listener) { const char *sender = g_dbus_method_invocation_get_sender(invocation); GDBusConnection *listener_conn; g_autoptr(GError) err = NULL; g_autoptr(GSocket) socket = NULL; g_autoptr(GSocketConnection) socket_conn = NULL; g_autofree char *guid = g_dbus_generate_guid(); DBusDisplayListener *listener; int fd; if (sender && g_hash_table_contains(ddc->listeners, sender)) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, "`%s` is already registered!", sender); return DBUS_METHOD_INVOCATION_HANDLED; } #ifdef G_OS_WIN32 if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) { return DBUS_METHOD_INVOCATION_HANDLED; } #else fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); if (err) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Couldn't get peer fd: %s", err->message); return DBUS_METHOD_INVOCATION_HANDLED; } #endif socket = g_socket_new_from_fd(fd, &err); if (err) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Couldn't make a socket: %s", err->message); #ifdef G_OS_WIN32 closesocket(fd); #else close(fd); #endif return DBUS_METHOD_INVOCATION_HANDLED; } socket_conn = g_socket_connection_factory_create_connection(socket); qemu_dbus_display1_console_complete_register_listener( ddc->iface, invocation #ifdef G_OS_UNIX , NULL #endif ); listener_conn = g_dbus_connection_new_sync( G_IO_STREAM(socket_conn), guid, G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER, NULL, NULL, &err); if (err) { error_report("Failed to setup peer connection: %s", err->message); return DBUS_METHOD_INVOCATION_HANDLED; } listener = dbus_display_listener_new(sender, listener_conn, ddc); if (!listener) { return DBUS_METHOD_INVOCATION_HANDLED; } g_hash_table_insert(ddc->listeners, (gpointer)dbus_display_listener_get_bus_name(listener), listener); g_object_connect(listener_conn, "swapped-signal::closed", listener_vanished_cb, listener, NULL); trace_dbus_registered_listener(sender); return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_kbd_press(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint arg_keycode) { QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); trace_dbus_kbd_press(arg_keycode); qkbd_state_key_event(ddc->kbd, qcode, true); qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_kbd_release(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint arg_keycode) { QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode); trace_dbus_kbd_release(arg_keycode); qkbd_state_key_event(ddc->kbd, qcode, false); qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static void dbus_kbd_qemu_leds_updated(void *data, int ledstate) { DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data); qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate); } static gboolean dbus_mouse_rel_motion(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, int dx, int dy) { trace_dbus_mouse_rel_motion(dx, dy); if (qemu_input_is_absolute(ddc->dcl.con)) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, "Mouse is not relative"); return DBUS_METHOD_INVOCATION_HANDLED; } qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx); qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy); qemu_input_event_sync(); qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_touch_send_event(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint kind, uint64_t num_slot, double x, double y) { Error *error = NULL; int width, height; trace_dbus_touch_send_event(kind, num_slot, x, y); if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN && kind != INPUT_MULTI_TOUCH_TYPE_UPDATE && kind != INPUT_MULTI_TOUCH_TYPE_CANCEL && kind != INPUT_MULTI_TOUCH_TYPE_END) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, "Invalid touch event kind"); return DBUS_METHOD_INVOCATION_HANDLED; } width = qemu_console_get_width(ddc->dcl.con, 0); height = qemu_console_get_height(ddc->dcl.con, 0); console_handle_touch_event(ddc->dcl.con, touch_slots, num_slot, width, height, x, y, kind, &error); if (error != NULL) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, error_get_pretty(error), NULL); error_free(error); } else { qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch, invocation); } return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_mouse_set_pos(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint x, guint y) { int width, height; trace_dbus_mouse_set_pos(x, y); if (!qemu_input_is_absolute(ddc->dcl.con)) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, "Mouse is not absolute"); return DBUS_METHOD_INVOCATION_HANDLED; } width = qemu_console_get_width(ddc->dcl.con, 0); height = qemu_console_get_height(ddc->dcl.con, 0); if (x >= width || y >= height) { g_dbus_method_invocation_return_error( invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_INVALID, "Invalid mouse position"); return DBUS_METHOD_INVOCATION_HANDLED; } qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width); qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height); qemu_input_event_sync(); qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_mouse_press(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint button) { trace_dbus_mouse_press(button); qemu_input_queue_btn(ddc->dcl.con, button, true); qemu_input_event_sync(); qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static gboolean dbus_mouse_release(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint button) { trace_dbus_mouse_release(button); qemu_input_queue_btn(ddc->dcl.con, button, false); qemu_input_event_sync(); qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation); return DBUS_METHOD_INVOCATION_HANDLED; } static void dbus_mouse_update_is_absolute(DBusDisplayConsole *ddc) { g_object_set(ddc->iface_mouse, "is-absolute", qemu_input_is_absolute(ddc->dcl.con), NULL); } static void dbus_mouse_mode_change(Notifier *notify, void *data) { DBusDisplayConsole *ddc = container_of(notify, DBusDisplayConsole, mouse_mode_notifier); dbus_mouse_update_is_absolute(ddc); } int dbus_display_console_get_index(DBusDisplayConsole *ddc) { return qemu_console_get_index(ddc->dcl.con); } DBusDisplayConsole * dbus_display_console_new(DBusDisplay *display, QemuConsole *con) { g_autofree char *path = NULL; g_autofree char *label = NULL; char device_addr[256] = ""; DBusDisplayConsole *ddc; int idx, i; const char *interfaces[] = { "org.qemu.Display1.Keyboard", "org.qemu.Display1.Mouse", "org.qemu.Display1.MultiTouch", NULL }; assert(display); assert(con); label = qemu_console_get_label(con); idx = qemu_console_get_index(con); path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx); ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE, "g-object-path", path, NULL); ddc->display = display; ddc->dcl.con = con; /* handle errors, and skip non graphics? */ qemu_console_fill_device_address( con, device_addr, sizeof(device_addr), NULL); ddc->iface = qemu_dbus_display1_console_skeleton_new(); g_object_set(ddc->iface, "label", label, "type", qemu_console_is_graphic(con) ? "Graphic" : "Text", "head", qemu_console_get_head(con), "width", qemu_console_get_width(con, 0), "height", qemu_console_get_height(con, 0), "device-address", device_addr, "interfaces", interfaces, NULL); g_object_connect(ddc->iface, "swapped-signal::handle-register-listener", dbus_console_register_listener, ddc, "swapped-signal::handle-set-uiinfo", dbus_console_set_ui_info, ddc, NULL); g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), G_DBUS_INTERFACE_SKELETON(ddc->iface)); ddc->kbd = qkbd_state_init(con); ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new(); qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc); g_object_connect(ddc->iface_kbd, "swapped-signal::handle-press", dbus_kbd_press, ddc, "swapped-signal::handle-release", dbus_kbd_release, ddc, NULL); g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd)); ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new(); g_object_connect(ddc->iface_mouse, "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc, "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc, "swapped-signal::handle-press", dbus_mouse_press, ddc, "swapped-signal::handle-release", dbus_mouse_release, ddc, NULL); g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new(); g_object_connect(ddc->iface_touch, "swapped-signal::handle-send-event", dbus_touch_send_event, ddc, NULL); qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch, INPUT_EVENT_SLOTS_MAX); g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), G_DBUS_INTERFACE_SKELETON(ddc->iface_touch)); for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { struct touch_slot *slot = &touch_slots[i]; slot->tracking_id = -1; } register_displaychangelistener(&ddc->dcl); ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); dbus_mouse_update_is_absolute(ddc); return ddc; }