| /* |
| * QEMU DBus display console |
| * |
| * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com> |
| * |
| * 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 "qapi/error.h" |
| #include "ui/input.h" |
| #include "ui/kbd-state.h" |
| #include "trace.h" |
| |
| #include <gio/gunixfdlist.h> |
| |
| #include "dbus.h" |
| |
| struct _DBusDisplayConsole { |
| GDBusObjectSkeleton parent_instance; |
| DisplayChangeListener dcl; |
| |
| DBusDisplay *display; |
| GHashTable *listeners; |
| QemuDBusDisplay1Console *iface; |
| |
| QemuDBusDisplay1Keyboard *iface_kbd; |
| QKbdState *kbd; |
| |
| QemuDBusDisplay1Mouse *iface_mouse; |
| 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) |
| { |
| 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_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; |
| } |
| |
| static gboolean |
| dbus_console_register_listener(DBusDisplayConsole *ddc, |
| GDBusMethodInvocation *invocation, |
| GUnixFDList *fd_list, |
| 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; |
| } |
| |
| 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; |
| } |
| |
| 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); |
| close(fd); |
| return DBUS_METHOD_INVOCATION_HANDLED; |
| } |
| socket_conn = g_socket_connection_factory_create_connection(socket); |
| |
| qemu_dbus_display1_console_complete_register_listener( |
| ddc->iface, invocation, NULL); |
| |
| 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()) { |
| 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_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()) { |
| 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_mode_change(Notifier *notify, void *data) |
| { |
| DBusDisplayConsole *ddc = |
| container_of(notify, DBusDisplayConsole, mouse_mode_notifier); |
| |
| g_object_set(ddc->iface_mouse, |
| "is-absolute", qemu_input_is_absolute(), |
| NULL); |
| } |
| |
| 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; |
| |
| 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, |
| 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)); |
| |
| register_displaychangelistener(&ddc->dcl); |
| ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; |
| qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); |
| |
| return ddc; |
| } |