master
c 456 lines 13.3 KB
Raw
1 /*
2 * D-Bus display listener — scanout, update and cursor handling.
3 *
4 * Copyright (C) 2026 Red Hat, Inc.
5 *
6 * SPDX-License-Identifier: GPL-2.0-or-later
7 */
8
9 #include "qemu/osdep.h"
10
11 #include "qemu/sockets.h"
12 #include "qemu/error-report.h"
13 #include "ui/console-priv.h"
14 #include "ui/dbus-display1.h"
15 #include "ui/surface.h"
16 #include "trace.h"
17 #include "qemu-vnc.h"
18
19 typedef struct ConsoleData {
20 QemuDBusDisplay1Console *console_proxy;
21 QemuDBusDisplay1Keyboard *keyboard_proxy;
22 QemuDBusDisplay1Mouse *mouse_proxy;
23 QemuGraphicConsole *gfx_con;
24 GDBusConnection *listener_conn;
25 /*
26 * When true the surface is backed by a read-only mmap (ScanoutMap path)
27 * and Update messages must be rejected because compositing into the
28 * surface is not possible. The plain Scanout path provides a writable
29 * copy and clears this flag.
30 */
31 bool read_only;
32 } ConsoleData;
33
34 static void display_ui_info(void *opaque, uint32_t head, QemuUIInfo *info)
35 {
36 ConsoleData *cd = opaque;
37 g_autoptr(GError) err = NULL;
38
39 if (!cd || !cd->console_proxy) {
40 return;
41 }
42
43 qemu_dbus_display1_console_call_set_uiinfo_sync(
44 cd->console_proxy,
45 info->width_mm, info->height_mm,
46 info->xoff, info->yoff,
47 info->width, info->height,
48 G_DBUS_CALL_FLAGS_NONE, -1, NULL, &err);
49 if (err) {
50 error_report("SetUIInfo failed: %s", err->message);
51 }
52 }
53
54 static void
55 scanout_image_destroy(pixman_image_t *image, void *data)
56 {
57 g_variant_unref(data);
58 }
59
60 typedef struct {
61 void *addr;
62 size_t len;
63 } ScanoutMapData;
64
65 static void
66 scanout_map_destroy(pixman_image_t *image, void *data)
67 {
68 ScanoutMapData *map = data;
69 munmap(map->addr, map->len);
70 g_free(map);
71 }
72
73 static gboolean
74 on_scanout(QemuDBusDisplay1Listener *listener,
75 GDBusMethodInvocation *invocation,
76 guint width, guint height, guint stride,
77 guint pixman_format, GVariant *data,
78 gpointer user_data)
79 {
80 ConsoleData *cd = user_data;
81 QemuConsole *con = QEMU_CONSOLE(cd->gfx_con);
82 gsize size;
83 const uint8_t *pixels;
84 pixman_image_t *image;
85 DisplaySurface *surface;
86
87 trace_qemu_vnc_scanout(width, height, stride, pixman_format);
88
89 pixels = g_variant_get_fixed_array(data, &size, 1);
90
91 image = pixman_image_create_bits((pixman_format_code_t)pixman_format,
92 width, height, (uint32_t *)pixels, stride);
93 assert(image);
94
95 g_variant_ref(data);
96 pixman_image_set_destroy_function(image, scanout_image_destroy, data);
97
98 cd->read_only = false;
99 surface = qemu_create_displaysurface_pixman(image);
100 qemu_console_set_surface(con, surface);
101
102 qemu_dbus_display1_listener_complete_scanout(listener, invocation);
103 return DBUS_METHOD_INVOCATION_HANDLED;
104 }
105
106 static gboolean
107 on_update(QemuDBusDisplay1Listener *listener,
108 GDBusMethodInvocation *invocation,
109 gint x, gint y, gint w, gint h,
110 guint stride, guint pixman_format, GVariant *data,
111 gpointer user_data)
112 {
113 ConsoleData *cd = user_data;
114 QemuConsole *con = QEMU_CONSOLE(cd->gfx_con);
115 DisplaySurface *surface = qemu_console_surface(con);
116 gsize size;
117 const uint8_t *pixels;
118 pixman_image_t *src;
119
120 trace_qemu_vnc_update(x, y, w, h, stride, pixman_format);
121 if (!surface || cd->read_only) {
122 g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
123 G_DBUS_ERROR_FAILED, "No active or writable console");
124 return DBUS_METHOD_INVOCATION_HANDLED;
125 }
126
127 pixels = g_variant_get_fixed_array(data, &size, 1);
128 src = pixman_image_create_bits((pixman_format_code_t)pixman_format,
129 w, h, (uint32_t *)pixels, stride);
130 assert(src);
131 pixman_image_composite(PIXMAN_OP_SRC, src, NULL,
132 surface->image,
133 0, 0, 0, 0, x, y, w, h);
134 pixman_image_unref(src);
135
136 qemu_console_update(con, x, y, w, h);
137
138 qemu_dbus_display1_listener_complete_update(listener, invocation);
139 return DBUS_METHOD_INVOCATION_HANDLED;
140 }
141
142 static gboolean
143 on_scanout_map(QemuDBusDisplay1ListenerUnixMap *listener,
144 GDBusMethodInvocation *invocation,
145 GUnixFDList *fd_list,
146 GVariant *arg_handle,
147 guint offset, guint width, guint height,
148 guint stride, guint pixman_format,
149 gpointer user_data)
150 {
151 ConsoleData *cd = user_data;
152 gint32 handle = g_variant_get_handle(arg_handle);
153 g_autoptr(GError) err = NULL;
154 DisplaySurface *surface;
155 int fd;
156 void *addr;
157 size_t len = (size_t)height * stride;
158 pixman_image_t *image;
159
160 trace_qemu_vnc_scanout_map(width, height, stride, pixman_format, offset);
161
162 fd = g_unix_fd_list_get(fd_list, handle, &err);
163 if (fd < 0) {
164 g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
165 G_DBUS_ERROR_FAILED, "Failed to get fd: %s", err->message);
166 return DBUS_METHOD_INVOCATION_HANDLED;
167 }
168
169 /* MAP_PRIVATE: we only read; avoid propagating writes back to QEMU */
170 addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, offset);
171 close(fd);
172 if (addr == MAP_FAILED) {
173 g_dbus_method_invocation_return_error(invocation, G_DBUS_ERROR,
174 G_DBUS_ERROR_FAILED, "mmap failed: %s", g_strerror(errno));
175 return DBUS_METHOD_INVOCATION_HANDLED;
176 }
177
178 image = pixman_image_create_bits((pixman_format_code_t)pixman_format,
179 width, height, addr, stride);
180 assert(image);
181 {
182 ScanoutMapData *map = g_new0(ScanoutMapData, 1);
183 map->addr = addr;
184 map->len = len;
185 pixman_image_set_destroy_function(image, scanout_map_destroy, map);
186 }
187
188 cd->read_only = true;
189 surface = qemu_create_displaysurface_pixman(image);
190 qemu_console_set_surface(QEMU_CONSOLE(cd->gfx_con), surface);
191
192 qemu_dbus_display1_listener_unix_map_complete_scanout_map(
193 listener, invocation, NULL);
194 return DBUS_METHOD_INVOCATION_HANDLED;
195 }
196
197 static gboolean
198 on_update_map(QemuDBusDisplay1ListenerUnixMap *listener,
199 GDBusMethodInvocation *invocation,
200 guint x, guint y, guint w, guint h,
201 gpointer user_data)
202 {
203 ConsoleData *cd = user_data;
204
205 trace_qemu_vnc_update_map(x, y, w, h);
206
207 qemu_console_update(QEMU_CONSOLE(cd->gfx_con), x, y, w, h);
208
209 qemu_dbus_display1_listener_unix_map_complete_update_map(
210 listener, invocation);
211 return DBUS_METHOD_INVOCATION_HANDLED;
212 }
213
214 static gboolean
215 on_cursor_define(QemuDBusDisplay1Listener *listener,
216 GDBusMethodInvocation *invocation,
217 gint width, gint height,
218 gint hot_x, gint hot_y,
219 GVariant *data,
220 gpointer user_data)
221 {
222 ConsoleData *cd = user_data;
223 gsize size;
224 const uint8_t *pixels;
225 QEMUCursor *c;
226
227 trace_qemu_vnc_cursor_define(width, height, hot_x, hot_y);
228
229 c = cursor_alloc(width, height);
230 if (!c) {
231 qemu_dbus_display1_listener_complete_cursor_define(
232 listener, invocation);
233 return DBUS_METHOD_INVOCATION_HANDLED;
234 }
235
236 c->hot_x = hot_x;
237 c->hot_y = hot_y;
238
239 pixels = g_variant_get_fixed_array(data, &size, 1);
240 memcpy(c->data, pixels, MIN(size, (gsize)width * height * 4));
241
242 qemu_console_set_cursor(QEMU_CONSOLE(cd->gfx_con), c);
243 cursor_unref(c);
244
245 qemu_dbus_display1_listener_complete_cursor_define(
246 listener, invocation);
247 return DBUS_METHOD_INVOCATION_HANDLED;
248 }
249
250 typedef struct {
251 GMainLoop *loop;
252 GThread *thread;
253 GDBusConnection *listener_conn;
254 } ListenerSetupData;
255
256 static void
257 on_register_listener_finished(GObject *source_object,
258 GAsyncResult *res,
259 gpointer user_data)
260 {
261 ListenerSetupData *data = user_data;
262 g_autoptr(GError) err = NULL;
263
264 qemu_dbus_display1_console_call_register_listener_finish(
265 QEMU_DBUS_DISPLAY1_CONSOLE(source_object),
266 NULL,
267 res, &err);
268
269 if (err) {
270 error_report("RegisterListener failed: %s", err->message);
271 g_main_loop_quit(data->loop);
272 return;
273 }
274
275 data->listener_conn = g_thread_join(data->thread);
276 g_main_loop_quit(data->loop);
277 }
278
279 static GDBusConnection *
280 console_register_display_listener(QemuDBusDisplay1Console *console)
281 {
282 g_autoptr(GError) err = NULL;
283 g_autoptr(GMainLoop) loop = NULL;
284 g_autoptr(GUnixFDList) fd_list = NULL;
285 ListenerSetupData data = { 0 };
286 int pair[2];
287 int idx;
288
289 if (qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
290 error_report("socketpair failed: %s", strerror(errno));
291 return NULL;
292 }
293
294 fd_list = g_unix_fd_list_new();
295 idx = g_unix_fd_list_append(fd_list, pair[1], &err);
296 close(pair[1]);
297 if (idx < 0) {
298 close(pair[0]);
299 error_report("Failed to append fd: %s", err->message);
300 return NULL;
301 }
302
303 loop = g_main_loop_new(NULL, FALSE);
304 data.loop = loop;
305 data.thread = p2p_dbus_thread_new(pair[0]);
306
307 qemu_dbus_display1_console_call_register_listener(
308 console,
309 g_variant_new_handle(idx),
310 G_DBUS_CALL_FLAGS_NONE,
311 -1,
312 fd_list,
313 NULL,
314 on_register_listener_finished,
315 &data);
316
317 g_main_loop_run(loop);
318
319 return data.listener_conn;
320 }
321
322 static void
323 setup_display_listener(ConsoleData *cd)
324 {
325 g_autoptr(GDBusObjectSkeleton) obj = NULL;
326 GDBusObjectManagerServer *server;
327 QemuDBusDisplay1Listener *iface;
328 QemuDBusDisplay1ListenerUnixMap *iface_map;
329
330 server = g_dbus_object_manager_server_new(DBUS_DISPLAY1_ROOT);
331 obj = g_dbus_object_skeleton_new(DBUS_DISPLAY1_ROOT "/Listener");
332
333 /* Main listener interface */
334 iface = qemu_dbus_display1_listener_skeleton_new();
335 g_object_connect(iface,
336 "signal::handle-scanout", on_scanout, cd,
337 "signal::handle-update", on_update, cd,
338 "signal::handle-cursor-define", on_cursor_define, cd,
339 NULL);
340 g_dbus_object_skeleton_add_interface(obj,
341 G_DBUS_INTERFACE_SKELETON(iface));
342
343 /* Unix shared memory map interface */
344 iface_map = qemu_dbus_display1_listener_unix_map_skeleton_new();
345 g_object_connect(iface_map,
346 "signal::handle-scanout-map", on_scanout_map, cd,
347 "signal::handle-update-map", on_update_map, cd,
348 NULL);
349 g_dbus_object_skeleton_add_interface(obj,
350 G_DBUS_INTERFACE_SKELETON(iface_map));
351
352 {
353 const gchar *ifaces[] = {
354 "org.qemu.Display1.Listener.Unix.Map", NULL
355 };
356 g_object_set(iface, "interfaces", ifaces, NULL);
357 }
358
359 g_dbus_object_manager_server_export(server, obj);
360 g_dbus_object_manager_server_set_connection(server,
361 cd->listener_conn);
362
363 g_dbus_connection_start_message_processing(cd->listener_conn);
364 }
365
366 static const GraphicHwOps vnc_hw_ops = {
367 .ui_info = display_ui_info,
368 };
369
370 bool console_setup(GDBusConnection *bus, const char *bus_name,
371 const char *console_path)
372 {
373 g_autoptr(GError) err = NULL;
374 ConsoleData *cd;
375 QemuConsole *con;
376
377 cd = g_new0(ConsoleData, 1);
378
379 cd->console_proxy = qemu_dbus_display1_console_proxy_new_sync(
380 bus, G_DBUS_PROXY_FLAGS_NONE, bus_name,
381 console_path, NULL, &err);
382 if (!cd->console_proxy) {
383 error_report("Failed to create console proxy for %s: %s",
384 console_path, err->message);
385 g_free(cd);
386 return false;
387 }
388
389 cd->keyboard_proxy = QEMU_DBUS_DISPLAY1_KEYBOARD(
390 qemu_dbus_display1_keyboard_proxy_new_sync(
391 bus, G_DBUS_PROXY_FLAGS_NONE, bus_name,
392 console_path, NULL, &err));
393 if (!cd->keyboard_proxy) {
394 error_report("Failed to create keyboard proxy for %s: %s",
395 console_path, err->message);
396 g_object_unref(cd->console_proxy);
397 g_free(cd);
398 return false;
399 }
400
401 g_clear_error(&err);
402 cd->mouse_proxy = QEMU_DBUS_DISPLAY1_MOUSE(
403 qemu_dbus_display1_mouse_proxy_new_sync(
404 bus, G_DBUS_PROXY_FLAGS_NONE, bus_name,
405 console_path, NULL, &err));
406 if (!cd->mouse_proxy) {
407 error_report("Failed to create mouse proxy for %s: %s",
408 console_path, err->message);
409 g_object_unref(cd->keyboard_proxy);
410 g_object_unref(cd->console_proxy);
411 g_free(cd);
412 return false;
413 }
414
415 con = qemu_graphic_console_create(NULL, 0, &vnc_hw_ops, cd);
416 cd->gfx_con = QEMU_GRAPHIC_CONSOLE(con);
417
418 cd->listener_conn = console_register_display_listener(
419 cd->console_proxy);
420 if (!cd->listener_conn) {
421 error_report("Failed to setup D-Bus listener for %s",
422 console_path);
423 g_object_unref(cd->mouse_proxy);
424 g_object_unref(cd->keyboard_proxy);
425 g_object_unref(cd->console_proxy);
426 g_free(cd);
427 return false;
428 }
429
430 setup_display_listener(cd);
431 input_setup(cd->keyboard_proxy, cd->mouse_proxy);
432
433 return true;
434 }
435
436 QemuDBusDisplay1Keyboard *console_get_keyboard(const QemuConsole *con)
437 {
438 ConsoleData *cd;
439
440 if (!QEMU_IS_GRAPHIC_CONSOLE(con)) {
441 return NULL;
442 }
443 cd = con->hw;
444 return cd ? cd->keyboard_proxy : NULL;
445 }
446
447 QemuDBusDisplay1Mouse *console_get_mouse(const QemuConsole *con)
448 {
449 ConsoleData *cd;
450
451 if (!QEMU_IS_GRAPHIC_CONSOLE(con)) {
452 return NULL;
453 }
454 cd = con->hw;
455 return cd ? cd->mouse_proxy : NULL;
456 }