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