xref: /openbmc/qemu/ui/dbus-console.c (revision 39324b49)
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 #ifdef G_OS_UNIX
32 #include <gio/gunixfdlist.h>
33 #endif
34 
35 #include "dbus.h"
36 
37 static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX];
38 
39 struct _DBusDisplayConsole {
40     GDBusObjectSkeleton parent_instance;
41     DisplayChangeListener dcl;
42 
43     DBusDisplay *display;
44     GHashTable *listeners;
45     QemuDBusDisplay1Console *iface;
46 
47     QemuDBusDisplay1Keyboard *iface_kbd;
48     QKbdState *kbd;
49 
50     QemuDBusDisplay1Mouse *iface_mouse;
51     QemuDBusDisplay1MultiTouch *iface_touch;
52     gboolean last_set;
53     guint last_x;
54     guint last_y;
55     Notifier mouse_mode_notifier;
56 };
57 
58 G_DEFINE_TYPE(DBusDisplayConsole,
59               dbus_display_console,
60               G_TYPE_DBUS_OBJECT_SKELETON)
61 
62 static void
63 dbus_display_console_set_size(DBusDisplayConsole *ddc,
64                               uint32_t width, uint32_t height)
65 {
66     g_object_set(ddc->iface,
67                  "width", width,
68                  "height", height,
69                  NULL);
70 }
71 
72 static void
73 dbus_gfx_switch(DisplayChangeListener *dcl,
74                 struct DisplaySurface *new_surface)
75 {
76     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
77 
78     dbus_display_console_set_size(ddc,
79                                   surface_width(new_surface),
80                                   surface_height(new_surface));
81 }
82 
83 static void
84 dbus_gfx_update(DisplayChangeListener *dcl,
85                 int x, int y, int w, int h)
86 {
87 }
88 
89 static void
90 dbus_gl_scanout_disable(DisplayChangeListener *dcl)
91 {
92 }
93 
94 static void
95 dbus_gl_scanout_texture(DisplayChangeListener *dcl,
96                         uint32_t tex_id,
97                         bool backing_y_0_top,
98                         uint32_t backing_width,
99                         uint32_t backing_height,
100                         uint32_t x, uint32_t y,
101                         uint32_t w, uint32_t h)
102 {
103     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
104 
105     dbus_display_console_set_size(ddc, w, h);
106 }
107 
108 static void
109 dbus_gl_scanout_dmabuf(DisplayChangeListener *dcl,
110                        QemuDmaBuf *dmabuf)
111 {
112     DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl);
113 
114     dbus_display_console_set_size(ddc,
115                                   dmabuf->width,
116                                   dmabuf->height);
117 }
118 
119 static void
120 dbus_gl_scanout_update(DisplayChangeListener *dcl,
121                        uint32_t x, uint32_t y,
122                        uint32_t w, uint32_t h)
123 {
124 }
125 
126 const DisplayChangeListenerOps dbus_console_dcl_ops = {
127     .dpy_name                = "dbus-console",
128     .dpy_gfx_switch          = dbus_gfx_switch,
129     .dpy_gfx_update          = dbus_gfx_update,
130     .dpy_gl_scanout_disable  = dbus_gl_scanout_disable,
131     .dpy_gl_scanout_texture  = dbus_gl_scanout_texture,
132     .dpy_gl_scanout_dmabuf   = dbus_gl_scanout_dmabuf,
133     .dpy_gl_update           = dbus_gl_scanout_update,
134 };
135 
136 static void
137 dbus_display_console_init(DBusDisplayConsole *object)
138 {
139     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
140 
141     ddc->listeners = g_hash_table_new_full(g_str_hash, g_str_equal,
142                                             NULL, g_object_unref);
143     ddc->dcl.ops = &dbus_console_dcl_ops;
144 }
145 
146 static void
147 dbus_display_console_dispose(GObject *object)
148 {
149     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(object);
150 
151     unregister_displaychangelistener(&ddc->dcl);
152     g_clear_object(&ddc->iface_kbd);
153     g_clear_object(&ddc->iface);
154     g_clear_pointer(&ddc->listeners, g_hash_table_unref);
155     g_clear_pointer(&ddc->kbd, qkbd_state_free);
156 
157     G_OBJECT_CLASS(dbus_display_console_parent_class)->dispose(object);
158 }
159 
160 static void
161 dbus_display_console_class_init(DBusDisplayConsoleClass *klass)
162 {
163     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
164 
165     gobject_class->dispose = dbus_display_console_dispose;
166 }
167 
168 static void
169 listener_vanished_cb(DBusDisplayListener *listener)
170 {
171     DBusDisplayConsole *ddc = dbus_display_listener_get_console(listener);
172     const char *name = dbus_display_listener_get_bus_name(listener);
173 
174     trace_dbus_listener_vanished(name);
175 
176     g_hash_table_remove(ddc->listeners, name);
177     qkbd_state_lift_all_keys(ddc->kbd);
178 }
179 
180 static gboolean
181 dbus_console_set_ui_info(DBusDisplayConsole *ddc,
182                          GDBusMethodInvocation *invocation,
183                          guint16 arg_width_mm,
184                          guint16 arg_height_mm,
185                          gint arg_xoff,
186                          gint arg_yoff,
187                          guint arg_width,
188                          guint arg_height)
189 {
190     QemuUIInfo info = {
191         .width_mm = arg_width_mm,
192         .height_mm = arg_height_mm,
193         .xoff = arg_xoff,
194         .yoff = arg_yoff,
195         .width = arg_width,
196         .height = arg_height,
197     };
198 
199     if (!dpy_ui_info_supported(ddc->dcl.con)) {
200         g_dbus_method_invocation_return_error(invocation,
201                                               DBUS_DISPLAY_ERROR,
202                                               DBUS_DISPLAY_ERROR_UNSUPPORTED,
203                                               "SetUIInfo is not supported");
204         return DBUS_METHOD_INVOCATION_HANDLED;
205     }
206 
207     dpy_set_ui_info(ddc->dcl.con, &info, false);
208     qemu_dbus_display1_console_complete_set_uiinfo(ddc->iface, invocation);
209     return DBUS_METHOD_INVOCATION_HANDLED;
210 }
211 
212 #ifdef G_OS_WIN32
213 bool
214 dbus_win32_import_socket(GDBusMethodInvocation *invocation,
215                          GVariant *arg_listener, int *socket)
216 {
217     gsize n;
218     WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1);
219 
220     if (!info || n != sizeof(*info)) {
221         g_dbus_method_invocation_return_error(
222             invocation,
223             DBUS_DISPLAY_ERROR,
224             DBUS_DISPLAY_ERROR_FAILED,
225             "Failed to get socket infos");
226         return false;
227     }
228 
229     *socket = WSASocketW(FROM_PROTOCOL_INFO,
230                          FROM_PROTOCOL_INFO,
231                          FROM_PROTOCOL_INFO,
232                          info, 0, 0);
233     if (*socket == INVALID_SOCKET) {
234         g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError());
235         g_dbus_method_invocation_return_error(
236             invocation,
237             DBUS_DISPLAY_ERROR,
238             DBUS_DISPLAY_ERROR_FAILED,
239             "Couldn't create socket: %s", emsg);
240         return false;
241     }
242 
243     return true;
244 }
245 #endif
246 
247 static gboolean
248 dbus_console_register_listener(DBusDisplayConsole *ddc,
249                                GDBusMethodInvocation *invocation,
250 #ifdef G_OS_UNIX
251                                GUnixFDList *fd_list,
252 #endif
253                                GVariant *arg_listener)
254 {
255     const char *sender = g_dbus_method_invocation_get_sender(invocation);
256     GDBusConnection *listener_conn;
257     g_autoptr(GError) err = NULL;
258     g_autoptr(GSocket) socket = NULL;
259     g_autoptr(GSocketConnection) socket_conn = NULL;
260     g_autofree char *guid = g_dbus_generate_guid();
261     DBusDisplayListener *listener;
262     int fd;
263 
264     if (sender && g_hash_table_contains(ddc->listeners, sender)) {
265         g_dbus_method_invocation_return_error(
266             invocation,
267             DBUS_DISPLAY_ERROR,
268             DBUS_DISPLAY_ERROR_INVALID,
269             "`%s` is already registered!",
270             sender);
271         return DBUS_METHOD_INVOCATION_HANDLED;
272     }
273 
274 #ifdef G_OS_WIN32
275     if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) {
276         return DBUS_METHOD_INVOCATION_HANDLED;
277     }
278 #else
279     fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err);
280     if (err) {
281         g_dbus_method_invocation_return_error(
282             invocation,
283             DBUS_DISPLAY_ERROR,
284             DBUS_DISPLAY_ERROR_FAILED,
285             "Couldn't get peer fd: %s", err->message);
286         return DBUS_METHOD_INVOCATION_HANDLED;
287     }
288 #endif
289 
290     socket = g_socket_new_from_fd(fd, &err);
291     if (err) {
292         g_dbus_method_invocation_return_error(
293             invocation,
294             DBUS_DISPLAY_ERROR,
295             DBUS_DISPLAY_ERROR_FAILED,
296             "Couldn't make a socket: %s", err->message);
297 #ifdef G_OS_WIN32
298         closesocket(fd);
299 #else
300         close(fd);
301 #endif
302         return DBUS_METHOD_INVOCATION_HANDLED;
303     }
304     socket_conn = g_socket_connection_factory_create_connection(socket);
305 
306     qemu_dbus_display1_console_complete_register_listener(
307         ddc->iface, invocation
308 #ifdef G_OS_UNIX
309         , NULL
310 #endif
311     );
312 
313     listener_conn = g_dbus_connection_new_sync(
314         G_IO_STREAM(socket_conn),
315         guid,
316         G_DBUS_CONNECTION_FLAGS_AUTHENTICATION_SERVER,
317         NULL, NULL, &err);
318     if (err) {
319         error_report("Failed to setup peer connection: %s", err->message);
320         return DBUS_METHOD_INVOCATION_HANDLED;
321     }
322 
323     listener = dbus_display_listener_new(sender, listener_conn, ddc);
324     if (!listener) {
325         return DBUS_METHOD_INVOCATION_HANDLED;
326     }
327 
328     g_hash_table_insert(ddc->listeners,
329                         (gpointer)dbus_display_listener_get_bus_name(listener),
330                         listener);
331     g_object_connect(listener_conn,
332                      "swapped-signal::closed", listener_vanished_cb, listener,
333                      NULL);
334 
335     trace_dbus_registered_listener(sender);
336     return DBUS_METHOD_INVOCATION_HANDLED;
337 }
338 
339 static gboolean
340 dbus_kbd_press(DBusDisplayConsole *ddc,
341                GDBusMethodInvocation *invocation,
342                guint arg_keycode)
343 {
344     QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
345 
346     trace_dbus_kbd_press(arg_keycode);
347 
348     qkbd_state_key_event(ddc->kbd, qcode, true);
349 
350     qemu_dbus_display1_keyboard_complete_press(ddc->iface_kbd, invocation);
351 
352     return DBUS_METHOD_INVOCATION_HANDLED;
353 }
354 
355 static gboolean
356 dbus_kbd_release(DBusDisplayConsole *ddc,
357                  GDBusMethodInvocation *invocation,
358                  guint arg_keycode)
359 {
360     QKeyCode qcode = qemu_input_key_number_to_qcode(arg_keycode);
361 
362     trace_dbus_kbd_release(arg_keycode);
363 
364     qkbd_state_key_event(ddc->kbd, qcode, false);
365 
366     qemu_dbus_display1_keyboard_complete_release(ddc->iface_kbd, invocation);
367 
368     return DBUS_METHOD_INVOCATION_HANDLED;
369 }
370 
371 static void
372 dbus_kbd_qemu_leds_updated(void *data, int ledstate)
373 {
374     DBusDisplayConsole *ddc = DBUS_DISPLAY_CONSOLE(data);
375 
376     qemu_dbus_display1_keyboard_set_modifiers(ddc->iface_kbd, ledstate);
377 }
378 
379 static gboolean
380 dbus_mouse_rel_motion(DBusDisplayConsole *ddc,
381                       GDBusMethodInvocation *invocation,
382                       int dx, int dy)
383 {
384     trace_dbus_mouse_rel_motion(dx, dy);
385 
386     if (qemu_input_is_absolute()) {
387         g_dbus_method_invocation_return_error(
388             invocation, DBUS_DISPLAY_ERROR,
389             DBUS_DISPLAY_ERROR_INVALID,
390             "Mouse is not relative");
391         return DBUS_METHOD_INVOCATION_HANDLED;
392     }
393 
394     qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_X, dx);
395     qemu_input_queue_rel(ddc->dcl.con, INPUT_AXIS_Y, dy);
396     qemu_input_event_sync();
397 
398     qemu_dbus_display1_mouse_complete_rel_motion(ddc->iface_mouse,
399                                                     invocation);
400 
401     return DBUS_METHOD_INVOCATION_HANDLED;
402 }
403 
404 static gboolean
405 dbus_touch_send_event(DBusDisplayConsole *ddc,
406                       GDBusMethodInvocation *invocation,
407                       guint kind, uint64_t num_slot,
408                       double x, double y)
409 {
410     Error *error = NULL;
411     int width, height;
412     trace_dbus_touch_send_event(kind, num_slot, x, y);
413 
414     if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN &&
415         kind != INPUT_MULTI_TOUCH_TYPE_UPDATE &&
416         kind != INPUT_MULTI_TOUCH_TYPE_CANCEL &&
417         kind != INPUT_MULTI_TOUCH_TYPE_END)
418     {
419         g_dbus_method_invocation_return_error(
420             invocation, DBUS_DISPLAY_ERROR,
421             DBUS_DISPLAY_ERROR_INVALID,
422             "Invalid touch event kind");
423         return DBUS_METHOD_INVOCATION_HANDLED;
424     }
425     width = qemu_console_get_width(ddc->dcl.con, 0);
426     height = qemu_console_get_height(ddc->dcl.con, 0);
427 
428     console_handle_touch_event(ddc->dcl.con, touch_slots,
429                                num_slot, width, height,
430                                x, y, kind, &error);
431     if (error != NULL) {
432         g_dbus_method_invocation_return_error(
433             invocation, DBUS_DISPLAY_ERROR,
434             DBUS_DISPLAY_ERROR_INVALID,
435             error_get_pretty(error), NULL);
436         error_free(error);
437     } else {
438         qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch,
439                                                            invocation);
440     }
441     return DBUS_METHOD_INVOCATION_HANDLED;
442 }
443 
444 static gboolean
445 dbus_mouse_set_pos(DBusDisplayConsole *ddc,
446                    GDBusMethodInvocation *invocation,
447                    guint x, guint y)
448 {
449     int width, height;
450 
451     trace_dbus_mouse_set_pos(x, y);
452 
453     if (!qemu_input_is_absolute()) {
454         g_dbus_method_invocation_return_error(
455             invocation, DBUS_DISPLAY_ERROR,
456             DBUS_DISPLAY_ERROR_INVALID,
457             "Mouse is not absolute");
458         return DBUS_METHOD_INVOCATION_HANDLED;
459     }
460 
461     width = qemu_console_get_width(ddc->dcl.con, 0);
462     height = qemu_console_get_height(ddc->dcl.con, 0);
463     if (x >= width || y >= height) {
464         g_dbus_method_invocation_return_error(
465             invocation, DBUS_DISPLAY_ERROR,
466             DBUS_DISPLAY_ERROR_INVALID,
467             "Invalid mouse position");
468         return DBUS_METHOD_INVOCATION_HANDLED;
469     }
470     qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_X, x, 0, width);
471     qemu_input_queue_abs(ddc->dcl.con, INPUT_AXIS_Y, y, 0, height);
472     qemu_input_event_sync();
473 
474     qemu_dbus_display1_mouse_complete_set_abs_position(ddc->iface_mouse,
475                                                           invocation);
476 
477     return DBUS_METHOD_INVOCATION_HANDLED;
478 }
479 
480 static gboolean
481 dbus_mouse_press(DBusDisplayConsole *ddc,
482                  GDBusMethodInvocation *invocation,
483                  guint button)
484 {
485     trace_dbus_mouse_press(button);
486 
487     qemu_input_queue_btn(ddc->dcl.con, button, true);
488     qemu_input_event_sync();
489 
490     qemu_dbus_display1_mouse_complete_press(ddc->iface_mouse, invocation);
491 
492     return DBUS_METHOD_INVOCATION_HANDLED;
493 }
494 
495 static gboolean
496 dbus_mouse_release(DBusDisplayConsole *ddc,
497                    GDBusMethodInvocation *invocation,
498                    guint button)
499 {
500     trace_dbus_mouse_release(button);
501 
502     qemu_input_queue_btn(ddc->dcl.con, button, false);
503     qemu_input_event_sync();
504 
505     qemu_dbus_display1_mouse_complete_release(ddc->iface_mouse, invocation);
506 
507     return DBUS_METHOD_INVOCATION_HANDLED;
508 }
509 
510 static void
511 dbus_mouse_update_is_absolute(DBusDisplayConsole *ddc)
512 {
513     g_object_set(ddc->iface_mouse,
514                  "is-absolute", qemu_input_is_absolute(),
515                  NULL);
516 }
517 
518 static void
519 dbus_mouse_mode_change(Notifier *notify, void *data)
520 {
521     DBusDisplayConsole *ddc =
522         container_of(notify, DBusDisplayConsole, mouse_mode_notifier);
523 
524     dbus_mouse_update_is_absolute(ddc);
525 }
526 
527 int dbus_display_console_get_index(DBusDisplayConsole *ddc)
528 {
529     return qemu_console_get_index(ddc->dcl.con);
530 }
531 
532 DBusDisplayConsole *
533 dbus_display_console_new(DBusDisplay *display, QemuConsole *con)
534 {
535     g_autofree char *path = NULL;
536     g_autofree char *label = NULL;
537     char device_addr[256] = "";
538     DBusDisplayConsole *ddc;
539     int idx, i;
540     const char *interfaces[] = {
541         "org.qemu.Display1.Keyboard",
542         "org.qemu.Display1.Mouse",
543         "org.qemu.Display1.MultiTouch",
544         NULL
545     };
546 
547     assert(display);
548     assert(con);
549 
550     label = qemu_console_get_label(con);
551     idx = qemu_console_get_index(con);
552     path = g_strdup_printf(DBUS_DISPLAY1_ROOT "/Console_%d", idx);
553     ddc = g_object_new(DBUS_DISPLAY_TYPE_CONSOLE,
554                         "g-object-path", path,
555                         NULL);
556     ddc->display = display;
557     ddc->dcl.con = con;
558     /* handle errors, and skip non graphics? */
559     qemu_console_fill_device_address(
560         con, device_addr, sizeof(device_addr), NULL);
561 
562     ddc->iface = qemu_dbus_display1_console_skeleton_new();
563     g_object_set(ddc->iface,
564         "label", label,
565         "type", qemu_console_is_graphic(con) ? "Graphic" : "Text",
566         "head", qemu_console_get_head(con),
567         "width", qemu_console_get_width(con, 0),
568         "height", qemu_console_get_height(con, 0),
569         "device-address", device_addr,
570         "interfaces", interfaces,
571         NULL);
572     g_object_connect(ddc->iface,
573         "swapped-signal::handle-register-listener",
574         dbus_console_register_listener, ddc,
575         "swapped-signal::handle-set-uiinfo",
576         dbus_console_set_ui_info, ddc,
577         NULL);
578     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
579         G_DBUS_INTERFACE_SKELETON(ddc->iface));
580 
581     ddc->kbd = qkbd_state_init(con);
582     ddc->iface_kbd = qemu_dbus_display1_keyboard_skeleton_new();
583     qemu_add_led_event_handler(dbus_kbd_qemu_leds_updated, ddc);
584     g_object_connect(ddc->iface_kbd,
585         "swapped-signal::handle-press", dbus_kbd_press, ddc,
586         "swapped-signal::handle-release", dbus_kbd_release, ddc,
587         NULL);
588     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
589         G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd));
590 
591     ddc->iface_mouse = qemu_dbus_display1_mouse_skeleton_new();
592     g_object_connect(ddc->iface_mouse,
593         "swapped-signal::handle-set-abs-position", dbus_mouse_set_pos, ddc,
594         "swapped-signal::handle-rel-motion", dbus_mouse_rel_motion, ddc,
595         "swapped-signal::handle-press", dbus_mouse_press, ddc,
596         "swapped-signal::handle-release", dbus_mouse_release, ddc,
597         NULL);
598     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
599         G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse));
600 
601     ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new();
602     g_object_connect(ddc->iface_touch,
603         "swapped-signal::handle-send-event", dbus_touch_send_event, ddc,
604         NULL);
605     qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch,
606                                                  INPUT_EVENT_SLOTS_MAX);
607     g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc),
608         G_DBUS_INTERFACE_SKELETON(ddc->iface_touch));
609 
610     for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) {
611         struct touch_slot *slot = &touch_slots[i];
612         slot->tracking_id = -1;
613     }
614 
615     register_displaychangelistener(&ddc->dcl);
616     ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change;
617     qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier);
618     dbus_mouse_update_is_absolute(ddc);
619 
620     return ddc;
621 }
622