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 "qapi/error.h"
27 #include "ui/kbd-state.h"
30 #include <gio/gunixfdlist.h>
34 struct _DBusDisplayConsole
{
35 GDBusObjectSkeleton parent_instance
;
36 DisplayChangeListener dcl
;
40 GHashTable
*listeners
;
41 QemuDBusDisplay1Console
*iface
;
43 QemuDBusDisplay1Keyboard
*iface_kbd
;
46 QemuDBusDisplay1Mouse
*iface_mouse
;
50 Notifier mouse_mode_notifier
;
53 G_DEFINE_TYPE(DBusDisplayConsole
,
55 G_TYPE_DBUS_OBJECT_SKELETON
)
58 dbus_display_console_set_size(DBusDisplayConsole
*ddc
,
59 uint32_t width
, uint32_t height
)
61 g_object_set(ddc
->iface
,
68 dbus_gfx_switch(DisplayChangeListener
*dcl
,
69 struct DisplaySurface
*new_surface
)
71 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
73 dbus_display_console_set_size(ddc
,
74 surface_width(new_surface
),
75 surface_height(new_surface
));
79 dbus_gfx_update(DisplayChangeListener
*dcl
,
80 int x
, int y
, int w
, int h
)
85 dbus_gl_scanout_disable(DisplayChangeListener
*dcl
)
90 dbus_gl_scanout_texture(DisplayChangeListener
*dcl
,
93 uint32_t backing_width
,
94 uint32_t backing_height
,
95 uint32_t x
, uint32_t y
,
96 uint32_t w
, uint32_t h
)
98 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
100 dbus_display_console_set_size(ddc
, w
, h
);
104 dbus_gl_scanout_dmabuf(DisplayChangeListener
*dcl
,
107 DBusDisplayConsole
*ddc
= container_of(dcl
, DBusDisplayConsole
, dcl
);
109 dbus_display_console_set_size(ddc
,
115 dbus_gl_scanout_update(DisplayChangeListener
*dcl
,
116 uint32_t x
, uint32_t y
,
117 uint32_t w
, uint32_t h
)
121 static 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
,
132 dbus_display_console_init(DBusDisplayConsole
*object
)
134 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(object
);
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
;
142 dbus_display_console_dispose(GObject
*object
)
144 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(object
);
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
);
152 G_OBJECT_CLASS(dbus_display_console_parent_class
)->dispose(object
);
156 dbus_display_console_class_init(DBusDisplayConsoleClass
*klass
)
158 GObjectClass
*gobject_class
= G_OBJECT_CLASS(klass
);
160 gobject_class
->dispose
= dbus_display_console_dispose
;
164 listener_vanished_cb(DBusDisplayListener
*listener
)
166 DBusDisplayConsole
*ddc
= dbus_display_listener_get_console(listener
);
167 const char *name
= dbus_display_listener_get_bus_name(listener
);
169 trace_dbus_listener_vanished(name
);
171 g_hash_table_remove(ddc
->listeners
, name
);
172 qkbd_state_lift_all_keys(ddc
->kbd
);
176 dbus_console_set_ui_info(DBusDisplayConsole
*ddc
,
177 GDBusMethodInvocation
*invocation
,
178 guint16 arg_width_mm
,
179 guint16 arg_height_mm
,
186 .width_mm
= arg_width_mm
,
187 .height_mm
= arg_height_mm
,
191 .height
= arg_height
,
194 if (!dpy_ui_info_supported(ddc
->con
)) {
195 g_dbus_method_invocation_return_error(invocation
,
197 DBUS_DISPLAY_ERROR_UNSUPPORTED
,
198 "SetUIInfo is not supported");
199 return DBUS_METHOD_INVOCATION_HANDLED
;
202 dpy_set_ui_info(ddc
->con
, &info
, false);
203 qemu_dbus_display1_console_complete_set_uiinfo(ddc
->iface
, invocation
);
204 return DBUS_METHOD_INVOCATION_HANDLED
;
208 dbus_console_register_listener(DBusDisplayConsole
*ddc
,
209 GDBusMethodInvocation
*invocation
,
210 GUnixFDList
*fd_list
,
211 GVariant
*arg_listener
)
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
;
222 if (sender
&& g_hash_table_contains(ddc
->listeners
, sender
)) {
223 g_dbus_method_invocation_return_error(
226 DBUS_DISPLAY_ERROR_INVALID
,
227 "`%s` is already registered!",
229 return DBUS_METHOD_INVOCATION_HANDLED
;
232 fd
= g_unix_fd_list_get(fd_list
, g_variant_get_handle(arg_listener
), &err
);
234 g_dbus_method_invocation_return_error(
237 DBUS_DISPLAY_ERROR_FAILED
,
238 "Couldn't get peer fd: %s", err
->message
);
239 return DBUS_METHOD_INVOCATION_HANDLED
;
242 socket
= g_socket_new_from_fd(fd
, &err
);
244 g_dbus_method_invocation_return_error(
247 DBUS_DISPLAY_ERROR_FAILED
,
248 "Couldn't make a socket: %s", err
->message
);
250 return DBUS_METHOD_INVOCATION_HANDLED
;
252 socket_conn
= g_socket_connection_factory_create_connection(socket
);
254 qemu_dbus_display1_console_complete_register_listener(
255 ddc
->iface
, invocation
, NULL
);
257 listener_conn
= g_dbus_connection_new_sync(
258 G_IO_STREAM(socket_conn
),
260 G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER
,
263 error_report("Failed to setup peer connection: %s", err
->message
);
264 return DBUS_METHOD_INVOCATION_HANDLED
;
267 listener
= dbus_display_listener_new(sender
, listener_conn
, ddc
);
269 return DBUS_METHOD_INVOCATION_HANDLED
;
272 g_hash_table_insert(ddc
->listeners
,
273 (gpointer
)dbus_display_listener_get_bus_name(listener
),
275 g_object_connect(listener_conn
,
276 "swapped-signal::closed", listener_vanished_cb
, listener
,
279 trace_dbus_registered_listener(sender
);
280 return DBUS_METHOD_INVOCATION_HANDLED
;
284 dbus_kbd_press(DBusDisplayConsole
*ddc
,
285 GDBusMethodInvocation
*invocation
,
288 QKeyCode qcode
= qemu_input_key_number_to_qcode(arg_keycode
);
290 trace_dbus_kbd_press(arg_keycode
);
292 qkbd_state_key_event(ddc
->kbd
, qcode
, true);
294 qemu_dbus_display1_keyboard_complete_press(ddc
->iface_kbd
, invocation
);
296 return DBUS_METHOD_INVOCATION_HANDLED
;
300 dbus_kbd_release(DBusDisplayConsole
*ddc
,
301 GDBusMethodInvocation
*invocation
,
304 QKeyCode qcode
= qemu_input_key_number_to_qcode(arg_keycode
);
306 trace_dbus_kbd_release(arg_keycode
);
308 qkbd_state_key_event(ddc
->kbd
, qcode
, false);
310 qemu_dbus_display1_keyboard_complete_release(ddc
->iface_kbd
, invocation
);
312 return DBUS_METHOD_INVOCATION_HANDLED
;
316 dbus_kbd_qemu_leds_updated(void *data
, int ledstate
)
318 DBusDisplayConsole
*ddc
= DBUS_DISPLAY_CONSOLE(data
);
320 qemu_dbus_display1_keyboard_set_modifiers(ddc
->iface_kbd
, ledstate
);
324 dbus_mouse_rel_motion(DBusDisplayConsole
*ddc
,
325 GDBusMethodInvocation
*invocation
,
328 trace_dbus_mouse_rel_motion(dx
, dy
);
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
;
338 qemu_input_queue_rel(ddc
->con
, INPUT_AXIS_X
, dx
);
339 qemu_input_queue_rel(ddc
->con
, INPUT_AXIS_Y
, dy
);
340 qemu_input_event_sync();
342 qemu_dbus_display1_mouse_complete_rel_motion(ddc
->iface_mouse
,
345 return DBUS_METHOD_INVOCATION_HANDLED
;
349 dbus_mouse_set_pos(DBusDisplayConsole
*ddc
,
350 GDBusMethodInvocation
*invocation
,
355 trace_dbus_mouse_set_pos(x
, y
);
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
;
365 width
= qemu_console_get_width(ddc
->con
, 0);
366 height
= qemu_console_get_height(ddc
->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
;
374 qemu_input_queue_abs(ddc
->con
, INPUT_AXIS_X
, x
, 0, width
);
375 qemu_input_queue_abs(ddc
->con
, INPUT_AXIS_Y
, y
, 0, height
);
376 qemu_input_event_sync();
378 qemu_dbus_display1_mouse_complete_set_abs_position(ddc
->iface_mouse
,
381 return DBUS_METHOD_INVOCATION_HANDLED
;
385 dbus_mouse_press(DBusDisplayConsole
*ddc
,
386 GDBusMethodInvocation
*invocation
,
389 trace_dbus_mouse_press(button
);
391 qemu_input_queue_btn(ddc
->con
, button
, true);
392 qemu_input_event_sync();
394 qemu_dbus_display1_mouse_complete_press(ddc
->iface_mouse
, invocation
);
396 return DBUS_METHOD_INVOCATION_HANDLED
;
400 dbus_mouse_release(DBusDisplayConsole
*ddc
,
401 GDBusMethodInvocation
*invocation
,
404 trace_dbus_mouse_release(button
);
406 qemu_input_queue_btn(ddc
->con
, button
, false);
407 qemu_input_event_sync();
409 qemu_dbus_display1_mouse_complete_release(ddc
->iface_mouse
, invocation
);
411 return DBUS_METHOD_INVOCATION_HANDLED
;
415 dbus_mouse_mode_change(Notifier
*notify
, void *data
)
417 DBusDisplayConsole
*ddc
=
418 container_of(notify
, DBusDisplayConsole
, mouse_mode_notifier
);
420 g_object_set(ddc
->iface_mouse
,
421 "is-absolute", qemu_input_is_absolute(),
425 int dbus_display_console_get_index(DBusDisplayConsole
*ddc
)
427 return qemu_console_get_index(ddc
->con
);
431 dbus_display_console_new(DBusDisplay
*display
, QemuConsole
*con
)
433 g_autofree
char *path
= NULL
;
434 g_autofree
char *label
= NULL
;
435 char device_addr
[256] = "";
436 DBusDisplayConsole
*ddc
;
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
,
448 ddc
->display
= display
;
450 /* handle errors, and skip non graphics? */
451 qemu_console_fill_device_address(
452 con
, device_addr
, sizeof(device_addr
), NULL
);
454 ddc
->iface
= qemu_dbus_display1_console_skeleton_new();
455 g_object_set(ddc
->iface
,
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
,
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
,
469 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
470 G_DBUS_INTERFACE_SKELETON(ddc
->iface
));
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
,
479 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
480 G_DBUS_INTERFACE_SKELETON(ddc
->iface_kbd
));
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
,
489 g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc
),
490 G_DBUS_INTERFACE_SKELETON(ddc
->iface_mouse
));
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
);