2 * QEMU DBus display console
4 * Copyright (c) 2021 Marc-André Lureau <marcandre.lureau@redhat.com>
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:
13 * The above copyright notice and this permission notice shall be included in
14 * all copies or substantial portions of the Software.
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
24 #include "qemu/osdep.h"
25 #include "qemu/error-report.h"
26 #include "qapi/error.h"
28 #include "ui/kbd-state.h"
32 #include <gio/gunixfdlist.h>
37 static struct touch_slot touch_slots
[INPUT_EVENT_SLOTS_MAX
];
39 struct _DBusDisplayConsole
{
40 GDBusObjectSkeleton parent_instance
;
41 DisplayChangeListener dcl
;
44 GHashTable
*listeners
;
45 QemuDBusDisplay1Console
*iface
;
47 QemuDBusDisplay1Keyboard
*iface_kbd
;
50 QemuDBusDisplay1Mouse
*iface_mouse
;
51 QemuDBusDisplay1MultiTouch
*iface_touch
;
55 Notifier mouse_mode_notifier
;
58 G_DEFINE_TYPE(DBusDisplayConsole
,
60 G_TYPE_DBUS_OBJECT_SKELETON
)
63 dbus_display_console_set_size(DBusDisplayConsole
*ddc
,
64 uint32_t width
, uint32_t height
)
66 g_object_set(ddc
->iface
,
73 dbus_gfx_switch(DisplayChangeListener
*dcl
,
74 struct DisplaySurface
*new_surface
)
76 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
78 dbus_display_console_set_size(ddc
,
79 surface_width(new_surface
),
80 surface_height(new_surface
));
84 dbus_gfx_update(DisplayChangeListener
*dcl
,
85 int x
, int y
, int w
, int h
)
90 dbus_gl_scanout_disable(DisplayChangeListener
*dcl
)
95 dbus_gl_scanout_texture(DisplayChangeListener
*dcl
,
98 uint32_t backing_width
,
99 uint32_t backing_height
,
100 uint32_t x
, uint32_t y
,
101 uint32_t w
, uint32_t h
,
104 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
106 dbus_display_console_set_size(ddc
, w
, h
);
110 dbus_gl_scanout_dmabuf(DisplayChangeListener
*dcl
,
113 uint32_t width
, height
;
115 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
117 width
= qemu_dmabuf_get_width(dmabuf
);
118 height
= qemu_dmabuf_get_height(dmabuf
);
120 dbus_display_console_set_size(ddc
, width
, height
);
124 dbus_gl_scanout_update(DisplayChangeListener
*dcl
,
125 uint32_t x
, uint32_t y
,
126 uint32_t w
, uint32_t h
)
130 const DisplayChangeListenerOps dbus_console_dcl_ops
= {
131 .dpy_name
= "dbus-console",
132 .dpy_gfx_switch
= dbus_gfx_switch
,
133 .dpy_gfx_update
= dbus_gfx_update
,
134 .dpy_gl_scanout_disable
= dbus_gl_scanout_disable
,
135 .dpy_gl_scanout_texture
= dbus_gl_scanout_texture
,
136 .dpy_gl_scanout_dmabuf
= dbus_gl_scanout_dmabuf
,
137 .dpy_gl_update
= dbus_gl_scanout_update
,
141 dbus_display_console_init(DBusDisplayConsole
*object
)
143 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(object
);
145 ddc
->listeners
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
146 NULL
, g_object_unref
);
147 ddc
->dcl
.ops
= &dbus_console_dcl_ops
;
151 dbus_display_console_dispose(GObject
*object
)
153 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(object
);
155 unregister_displaychangelistener(&ddc
->dcl
);
156 g_clear_object(&ddc
->iface_touch
);
157 g_clear_object(&ddc
->iface_mouse
);
158 g_clear_object(&ddc
->iface_kbd
);
159 g_clear_object(&ddc
->iface
);
160 g_clear_pointer(&ddc
->listeners
, g_hash_table_unref
);
161 g_clear_pointer(&ddc
->kbd
, qkbd_state_free
);
163 G_OBJECT_CLASS(dbus_display_console_parent_class
)->dispose(object
);
167 dbus_display_console_class_init(DBusDisplayConsoleClass
*klass
)
169 GObjectClass
*gobject_class
= G_OBJECT_CLASS(klass
);
171 gobject_class
->dispose
= dbus_display_console_dispose
;
175 listener_vanished_cb(DBusDisplayListener
*listener
)
177 DBusDisplayConsole
*ddc
= dbus_display_listener_get_console(listener
);
178 const char *name
= dbus_display_listener_get_bus_name(listener
);
180 trace_dbus_listener_vanished(name
);
182 g_hash_table_remove(ddc
->listeners
, name
);
183 qkbd_state_lift_all_keys(ddc
->kbd
);
187 dbus_console_set_ui_info(DBusDisplayConsole
*ddc
,
188 GDBusMethodInvocation
*invocation
,
189 guint16 arg_width_mm
,
190 guint16 arg_height_mm
,
197 .width_mm
= arg_width_mm
,
198 .height_mm
= arg_height_mm
,
202 .height
= arg_height
,
205 if (!dpy_ui_info_supported(ddc
->dcl
.con
)) {
206 g_dbus_method_invocation_return_error(invocation
,
208 DBUS_DISPLAY_ERROR_UNSUPPORTED
,
209 "SetUIInfo is not supported");
210 return DBUS_METHOD_INVOCATION_HANDLED
;
213 dpy_set_ui_info(ddc
->dcl
.con
, &info
, false);
214 qemu_dbus_display1_console_complete_set_uiinfo(ddc
->iface
, invocation
);
215 return DBUS_METHOD_INVOCATION_HANDLED
;
220 dbus_win32_import_socket(GDBusMethodInvocation
*invocation
,
221 GVariant
*arg_listener
, int *socket
)
224 WSAPROTOCOL_INFOW
*info
= (void *)g_variant_get_fixed_array(arg_listener
, &n
, 1);
226 if (!info
|| n
!= sizeof(*info
)) {
227 g_dbus_method_invocation_return_error(
230 DBUS_DISPLAY_ERROR_FAILED
,
231 "Failed to get socket infos");
235 *socket
= WSASocketW(FROM_PROTOCOL_INFO
,
239 if (*socket
== INVALID_SOCKET
) {
240 g_autofree gchar
*emsg
= g_win32_error_message(WSAGetLastError());
241 g_dbus_method_invocation_return_error(
244 DBUS_DISPLAY_ERROR_FAILED
,
245 "Couldn't create socket: %s", emsg
);
254 dbus_console_register_listener(DBusDisplayConsole
*ddc
,
255 GDBusMethodInvocation
*invocation
,
257 GUnixFDList
*fd_list
,
259 GVariant
*arg_listener
)
261 const char *sender
= g_dbus_method_invocation_get_sender(invocation
);
262 GDBusConnection
*listener_conn
;
263 g_autoptr(GError
) err
= NULL
;
264 g_autoptr(GSocket
) socket
= NULL
;
265 g_autoptr(GSocketConnection
) socket_conn
= NULL
;
266 g_autofree
char *guid
= g_dbus_generate_guid();
267 DBusDisplayListener
*listener
;
270 if (sender
&& g_hash_table_contains(ddc
->listeners
, sender
)) {
271 g_dbus_method_invocation_return_error(
274 DBUS_DISPLAY_ERROR_INVALID
,
275 "`%s` is already registered!",
277 return DBUS_METHOD_INVOCATION_HANDLED
;
281 if (!dbus_win32_import_socket(invocation
, arg_listener
, &fd
)) {
282 return DBUS_METHOD_INVOCATION_HANDLED
;
285 fd
= g_unix_fd_list_get(fd_list
, g_variant_get_handle(arg_listener
), &err
);
287 g_dbus_method_invocation_return_error(
290 DBUS_DISPLAY_ERROR_FAILED
,
291 "Couldn't get peer fd: %s", err
->message
);
292 return DBUS_METHOD_INVOCATION_HANDLED
;
296 socket
= g_socket_new_from_fd(fd
, &err
);
298 g_dbus_method_invocation_return_error(
301 DBUS_DISPLAY_ERROR_FAILED
,
302 "Couldn't make a socket: %s", err
->message
);
308 return DBUS_METHOD_INVOCATION_HANDLED
;
310 socket_conn
= g_socket_connection_factory_create_connection(socket
);
312 qemu_dbus_display1_console_complete_register_listener(
313 ddc
->iface
, invocation
319 listener_conn
= g_dbus_connection_new_sync(
320 G_IO_STREAM(socket_conn
),
322 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER
,
325 error_report("Failed to setup peer connection: %s", err
->message
);
326 return DBUS_METHOD_INVOCATION_HANDLED
;
329 listener
= dbus_display_listener_new(sender
, listener_conn
, ddc
);
331 return DBUS_METHOD_INVOCATION_HANDLED
;
334 g_hash_table_insert(ddc
->listeners
,
335 (gpointer
)dbus_display_listener_get_bus_name(listener
),
337 g_object_connect(listener_conn
,
338 "swapped-signal::closed", listener_vanished_cb
, listener
,
341 trace_dbus_registered_listener(sender
);
342 return DBUS_METHOD_INVOCATION_HANDLED
;
346 dbus_kbd_press(DBusDisplayConsole
*ddc
,
347 GDBusMethodInvocation
*invocation
,
350 QKeyCode qcode
= qemu_input_key_number_to_qcode(arg_keycode
);
352 trace_dbus_kbd_press(arg_keycode
);
354 qkbd_state_key_event(ddc
->kbd
, qcode
, true);
356 qemu_dbus_display1_keyboard_complete_press(ddc
->iface_kbd
, invocation
);
358 return DBUS_METHOD_INVOCATION_HANDLED
;
362 dbus_kbd_release(DBusDisplayConsole
*ddc
,
363 GDBusMethodInvocation
*invocation
,
366 QKeyCode qcode
= qemu_input_key_number_to_qcode(arg_keycode
);
368 trace_dbus_kbd_release(arg_keycode
);
370 qkbd_state_key_event(ddc
->kbd
, qcode
, false);
372 qemu_dbus_display1_keyboard_complete_release(ddc
->iface_kbd
, invocation
);
374 return DBUS_METHOD_INVOCATION_HANDLED
;
378 dbus_kbd_qemu_leds_updated(void *data
, int ledstate
)
380 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(data
);
382 qemu_dbus_display1_keyboard_set_modifiers(ddc
->iface_kbd
, ledstate
);
386 dbus_mouse_rel_motion(DBusDisplayConsole
*ddc
,
387 GDBusMethodInvocation
*invocation
,
390 trace_dbus_mouse_rel_motion(dx
, dy
);
392 if (qemu_input_is_absolute(ddc
->dcl
.con
)) {
393 g_dbus_method_invocation_return_error(
394 invocation
, DBUS_DISPLAY_ERROR
,
395 DBUS_DISPLAY_ERROR_INVALID
,
396 "Mouse is not relative");
397 return DBUS_METHOD_INVOCATION_HANDLED
;
400 qemu_input_queue_rel(ddc
->dcl
.con
, INPUT_AXIS_X
, dx
);
401 qemu_input_queue_rel(ddc
->dcl
.con
, INPUT_AXIS_Y
, dy
);
402 qemu_input_event_sync();
404 qemu_dbus_display1_mouse_complete_rel_motion(ddc
->iface_mouse
,
407 return DBUS_METHOD_INVOCATION_HANDLED
;
411 dbus_touch_send_event(DBusDisplayConsole
*ddc
,
412 GDBusMethodInvocation
*invocation
,
413 guint kind
, uint64_t num_slot
,
418 trace_dbus_touch_send_event(kind
, num_slot
, x
, y
);
420 if (kind
!= INPUT_MULTI_TOUCH_TYPE_BEGIN
&&
421 kind
!= INPUT_MULTI_TOUCH_TYPE_UPDATE
&&
422 kind
!= INPUT_MULTI_TOUCH_TYPE_CANCEL
&&
423 kind
!= INPUT_MULTI_TOUCH_TYPE_END
)
425 g_dbus_method_invocation_return_error(
426 invocation
, DBUS_DISPLAY_ERROR
,
427 DBUS_DISPLAY_ERROR_INVALID
,
428 "Invalid touch event kind");
429 return DBUS_METHOD_INVOCATION_HANDLED
;
431 width
= qemu_console_get_width(ddc
->dcl
.con
, 0);
432 height
= qemu_console_get_height(ddc
->dcl
.con
, 0);
434 console_handle_touch_event(ddc
->dcl
.con
, touch_slots
,
435 num_slot
, width
, height
,
438 g_dbus_method_invocation_return_error(
439 invocation
, DBUS_DISPLAY_ERROR
,
440 DBUS_DISPLAY_ERROR_INVALID
,
441 error_get_pretty(error
), NULL
);
444 qemu_dbus_display1_multi_touch_complete_send_event(ddc
->iface_touch
,
447 return DBUS_METHOD_INVOCATION_HANDLED
;
451 dbus_mouse_set_pos(DBusDisplayConsole
*ddc
,
452 GDBusMethodInvocation
*invocation
,
457 trace_dbus_mouse_set_pos(x
, y
);
459 if (!qemu_input_is_absolute(ddc
->dcl
.con
)) {
460 g_dbus_method_invocation_return_error(
461 invocation
, DBUS_DISPLAY_ERROR
,
462 DBUS_DISPLAY_ERROR_INVALID
,
463 "Mouse is not absolute");
464 return DBUS_METHOD_INVOCATION_HANDLED
;
467 width
= qemu_console_get_width(ddc
->dcl
.con
, 0);
468 height
= qemu_console_get_height(ddc
->dcl
.con
, 0);
469 if (x
>= width
|| y
>= height
) {
470 g_dbus_method_invocation_return_error(
471 invocation
, DBUS_DISPLAY_ERROR
,
472 DBUS_DISPLAY_ERROR_INVALID
,
473 "Invalid mouse position");
474 return DBUS_METHOD_INVOCATION_HANDLED
;
476 qemu_input_queue_abs(ddc
->dcl
.con
, INPUT_AXIS_X
, x
, 0, width
);
477 qemu_input_queue_abs(ddc
->dcl
.con
, INPUT_AXIS_Y
, y
, 0, height
);
478 qemu_input_event_sync();
480 qemu_dbus_display1_mouse_complete_set_abs_position(ddc
->iface_mouse
,
483 return DBUS_METHOD_INVOCATION_HANDLED
;
487 dbus_mouse_press(DBusDisplayConsole
*ddc
,
488 GDBusMethodInvocation
*invocation
,
491 trace_dbus_mouse_press(button
);
493 qemu_input_queue_btn(ddc
->dcl
.con
, button
, true);
494 qemu_input_event_sync();
496 qemu_dbus_display1_mouse_complete_press(ddc
->iface_mouse
, invocation
);
498 return DBUS_METHOD_INVOCATION_HANDLED
;
502 dbus_mouse_release(DBusDisplayConsole
*ddc
,
503 GDBusMethodInvocation
*invocation
,
506 trace_dbus_mouse_release(button
);
508 qemu_input_queue_btn(ddc
->dcl
.con
, button
, false);
509 qemu_input_event_sync();
511 qemu_dbus_display1_mouse_complete_release(ddc
->iface_mouse
, invocation
);
513 return DBUS_METHOD_INVOCATION_HANDLED
;
517 dbus_mouse_update_is_absolute(DBusDisplayConsole
*ddc
)
519 g_object_set(ddc
->iface_mouse
,
520 "is-absolute", qemu_input_is_absolute(ddc
->dcl
.con
),
525 dbus_mouse_mode_change(Notifier
*notify
, void *data
)
527 DBusDisplayConsole
*ddc
=
528 container_of(notify
, DBusDisplayConsole
, mouse_mode_notifier
);
530 dbus_mouse_update_is_absolute(ddc
);
533 int dbus_display_console_get_index(DBusDisplayConsole
*ddc
)
535 return qemu_console_get_index(ddc
->dcl
.con
);
539 dbus_display_console_new(DBusDisplay
*display
, QemuConsole
*con
)
541 g_autofree
char *path
= NULL
;
542 g_autofree
char *label
= NULL
;
543 char device_addr
[256] = "";
544 DBusDisplayConsole
*ddc
;
546 const char *interfaces
[] = {
547 "org.qemu.Display1.Keyboard",
548 "org.qemu.Display1.Mouse",
549 "org.qemu.Display1.MultiTouch",
556 label
= qemu_console_get_label(con
);
557 idx
= qemu_console_get_index(con
);
558 path
= g_strdup_printf(DBUS_DISPLAY1_ROOT
"/Console_%d", idx
);
559 ddc
= g_object_new(DBUS_DISPLAY_TYPE_CONSOLE
,
560 "g-object-path", path
,
562 ddc
->display
= display
;
564 /* handle errors, and skip non graphics? */
565 qemu_console_fill_device_address(
566 con
, device_addr
, sizeof(device_addr
), NULL
);
568 ddc
->iface
= qemu_dbus_display1_console_skeleton_new();
569 g_object_set(ddc
->iface
,
571 "type", qemu_console_is_graphic(con
) ? "Graphic" : "Text",
572 "head", qemu_console_get_head(con
),
573 "width", qemu_console_get_width(con
, 0),
574 "height", qemu_console_get_height(con
, 0),
575 "device-address", device_addr
,
576 "interfaces", interfaces
,
578 g_object_connect(ddc
->iface
,
579 "swapped-signal::handle-register-listener",
580 dbus_console_register_listener
, ddc
,
581 "swapped-signal::handle-set-uiinfo",
582 dbus_console_set_ui_info
, ddc
,
584 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
585 G_DBUS_INTERFACE_SKELETON(ddc
->iface
));
587 ddc
->kbd
= qkbd_state_init(con
);
588 ddc
->iface_kbd
= qemu_dbus_display1_keyboard_skeleton_new();
589 qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated
, ddc
);
590 g_object_connect(ddc
->iface_kbd
,
591 "swapped-signal::handle-press", dbus_kbd_press
, ddc
,
592 "swapped-signal::handle-release", dbus_kbd_release
, ddc
,
594 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
595 G_DBUS_INTERFACE_SKELETON(ddc
->iface_kbd
));
597 ddc
->iface_mouse
= qemu_dbus_display1_mouse_skeleton_new();
598 g_object_connect(ddc
->iface_mouse
,
599 "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos
, ddc
,
600 "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion
, ddc
,
601 "swapped-signal::handle-press", dbus_mouse_press
, ddc
,
602 "swapped-signal::handle-release", dbus_mouse_release
, ddc
,
604 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
605 G_DBUS_INTERFACE_SKELETON(ddc
->iface_mouse
));
607 ddc
->iface_touch
= qemu_dbus_display1_multi_touch_skeleton_new();
608 g_object_connect(ddc
->iface_touch
,
609 "swapped-signal::handle-send-event", dbus_touch_send_event
, ddc
,
611 qemu_dbus_display1_multi_touch_set_max_slots(ddc
->iface_touch
,
612 INPUT_EVENT_SLOTS_MAX
);
613 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
614 G_DBUS_INTERFACE_SKELETON(ddc
->iface_touch
));
616 for (i
= 0; i
< INPUT_EVENT_SLOTS_MAX
; i
++) {
617 struct touch_slot
*slot
= &touch_slots
[i
];
618 slot
->tracking_id
= -1;
621 register_displaychangelistener(&ddc
->dcl
);
622 ddc
->mouse_mode_notifier
.notify
= dbus_mouse_mode_change
;
623 qemu_add_mouse_mode_change_notifier(&ddc
->mouse_mode_notifier
);
624 dbus_mouse_update_is_absolute(ddc
);