]>
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 "sysemu/sysemu.h" | |
26 | #include "dbus.h" | |
27 | #include <gio/gunixfdlist.h> | |
28 | ||
29 | #include "ui/shader.h" | |
30 | #include "ui/egl-helpers.h" | |
31 | #include "ui/egl-context.h" | |
32 | #include "trace.h" | |
33 | ||
34 | struct _DBusDisplayListener { | |
35 | GObject parent; | |
36 | ||
37 | char *bus_name; | |
38 | DBusDisplayConsole *console; | |
39 | GDBusConnection *conn; | |
40 | ||
41 | QemuDBusDisplay1Listener *proxy; | |
42 | ||
43 | DisplayChangeListener dcl; | |
44 | DisplaySurface *ds; | |
45 | QemuGLShader *gls; | |
46 | int gl_updates; | |
47 | }; | |
48 | ||
49 | G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT) | |
50 | ||
51 | static void dbus_update_gl_cb(GObject *source_object, | |
52 | GAsyncResult *res, | |
53 | gpointer user_data) | |
54 | { | |
55 | g_autoptr(GError) err = NULL; | |
56 | DBusDisplayListener *ddl = user_data; | |
57 | ||
58 | if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy, | |
59 | res, &err)) { | |
60 | error_report("Failed to call update: %s", err->message); | |
61 | } | |
62 | ||
63 | graphic_hw_gl_block(ddl->dcl.con, false); | |
64 | g_object_unref(ddl); | |
65 | } | |
66 | ||
67 | static void dbus_call_update_gl(DBusDisplayListener *ddl, | |
68 | int x, int y, int w, int h) | |
69 | { | |
70 | graphic_hw_gl_block(ddl->dcl.con, true); | |
71 | glFlush(); | |
72 | qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy, | |
73 | x, y, w, h, | |
74 | G_DBUS_CALL_FLAGS_NONE, | |
75 | DBUS_DEFAULT_TIMEOUT, NULL, | |
76 | dbus_update_gl_cb, | |
77 | g_object_ref(ddl)); | |
78 | } | |
79 | ||
80 | static void dbus_scanout_disable(DisplayChangeListener *dcl) | |
81 | { | |
82 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
83 | ||
84 | ddl->ds = NULL; | |
85 | qemu_dbus_display1_listener_call_disable( | |
86 | ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); | |
87 | } | |
88 | ||
89 | static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, | |
90 | QemuDmaBuf *dmabuf) | |
91 | { | |
92 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
93 | g_autoptr(GError) err = NULL; | |
94 | g_autoptr(GUnixFDList) fd_list = NULL; | |
95 | ||
96 | fd_list = g_unix_fd_list_new(); | |
97 | if (g_unix_fd_list_append(fd_list, dmabuf->fd, &err) != 0) { | |
98 | error_report("Failed to setup dmabuf fdlist: %s", err->message); | |
99 | return; | |
100 | } | |
101 | ||
102 | qemu_dbus_display1_listener_call_scanout_dmabuf( | |
103 | ddl->proxy, | |
104 | g_variant_new_handle(0), | |
105 | dmabuf->width, | |
106 | dmabuf->height, | |
107 | dmabuf->stride, | |
108 | dmabuf->fourcc, | |
109 | dmabuf->modifier, | |
110 | dmabuf->y0_top, | |
111 | G_DBUS_CALL_FLAGS_NONE, | |
112 | -1, | |
113 | fd_list, | |
114 | NULL, NULL, NULL); | |
115 | } | |
116 | ||
117 | static void dbus_scanout_texture(DisplayChangeListener *dcl, | |
118 | uint32_t tex_id, | |
119 | bool backing_y_0_top, | |
120 | uint32_t backing_width, | |
121 | uint32_t backing_height, | |
122 | uint32_t x, uint32_t y, | |
123 | uint32_t w, uint32_t h) | |
124 | { | |
125 | QemuDmaBuf dmabuf = { | |
126 | .width = backing_width, | |
127 | .height = backing_height, | |
128 | .y0_top = backing_y_0_top, | |
129 | }; | |
130 | ||
131 | assert(tex_id); | |
132 | dmabuf.fd = egl_get_fd_for_texture( | |
133 | tex_id, (EGLint *)&dmabuf.stride, | |
134 | (EGLint *)&dmabuf.fourcc, | |
135 | &dmabuf.modifier); | |
136 | if (dmabuf.fd < 0) { | |
137 | error_report("%s: failed to get fd for texture", __func__); | |
138 | return; | |
139 | } | |
140 | ||
141 | dbus_scanout_dmabuf(dcl, &dmabuf); | |
142 | close(dmabuf.fd); | |
143 | } | |
144 | ||
145 | static void dbus_cursor_dmabuf(DisplayChangeListener *dcl, | |
146 | QemuDmaBuf *dmabuf, bool have_hot, | |
147 | uint32_t hot_x, uint32_t hot_y) | |
148 | { | |
149 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
150 | DisplaySurface *ds; | |
151 | GVariant *v_data = NULL; | |
152 | egl_fb cursor_fb; | |
153 | ||
154 | if (!dmabuf) { | |
155 | qemu_dbus_display1_listener_call_mouse_set( | |
156 | ddl->proxy, 0, 0, false, | |
157 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); | |
158 | return; | |
159 | } | |
160 | ||
161 | egl_dmabuf_import_texture(dmabuf); | |
162 | if (!dmabuf->texture) { | |
163 | return; | |
164 | } | |
165 | egl_fb_setup_for_tex(&cursor_fb, dmabuf->width, dmabuf->height, | |
166 | dmabuf->texture, false); | |
167 | ds = qemu_create_displaysurface(dmabuf->width, dmabuf->height); | |
168 | egl_fb_read(ds, &cursor_fb); | |
169 | ||
170 | v_data = g_variant_new_from_data( | |
171 | G_VARIANT_TYPE("ay"), | |
172 | surface_data(ds), | |
173 | surface_width(ds) * surface_height(ds) * 4, | |
174 | TRUE, | |
175 | (GDestroyNotify)qemu_free_displaysurface, | |
176 | ds); | |
177 | qemu_dbus_display1_listener_call_cursor_define( | |
178 | ddl->proxy, | |
179 | surface_width(ds), | |
180 | surface_height(ds), | |
181 | hot_x, | |
182 | hot_y, | |
183 | v_data, | |
184 | G_DBUS_CALL_FLAGS_NONE, | |
185 | -1, | |
186 | NULL, | |
187 | NULL, | |
188 | NULL); | |
189 | } | |
190 | ||
191 | static void dbus_cursor_position(DisplayChangeListener *dcl, | |
192 | uint32_t pos_x, uint32_t pos_y) | |
193 | { | |
194 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
195 | ||
196 | qemu_dbus_display1_listener_call_mouse_set( | |
197 | ddl->proxy, pos_x, pos_y, true, | |
198 | G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); | |
199 | } | |
200 | ||
201 | static void dbus_release_dmabuf(DisplayChangeListener *dcl, | |
202 | QemuDmaBuf *dmabuf) | |
203 | { | |
204 | dbus_scanout_disable(dcl); | |
205 | } | |
206 | ||
207 | static void dbus_scanout_update(DisplayChangeListener *dcl, | |
208 | uint32_t x, uint32_t y, | |
209 | uint32_t w, uint32_t h) | |
210 | { | |
211 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
212 | ||
213 | dbus_call_update_gl(ddl, x, y, w, h); | |
214 | } | |
215 | ||
216 | static void dbus_gl_refresh(DisplayChangeListener *dcl) | |
217 | { | |
218 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
219 | ||
220 | graphic_hw_update(dcl->con); | |
221 | ||
222 | if (!ddl->ds || qemu_console_is_gl_blocked(ddl->dcl.con)) { | |
223 | return; | |
224 | } | |
225 | ||
226 | if (ddl->gl_updates) { | |
227 | dbus_call_update_gl(ddl, 0, 0, | |
228 | surface_width(ddl->ds), surface_height(ddl->ds)); | |
229 | ddl->gl_updates = 0; | |
230 | } | |
231 | } | |
232 | ||
233 | static void dbus_refresh(DisplayChangeListener *dcl) | |
234 | { | |
235 | graphic_hw_update(dcl->con); | |
236 | } | |
237 | ||
238 | static void dbus_gl_gfx_update(DisplayChangeListener *dcl, | |
239 | int x, int y, int w, int h) | |
240 | { | |
241 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
242 | ||
243 | if (ddl->ds) { | |
244 | surface_gl_update_texture(ddl->gls, ddl->ds, x, y, w, h); | |
245 | } | |
246 | ||
247 | ddl->gl_updates++; | |
248 | } | |
249 | ||
250 | static void dbus_gfx_update(DisplayChangeListener *dcl, | |
251 | int x, int y, int w, int h) | |
252 | { | |
253 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
254 | pixman_image_t *img; | |
255 | GVariant *v_data; | |
256 | size_t stride; | |
257 | ||
258 | assert(ddl->ds); | |
259 | stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8); | |
260 | ||
261 | trace_dbus_update(x, y, w, h); | |
262 | ||
263 | /* make a copy, since gvariant only handles linear data */ | |
264 | img = pixman_image_create_bits(surface_format(ddl->ds), | |
265 | w, h, NULL, stride); | |
266 | pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img, | |
267 | x, y, 0, 0, 0, 0, w, h); | |
268 | ||
269 | v_data = g_variant_new_from_data( | |
270 | G_VARIANT_TYPE("ay"), | |
271 | pixman_image_get_data(img), | |
272 | pixman_image_get_stride(img) * h, | |
273 | TRUE, | |
274 | (GDestroyNotify)pixman_image_unref, | |
275 | img); | |
276 | qemu_dbus_display1_listener_call_update(ddl->proxy, | |
277 | x, y, w, h, pixman_image_get_stride(img), pixman_image_get_format(img), | |
278 | v_data, | |
279 | G_DBUS_CALL_FLAGS_NONE, | |
280 | DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); | |
281 | } | |
282 | ||
283 | static void dbus_gl_gfx_switch(DisplayChangeListener *dcl, | |
284 | struct DisplaySurface *new_surface) | |
285 | { | |
286 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
287 | ||
288 | if (ddl->ds) { | |
289 | surface_gl_destroy_texture(ddl->gls, ddl->ds); | |
290 | } | |
291 | ddl->ds = new_surface; | |
292 | if (ddl->ds) { | |
293 | int width = surface_width(ddl->ds); | |
294 | int height = surface_height(ddl->ds); | |
295 | ||
296 | surface_gl_create_texture(ddl->gls, ddl->ds); | |
297 | /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */ | |
298 | dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false, | |
299 | width, height, 0, 0, width, height); | |
300 | } | |
301 | } | |
302 | ||
303 | static void dbus_gfx_switch(DisplayChangeListener *dcl, | |
304 | struct DisplaySurface *new_surface) | |
305 | { | |
306 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
307 | GVariant *v_data = NULL; | |
308 | ||
309 | ddl->ds = new_surface; | |
310 | if (!ddl->ds) { | |
311 | /* why not call disable instead? */ | |
312 | return; | |
313 | } | |
314 | ||
315 | v_data = g_variant_new_from_data( | |
316 | G_VARIANT_TYPE("ay"), | |
317 | surface_data(ddl->ds), | |
318 | surface_stride(ddl->ds) * surface_height(ddl->ds), | |
319 | TRUE, | |
320 | (GDestroyNotify)pixman_image_unref, | |
321 | pixman_image_ref(ddl->ds->image)); | |
322 | qemu_dbus_display1_listener_call_scanout(ddl->proxy, | |
323 | surface_width(ddl->ds), | |
324 | surface_height(ddl->ds), | |
325 | surface_stride(ddl->ds), | |
326 | surface_format(ddl->ds), | |
327 | v_data, | |
328 | G_DBUS_CALL_FLAGS_NONE, | |
329 | DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); | |
330 | } | |
331 | ||
332 | static void dbus_mouse_set(DisplayChangeListener *dcl, | |
333 | int x, int y, int on) | |
334 | { | |
335 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
336 | ||
337 | qemu_dbus_display1_listener_call_mouse_set( | |
338 | ddl->proxy, x, y, on, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); | |
339 | } | |
340 | ||
341 | static void dbus_cursor_define(DisplayChangeListener *dcl, | |
342 | QEMUCursor *c) | |
343 | { | |
344 | DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); | |
345 | GVariant *v_data = NULL; | |
346 | ||
347 | cursor_get(c); | |
348 | v_data = g_variant_new_from_data( | |
349 | G_VARIANT_TYPE("ay"), | |
350 | c->data, | |
351 | c->width * c->height * 4, | |
352 | TRUE, | |
353 | (GDestroyNotify)cursor_put, | |
354 | c); | |
355 | ||
356 | qemu_dbus_display1_listener_call_cursor_define( | |
357 | ddl->proxy, | |
358 | c->width, | |
359 | c->height, | |
360 | c->hot_x, | |
361 | c->hot_y, | |
362 | v_data, | |
363 | G_DBUS_CALL_FLAGS_NONE, | |
364 | -1, | |
365 | NULL, | |
366 | NULL, | |
367 | NULL); | |
368 | } | |
369 | ||
370 | const DisplayChangeListenerOps dbus_gl_dcl_ops = { | |
371 | .dpy_name = "dbus-gl", | |
372 | .dpy_gfx_update = dbus_gl_gfx_update, | |
373 | .dpy_gfx_switch = dbus_gl_gfx_switch, | |
374 | .dpy_gfx_check_format = console_gl_check_format, | |
375 | .dpy_refresh = dbus_gl_refresh, | |
376 | .dpy_mouse_set = dbus_mouse_set, | |
377 | .dpy_cursor_define = dbus_cursor_define, | |
378 | ||
379 | .dpy_gl_scanout_disable = dbus_scanout_disable, | |
380 | .dpy_gl_scanout_texture = dbus_scanout_texture, | |
381 | .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf, | |
382 | .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf, | |
383 | .dpy_gl_cursor_position = dbus_cursor_position, | |
384 | .dpy_gl_release_dmabuf = dbus_release_dmabuf, | |
385 | .dpy_gl_update = dbus_scanout_update, | |
386 | }; | |
387 | ||
388 | const DisplayChangeListenerOps dbus_dcl_ops = { | |
389 | .dpy_name = "dbus", | |
390 | .dpy_gfx_update = dbus_gfx_update, | |
391 | .dpy_gfx_switch = dbus_gfx_switch, | |
392 | .dpy_refresh = dbus_refresh, | |
393 | .dpy_mouse_set = dbus_mouse_set, | |
394 | .dpy_cursor_define = dbus_cursor_define, | |
395 | }; | |
396 | ||
397 | static void | |
398 | dbus_display_listener_dispose(GObject *object) | |
399 | { | |
400 | DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object); | |
401 | ||
402 | unregister_displaychangelistener(&ddl->dcl); | |
403 | g_clear_object(&ddl->conn); | |
404 | g_clear_pointer(&ddl->bus_name, g_free); | |
405 | g_clear_object(&ddl->proxy); | |
406 | g_clear_pointer(&ddl->gls, qemu_gl_fini_shader); | |
407 | ||
408 | G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object); | |
409 | } | |
410 | ||
411 | static void | |
412 | dbus_display_listener_constructed(GObject *object) | |
413 | { | |
414 | DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object); | |
415 | ||
416 | if (display_opengl) { | |
417 | ddl->gls = qemu_gl_init_shader(); | |
418 | ddl->dcl.ops = &dbus_gl_dcl_ops; | |
419 | } else { | |
420 | ddl->dcl.ops = &dbus_dcl_ops; | |
421 | } | |
422 | ||
423 | G_OBJECT_CLASS(dbus_display_listener_parent_class)->constructed(object); | |
424 | } | |
425 | ||
426 | static void | |
427 | dbus_display_listener_class_init(DBusDisplayListenerClass *klass) | |
428 | { | |
429 | GObjectClass *object_class = G_OBJECT_CLASS(klass); | |
430 | ||
431 | object_class->dispose = dbus_display_listener_dispose; | |
432 | object_class->constructed = dbus_display_listener_constructed; | |
433 | } | |
434 | ||
435 | static void | |
436 | dbus_display_listener_init(DBusDisplayListener *ddl) | |
437 | { | |
438 | } | |
439 | ||
440 | const char * | |
441 | dbus_display_listener_get_bus_name(DBusDisplayListener *ddl) | |
442 | { | |
99997823 | 443 | return ddl->bus_name ?: "p2p"; |
142ca628 MAL |
444 | } |
445 | ||
446 | DBusDisplayConsole * | |
447 | dbus_display_listener_get_console(DBusDisplayListener *ddl) | |
448 | { | |
449 | return ddl->console; | |
450 | } | |
451 | ||
452 | DBusDisplayListener * | |
453 | dbus_display_listener_new(const char *bus_name, | |
454 | GDBusConnection *conn, | |
455 | DBusDisplayConsole *console) | |
456 | { | |
457 | DBusDisplayListener *ddl; | |
458 | QemuConsole *con; | |
459 | g_autoptr(GError) err = NULL; | |
460 | ||
461 | ddl = g_object_new(DBUS_DISPLAY_TYPE_LISTENER, NULL); | |
462 | ddl->proxy = | |
463 | qemu_dbus_display1_listener_proxy_new_sync(conn, | |
464 | G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, | |
465 | NULL, | |
466 | "/org/qemu/Display1/Listener", | |
467 | NULL, | |
468 | &err); | |
469 | if (!ddl->proxy) { | |
470 | error_report("Failed to setup proxy: %s", err->message); | |
471 | g_object_unref(conn); | |
472 | g_object_unref(ddl); | |
473 | return NULL; | |
474 | } | |
475 | ||
476 | ddl->bus_name = g_strdup(bus_name); | |
477 | ddl->conn = conn; | |
478 | ddl->console = console; | |
479 | ||
480 | con = qemu_console_lookup_by_index(dbus_display_console_get_index(console)); | |
481 | assert(con); | |
482 | ddl->dcl.con = con; | |
483 | register_displaychangelistener(&ddl->dcl); | |
484 | ||
485 | return ddl; | |
486 | } |