]>
Commit | Line | Data |
---|---|---|
142ca628 MAL |
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 | QemuConsole *con; | |
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 | 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, | |
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->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->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 | ||
99997823 | 222 | if (sender && g_hash_table_contains(ddc->listeners, sender)) { |
142ca628 MAL |
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->con, INPUT_AXIS_X, dx); | |
339 | qemu_input_queue_rel(ddc->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->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; | |
373 | } | |
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(); | |
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->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->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_mode_change(Notifier *notify, void *data) | |
416 | { | |
417 | DBusDisplayConsole *ddc = | |
418 | container_of(notify, DBusDisplayConsole, mouse_mode_notifier); | |
419 | ||
420 | g_object_set(ddc->iface_mouse, | |
421 | "is-absolute", qemu_input_is_absolute(), | |
422 | NULL); | |
423 | } | |
424 | ||
425 | int dbus_display_console_get_index(DBusDisplayConsole *ddc) | |
426 | { | |
427 | return qemu_console_get_index(ddc->con); | |
428 | } | |
429 | ||
430 | DBusDisplayConsole * | |
431 | dbus_display_console_new(DBusDisplay *display, QemuConsole *con) | |
432 | { | |
433 | g_autofree char *path = NULL; | |
434 | g_autofree char *label = NULL; | |
435 | char device_addr[256] = ""; | |
436 | DBusDisplayConsole *ddc; | |
437 | int idx; | |
438 | ||
439 | assert(display); | |
440 | assert(con); | |
441 | ||
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, | |
447 | NULL); | |
448 | ddc->display = display; | |
449 | ddc->con = con; | |
450 | /* handle errors, and skip non graphics? */ | |
451 | qemu_console_fill_device_address( | |
452 | con, device_addr, sizeof(device_addr), NULL); | |
453 | ||
454 | ddc->iface = qemu_dbus_display1_console_skeleton_new(); | |
455 | g_object_set(ddc->iface, | |
456 | "label", label, | |
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, | |
462 | NULL); | |
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, | |
468 | NULL); | |
469 | g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), | |
470 | G_DBUS_INTERFACE_SKELETON(ddc->iface)); | |
471 | ||
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, | |
478 | NULL); | |
479 | g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), | |
480 | G_DBUS_INTERFACE_SKELETON(ddc->iface_kbd)); | |
481 | ||
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, | |
488 | NULL); | |
489 | g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), | |
490 | G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); | |
491 | ||
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); | |
495 | ||
496 | return ddc; | |
497 | } |