xref: /qemu/ui/dbus-console.c (revision 83ecdb18)
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_update_is_absolute(DBusDisplayConsole *ddc)
416 {
417     g_object_set(ddc->iface_mouse,
418                  "is-absolute", qemu_input_is_absolute(),
419                  NULL);
420 }
421 
422 static void
423 dbus_mouse_mode_change(Notifier *notify, void *data)
424 {
425     DBusDisplayConsole *ddc =
426         container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
427 
428     dbus_mouse_update_is_absolute(ddc);
429 }
430 
431 int dbus_display_console_get_index(DBusDisplayConsole *ddc)
432 {
433     return qemu_console_get_index(ddc->dcl.con);
434 }
435 
436 DBusDisplayConsole *
437 dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
438 {
439     g_autofree char *path = NULL;
440     g_autofree char *label = NULL;
441     char device_addr[256] = "";
442     DBusDisplayConsole *ddc;
443     int idx;
444 
445     assert(display);
446     assert(con);
447 
448     label = qemu_console_get_label(con);
449     idx = qemu_console_get_index(con);
450     path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
451     ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
452                         "g-object-path", path,
453                         NULL);
454     ddc->display = display;
455     ddc->dcl.con = con;
456     /* handle errors, and skip non graphics? */
457     qemu_console_fill_device_address(
458         con, device_addr, sizeof(device_addr), NULL);
459 
460     ddc->iface = qemu_dbus_display1_console_skeleton_new();
461     g_object_set(ddc->iface,
462         "label", label,
463         "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
464         "head", qemu_console_get_head(con),
465         "width", qemu_console_get_width(con, 0),
466         "height", qemu_console_get_height(con, 0),
467         "device-address", device_addr,
468         NULL);
469     g_object_connect(ddc->iface,
470         "swapped-signal::handle-register-listener",
471         dbus_console_register_listener, ddc,
472         "swapped-signal::handle-set-uiinfo",
473         dbus_console_set_ui_info, ddc,
474         NULL);
475     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
476         G_DBUS_INTERFACE_SKELETON(ddc->iface));
477 
478     ddc->kbd = qkbd_state_init(con);
479     ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
480     qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
481     g_object_connect(ddc->iface_kbd,
482         "swapped-signal::handle-press", dbus_kbd_press, ddc,
483         "swapped-signal::handle-release", dbus_kbd_release, ddc,
484         NULL);
485     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
486         G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
487 
488     ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
489     g_object_connect(ddc->iface_mouse,
490         "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
491         "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
492         "swapped-signal::handle-press", dbus_mouse_press, ddc,
493         "swapped-signal::handle-release", dbus_mouse_release, ddc,
494         NULL);
495     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
496         G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
497 
498     register_displaychangelistener(&ddc->dcl);
499     ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
500     qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
501     dbus_mouse_update_is_absolute(ddc);
502 
503     return ddc;
504 }
505