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