diff options
42 files changed, 1953 insertions, 215 deletions
diff --git a/MAINTAINERS b/MAINTAINERS index e07746a..21a587c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3202,6 +3202,7 @@ F: docs/interop/dbus* F: docs/sphinx/dbus* F: docs/sphinx/fakedbusdoc.py F: tests/qtest/dbus* +F: scripts/xml-preprocess* Seccomp M: Daniel P. Berrange <berrange@redhat.com> diff --git a/audio/dbusaudio.c b/audio/dbusaudio.c index fece74f..7a11fbf 100644 --- a/audio/dbusaudio.c +++ b/audio/dbusaudio.c @@ -29,7 +29,11 @@ #include "qemu/timer.h" #include "qemu/dbus.h" +#ifdef G_OS_UNIX #include <gio/gunixfdlist.h> +#endif + +#include "ui/dbus.h" #include "ui/dbus-display1.h" #define AUDIO_CAP "dbus" @@ -444,7 +448,9 @@ listener_in_vanished_cb(GDBusConnection *connection, static gboolean dbus_audio_register_listener(AudioState *s, GDBusMethodInvocation *invocation, +#ifdef G_OS_UNIX GUnixFDList *fd_list, +#endif GVariant *arg_listener, bool out) { @@ -471,6 +477,11 @@ dbus_audio_register_listener(AudioState *s, return DBUS_METHOD_INVOCATION_HANDLED; } +#ifdef G_OS_WIN32 + if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } +#else fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); if (err) { g_dbus_method_invocation_return_error(invocation, @@ -480,6 +491,7 @@ dbus_audio_register_listener(AudioState *s, err->message); return DBUS_METHOD_INVOCATION_HANDLED; } +#endif socket = g_socket_new_from_fd(fd, &err); if (err) { @@ -488,15 +500,28 @@ dbus_audio_register_listener(AudioState *s, DBUS_DISPLAY_ERROR_FAILED, "Couldn't make a socket: %s", err->message); +#ifdef G_OS_WIN32 + closesocket(fd); +#else + close(fd); +#endif return DBUS_METHOD_INVOCATION_HANDLED; } socket_conn = g_socket_connection_factory_create_connection(socket); if (out) { qemu_dbus_display1_audio_complete_register_out_listener( - da->iface, invocation, NULL); + da->iface, invocation +#ifdef G_OS_UNIX + , NULL +#endif + ); } else { qemu_dbus_display1_audio_complete_register_in_listener( - da->iface, invocation, NULL); + da->iface, invocation +#ifdef G_OS_UNIX + , NULL +#endif + ); } listener_conn = @@ -574,22 +599,32 @@ dbus_audio_register_listener(AudioState *s, static gboolean dbus_audio_register_out_listener(AudioState *s, GDBusMethodInvocation *invocation, +#ifdef G_OS_UNIX GUnixFDList *fd_list, +#endif GVariant *arg_listener) { return dbus_audio_register_listener(s, invocation, - fd_list, arg_listener, true); +#ifdef G_OS_UNIX + fd_list, +#endif + arg_listener, true); } static gboolean dbus_audio_register_in_listener(AudioState *s, GDBusMethodInvocation *invocation, +#ifdef G_OS_UNIX GUnixFDList *fd_list, +#endif GVariant *arg_listener) { return dbus_audio_register_listener(s, invocation, - fd_list, arg_listener, false); +#ifdef G_OS_UNIX + fd_list, +#endif + arg_listener, false); } static void diff --git a/chardev/char-win-stdio.c b/chardev/char-win-stdio.c index eb830ea..1a18999 100644 --- a/chardev/char-win-stdio.c +++ b/chardev/char-win-stdio.c @@ -190,7 +190,7 @@ static void qemu_chr_open_stdio(Chardev *chr, } } - dwMode |= ENABLE_LINE_INPUT; + dwMode |= ENABLE_LINE_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT; if (is_console) { /* set the terminal in raw mode */ diff --git a/hw/display/virtio-gpu-udmabuf.c b/hw/display/virtio-gpu-udmabuf.c index 69e2cf0..ef1a740 100644 --- a/hw/display/virtio-gpu-udmabuf.c +++ b/hw/display/virtio-gpu-udmabuf.c @@ -132,7 +132,8 @@ void virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res) void *pdata = NULL; res->dmabuf_fd = -1; - if (res->iov_cnt == 1) { + if (res->iov_cnt == 1 && + res->iov[0].iov_len < 4096) { pdata = res->iov[0].iov_base; } else { virtio_gpu_create_udmabuf(res); diff --git a/hw/display/virtio-gpu-virgl.c b/hw/display/virtio-gpu-virgl.c index 1c47603..8bb7a2c 100644 --- a/hw/display/virtio-gpu-virgl.c +++ b/hw/display/virtio-gpu-virgl.c @@ -18,9 +18,17 @@ #include "hw/virtio/virtio.h" #include "hw/virtio/virtio-gpu.h" +#include "ui/egl-helpers.h" + #include <virglrenderer.h> -static struct virgl_renderer_callbacks virtio_gpu_3d_cbs; +#if VIRGL_RENDERER_CALLBACKS_VERSION >= 4 +static void * +virgl_get_egl_display(G_GNUC_UNUSED void *cookie) +{ + return qemu_egl_display; +} +#endif static void virgl_cmd_create_resource_2d(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) @@ -145,7 +153,6 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { struct virtio_gpu_set_scanout ss; - struct virgl_renderer_resource_info info; int ret; VIRTIO_GPU_FILL_CMD(ss); @@ -160,10 +167,20 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g, } g->parent_obj.enable = 1; - memset(&info, 0, sizeof(info)); - if (ss.resource_id && ss.r.width && ss.r.height) { + struct virgl_renderer_resource_info info; + void *d3d_tex2d = NULL; + +#ifdef HAVE_VIRGL_D3D_INFO_EXT + struct virgl_renderer_resource_info_ext ext; + memset(&ext, 0, sizeof(ext)); + ret = virgl_renderer_resource_get_info_ext(ss.resource_id, &ext); + info = ext.base; + d3d_tex2d = ext.d3d_tex2d; +#else + memset(&info, 0, sizeof(info)); ret = virgl_renderer_resource_get_info(ss.resource_id, &info); +#endif if (ret == -1) { qemu_log_mask(LOG_GUEST_ERROR, "%s: illegal resource specified %d\n", @@ -178,7 +195,8 @@ static void virgl_cmd_set_scanout(VirtIOGPU *g, g->parent_obj.scanout[ss.scanout_id].con, info.tex_id, info.flags & VIRTIO_GPU_RESOURCE_FLAG_Y_0_TOP, info.width, info.height, - ss.r.x, ss.r.y, ss.r.width, ss.r.height); + ss.r.x, ss.r.y, ss.r.width, ss.r.height, + d3d_tex2d); } else { dpy_gfx_replace_surface( g->parent_obj.scanout[ss.scanout_id].con, NULL); @@ -607,8 +625,21 @@ void virtio_gpu_virgl_reset(VirtIOGPU *g) int virtio_gpu_virgl_init(VirtIOGPU *g) { int ret; + uint32_t flags = 0; + +#if VIRGL_RENDERER_CALLBACKS_VERSION >= 4 + if (qemu_egl_display) { + virtio_gpu_3d_cbs.version = 4; + virtio_gpu_3d_cbs.get_egl_display = virgl_get_egl_display; + } +#endif +#ifdef VIRGL_RENDERER_D3D11_SHARE_TEXTURE + if (qemu_egl_angle_d3d) { + flags |= VIRGL_RENDERER_D3D11_SHARE_TEXTURE; + } +#endif - ret = virgl_renderer_init(g, 0, &virtio_gpu_3d_cbs); + ret = virgl_renderer_init(g, flags, &virtio_gpu_3d_cbs); if (ret != 0) { error_report("virgl could not be initialized: %d", ret); return ret; diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c index 66cddd9..347e17d 100644 --- a/hw/display/virtio-gpu.c +++ b/hw/display/virtio-gpu.c @@ -258,6 +258,16 @@ static uint32_t calc_image_hostmem(pixman_format_code_t pformat, return height * stride; } +#ifdef WIN32 +static void +win32_pixman_image_destroy(pixman_image_t *image, void *data) +{ + HANDLE handle = data; + + qemu_win32_map_free(pixman_image_get_data(image), handle, &error_warn); +} +#endif + static void virtio_gpu_resource_create_2d(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { @@ -304,12 +314,27 @@ static void virtio_gpu_resource_create_2d(VirtIOGPU *g, res->hostmem = calc_image_hostmem(pformat, c2d.width, c2d.height); if (res->hostmem + g->hostmem < g->conf_max_hostmem) { + void *bits = NULL; +#ifdef WIN32 + bits = qemu_win32_map_alloc(res->hostmem, &res->handle, &error_warn); + if (!bits) { + goto end; + } +#endif res->image = pixman_image_create_bits(pformat, c2d.width, c2d.height, - NULL, 0); + bits, res->hostmem / c2d.height); +#ifdef WIN32 + if (res->image) { + pixman_image_set_destroy_function(res->image, win32_pixman_image_destroy, res->handle); + } +#endif } +#ifdef WIN32 +end: +#endif if (!res->image) { qemu_log_mask(LOG_GUEST_ERROR, "%s: resource creation failed %d %d %d\n", @@ -438,11 +463,11 @@ static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g, struct virtio_gpu_ctrl_command *cmd) { struct virtio_gpu_simple_resource *res; - int h; + int h, bpp; uint32_t src_offset, dst_offset, stride; - int bpp; pixman_format_code_t format; struct virtio_gpu_transfer_to_host_2d t2d; + void *img_data; VIRTIO_GPU_FILL_CMD(t2d); virtio_gpu_t2d_bswap(&t2d); @@ -471,23 +496,23 @@ static void virtio_gpu_transfer_to_host_2d(VirtIOGPU *g, format = pixman_image_get_format(res->image); bpp = DIV_ROUND_UP(PIXMAN_FORMAT_BPP(format), 8); stride = pixman_image_get_stride(res->image); + img_data = pixman_image_get_data(res->image); - if (t2d.offset || t2d.r.x || t2d.r.y || - t2d.r.width != pixman_image_get_width(res->image)) { - void *img_data = pixman_image_get_data(res->image); + if (t2d.r.x || t2d.r.width != pixman_image_get_width(res->image)) { for (h = 0; h < t2d.r.height; h++) { src_offset = t2d.offset + stride * h; dst_offset = (t2d.r.y + h) * stride + (t2d.r.x * bpp); iov_to_buf(res->iov, res->iov_cnt, src_offset, - (uint8_t *)img_data - + dst_offset, t2d.r.width * bpp); + (uint8_t *)img_data + dst_offset, + t2d.r.width * bpp); } } else { - iov_to_buf(res->iov, res->iov_cnt, 0, - pixman_image_get_data(res->image), - pixman_image_get_stride(res->image) - * pixman_image_get_height(res->image)); + src_offset = t2d.offset; + dst_offset = t2d.r.y * stride + t2d.r.x * bpp; + iov_to_buf(res->iov, res->iov_cnt, src_offset, + (uint8_t *)img_data + dst_offset, + stride * t2d.r.height); } } @@ -498,6 +523,8 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g, struct virtio_gpu_resource_flush rf; struct virtio_gpu_scanout *scanout; pixman_region16_t flush_region; + bool within_bounds = false; + bool update_submitted = false; int i; VIRTIO_GPU_FILL_CMD(rf); @@ -518,13 +545,28 @@ static void virtio_gpu_resource_flush(VirtIOGPU *g, rf.r.x < scanout->x + scanout->width && rf.r.x + rf.r.width >= scanout->x && rf.r.y < scanout->y + scanout->height && - rf.r.y + rf.r.height >= scanout->y && - console_has_gl(scanout->con)) { - dpy_gl_update(scanout->con, 0, 0, scanout->width, - scanout->height); + rf.r.y + rf.r.height >= scanout->y) { + within_bounds = true; + + if (console_has_gl(scanout->con)) { + dpy_gl_update(scanout->con, 0, 0, scanout->width, + scanout->height); + update_submitted = true; + } } } - return; + + if (update_submitted) { + return; + } + if (!within_bounds) { + qemu_log_mask(LOG_GUEST_ERROR, "%s: flush bounds outside scanouts" + " bounds for flush %d: %d %d %d %d\n", + __func__, rf.resource_id, rf.r.x, rf.r.y, + rf.r.width, rf.r.height); + cmd->error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER; + return; + } } if (!res->blob && @@ -634,8 +676,10 @@ static void virtio_gpu_do_set_scanout(VirtIOGPU *g, if (console_has_gl(scanout->con)) { if (!virtio_gpu_update_dmabuf(g, scanout_id, res, fb, r)) { virtio_gpu_update_scanout(g, scanout_id, res, r); - return; + } else { + *error = VIRTIO_GPU_RESP_ERR_OUT_OF_MEMORY; } + return; } data = res->blob; @@ -666,6 +710,9 @@ static void virtio_gpu_do_set_scanout(VirtIOGPU *g, *error = VIRTIO_GPU_RESP_ERR_UNSPEC; return; } +#ifdef WIN32 + qemu_displaysurface_win32_set_handle(scanout->ds, res->handle, fb->offset); +#endif pixman_image_unref(rect); dpy_gfx_replace_surface(g->parent_obj.scanout[scanout_id].con, @@ -1209,6 +1256,7 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size, struct virtio_gpu_simple_resource *res; struct virtio_gpu_scanout *scanout; uint32_t resource_id, pformat; + void *bits = NULL; int i; g->hostmem = 0; @@ -1233,15 +1281,23 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size, g_free(res); return -EINVAL; } + + res->hostmem = calc_image_hostmem(pformat, res->width, res->height); +#ifdef WIN32 + bits = qemu_win32_map_alloc(res->hostmem, &res->handle, &error_warn); + if (!bits) { + g_free(res); + return -EINVAL; + } +#endif res->image = pixman_image_create_bits(pformat, res->width, res->height, - NULL, 0); + bits, res->hostmem / res->height); if (!res->image) { g_free(res); return -EINVAL; } - res->hostmem = calc_image_hostmem(pformat, res->width, res->height); res->addrs = g_new(uint64_t, res->iov_cnt); res->iov = g_new(struct iovec, res->iov_cnt); @@ -1302,6 +1358,9 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size, if (!scanout->ds) { return -EINVAL; } +#ifdef WIN32 + qemu_displaysurface_win32_set_handle(scanout->ds, res->handle, 0); +#endif dpy_gfx_replace_surface(scanout->con, scanout->ds); dpy_gfx_update_full(scanout->con); diff --git a/include/hw/virtio/virtio-gpu.h b/include/hw/virtio/virtio-gpu.h index 2e28507..7a5f805 100644 --- a/include/hw/virtio/virtio-gpu.h +++ b/include/hw/virtio/virtio-gpu.h @@ -48,6 +48,9 @@ struct virtio_gpu_simple_resource { unsigned int iov_cnt; uint32_t scanout_bitmask; pixman_image_t *image; +#ifdef WIN32 + HANDLE handle; +#endif uint64_t hostmem; uint64_t blob_size; diff --git a/include/sysemu/os-win32.h b/include/sysemu/os-win32.h index 65f6c9e..91aa0d7 100644 --- a/include/sysemu/os-win32.h +++ b/include/sysemu/os-win32.h @@ -263,6 +263,9 @@ EXCEPTION_DISPOSITION win32_close_exception_handler(struct _EXCEPTION_RECORD*, void*, struct _CONTEXT*, void*); +void *qemu_win32_map_alloc(size_t size, HANDLE *h, Error **errp); +void qemu_win32_map_free(void *ptr, HANDLE h, Error **errp); + #ifdef __cplusplus } #endif diff --git a/include/ui/console.h b/include/ui/console.h index ae5ec46..f27b2aa 100644 --- a/include/ui/console.h +++ b/include/ui/console.h @@ -5,6 +5,7 @@ #include "qom/object.h" #include "qemu/notify.h" #include "qapi/qapi-types-ui.h" +#include "ui/input.h" #ifdef CONFIG_OPENGL # include <epoxy/gl.h> @@ -95,6 +96,20 @@ bool kbd_put_qcode_console(QemuConsole *s, int qcode, bool ctrl); void kbd_put_string_console(QemuConsole *s, const char *str, int len); void kbd_put_keysym(int keysym); +/* Touch devices */ +typedef struct touch_slot { + int x; + int y; + int tracking_id; +} touch_slot; + +void console_handle_touch_event(QemuConsole *con, + struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX], + uint64_t num_slot, + int width, int height, + double x, double y, + InputMultiTouchType type, + Error **errp); /* consoles */ #define TYPE_QEMU_CONSOLE "qemu-console" @@ -117,6 +132,7 @@ typedef struct ScanoutTexture { uint32_t y; uint32_t width; uint32_t height; + void *d3d_tex2d; } ScanoutTexture; typedef struct DisplaySurface { @@ -128,6 +144,10 @@ typedef struct DisplaySurface { GLenum gltype; GLuint texture; #endif +#ifdef WIN32 + HANDLE handle; + uint32_t handle_offset; +#endif } DisplaySurface; typedef struct QemuUIInfo { @@ -251,7 +271,8 @@ typedef struct DisplayChangeListenerOps { uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h); + uint32_t w, uint32_t h, + void *d3d_tex2d); /* optional (default to true if has dpy_gl_scanout_dmabuf) */ bool (*dpy_has_dmabuf)(DisplayChangeListener *dcl); /* optional */ @@ -314,6 +335,10 @@ DisplaySurface *qemu_create_displaysurface_from(int width, int height, DisplaySurface *qemu_create_displaysurface_pixman(pixman_image_t *image); DisplaySurface *qemu_create_placeholder_surface(int w, int h, const char *msg); +#ifdef WIN32 +void qemu_displaysurface_win32_set_handle(DisplaySurface *surface, + HANDLE h, uint32_t offset); +#endif PixelFormat qemu_default_pixelformat(int bpp); DisplaySurface *qemu_create_displaysurface(int width, int height); @@ -355,7 +380,8 @@ void dpy_gl_scanout_disable(QemuConsole *con); void dpy_gl_scanout_texture(QemuConsole *con, uint32_t backing_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, - uint32_t x, uint32_t y, uint32_t w, uint32_t h); + uint32_t x, uint32_t y, uint32_t w, uint32_t h, + void *d3d_tex2d); void dpy_gl_scanout_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf); void dpy_gl_cursor_dmabuf(QemuConsole *con, QemuDmaBuf *dmabuf, diff --git a/include/ui/egl-helpers.h b/include/ui/egl-helpers.h index 53d953d..4b8c0d2 100644 --- a/include/ui/egl-helpers.h +++ b/include/ui/egl-helpers.h @@ -12,6 +12,7 @@ extern EGLDisplay *qemu_egl_display; extern EGLConfig qemu_egl_config; extern DisplayGLMode qemu_egl_mode; +extern bool qemu_egl_angle_d3d; typedef struct egl_fb { int width; @@ -31,16 +32,18 @@ void egl_fb_setup_for_tex(egl_fb *fb, int width, int height, void egl_fb_setup_new_tex(egl_fb *fb, int width, int height); void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip); void egl_fb_read(DisplaySurface *dst, egl_fb *src); +void egl_fb_read_rect(DisplaySurface *dst, egl_fb *src, int x, int y, int w, int h); void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip); void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip, int x, int y, double scale_x, double scale_y); +extern EGLContext qemu_egl_rn_ctx; + #ifdef CONFIG_GBM extern int qemu_egl_rn_fd; extern struct gbm_device *qemu_egl_rn_gbm_dev; -extern EGLContext qemu_egl_rn_ctx; int egl_rendernode_init(const char *rendernode, DisplayGLMode mode); int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc, @@ -62,9 +65,15 @@ int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode); #endif +#ifdef WIN32 +int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode); +#endif + EGLContext qemu_egl_init_ctx(void); bool qemu_egl_has_dmabuf(void); bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp); +const char *qemu_egl_get_error_string(void); + #endif /* EGL_HELPERS_H */ diff --git a/include/ui/gtk.h b/include/ui/gtk.h index ae0f537..aa3d637 100644 --- a/include/ui/gtk.h +++ b/include/ui/gtk.h @@ -175,7 +175,8 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h); + uint32_t w, uint32_t h, + void *d3d_tex2d); void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf); void gd_egl_cursor_dmabuf(DisplayChangeListener *dcl, @@ -211,7 +212,8 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h); + uint32_t w, uint32_t h, + void *d3d_tex2d); void gd_gl_area_scanout_disable(DisplayChangeListener *dcl); void gd_gl_area_scanout_flush(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h); diff --git a/include/ui/sdl2.h b/include/ui/sdl2.h index 8fb7e08..e3acc7c 100644 --- a/include/ui/sdl2.h +++ b/include/ui/sdl2.h @@ -90,7 +90,8 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h); + uint32_t w, uint32_t h, + void *d3d_tex2d); void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h); diff --git a/meson.build b/meson.build index b409788..1631e60 100644 --- a/meson.build +++ b/meson.build @@ -838,6 +838,8 @@ if gdbus_codegen.found() and get_option('cfi') gdbus_codegen_error = '@0@ uses gdbus-codegen, which does not support control flow integrity' endif +xml_pp = find_program('scripts/xml-preprocess.py') + lttng = not_found if 'ust' in get_option('trace_backends') lttng = dependency('lttng-ust', required: true, version: '>= 2.1', @@ -1070,6 +1072,12 @@ if not get_option('virglrenderer').auto() or have_system or have_vhost_user_gpu virgl = dependency('virglrenderer', method: 'pkg-config', required: get_option('virglrenderer')) + if virgl.found() + config_host_data.set('HAVE_VIRGL_D3D_INFO_EXT', + cc.has_member('struct virgl_renderer_resource_info_ext', 'd3d_tex2d', + prefix: '#include <virglrenderer.h>', + dependencies: virgl)) + endif endif blkio = not_found if not get_option('blkio').auto() or have_block @@ -1985,8 +1993,6 @@ dbus_display = get_option('dbus_display') \ error_message: '-display dbus requires glib>=2.64') \ .require(gdbus_codegen.found(), error_message: gdbus_codegen_error.format('-display dbus')) \ - .require(targetos != 'windows', - error_message: '-display dbus is not available on Windows') \ .allowed() have_virtfs = get_option('virtfs') \ diff --git a/qapi/ui.json b/qapi/ui.json index 2755395..bb06fb6 100644 --- a/qapi/ui.json +++ b/qapi/ui.json @@ -1484,8 +1484,7 @@ { 'name': 'none' }, { 'name': 'gtk', 'if': 'CONFIG_GTK' }, { 'name': 'sdl', 'if': 'CONFIG_SDL' }, - { 'name': 'egl-headless', - 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }, + { 'name': 'egl-headless', 'if': 'CONFIG_OPENGL' }, { 'name': 'curses', 'if': 'CONFIG_CURSES' }, { 'name': 'cocoa', 'if': 'CONFIG_COCOA' }, { 'name': 'spice-app', 'if': 'CONFIG_SPICE' }, @@ -1525,7 +1524,7 @@ 'cocoa': { 'type': 'DisplayCocoa', 'if': 'CONFIG_COCOA' }, 'curses': { 'type': 'DisplayCurses', 'if': 'CONFIG_CURSES' }, 'egl-headless': { 'type': 'DisplayEGLHeadless', - 'if': { 'all': ['CONFIG_OPENGL', 'CONFIG_GBM'] } }, + 'if': 'CONFIG_OPENGL' }, 'dbus': { 'type': 'DisplayDBus', 'if': 'CONFIG_DBUS_DISPLAY' }, 'sdl': { 'type': 'DisplaySDL', 'if': 'CONFIG_SDL' } } diff --git a/scripts/meson.build b/scripts/meson.build index 1c89e10..532277f 100644 --- a/scripts/meson.build +++ b/scripts/meson.build @@ -1,3 +1,5 @@ if stap.found() install_data('qemu-trace-stap', install_dir: get_option('bindir')) endif + +test('xml-preprocess', files('xml-preprocess-test.py'), suite: ['unit']) diff --git a/scripts/xml-preprocess-test.py b/scripts/xml-preprocess-test.py new file mode 100644 index 0000000..dd92579 --- /dev/null +++ b/scripts/xml-preprocess-test.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2023 Red Hat, Inc. +# +# SPDX-License-Identifier: MIT +"""Unit tests for xml-preprocess""" + +import contextlib +import importlib +import os +import platform +import subprocess +import tempfile +import unittest +from io import StringIO + +xmlpp = importlib.import_module("xml-preprocess") + + +class TestXmlPreprocess(unittest.TestCase): + """Tests for xml-preprocess.Preprocessor""" + + def test_preprocess_xml(self): + with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file: + temp_file.write("<root></root>") + temp_file_name = temp_file.name + result = xmlpp.preprocess_xml(temp_file_name) + self.assertEqual(result, "<root></root>") + os.remove(temp_file_name) + + def test_save_xml(self): + with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_file: + temp_file_name = temp_file.name + xmlpp.save_xml("<root></root>", temp_file_name) + self.assertTrue(os.path.isfile(temp_file_name)) + os.remove(temp_file_name) + + def test_include(self): + with tempfile.NamedTemporaryFile(mode="w", delete=False) as inc_file: + inc_file.write("<included>Content from included file</included>") + inc_file_name = inc_file.name + xml_str = f"<?include {inc_file_name} ?>" + expected = "<included>Content from included file</included>" + xpp = xmlpp.Preprocessor() + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + os.remove(inc_file_name) + self.assertRaises(FileNotFoundError, xpp.preprocess, xml_str) + + def test_envvar(self): + os.environ["TEST_ENV_VAR"] = "TestValue" + xml_str = "<root>$(env.TEST_ENV_VAR)</root>" + expected = "<root>TestValue</root>" + xpp = xmlpp.Preprocessor() + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + self.assertRaises(KeyError, xpp.preprocess, "$(env.UNKNOWN)") + + def test_sys_var(self): + xml_str = "<root>$(sys.ARCH)</root>" + expected = f"<root>{platform.architecture()[0]}</root>" + xpp = xmlpp.Preprocessor() + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + self.assertRaises(KeyError, xpp.preprocess, "$(sys.UNKNOWN)") + + def test_cus_var(self): + xml_str = "<root>$(var.USER)</root>" + expected = "<root></root>" + xpp = xmlpp.Preprocessor() + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + xml_str = "<?define USER=FOO?><root>$(var.USER)</root>" + expected = "<root>FOO</root>" + xpp = xmlpp.Preprocessor() + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + + def test_error_warning(self): + xml_str = "<root><?warning \"test warn\"?></root>" + expected = "<root></root>" + xpp = xmlpp.Preprocessor() + out = StringIO() + with contextlib.redirect_stdout(out): + result = xpp.preprocess(xml_str) + self.assertEqual(result, expected) + self.assertEqual(out.getvalue(), "[Warning]: test warn\n") + self.assertRaises(RuntimeError, xpp.preprocess, "<?error \"test\"?>") + + def test_cmd(self): + xpp = xmlpp.Preprocessor() + result = xpp.preprocess('<root><?cmd "echo hello world"?></root>') + self.assertEqual(result, "<root>hello world</root>") + self.assertRaises( + subprocess.CalledProcessError, + xpp.preprocess, '<?cmd "test-unknown-cmd"?>' + ) + + def test_foreach(self): + xpp = xmlpp.Preprocessor() + result = xpp.preprocess( + '<root><?foreach x in a;b;c?>$(var.x)<?endforeach?></root>' + ) + self.assertEqual(result, "<root>abc</root>") + + def test_if_elseif(self): + xpp = xmlpp.Preprocessor() + result = xpp.preprocess('<root><?if True?>ok<?endif?></root>') + self.assertEqual(result, "<root>ok</root>") + result = xpp.preprocess('<root><?if False?>ok<?endif?></root>') + self.assertEqual(result, "<root></root>") + result = xpp.preprocess('<root><?if True?>ok<?else?>ko<?endif?></root>') + self.assertEqual(result, "<root>ok</root>") + result = xpp.preprocess('<root><?if False?>ok<?else?>ko<?endif?></root>') + self.assertEqual(result, "<root>ko</root>") + result = xpp.preprocess( + '<root><?if False?>ok<?elseif True?>ok2<?else?>ko<?endif?></root>' + ) + self.assertEqual(result, "<root>ok2</root>") + result = xpp.preprocess( + '<root><?if False?>ok<?elseif False?>ok<?else?>ko<?endif?></root>' + ) + self.assertEqual(result, "<root>ko</root>") + + def test_ifdef(self): + xpp = xmlpp.Preprocessor() + result = xpp.preprocess('<root><?ifdef USER?>ok<?else?>ko<?endif?></root>') + self.assertEqual(result, "<root>ko</root>") + result = xpp.preprocess( + '<?define USER=FOO?><root><?ifdef USER?>ok<?else?>ko<?endif?></root>' + ) + self.assertEqual(result, "<root>ok</root>") + + +if __name__ == "__main__": + unittest.main() diff --git a/scripts/xml-preprocess.py b/scripts/xml-preprocess.py new file mode 100755 index 0000000..57f1d28 --- /dev/null +++ b/scripts/xml-preprocess.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2017-2019 Tony Su +# Copyright (c) 2023 Red Hat, Inc. +# +# SPDX-License-Identifier: MIT +# +# Adapted from https://github.com/peitaosu/XML-Preprocessor +# +"""This is a XML Preprocessor which can be used to process your XML file before +you use it, to process conditional statements, variables, iteration +statements, error/warning, execute command, etc. + +## XML Schema + +### Include Files +``` +<?include path/to/file ?> +``` + +### Variables +``` +$(env.EnvironmentVariable) + +$(sys.SystemVariable) + +$(var.CustomVariable) +``` + +### Conditional Statements +``` +<?if ?> + +<?ifdef ?> + +<?ifndef ?> + +<?else?> + +<?elseif ?> + +<?endif?> +``` + +### Iteration Statements +``` +<?foreach VARNAME in 1;2;3?> + $(var.VARNAME) +<?endforeach?> +``` + +### Errors and Warnings +``` +<?error "This is error message!" ?> + +<?warning "This is warning message!" ?> +``` + +### Commands +``` +<? cmd "echo hello world" ?> +``` +""" + +import os +import platform +import re +import subprocess +import sys +from typing import Optional +from xml.dom import minidom + + +class Preprocessor(): + """This class holds the XML preprocessing state""" + + def __init__(self): + self.sys_vars = { + "ARCH": platform.architecture()[0], + "SOURCE": os.path.abspath(__file__), + "CURRENT": os.getcwd(), + } + self.cus_vars = {} + + def _pp_include(self, xml_str: str) -> str: + include_regex = r"(<\?include([\w\s\\/.:_-]+)\s*\?>)" + matches = re.findall(include_regex, xml_str) + for group_inc, group_xml in matches: + inc_file_path = group_xml.strip() + with open(inc_file_path, "r", encoding="utf-8") as inc_file: + inc_file_content = inc_file.read() + xml_str = xml_str.replace(group_inc, inc_file_content) + return xml_str + + def _pp_env_var(self, xml_str: str) -> str: + envvar_regex = r"(\$\(env\.(\w+)\))" + matches = re.findall(envvar_regex, xml_str) + for group_env, group_var in matches: + xml_str = xml_str.replace(group_env, os.environ[group_var]) + return xml_str + + def _pp_sys_var(self, xml_str: str) -> str: + sysvar_regex = r"(\$\(sys\.(\w+)\))" + matches = re.findall(sysvar_regex, xml_str) + for group_sys, group_var in matches: + xml_str = xml_str.replace(group_sys, self.sys_vars[group_var]) + return xml_str + + def _pp_cus_var(self, xml_str: str) -> str: + define_regex = r"(<\?define\s*(\w+)\s*=\s*([\w\s\"]+)\s*\?>)" + matches = re.findall(define_regex, xml_str) + for group_def, group_name, group_var in matches: + group_name = group_name.strip() + group_var = group_var.strip().strip("\"") + self.cus_vars[group_name] = group_var + xml_str = xml_str.replace(group_def, "") + cusvar_regex = r"(\$\(var\.(\w+)\))" + matches = re.findall(cusvar_regex, xml_str) + for group_cus, group_var in matches: + xml_str = xml_str.replace( + group_cus, + self.cus_vars.get(group_var, "") + ) + return xml_str + + def _pp_foreach(self, xml_str: str) -> str: + foreach_regex = r"(<\?foreach\s+(\w+)\s+in\s+([\w;]+)\s*\?>(.*)<\?endforeach\?>)" + matches = re.findall(foreach_regex, xml_str) + for group_for, group_name, group_vars, group_text in matches: + group_texts = "" + for var in group_vars.split(";"): + self.cus_vars[group_name] = var + group_texts += self._pp_cus_var(group_text) + xml_str = xml_str.replace(group_for, group_texts) + return xml_str + + def _pp_error_warning(self, xml_str: str) -> str: + error_regex = r"<\?error\s*\"([^\"]+)\"\s*\?>" + matches = re.findall(error_regex, xml_str) + for group_var in matches: + raise RuntimeError("[Error]: " + group_var) + warning_regex = r"(<\?warning\s*\"([^\"]+)\"\s*\?>)" + matches = re.findall(warning_regex, xml_str) + for group_wrn, group_var in matches: + print("[Warning]: " + group_var) + xml_str = xml_str.replace(group_wrn, "") + return xml_str + + def _pp_if_eval(self, xml_str: str) -> str: + ifelif_regex = ( + r"(<\?(if|elseif)\s*([^\"\s=<>!]+)\s*([!=<>]+)\s*\"*([^\"=<>!]+)\"*\s*\?>)" + ) + matches = re.findall(ifelif_regex, xml_str) + for ifelif, tag, left, operator, right in matches: + if "<" in operator or ">" in operator: + result = eval(f"{left} {operator} {right}") + else: + result = eval(f'"{left}" {operator} "{right}"') + xml_str = xml_str.replace(ifelif, f"<?{tag} {result}?>") + return xml_str + + def _pp_ifdef_ifndef(self, xml_str: str) -> str: + ifndef_regex = r"(<\?(ifdef|ifndef)\s*([\w]+)\s*\?>)" + matches = re.findall(ifndef_regex, xml_str) + for group_ifndef, group_tag, group_var in matches: + if group_tag == "ifdef": + result = group_var in self.cus_vars + else: + result = group_var not in self.cus_vars + xml_str = xml_str.replace(group_ifndef, f"<?if {result}?>") + return xml_str + + def _pp_if_elseif(self, xml_str: str) -> str: + if_elif_else_regex = ( + r"(<\?if\s(True|False)\?>" + r"(.*?)" + r"<\?elseif\s(True|False)\?>" + r"(.*?)" + r"<\?else\?>" + r"(.*?)" + r"<\?endif\?>)" + ) + if_else_regex = ( + r"(<\?if\s(True|False)\?>" + r"(.*?)" + r"<\?else\?>" + r"(.*?)" + r"<\?endif\?>)" + ) + if_regex = r"(<\?if\s(True|False)\?>(.*?)<\?endif\?>)" + matches = re.findall(if_elif_else_regex, xml_str, re.DOTALL) + for (group_full, group_if, group_if_elif, group_elif, + group_elif_else, group_else) in matches: + result = "" + if group_if == "True": + result = group_if_elif + elif group_elif == "True": + result = group_elif_else + else: + result = group_else + xml_str = xml_str.replace(group_full, result) + matches = re.findall(if_else_regex, xml_str, re.DOTALL) + for group_full, group_if, group_if_else, group_else in matches: + result = "" + if group_if == "True": + result = group_if_else + else: + result = group_else + xml_str = xml_str.replace(group_full, result) + matches = re.findall(if_regex, xml_str, re.DOTALL) + for group_full, group_if, group_text in matches: + result = "" + if group_if == "True": + result = group_text + xml_str = xml_str.replace(group_full, result) + return xml_str + + def _pp_command(self, xml_str: str) -> str: + cmd_regex = r"(<\?cmd\s*\"([^\"]+)\"\s*\?>)" + matches = re.findall(cmd_regex, xml_str) + for group_cmd, group_exec in matches: + output = subprocess.check_output( + group_exec, shell=True, + text=True, stderr=subprocess.STDOUT + ) + xml_str = xml_str.replace(group_cmd, output) + return xml_str + + def _pp_blanks(self, xml_str: str) -> str: + right_blank_regex = r">[\n\s\t\r]*" + left_blank_regex = r"[\n\s\t\r]*<" + xml_str = re.sub(right_blank_regex, ">", xml_str) + xml_str = re.sub(left_blank_regex, "<", xml_str) + return xml_str + + def preprocess(self, xml_str: str) -> str: + fns = [ + self._pp_blanks, + self._pp_include, + self._pp_foreach, + self._pp_env_var, + self._pp_sys_var, + self._pp_cus_var, + self._pp_if_eval, + self._pp_ifdef_ifndef, + self._pp_if_elseif, + self._pp_command, + self._pp_error_warning, + ] + + while True: + changed = False + for func in fns: + out_xml = func(xml_str) + if not changed and out_xml != xml_str: + changed = True + xml_str = out_xml + if not changed: + break + + return xml_str + + +def preprocess_xml(path: str) -> str: + with open(path, "r", encoding="utf-8") as original_file: + input_xml = original_file.read() + + proc = Preprocessor() + return proc.preprocess(input_xml) + + +def save_xml(xml_str: str, path: Optional[str]): + xml = minidom.parseString(xml_str) + with open(path, "w", encoding="utf-8") if path else sys.stdout as output_file: + output_file.write(xml.toprettyxml()) + + +def main(): + if len(sys.argv) < 2: + print("Usage: xml-preprocessor input.xml [output.xml]") + sys.exit(1) + + output_file = None + if len(sys.argv) == 3: + output_file = sys.argv[2] + + input_file = sys.argv[1] + output_xml = preprocess_xml(input_file) + save_xml(output_xml, output_file) + + +if __name__ == "__main__": + main() diff --git a/tests/qtest/dbus-display-test.c b/tests/qtest/dbus-display-test.c index fef025a..21edaa1 100644 --- a/tests/qtest/dbus-display-test.c +++ b/tests/qtest/dbus-display-test.c @@ -1,4 +1,5 @@ #include "qemu/osdep.h" +#include "qemu/sockets.h" #include "qemu/dbus.h" #include "qemu/sockets.h" #include <gio/gio.h> @@ -14,7 +15,11 @@ test_dbus_p2p_from_fd(int fd) g_autoptr(GSocketConnection) socketc = NULL; GDBusConnection *conn; +#ifdef WIN32 + socket = g_socket_new_from_fd(_get_osfhandle(fd), &err); +#else socket = g_socket_new_from_fd(fd, &err); +#endif g_assert_no_error(err); socketc = g_socket_connection_factory_create_connection(socket); @@ -126,7 +131,10 @@ test_dbus_console_registered(GObject *source_object, qemu_dbus_display1_console_call_register_listener_finish( QEMU_DBUS_DISPLAY1_CONSOLE(source_object), - NULL, res, &err); +#ifndef WIN32 + NULL, +#endif + res, &err); g_assert_no_error(err); test->listener_conn = g_thread_join(test->thread); @@ -145,17 +153,25 @@ test_dbus_display_console(void) g_autoptr(GError) err = NULL; g_autoptr(GDBusConnection) conn = NULL; g_autoptr(QemuDBusDisplay1ConsoleProxy) console = NULL; - g_autoptr(GUnixFDList) fd_list = NULL; g_autoptr(GMainLoop) loop = NULL; QTestState *qts = NULL; - int pair[2], idx; + int pair[2]; TestDBusConsoleRegister test; +#ifdef WIN32 + WSAPROTOCOL_INFOW info; + g_autoptr(GVariant) listener = NULL; +#else + g_autoptr(GUnixFDList) fd_list = NULL; + int idx; +#endif test_setup(&qts, &conn); g_assert_cmpint(qemu_socketpair(AF_UNIX, SOCK_STREAM, 0, pair), ==, 0); +#ifndef WIN32 fd_list = g_unix_fd_list_new(); idx = g_unix_fd_list_append(fd_list, pair[1], NULL); +#endif console = QEMU_DBUS_DISPLAY1_CONSOLE_PROXY( qemu_dbus_display1_console_proxy_new_sync( @@ -171,12 +187,33 @@ test_dbus_display_console(void) test.thread = g_thread_new(NULL, test_dbus_p2p_server_setup_thread, GINT_TO_POINTER(pair[0])); +#ifdef WIN32 + if (WSADuplicateSocketW(_get_osfhandle(pair[1]), + GetProcessId((HANDLE) qtest_pid(qts)), + &info) == SOCKET_ERROR) + { + g_autofree char *emsg = g_win32_error_message(WSAGetLastError()); + g_error("WSADuplicateSocket failed: %s", emsg); + } + close(pair[1]); + listener = g_variant_new_fixed_array(G_VARIANT_TYPE_BYTE, + &info, + sizeof(info), + 1); +#endif + qemu_dbus_display1_console_call_register_listener( QEMU_DBUS_DISPLAY1_CONSOLE(console), +#ifdef WIN32 + listener, +#else g_variant_new_handle(idx), +#endif G_DBUS_CALL_FLAGS_NONE, -1, +#ifndef WIN32 fd_list, +#endif NULL, test_dbus_console_registered, &test); diff --git a/tests/qtest/libqtest.c b/tests/qtest/libqtest.c index de03ef5..79152f0 100644 --- a/tests/qtest/libqtest.c +++ b/tests/qtest/libqtest.c @@ -142,6 +142,11 @@ static int socket_accept(int sock) return ret; } +pid_t qtest_pid(QTestState *s) +{ + return s->qemu_pid; +} + bool qtest_probe_child(QTestState *s) { pid_t pid = s->qemu_pid; diff --git a/tests/qtest/libqtest.h b/tests/qtest/libqtest.h index a12acf7..913acc3 100644 --- a/tests/qtest/libqtest.h +++ b/tests/qtest/libqtest.h @@ -985,4 +985,13 @@ void qtest_qom_set_bool(QTestState *s, const char *path, const char *property, * Returns: Value retrieved from property. */ bool qtest_qom_get_bool(QTestState *s, const char *path, const char *property); + +/** + * qtest_pid: + * @s: QTestState instance to operate on. + * + * Returns: the PID of the QEMU process, or <= 0 + */ +pid_t qtest_pid(QTestState *s); + #endif diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 5fa6833..74630f6 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -104,7 +104,7 @@ qtests_i386 = \ 'numa-test' ] -if dbus_display and targetos != 'windows' +if dbus_display qtests_i386 += ['dbus-display-test'] endif diff --git a/ui/console.c b/ui/console.c index e173731..c1544e0 100644 --- a/ui/console.c +++ b/ui/console.c @@ -1223,7 +1223,8 @@ static void displaychangelistener_display_console(DisplayChangeListener *dcl, con->scanout.texture.x, con->scanout.texture.y, con->scanout.texture.width, - con->scanout.texture.height); + con->scanout.texture.height, + con->scanout.texture.d3d_tex2d); } } @@ -1513,18 +1514,59 @@ static QemuConsole *new_console(DisplayState *ds, console_type_t console_type, return s; } +#ifdef WIN32 +void qemu_displaysurface_win32_set_handle(DisplaySurface *surface, + HANDLE h, uint32_t offset) +{ + assert(!surface->handle); + + surface->handle = h; + surface->handle_offset = offset; +} + +static void +win32_pixman_image_destroy(pixman_image_t *image, void *data) +{ + DisplaySurface *surface = data; + + if (!surface->handle) { + return; + } + + assert(surface->handle_offset == 0); + + qemu_win32_map_free( + pixman_image_get_data(surface->image), + surface->handle, + &error_warn + ); +} +#endif + DisplaySurface *qemu_create_displaysurface(int width, int height) { - DisplaySurface *surface = g_new0(DisplaySurface, 1); + DisplaySurface *surface; + void *bits = NULL; +#ifdef WIN32 + HANDLE handle = NULL; +#endif - trace_displaysurface_create(surface, width, height); - surface->format = PIXMAN_x8r8g8b8; - surface->image = pixman_image_create_bits(surface->format, - width, height, - NULL, width * 4); - assert(surface->image != NULL); + trace_displaysurface_create(width, height); + +#ifdef WIN32 + bits = qemu_win32_map_alloc(width * height * 4, &handle, &error_abort); +#endif + + surface = qemu_create_displaysurface_from( + width, height, + PIXMAN_x8r8g8b8, + width * 4, bits + ); surface->flags = QEMU_ALLOCATED_FLAG; +#ifdef WIN32 + qemu_displaysurface_win32_set_handle(surface, handle, 0); +#endif return surface; } @@ -1540,6 +1582,10 @@ DisplaySurface *qemu_create_displaysurface_from(int width, int height, width, height, (void *)data, linesize); assert(surface->image != NULL); +#ifdef WIN32 + pixman_image_set_destroy_function(surface->image, + win32_pixman_image_destroy, surface); +#endif return surface; } @@ -1635,6 +1681,71 @@ static bool console_compatible_with(QemuConsole *con, return true; } +void console_handle_touch_event(QemuConsole *con, + struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX], + uint64_t num_slot, + int width, int height, + double x, double y, + InputMultiTouchType type, + Error **errp) +{ + struct touch_slot *slot; + bool needs_sync = false; + int update; + int i; + + if (num_slot >= INPUT_EVENT_SLOTS_MAX) { + error_setg(errp, + "Unexpected touch slot number: % " PRId64" >= %d", + num_slot, INPUT_EVENT_SLOTS_MAX); + return; + } + + slot = &touch_slots[num_slot]; + slot->x = x; + slot->y = y; + + if (type == INPUT_MULTI_TOUCH_TYPE_BEGIN) { + slot->tracking_id = num_slot; + } + + for (i = 0; i < INPUT_EVENT_SLOTS_MAX; ++i) { + if (i == num_slot) { + update = type; + } else { + update = INPUT_MULTI_TOUCH_TYPE_UPDATE; + } + + slot = &touch_slots[i]; + + if (slot->tracking_id == -1) { + continue; + } + + if (update == INPUT_MULTI_TOUCH_TYPE_END) { + slot->tracking_id = -1; + qemu_input_queue_mtt(con, update, i, slot->tracking_id); + needs_sync = true; + } else { + qemu_input_queue_mtt(con, update, i, slot->tracking_id); + qemu_input_queue_btn(con, INPUT_BUTTON_TOUCH, true); + qemu_input_queue_mtt_abs(con, + INPUT_AXIS_X, (int) slot->x, + 0, width, + i, slot->tracking_id); + qemu_input_queue_mtt_abs(con, + INPUT_AXIS_Y, (int) slot->y, + 0, height, + i, slot->tracking_id); + needs_sync = true; + } + } + + if (needs_sync) { + qemu_input_event_sync(); + } +} + void qemu_console_set_display_gl_ctx(QemuConsole *con, DisplayGLCtx *gl) { /* display has opengl support */ @@ -2005,7 +2116,8 @@ void dpy_gl_scanout_texture(QemuConsole *con, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t width, uint32_t height) + uint32_t width, uint32_t height, + void *d3d_tex2d) { DisplayState *s = con->ds; DisplayChangeListener *dcl; @@ -2013,7 +2125,7 @@ void dpy_gl_scanout_texture(QemuConsole *con, con->scanout.kind = SCANOUT_TEXTURE; con->scanout.texture = (ScanoutTexture) { backing_id, backing_y_0_top, backing_width, backing_height, - x, y, width, height + x, y, width, height, d3d_tex2d, }; QLIST_FOREACH(dcl, &s->listeners, next) { if (con != (dcl->con ? dcl->con : active_console)) { @@ -2023,7 +2135,8 @@ void dpy_gl_scanout_texture(QemuConsole *con, dcl->ops->dpy_gl_scanout_texture(dcl, backing_id, backing_y_0_top, backing_width, backing_height, - x, y, width, height); + x, y, width, height, + d3d_tex2d); } } } @@ -2306,7 +2419,7 @@ QEMUCursor *qemu_console_get_cursor(QemuConsole *con) if (con == NULL) { con = active_console; } - return con->cursor; + return con ? con->cursor : NULL; } bool qemu_console_is_visible(QemuConsole *con) diff --git a/ui/dbus-chardev.c b/ui/dbus-chardev.c index 940ef93..1d3a712 100644 --- a/ui/dbus-chardev.c +++ b/ui/dbus-chardev.c @@ -27,7 +27,9 @@ #include "qemu/config-file.h" #include "qemu/option.h" +#ifdef G_OS_UNIX #include <gio/gunixfdlist.h> +#endif #include "dbus.h" @@ -112,13 +114,20 @@ static gboolean dbus_chr_register( DBusChardev *dc, GDBusMethodInvocation *invocation, +#ifdef G_OS_UNIX GUnixFDList *fd_list, +#endif GVariant *arg_stream, QemuDBusDisplay1Chardev *object) { g_autoptr(GError) err = NULL; int fd; +#ifdef G_OS_WIN32 + if (!dbus_win32_import_socket(invocation, arg_stream, &fd)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } +#else fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_stream), &err); if (err) { g_dbus_method_invocation_return_error( @@ -128,13 +137,18 @@ dbus_chr_register( "Couldn't get peer FD: %s", err->message); return DBUS_METHOD_INVOCATION_HANDLED; } +#endif if (qemu_chr_add_client(CHARDEV(dc), fd) < 0) { g_dbus_method_invocation_return_error(invocation, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Couldn't register FD!"); +#ifdef G_OS_WIN32 + closesocket(fd); +#else close(fd); +#endif return DBUS_METHOD_INVOCATION_HANDLED; } @@ -142,7 +156,11 @@ dbus_chr_register( "owner", g_dbus_method_invocation_get_sender(invocation), NULL); - qemu_dbus_display1_chardev_complete_register(object, invocation, NULL); + qemu_dbus_display1_chardev_complete_register(object, invocation +#ifndef G_OS_WIN32 + , NULL +#endif + ); return DBUS_METHOD_INVOCATION_HANDLED; } diff --git a/ui/dbus-console.c b/ui/dbus-console.c index f77bc49..e19774f 100644 --- a/ui/dbus-console.c +++ b/ui/dbus-console.c @@ -28,10 +28,14 @@ #include "ui/kbd-state.h" #include "trace.h" +#ifdef G_OS_UNIX #include <gio/gunixfdlist.h> +#endif #include "dbus.h" +static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX]; + struct _DBusDisplayConsole { GDBusObjectSkeleton parent_instance; DisplayChangeListener dcl; @@ -44,6 +48,7 @@ struct _DBusDisplayConsole { QKbdState *kbd; QemuDBusDisplay1Mouse *iface_mouse; + QemuDBusDisplay1MultiTouch *iface_touch; gboolean last_set; guint last_x; guint last_y; @@ -93,7 +98,8 @@ dbus_gl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { DBusDisplayConsole *ddc = container_of(dcl, DBusDisplayConsole, dcl); @@ -204,10 +210,47 @@ dbus_console_set_ui_info(DBusDisplayConsole *ddc, return DBUS_METHOD_INVOCATION_HANDLED; } +#ifdef G_OS_WIN32 +bool +dbus_win32_import_socket(GDBusMethodInvocation *invocation, + GVariant *arg_listener, int *socket) +{ + gsize n; + WSAPROTOCOL_INFOW *info = (void *)g_variant_get_fixed_array(arg_listener, &n, 1); + + if (!info || n != sizeof(*info)) { + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Failed to get socket infos"); + return false; + } + + *socket = WSASocketW(FROM_PROTOCOL_INFO, + FROM_PROTOCOL_INFO, + FROM_PROTOCOL_INFO, + info, 0, 0); + if (*socket == INVALID_SOCKET) { + g_autofree gchar *emsg = g_win32_error_message(WSAGetLastError()); + g_dbus_method_invocation_return_error( + invocation, + DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_FAILED, + "Couldn't create socket: %s", emsg); + return false; + } + + return true; +} +#endif + static gboolean dbus_console_register_listener(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, +#ifdef G_OS_UNIX GUnixFDList *fd_list, +#endif GVariant *arg_listener) { const char *sender = g_dbus_method_invocation_get_sender(invocation); @@ -229,6 +272,11 @@ dbus_console_register_listener(DBusDisplayConsole *ddc, return DBUS_METHOD_INVOCATION_HANDLED; } +#ifdef G_OS_WIN32 + if (!dbus_win32_import_socket(invocation, arg_listener, &fd)) { + return DBUS_METHOD_INVOCATION_HANDLED; + } +#else fd = g_unix_fd_list_get(fd_list, g_variant_get_handle(arg_listener), &err); if (err) { g_dbus_method_invocation_return_error( @@ -238,6 +286,7 @@ dbus_console_register_listener(DBusDisplayConsole *ddc, "Couldn't get peer fd: %s", err->message); return DBUS_METHOD_INVOCATION_HANDLED; } +#endif socket = g_socket_new_from_fd(fd, &err); if (err) { @@ -246,13 +295,21 @@ dbus_console_register_listener(DBusDisplayConsole *ddc, DBUS_DISPLAY_ERROR, DBUS_DISPLAY_ERROR_FAILED, "Couldn't make a socket: %s", err->message); +#ifdef G_OS_WIN32 + closesocket(fd); +#else close(fd); +#endif return DBUS_METHOD_INVOCATION_HANDLED; } socket_conn = g_socket_connection_factory_create_connection(socket); qemu_dbus_display1_console_complete_register_listener( - ddc->iface, invocation, NULL); + ddc->iface, invocation +#ifdef G_OS_UNIX + , NULL +#endif + ); listener_conn = g_dbus_connection_new_sync( G_IO_STREAM(socket_conn), @@ -346,6 +403,46 @@ dbus_mouse_rel_motion(DBusDisplayConsole *ddc, } static gboolean +dbus_touch_send_event(DBusDisplayConsole *ddc, + GDBusMethodInvocation *invocation, + guint kind, uint64_t num_slot, + double x, double y) +{ + Error *error = NULL; + int width, height; + trace_dbus_touch_send_event(kind, num_slot, x, y); + + if (kind != INPUT_MULTI_TOUCH_TYPE_BEGIN && + kind != INPUT_MULTI_TOUCH_TYPE_UPDATE && + kind != INPUT_MULTI_TOUCH_TYPE_CANCEL && + kind != INPUT_MULTI_TOUCH_TYPE_END) + { + g_dbus_method_invocation_return_error( + invocation, DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_INVALID, + "Invalid touch event kind"); + return DBUS_METHOD_INVOCATION_HANDLED; + } + width = qemu_console_get_width(ddc->dcl.con, 0); + height = qemu_console_get_height(ddc->dcl.con, 0); + + console_handle_touch_event(ddc->dcl.con, touch_slots, + num_slot, width, height, + x, y, kind, &error); + if (error != NULL) { + g_dbus_method_invocation_return_error( + invocation, DBUS_DISPLAY_ERROR, + DBUS_DISPLAY_ERROR_INVALID, + error_get_pretty(error), NULL); + error_free(error); + } else { + qemu_dbus_display1_multi_touch_complete_send_event(ddc->iface_touch, + invocation); + } + return DBUS_METHOD_INVOCATION_HANDLED; +} + +static gboolean dbus_mouse_set_pos(DBusDisplayConsole *ddc, GDBusMethodInvocation *invocation, guint x, guint y) @@ -440,7 +537,13 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con) g_autofree char *label = NULL; char device_addr[256] = ""; DBusDisplayConsole *ddc; - int idx; + int idx, i; + const char *interfaces[] = { + "org.qemu.Display1.Keyboard", + "org.qemu.Display1.Mouse", + "org.qemu.Display1.MultiTouch", + NULL + }; assert(display); assert(con); @@ -465,6 +568,7 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con) "width", qemu_console_get_width(con, 0), "height", qemu_console_get_height(con, 0), "device-address", device_addr, + "interfaces", interfaces, NULL); g_object_connect(ddc->iface, "swapped-signal::handle-register-listener", @@ -495,6 +599,20 @@ dbus_display_console_new(DBusDisplay *display, QemuConsole *con) g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), G_DBUS_INTERFACE_SKELETON(ddc->iface_mouse)); + ddc->iface_touch = qemu_dbus_display1_multi_touch_skeleton_new(); + g_object_connect(ddc->iface_touch, + "swapped-signal::handle-send-event", dbus_touch_send_event, ddc, + NULL); + qemu_dbus_display1_multi_touch_set_max_slots(ddc->iface_touch, + INPUT_EVENT_SLOTS_MAX); + g_dbus_object_skeleton_add_interface(G_DBUS_OBJECT_SKELETON(ddc), + G_DBUS_INTERFACE_SKELETON(ddc->iface_touch)); + + for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { + struct touch_slot *slot = &touch_slots[i]; + slot->tracking_id = -1; + } + register_displaychangelistener(&ddc->dcl); ddc->mouse_mode_notifier.notify = dbus_mouse_mode_change; qemu_add_mouse_mode_change_notifier(&ddc->mouse_mode_notifier); diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml index c3b2293..f0e2fac 100644 --- a/ui/dbus-display1.xml +++ b/ui/dbus-display1.xml @@ -26,6 +26,20 @@ The list of consoles available on ``/org/qemu/Display1/Console_$id``. --> <property name="ConsoleIDs" type="au" access="read"/> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/VM object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- @@ -39,8 +53,9 @@ "Text" (see :dbus:prop:`Type` and other properties). Interactions with a console may be done with - :dbus:iface:`org.qemu.Display1.Keyboard` and - :dbus:iface:`org.qemu.Display1.Mouse` interfaces when available. + :dbus:iface:`org.qemu.Display1.Keyboard`, + :dbus:iface:`org.qemu.Display1.Mouse` and + :dbus:iface:`org.qemu.Display1.MultiTouch` interfaces when available. --> <interface name="org.qemu.Display1.Console"> <!-- @@ -56,7 +71,13 @@ :dbus:iface:`org.qemu.Display1.Listener` interface. --> <method name="RegisterListener"> + <?if $(env.TARGETOS) == windows?> + <arg type="ay" name="listener" direction="in"> + <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> + </arg> + <?else?> <arg type="h" name="listener" direction="in"/> + <?endif?> </method> <!-- @@ -120,12 +141,27 @@ The device address (ex: "pci/0000/02.0"). --> <property name="DeviceAddress" type="s" access="read"/> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + ``/org/qemu/Display1/Console_$id`` object, and can be used to detect the + capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- org.qemu.Display1.Keyboard: - This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see + This interface is optionally implemented on + ``/org/qemu/Display1/Console_$id`` (see :dbus:iface:`~org.qemu.Display1.Console`). --> <interface name="org.qemu.Display1.Keyboard"> @@ -164,7 +200,8 @@ <!-- org.qemu.Display1.Mouse: - This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see + This interface is optionally implemented on + ``/org/qemu/Display1/Console_$id`` (see :dbus:iface:`~org.qemu.Display1.Console` documentation). .. _dbus-button-values: @@ -237,6 +274,46 @@ </interface> <!-- + org.qemu.Display1.MultiTouch: + + This interface in implemented on ``/org/qemu/Display1/Console_$id`` (see + :dbus:iface:`~org.qemu.Display1.Console` documentation). + + .. _dbus-kind-values: + + **Kind values**:: + + Begin = 0 + Update = 1 + End = 2 + Cancel = 3 + --> + <interface name="org.qemu.Display1.MultiTouch"> + <!-- + SendEvent: + @kind: The touch event kind + @num_slot: The slot number. + @x: The x coordinates. + @y: The y coordinates. + + Send a touch gesture event. + --> + <method name="SendEvent"> + <arg type="u" name="kind" direction="in"/> + <arg type="t" name="num_slot" direction="in"/> + <arg type="d" name="x" direction="in"/> + <arg type="d" name="y" direction="in"/> + </method> + + <!-- + MaxSlots: + + The maximum number of slots. + --> + <property name="MaxSlots" type="i" access="read"/> + </interface> + + <!-- org.qemu.Display1.Listener: This client-side interface must be available on @@ -293,6 +370,7 @@ </arg> </method> + <?if $(env.TARGETOS) != windows?> <!-- ScanoutDMABUF: @dmabuf: the DMABUF file descriptor. @@ -331,6 +409,7 @@ <arg type="i" name="width" direction="in"/> <arg type="i" name="height" direction="in"/> </method> + <?endif?> <!-- Disable: @@ -374,6 +453,116 @@ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> </arg> </method> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/Listener object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> + </interface> + + <!-- + org.qemu.Display1.Listener.Win32.Map: + + This optional client-side interface can complement + org.qemu.Display1.Listener on ``/org/qemu/Display1/Listener`` for Windows + specific shared memory scanouts. + --> + <interface name="org.qemu.Display1.Listener.Win32.Map"> + <!-- + ScanoutMap: + @handle: the shared map handle value. + @offset: mapping offset. + @width: display width, in pixels. + @height: display height, in pixels. + @stride: stride, in bytes. + @pixman_format: image format (ex: ``PIXMAN_X8R8G8B8``). + + Resize and update the display content with a shared map. + --> + <method name="ScanoutMap"> + <arg type="t" name="handle" direction="in"/> + <arg type="u" name="offset" direction="in"/> + <arg type="u" name="width" direction="in"/> + <arg type="u" name="height" direction="in"/> + <arg type="u" name="stride" direction="in"/> + <arg type="u" name="pixman_format" direction="in"/> + </method> + + <!-- + UpdateMap: + @x: the X update position, in pixels. + @y: the Y update position, in pixels. + @width: the update width, in pixels. + @height: the update height, in pixels. + + Update the display content with the current shared map and the given region. + --> + <method name="UpdateMap"> + <arg type="i" name="x" direction="in"/> + <arg type="i" name="y" direction="in"/> + <arg type="i" name="width" direction="in"/> + <arg type="i" name="height" direction="in"/> + </method> + </interface> + + <!-- + org.qemu.Display1.Listener.Win32.D3d11: + + This optional client-side interface can complement + org.qemu.Display1.Listener on ``/org/qemu/Display1/Listener`` for Windows + specific Direct3D texture sharing of the scanouts. + --> + <interface name="org.qemu.Display1.Listener.Win32.D3d11"> + <!-- + ScanoutTexture2d: + @handle: the NT handle for the shared texture (to be opened back with ID3D11Device1::OpenSharedResource1). + @texture_width: texture width, in pixels. + @texture_height: texture height, in pixels. + @y0_top: whether Y position 0 is the top or not. + @x: the X scanout position, in pixels. + @y: the Y scanout position, in pixels. + @width: the scanout width, in pixels. + @height: the scanout height, in pixels. + + Resize and update the display content with a Direct3D 11 2D texture. + You must acquire and release the associated KeyedMutex 0 during rendering. + --> + <method name="ScanoutTexture2d"> + <arg type="t" name="handle" direction="in"/> + <arg type="u" name="texture_width" direction="in"/> + <arg type="u" name="texture_height" direction="in"/> + <arg type="b" name="y0_top" direction="in"/> + <arg type="u" name="x" direction="in"/> + <arg type="u" name="y" direction="in"/> + <arg type="u" name="width" direction="in"/> + <arg type="u" name="height" direction="in"/> + </method> + + <!-- + UpdateTexture2d: + @x: the X update position, in pixels. + @y: the Y update position, in pixels. + @width: the update width, in pixels. + @height: the update height, in pixels. + + Update the display content with the current Direct3D 2D texture and the given region. + You must acquire and release the associated KeyedMutex 0 during rendering. + --> + <method name="UpdateTexture2d"> + <arg type="i" name="x" direction="in"/> + <arg type="i" name="y" direction="in"/> + <arg type="i" name="width" direction="in"/> + <arg type="i" name="height" direction="in"/> + </method> </interface> <!-- @@ -471,6 +660,20 @@ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> </arg> </method> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/Clipboard object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- @@ -491,7 +694,13 @@ :dbus:iface:`org.qemu.Display1.AudioOutListener` interface. --> <method name="RegisterOutListener"> + <?if $(env.TARGETOS) == windows?> + <arg type="ay" name="listener" direction="in"> + <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> + </arg> + <?else?> <arg type="h" name="listener" direction="in"/> + <?endif?> </method> <!-- @@ -506,8 +715,28 @@ :dbus:iface:`org.qemu.Display1.AudioInListener` interface. --> <method name="RegisterInListener"> + <?if $(env.TARGETOS) == windows?> + <arg type="ay" name="listener" direction="in"> + <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> + </arg> + <?else?> <arg type="h" name="listener" direction="in"/> + <?endif?> </method> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/Audio object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- @@ -594,6 +823,20 @@ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> </arg> </method> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/AudioOutListener object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- @@ -682,6 +925,20 @@ <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> </arg> </method> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + /org/qemu/Display1/AudioInListener object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> <!-- @@ -719,7 +976,13 @@ The current handler, if any, will be replaced. --> <method name="Register"> + <?if $(env.TARGETOS) == windows?> + <arg type="ay" name="listener" direction="in"> + <annotation name="org.gtk.GDBus.C.ForceGVariant" value="true"/> + </arg> + <?else?> <arg type="h" name="stream" direction="in"/> + <?endif?> </method> <!-- @@ -757,5 +1020,19 @@ The D-Bus unique name of the registered handler. --> <property name="Owner" type="s" access="read"/> + + <!-- + Interfaces: + + This property lists extra interfaces provided by the + ``/org/qemu/Display1/Chardev_$i`` object, and can be used to detect + the capabilities with which they are communicating. + + Unlike the standard D-Bus Introspectable interface, querying this + property does not require parsing XML. + + (earlier version of the display interface do not provide this property) + --> + <property name="Interfaces" type="as" access="read"/> </interface> </node> diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c index 23034ee..e10162b 100644 --- a/ui/dbus-listener.c +++ b/ui/dbus-listener.c @@ -23,9 +23,16 @@ */ #include "qemu/osdep.h" #include "qemu/error-report.h" +#include "qapi/error.h" #include "sysemu/sysemu.h" #include "dbus.h" +#ifdef G_OS_UNIX #include <gio/gunixfdlist.h> +#endif +#ifdef WIN32 +#include <d3d11.h> +#include <dxgi1_2.h> +#endif #ifdef CONFIG_OPENGL #include "ui/shader.h" @@ -34,6 +41,15 @@ #endif #include "trace.h" +static void dbus_gfx_switch(DisplayChangeListener *dcl, + struct DisplaySurface *new_surface); + +enum share_kind { + SHARE_KIND_NONE, + SHARE_KIND_MAPPED, + SHARE_KIND_D3DTEX, +}; + struct _DBusDisplayListener { GObject parent; @@ -45,21 +61,142 @@ struct _DBusDisplayListener { DisplayChangeListener dcl; DisplaySurface *ds; + enum share_kind ds_share; + int gl_updates; + + bool ds_mapped; + bool can_share_map; + +#ifdef WIN32 + QemuDBusDisplay1ListenerWin32Map *map_proxy; + QemuDBusDisplay1ListenerWin32D3d11 *d3d11_proxy; + HANDLE peer_process; + ID3D11Texture2D *d3d_texture; +#ifdef CONFIG_OPENGL + egl_fb fb; +#endif +#endif }; G_DEFINE_TYPE(DBusDisplayListener, dbus_display_listener, G_TYPE_OBJECT) -#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) +static void dbus_gfx_update(DisplayChangeListener *dcl, + int x, int y, int w, int h); + +#ifdef CONFIG_OPENGL +static void dbus_scanout_disable(DisplayChangeListener *dcl) +{ + DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); + + qemu_dbus_display1_listener_call_disable( + ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); +} + +#ifdef WIN32 +static bool d3d_texture2d_share(ID3D11Texture2D *d3d_texture, + HANDLE *handle, Error **errp) +{ + IDXGIResource1 *dxgiResource = NULL; + HRESULT hr; + + hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture, + &IID_IDXGIResource1, + (void **)&dxgiResource); + if (FAILED(hr)) { + goto fail; + } + + hr = dxgiResource->lpVtbl->CreateSharedHandle( + dxgiResource, + NULL, + DXGI_SHARED_RESOURCE_READ | DXGI_SHARED_RESOURCE_WRITE, + NULL, + handle + ); + + dxgiResource->lpVtbl->Release(dxgiResource); + + if (SUCCEEDED(hr)) { + return true; + } + +fail: + error_setg_win32(errp, GetLastError(), "failed to create shared handle"); + return false; +} + +static bool d3d_texture2d_acquire0(ID3D11Texture2D *d3d_texture, Error **errp) +{ + IDXGIKeyedMutex *dxgiMutex = NULL; + HRESULT hr; + + hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture, + &IID_IDXGIKeyedMutex, + (void **)&dxgiMutex); + if (FAILED(hr)) { + goto fail; + } + + hr = dxgiMutex->lpVtbl->AcquireSync(dxgiMutex, 0, INFINITE); + + dxgiMutex->lpVtbl->Release(dxgiMutex); + + if (SUCCEEDED(hr)) { + return true; + } + +fail: + error_setg_win32(errp, GetLastError(), "failed to acquire texture mutex"); + return false; +} + +static bool d3d_texture2d_release0(ID3D11Texture2D *d3d_texture, Error **errp) +{ + IDXGIKeyedMutex *dxgiMutex = NULL; + HRESULT hr; + + hr = d3d_texture->lpVtbl->QueryInterface(d3d_texture, + &IID_IDXGIKeyedMutex, + (void **)&dxgiMutex); + if (FAILED(hr)) { + goto fail; + } + + hr = dxgiMutex->lpVtbl->ReleaseSync(dxgiMutex, 0); + + dxgiMutex->lpVtbl->Release(dxgiMutex); + + if (SUCCEEDED(hr)) { + return true; + } + +fail: + error_setg_win32(errp, GetLastError(), "failed to release texture mutex"); + return false; +} +#endif /* WIN32 */ + static void dbus_update_gl_cb(GObject *source_object, - GAsyncResult *res, - gpointer user_data) + GAsyncResult *res, + gpointer user_data) { g_autoptr(GError) err = NULL; DBusDisplayListener *ddl = user_data; + bool success; + +#ifdef CONFIG_GBM + success = qemu_dbus_display1_listener_call_update_dmabuf_finish( + ddl->proxy, res, &err); +#endif + +#ifdef WIN32 + success = qemu_dbus_display1_listener_win32_d3d11_call_update_texture2d_finish( + ddl->d3d11_proxy, res, &err); + d3d_texture2d_acquire0(ddl->d3d_texture, &error_warn); +#endif - if (!qemu_dbus_display1_listener_call_update_dmabuf_finish(ddl->proxy, - res, &err)) { + if (!success) { error_report("Failed to call update: %s", err->message); } @@ -67,28 +204,54 @@ static void dbus_update_gl_cb(GObject *source_object, g_object_unref(ddl); } -static void dbus_call_update_gl(DBusDisplayListener *ddl, +static void dbus_call_update_gl(DisplayChangeListener *dcl, int x, int y, int w, int h) { - graphic_hw_gl_block(ddl->dcl.con, true); + DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); + + trace_dbus_update_gl(x, y, w, h); + glFlush(); +#ifdef CONFIG_GBM + graphic_hw_gl_block(ddl->dcl.con, true); qemu_dbus_display1_listener_call_update_dmabuf(ddl->proxy, x, y, w, h, G_DBUS_CALL_FLAGS_NONE, DBUS_DEFAULT_TIMEOUT, NULL, dbus_update_gl_cb, g_object_ref(ddl)); -} - -static void dbus_scanout_disable(DisplayChangeListener *dcl) -{ - DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); +#endif - ddl->ds = NULL; - qemu_dbus_display1_listener_call_disable( - ddl->proxy, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); +#ifdef WIN32 + switch (ddl->ds_share) { + case SHARE_KIND_MAPPED: + egl_fb_read_rect(ddl->ds, &ddl->fb, x, y, w, h); + dbus_gfx_update(dcl, x, y, w, h); + break; + case SHARE_KIND_D3DTEX: + Error *err = NULL; + assert(ddl->d3d_texture); + + graphic_hw_gl_block(ddl->dcl.con, true); + if (!d3d_texture2d_release0(ddl->d3d_texture, &err)) { + error_report_err(err); + return; + } + qemu_dbus_display1_listener_win32_d3d11_call_update_texture2d( + ddl->d3d11_proxy, + x, y, w, h, + G_DBUS_CALL_FLAGS_NONE, + DBUS_DEFAULT_TIMEOUT, NULL, + dbus_update_gl_cb, + g_object_ref(ddl)); + break; + default: + g_warn_if_reached(); + } +#endif } +#ifdef CONFIG_GBM static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf) { @@ -117,15 +280,136 @@ static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, fd_list, NULL, NULL, NULL); } +#endif /* GBM */ +#endif /* OPENGL */ + +#ifdef WIN32 +static bool dbus_scanout_map(DBusDisplayListener *ddl) +{ + g_autoptr(GError) err = NULL; + BOOL success; + HANDLE target_handle; + + if (ddl->ds_share == SHARE_KIND_MAPPED) { + return true; + } + + if (!ddl->can_share_map || !ddl->ds->handle) { + return false; + } + + success = DuplicateHandle( + GetCurrentProcess(), + ddl->ds->handle, + ddl->peer_process, + &target_handle, + FILE_MAP_READ | SECTION_QUERY, + FALSE, 0); + if (!success) { + g_autofree char *msg = g_win32_error_message(GetLastError()); + g_debug("Failed to DuplicateHandle: %s", msg); + ddl->can_share_map = false; + return false; + } + + if (!qemu_dbus_display1_listener_win32_map_call_scanout_map_sync( + ddl->map_proxy, + GPOINTER_TO_UINT(target_handle), + ddl->ds->handle_offset, + surface_width(ddl->ds), + surface_height(ddl->ds), + surface_stride(ddl->ds), + surface_format(ddl->ds), + G_DBUS_CALL_FLAGS_NONE, + DBUS_DEFAULT_TIMEOUT, + NULL, + &err)) { + g_debug("Failed to call ScanoutMap: %s", err->message); + ddl->can_share_map = false; + return false; + } + + ddl->ds_share = SHARE_KIND_MAPPED; + + return true; +} + +static bool +dbus_scanout_share_d3d_texture( + DBusDisplayListener *ddl, + ID3D11Texture2D *tex, + bool backing_y_0_top, + uint32_t backing_width, + uint32_t backing_height, + uint32_t x, uint32_t y, + uint32_t w, uint32_t h) +{ + Error *err = NULL; + BOOL success; + HANDLE share_handle, target_handle; + + if (!d3d_texture2d_release0(tex, &err)) { + error_report_err(err); + return false; + } + + if (!d3d_texture2d_share(tex, &share_handle, &err)) { + error_report_err(err); + return false; + } + + success = DuplicateHandle( + GetCurrentProcess(), + share_handle, + ddl->peer_process, + &target_handle, + 0, + FALSE, DUPLICATE_SAME_ACCESS); + if (!success) { + g_autofree char *msg = g_win32_error_message(GetLastError()); + g_debug("Failed to DuplicateHandle: %s", msg); + CloseHandle(share_handle); + return false; + } + + qemu_dbus_display1_listener_win32_d3d11_call_scanout_texture2d( + ddl->d3d11_proxy, + GPOINTER_TO_INT(target_handle), + backing_width, + backing_height, + backing_y_0_top, + x, y, w, h, + G_DBUS_CALL_FLAGS_NONE, + -1, + NULL, NULL, NULL); + + CloseHandle(share_handle); + + if (!d3d_texture2d_acquire0(tex, &err)) { + error_report_err(err); + return false; + } + + ddl->d3d_texture = tex; + ddl->ds_share = SHARE_KIND_D3DTEX; + + return true; +} +#endif +#ifdef CONFIG_OPENGL static void dbus_scanout_texture(DisplayChangeListener *dcl, uint32_t tex_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { + trace_dbus_scanout_texture(tex_id, backing_y_0_top, + backing_width, backing_height, x, y, w, h); +#ifdef CONFIG_GBM QemuDmaBuf dmabuf = { .width = backing_width, .height = backing_height, @@ -148,8 +432,26 @@ static void dbus_scanout_texture(DisplayChangeListener *dcl, dbus_scanout_dmabuf(dcl, &dmabuf); close(dmabuf.fd); +#endif + +#ifdef WIN32 + DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); + + /* there must be a matching gfx_switch before */ + assert(surface_width(ddl->ds) == w); + assert(surface_height(ddl->ds) == h); + + if (d3d_tex2d) { + dbus_scanout_share_d3d_texture(ddl, d3d_tex2d, backing_y_0_top, + backing_width, backing_height, x, y, w, h); + } else { + dbus_scanout_map(ddl); + egl_fb_setup_for_tex(&ddl->fb, backing_width, backing_height, tex_id, false); + } +#endif } +#ifdef CONFIG_GBM static void dbus_cursor_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf, bool have_hot, uint32_t hot_x, uint32_t hot_y) @@ -196,7 +498,14 @@ static void dbus_cursor_dmabuf(DisplayChangeListener *dcl, NULL); } -static void dbus_cursor_position(DisplayChangeListener *dcl, +static void dbus_release_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf) +{ + dbus_scanout_disable(dcl); +} +#endif /* GBM */ + +static void dbus_gl_cursor_position(DisplayChangeListener *dcl, uint32_t pos_x, uint32_t pos_y) { DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); @@ -206,19 +515,11 @@ static void dbus_cursor_position(DisplayChangeListener *dcl, G_DBUS_CALL_FLAGS_NONE, -1, NULL, NULL, NULL); } -static void dbus_release_dmabuf(DisplayChangeListener *dcl, - QemuDmaBuf *dmabuf) -{ - dbus_scanout_disable(dcl); -} - static void dbus_scanout_update(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { - DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); - - dbus_call_update_gl(ddl, x, y, w, h); + dbus_call_update_gl(dcl, x, y, w, h); } static void dbus_gl_refresh(DisplayChangeListener *dcl) @@ -232,19 +533,19 @@ static void dbus_gl_refresh(DisplayChangeListener *dcl) } if (ddl->gl_updates) { - dbus_call_update_gl(ddl, 0, 0, + dbus_call_update_gl(dcl, 0, 0, surface_width(ddl->ds), surface_height(ddl->ds)); ddl->gl_updates = 0; } } -#endif +#endif /* OPENGL */ static void dbus_refresh(DisplayChangeListener *dcl) { graphic_hw_update(dcl->con); } -#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) +#ifdef CONFIG_OPENGL static void dbus_gl_gfx_update(DisplayChangeListener *dcl, int x, int y, int w, int h) { @@ -263,10 +564,20 @@ static void dbus_gfx_update(DisplayChangeListener *dcl, size_t stride; assert(ddl->ds); - stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8); trace_dbus_update(x, y, w, h); +#ifdef WIN32 + if (dbus_scanout_map(ddl)) { + qemu_dbus_display1_listener_win32_map_call_update_map( + ddl->map_proxy, + x, y, w, h, + G_DBUS_CALL_FLAGS_NONE, + DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); + return; + } +#endif + if (x == 0 && y == 0 && w == surface_width(ddl->ds) && h == surface_height(ddl->ds)) { v_data = g_variant_new_from_data( G_VARIANT_TYPE("ay"), @@ -288,6 +599,7 @@ static void dbus_gfx_update(DisplayChangeListener *dcl, } /* make a copy, since gvariant only handles linear data */ + stride = w * DIV_ROUND_UP(PIXMAN_FORMAT_BPP(surface_format(ddl->ds)), 8); img = pixman_image_create_bits(surface_format(ddl->ds), w, h, NULL, stride); pixman_image_composite(PIXMAN_OP_SRC, ddl->ds->image, NULL, img, @@ -307,20 +619,23 @@ static void dbus_gfx_update(DisplayChangeListener *dcl, DBUS_DEFAULT_TIMEOUT, NULL, NULL, NULL); } -#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) +#ifdef CONFIG_OPENGL static void dbus_gl_gfx_switch(DisplayChangeListener *dcl, struct DisplaySurface *new_surface) { DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); + trace_dbus_gl_gfx_switch(new_surface); + ddl->ds = new_surface; + ddl->ds_share = SHARE_KIND_NONE; if (ddl->ds) { int width = surface_width(ddl->ds); int height = surface_height(ddl->ds); /* TODO: lazy send dmabuf (there are unnecessary sent otherwise) */ dbus_scanout_texture(&ddl->dcl, ddl->ds->texture, false, - width, height, 0, 0, width, height); + width, height, 0, 0, width, height, NULL); } } #endif @@ -331,10 +646,7 @@ static void dbus_gfx_switch(DisplayChangeListener *dcl, DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); ddl->ds = new_surface; - if (!ddl->ds) { - /* why not call disable instead? */ - return; - } + ddl->ds_share = SHARE_KIND_NONE; } static void dbus_mouse_set(DisplayChangeListener *dcl, @@ -374,7 +686,7 @@ static void dbus_cursor_define(DisplayChangeListener *dcl, NULL); } -#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) +#ifdef CONFIG_OPENGL const DisplayChangeListenerOps dbus_gl_dcl_ops = { .dpy_name = "dbus-gl", .dpy_gfx_update = dbus_gl_gfx_update, @@ -386,10 +698,12 @@ const DisplayChangeListenerOps dbus_gl_dcl_ops = { .dpy_gl_scanout_disable = dbus_scanout_disable, .dpy_gl_scanout_texture = dbus_scanout_texture, +#ifdef CONFIG_GBM .dpy_gl_scanout_dmabuf = dbus_scanout_dmabuf, .dpy_gl_cursor_dmabuf = dbus_cursor_dmabuf, - .dpy_gl_cursor_position = dbus_cursor_position, .dpy_gl_release_dmabuf = dbus_release_dmabuf, +#endif + .dpy_gl_cursor_position = dbus_gl_cursor_position, .dpy_gl_update = dbus_scanout_update, }; #endif @@ -412,6 +726,14 @@ dbus_display_listener_dispose(GObject *object) g_clear_object(&ddl->conn); g_clear_pointer(&ddl->bus_name, g_free); g_clear_object(&ddl->proxy); +#ifdef WIN32 + g_clear_object(&ddl->map_proxy); + g_clear_object(&ddl->d3d11_proxy); + g_clear_pointer(&ddl->peer_process, CloseHandle); +#ifdef CONFIG_OPENGL + egl_fb_destroy(&ddl->fb); +#endif +#endif G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object); } @@ -422,7 +744,7 @@ dbus_display_listener_constructed(GObject *object) DBusDisplayListener *ddl = DBUS_DISPLAY_LISTENER(object); ddl->dcl.ops = &dbus_dcl_ops; -#if defined(CONFIG_OPENGL) && defined(CONFIG_GBM) +#ifdef CONFIG_OPENGL if (display_opengl) { ddl->dcl.ops = &dbus_gl_dcl_ops; } @@ -457,6 +779,130 @@ dbus_display_listener_get_console(DBusDisplayListener *ddl) return ddl->console; } +#ifdef WIN32 +static bool +dbus_display_listener_implements(DBusDisplayListener *ddl, const char *iface) +{ + QemuDBusDisplay1Listener *l = QEMU_DBUS_DISPLAY1_LISTENER(ddl->proxy); + bool implements; + + implements = g_strv_contains(qemu_dbus_display1_listener_get_interfaces(l), iface); + if (!implements) { + g_debug("Display listener does not implement: `%s`", iface); + } + + return implements; +} + +static bool +dbus_display_listener_setup_peer_process(DBusDisplayListener *ddl) +{ + g_autoptr(GError) err = NULL; + GDBusConnection *conn; + GIOStream *stream; + GSocket *sock; + g_autoptr(GCredentials) creds = NULL; + DWORD *pid; + + if (ddl->peer_process) { + return true; + } + + conn = g_dbus_proxy_get_connection(G_DBUS_PROXY(ddl->proxy)); + stream = g_dbus_connection_get_stream(conn); + + if (!G_IS_UNIX_CONNECTION(stream)) { + return false; + } + + sock = g_socket_connection_get_socket(G_SOCKET_CONNECTION(stream)); + creds = g_socket_get_credentials(sock, &err); + + if (!creds) { + g_debug("Failed to get peer credentials: %s", err->message); + return false; + } + + pid = g_credentials_get_native(creds, G_CREDENTIALS_TYPE_WIN32_PID); + + if (pid == NULL) { + g_debug("Failed to get peer PID"); + return false; + } + + ddl->peer_process = OpenProcess( + PROCESS_DUP_HANDLE | PROCESS_QUERY_INFORMATION, + false, *pid); + + if (!ddl->peer_process) { + g_autofree char *msg = g_win32_error_message(GetLastError()); + g_debug("Failed to OpenProcess: %s", msg); + return false; + } + + return true; +} +#endif + +static void +dbus_display_listener_setup_d3d11(DBusDisplayListener *ddl) +{ +#ifdef WIN32 + g_autoptr(GError) err = NULL; + + if (!dbus_display_listener_implements(ddl, + "org.qemu.Display1.Listener.Win32.D3d11")) { + return; + } + + if (!dbus_display_listener_setup_peer_process(ddl)) { + return; + } + + ddl->d3d11_proxy = + qemu_dbus_display1_listener_win32_d3d11_proxy_new_sync(ddl->conn, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "/org/qemu/Display1/Listener", + NULL, + &err); + if (!ddl->d3d11_proxy) { + g_debug("Failed to setup win32 d3d11 proxy: %s", err->message); + return; + } +#endif +} + +static void +dbus_display_listener_setup_shared_map(DBusDisplayListener *ddl) +{ +#ifdef WIN32 + g_autoptr(GError) err = NULL; + + if (!dbus_display_listener_implements(ddl, "org.qemu.Display1.Listener.Win32.Map")) { + return; + } + + if (!dbus_display_listener_setup_peer_process(ddl)) { + return; + } + + ddl->map_proxy = + qemu_dbus_display1_listener_win32_map_proxy_new_sync(ddl->conn, + G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, + NULL, + "/org/qemu/Display1/Listener", + NULL, + &err); + if (!ddl->map_proxy) { + g_debug("Failed to setup win32 map proxy: %s", err->message); + return; + } + + ddl->can_share_map = true; +#endif +} + DBusDisplayListener * dbus_display_listener_new(const char *bus_name, GDBusConnection *conn, @@ -485,6 +931,9 @@ dbus_display_listener_new(const char *bus_name, ddl->conn = conn; ddl->console = console; + dbus_display_listener_setup_shared_map(ddl); + dbus_display_listener_setup_d3d11(ddl); + con = qemu_console_lookup_by_index(dbus_display_console_get_index(console)); assert(con); ddl->dcl.con = con; @@ -47,10 +47,8 @@ static DBusDisplay *dbus_display; static QEMUGLContext dbus_create_context(DisplayGLCtx *dgc, QEMUGLParams *params) { -#ifdef CONFIG_GBM eglMakeCurrent(qemu_egl_display, EGL_NO_SURFACE, EGL_NO_SURFACE, qemu_egl_rn_ctx); -#endif return qemu_egl_create_context(dgc, params); } @@ -59,9 +57,7 @@ dbus_is_compatible_dcl(DisplayGLCtx *dgc, DisplayChangeListener *dcl) { return -#ifdef CONFIG_GBM dcl->ops == &dbus_gl_dcl_ops || -#endif dcl->ops == &dbus_console_dcl_ops; } @@ -62,6 +62,12 @@ struct DBusDisplay { Notifier notifier; }; +#ifdef WIN32 +bool +dbus_win32_import_socket(GDBusMethodInvocation *invocation, + GVariant *arg_listener, int *socket); +#endif + #define TYPE_DBUS_DISPLAY "dbus-display" OBJECT_DECLARE_SIMPLE_TYPE(DBusDisplay, DBUS_DISPLAY) diff --git a/ui/egl-context.c b/ui/egl-context.c index eb5f520..9e0df46 100644 --- a/ui/egl-context.c +++ b/ui/egl-context.c @@ -1,4 +1,5 @@ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "ui/egl-context.h" QEMUGLContext qemu_egl_create_context(DisplayGLCtx *dgc, @@ -32,6 +33,11 @@ void qemu_egl_destroy_context(DisplayGLCtx *dgc, QEMUGLContext ctx) int qemu_egl_make_context_current(DisplayGLCtx *dgc, QEMUGLContext ctx) { - return eglMakeCurrent(qemu_egl_display, - EGL_NO_SURFACE, EGL_NO_SURFACE, ctx); + if (!eglMakeCurrent(qemu_egl_display, + EGL_NO_SURFACE, EGL_NO_SURFACE, ctx)) { + error_report("egl: eglMakeCurrent failed: %s", qemu_egl_get_error_string()); + return -1; + } + + return 0; } diff --git a/ui/egl-headless.c b/ui/egl-headless.c index ef70e6a..d5637da 100644 --- a/ui/egl-headless.c +++ b/ui/egl-headless.c @@ -61,7 +61,8 @@ static void egl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { egl_dpy *edpy = container_of(dcl, egl_dpy, dcl); @@ -79,6 +80,8 @@ static void egl_scanout_texture(DisplayChangeListener *dcl, } } +#ifdef CONFIG_GBM + static void egl_scanout_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf) { @@ -89,7 +92,7 @@ static void egl_scanout_dmabuf(DisplayChangeListener *dcl, egl_scanout_texture(dcl, dmabuf->texture, false, dmabuf->width, dmabuf->height, - 0, 0, dmabuf->width, dmabuf->height); + 0, 0, dmabuf->width, dmabuf->height, NULL); } static void egl_cursor_dmabuf(DisplayChangeListener *dcl, @@ -110,6 +113,14 @@ static void egl_cursor_dmabuf(DisplayChangeListener *dcl, } } +static void egl_release_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf) +{ + egl_dmabuf_release_texture(dmabuf); +} + +#endif + static void egl_cursor_position(DisplayChangeListener *dcl, uint32_t pos_x, uint32_t pos_y) { @@ -119,12 +130,6 @@ static void egl_cursor_position(DisplayChangeListener *dcl, edpy->pos_y = pos_y; } -static void egl_release_dmabuf(DisplayChangeListener *dcl, - QemuDmaBuf *dmabuf) -{ - egl_dmabuf_release_texture(dmabuf); -} - static void egl_scanout_flush(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h) @@ -160,10 +165,12 @@ static const DisplayChangeListenerOps egl_ops = { .dpy_gl_scanout_disable = egl_scanout_disable, .dpy_gl_scanout_texture = egl_scanout_texture, +#ifdef CONFIG_GBM .dpy_gl_scanout_dmabuf = egl_scanout_dmabuf, .dpy_gl_cursor_dmabuf = egl_cursor_dmabuf, - .dpy_gl_cursor_position = egl_cursor_position, .dpy_gl_release_dmabuf = egl_release_dmabuf, +#endif + .dpy_gl_cursor_position = egl_cursor_position, .dpy_gl_update = egl_scanout_flush, }; diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index 4203163..8f9fbf5 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -15,21 +15,23 @@ * License along with this library; if not, see <http://www.gnu.org/licenses/>. */ #include "qemu/osdep.h" + #include "qemu/drm.h" #include "qemu/error-report.h" #include "ui/console.h" #include "ui/egl-helpers.h" #include "sysemu/sysemu.h" #include "qapi/error.h" +#include "trace.h" EGLDisplay *qemu_egl_display; EGLConfig qemu_egl_config; DisplayGLMode qemu_egl_mode; +bool qemu_egl_angle_d3d; /* ------------------------------------------------------------------ */ -#if defined(CONFIG_X11) || defined(CONFIG_GBM) -static const char *egl_get_error_string(void) +const char *qemu_egl_get_error_string(void) { EGLint error = eglGetError(); @@ -68,7 +70,6 @@ static const char *egl_get_error_string(void) return "Unknown EGL error"; } } -#endif static void egl_fb_delete_texture(egl_fb *fb) { @@ -171,6 +172,20 @@ void egl_fb_read(DisplaySurface *dst, egl_fb *src) GL_BGRA, GL_UNSIGNED_BYTE, surface_data(dst)); } +void egl_fb_read_rect(DisplaySurface *dst, egl_fb *src, int x, int y, int w, int h) +{ + assert(surface_width(dst) == src->width); + assert(surface_height(dst) == src->height); + assert(surface_format(dst) == PIXMAN_x8r8g8b8); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer); + glReadBuffer(GL_COLOR_ATTACHMENT0_EXT); + glPixelStorei(GL_PACK_ROW_LENGTH, surface_stride(dst) / 4); + glReadPixels(x, y, w, h, + GL_BGRA, GL_UNSIGNED_BYTE, surface_data(dst) + x * 4); + glPixelStorei(GL_PACK_ROW_LENGTH, 0); +} + void egl_texture_blit(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip) { glBindFramebuffer(GL_FRAMEBUFFER_EXT, dst->framebuffer); @@ -201,11 +216,12 @@ void egl_texture_blend(QemuGLShader *gls, egl_fb *dst, egl_fb *src, bool flip, /* ---------------------------------------------------------------------- */ +EGLContext qemu_egl_rn_ctx; + #ifdef CONFIG_GBM int qemu_egl_rn_fd; struct gbm_device *qemu_egl_rn_gbm_dev; -EGLContext qemu_egl_rn_ctx; int egl_rendernode_init(const char *rendernode, DisplayGLMode mode) { @@ -402,7 +418,7 @@ EGLSurface qemu_egl_init_surface_x11(EGLContext ectx, EGLNativeWindowType win) /* ---------------------------------------------------------------------- */ -#if defined(CONFIG_X11) || defined(CONFIG_GBM) +#if defined(CONFIG_X11) || defined(CONFIG_GBM) || defined(WIN32) /* * Taken from glamor_egl.h from the Xorg xserver, which is MIT licensed @@ -439,10 +455,8 @@ static EGLDisplay qemu_egl_get_display(EGLNativeDisplayType native, /* In practise any EGL 1.5 implementation would support the EXT extension */ if (epoxy_has_egl_extension(NULL, "EGL_EXT_platform_base")) { - PFNEGLGETPLATFORMDISPLAYEXTPROC getPlatformDisplayEXT = - (void *) eglGetProcAddress("eglGetPlatformDisplayEXT"); - if (getPlatformDisplayEXT && platform != 0) { - dpy = getPlatformDisplayEXT(platform, native, NULL); + if (platform != 0) { + dpy = eglGetPlatformDisplayEXT(platform, native, NULL); } } @@ -482,20 +496,20 @@ static int qemu_egl_init_dpy(EGLNativeDisplayType dpy, qemu_egl_display = qemu_egl_get_display(dpy, platform); if (qemu_egl_display == EGL_NO_DISPLAY) { - error_report("egl: eglGetDisplay failed: %s", egl_get_error_string()); + error_report("egl: eglGetDisplay failed: %s", qemu_egl_get_error_string()); return -1; } b = eglInitialize(qemu_egl_display, &major, &minor); if (b == EGL_FALSE) { - error_report("egl: eglInitialize failed: %s", egl_get_error_string()); + error_report("egl: eglInitialize failed: %s", qemu_egl_get_error_string()); return -1; } b = eglBindAPI(gles ? EGL_OPENGL_ES_API : EGL_OPENGL_API); if (b == EGL_FALSE) { error_report("egl: eglBindAPI failed (%s mode): %s", - gles ? "gles" : "core", egl_get_error_string()); + gles ? "gles" : "core", qemu_egl_get_error_string()); return -1; } @@ -504,7 +518,7 @@ static int qemu_egl_init_dpy(EGLNativeDisplayType dpy, &qemu_egl_config, 1, &n); if (b == EGL_FALSE || n != 1) { error_report("egl: eglChooseConfig failed (%s mode): %s", - gles ? "gles" : "core", egl_get_error_string()); + gles ? "gles" : "core", qemu_egl_get_error_string()); return -1; } @@ -512,6 +526,9 @@ static int qemu_egl_init_dpy(EGLNativeDisplayType dpy, return 0; } +#endif + +#if defined(CONFIG_X11) || defined(CONFIG_GBM) int qemu_egl_init_dpy_x11(EGLNativeDisplayType dpy, DisplayGLMode mode) { #ifdef EGL_KHR_platform_x11 @@ -529,7 +546,45 @@ int qemu_egl_init_dpy_mesa(EGLNativeDisplayType dpy, DisplayGLMode mode) return qemu_egl_init_dpy(dpy, 0, mode); #endif } +#endif + + +#ifdef WIN32 +int qemu_egl_init_dpy_win32(EGLNativeDisplayType dpy, DisplayGLMode mode) +{ + /* prefer GL ES, as that's what ANGLE supports */ + if (mode == DISPLAYGL_MODE_ON) { + mode = DISPLAYGL_MODE_ES; + } + + if (qemu_egl_init_dpy(dpy, 0, mode) < 0) { + return -1; + } + +#ifdef EGL_D3D11_DEVICE_ANGLE + if (epoxy_has_egl_extension(qemu_egl_display, "EGL_EXT_device_query")) { + EGLDeviceEXT device; + void *d3d11_device; + if (!eglQueryDisplayAttribEXT(qemu_egl_display, + EGL_DEVICE_EXT, + (EGLAttrib *)&device)) { + return 0; + } + + if (!eglQueryDeviceAttribEXT(device, + EGL_D3D11_DEVICE_ANGLE, + (EGLAttrib *)&d3d11_device)) { + return 0; + } + + trace_egl_init_d3d11_device(device); + qemu_egl_angle_d3d = device != NULL; + } +#endif + + return 0; +} #endif bool qemu_egl_has_dmabuf(void) @@ -581,15 +636,28 @@ bool egl_init(const char *rendernode, DisplayGLMode mode, Error **errp) return false; } -#ifdef CONFIG_GBM +#ifdef WIN32 + if (qemu_egl_init_dpy_win32(EGL_DEFAULT_DISPLAY, mode) < 0) { + error_setg(errp, "egl: init failed"); + return false; + } + qemu_egl_rn_ctx = qemu_egl_init_ctx(); + if (!qemu_egl_rn_ctx) { + error_setg(errp, "egl: egl_init_ctx failed"); + return false; + } +#elif defined(CONFIG_GBM) if (egl_rendernode_init(rendernode, mode) < 0) { error_setg(errp, "egl: render node init failed"); return false; } +#endif + + if (!qemu_egl_rn_ctx) { + error_setg(errp, "egl: not available on this platform"); + return false; + } + display_opengl = 1; return true; -#else - error_setg(errp, "egl: not available on this platform"); - return false; -#endif } diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index 1913004..d59b8cd 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -13,6 +13,7 @@ #include "qemu/osdep.h" #include "qemu/main-loop.h" +#include "qemu/error-report.h" #include "trace.h" @@ -223,7 +224,8 @@ void gd_egl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); @@ -257,7 +259,8 @@ void gd_egl_scanout_dmabuf(DisplayChangeListener *dcl, gd_egl_scanout_texture(dcl, dmabuf->texture, dmabuf->y0_top, dmabuf->width, dmabuf->height, - 0, 0, dmabuf->width, dmabuf->height); + dmabuf->x, dmabuf->y, dmabuf->scanout_width, + dmabuf->scanout_height, NULL); if (dmabuf->allow_fences) { vc->gfx.guest_fb.dmabuf = dmabuf; @@ -368,6 +371,11 @@ int gd_egl_make_current(DisplayGLCtx *dgc, { VirtualConsole *vc = container_of(dgc, VirtualConsole, gfx.dgc); - return eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, - vc->gfx.esurface, ctx); + if (!eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, + vc->gfx.esurface, ctx)) { + error_report("egl: eglMakeCurrent failed: %s", qemu_egl_get_error_string()); + return -1; + } + + return 0; } diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c index c384a15..7367dfd 100644 --- a/ui/gtk-gl-area.c +++ b/ui/gtk-gl-area.c @@ -244,7 +244,8 @@ void gd_gl_area_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); @@ -299,7 +300,8 @@ void gd_gl_area_scanout_dmabuf(DisplayChangeListener *dcl, gd_gl_area_scanout_texture(dcl, dmabuf->texture, dmabuf->y0_top, dmabuf->width, dmabuf->height, - 0, 0, dmabuf->width, dmabuf->height); + dmabuf->x, dmabuf->y, dmabuf->scanout_width, + dmabuf->scanout_height, NULL); if (dmabuf->allow_fences) { vc->gfx.guest_fb.dmabuf = dmabuf; @@ -130,11 +130,6 @@ typedef struct VCChardev VCChardev; DECLARE_INSTANCE_CHECKER(VCChardev, VC_CHARDEV, TYPE_CHARDEV_VC) -struct touch_slot { - int x; - int y; - int tracking_id; -}; static struct touch_slot touch_slots[INPUT_EVENT_SLOTS_MAX]; bool gtk_use_gl_area; @@ -588,7 +583,12 @@ static void gd_gl_release_dmabuf(DisplayChangeListener *dcl, QemuDmaBuf *dmabuf) { #ifdef CONFIG_GBM + VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); + egl_dmabuf_release_texture(dmabuf); + if (vc->gfx.guest_fb.dmabuf == dmabuf) { + vc->gfx.guest_fb.dmabuf = NULL; + } #endif } @@ -1068,27 +1068,12 @@ static gboolean gd_touch_event(GtkWidget *widget, GdkEventTouch *touch, void *opaque) { VirtualConsole *vc = opaque; - struct touch_slot *slot; uint64_t num_slot = GPOINTER_TO_UINT(touch->sequence); - bool needs_sync = false; - int update; int type = -1; - int i; - - if (num_slot >= INPUT_EVENT_SLOTS_MAX) { - warn_report("gtk: unexpected touch slot number: % " PRId64" >= %d\n", - num_slot, INPUT_EVENT_SLOTS_MAX); - return FALSE; - } - - slot = &touch_slots[num_slot]; - slot->x = touch->x; - slot->y = touch->y; switch (touch->type) { case GDK_TOUCH_BEGIN: type = INPUT_MULTI_TOUCH_TYPE_BEGIN; - slot->tracking_id = num_slot; break; case GDK_TOUCH_UPDATE: type = INPUT_MULTI_TOUCH_TYPE_UPDATE; @@ -1099,44 +1084,13 @@ static gboolean gd_touch_event(GtkWidget *widget, GdkEventTouch *touch, break; default: warn_report("gtk: unexpected touch event type\n"); + return FALSE; } - for (i = 0; i < INPUT_EVENT_SLOTS_MAX; ++i) { - if (i == num_slot) { - update = type; - } else { - update = INPUT_MULTI_TOUCH_TYPE_UPDATE; - } - - slot = &touch_slots[i]; - - if (slot->tracking_id == -1) { - continue; - } - - if (update == INPUT_MULTI_TOUCH_TYPE_END) { - slot->tracking_id = -1; - qemu_input_queue_mtt(vc->gfx.dcl.con, update, i, slot->tracking_id); - needs_sync = true; - } else { - qemu_input_queue_mtt(vc->gfx.dcl.con, update, i, slot->tracking_id); - qemu_input_queue_btn(vc->gfx.dcl.con, INPUT_BUTTON_TOUCH, true); - qemu_input_queue_mtt_abs(vc->gfx.dcl.con, - INPUT_AXIS_X, (int) slot->x, - 0, surface_width(vc->gfx.ds), - i, slot->tracking_id); - qemu_input_queue_mtt_abs(vc->gfx.dcl.con, - INPUT_AXIS_Y, (int) slot->y, - 0, surface_height(vc->gfx.ds), - i, slot->tracking_id); - needs_sync = true; - } - } - - if (needs_sync) { - qemu_input_event_sync(); - } - + console_handle_touch_event(vc->gfx.dcl.con, touch_slots, + num_slot, surface_width(vc->gfx.ds), + surface_height(vc->gfx.ds), touch->x, + touch->y, type, &error_warn); return TRUE; } diff --git a/ui/meson.build b/ui/meson.build index a5506ac..d81609f 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -65,18 +65,25 @@ if opengl.found() ui_modules += {'opengl' : opengl_ss} endif -if opengl.found() and gbm.found() +if opengl.found() egl_headless_ss = ss.source_set() - egl_headless_ss.add(when: [opengl, gbm, pixman], - if_true: files('egl-headless.c')) + egl_headless_ss.add(when: [opengl, pixman], + if_true: [files('egl-headless.c'), gbm]) ui_modules += {'egl-headless' : egl_headless_ss} endif if dbus_display dbus_ss = ss.source_set() + env = environment() + env.set('TARGETOS', targetos) + xml = custom_target('dbus-display preprocess', + input: 'dbus-display1.xml', + output: 'dbus-display1.xml', + env: env, + command: [xml_pp, '@INPUT@', '@OUTPUT@']) dbus_display1 = custom_target('dbus-display gdbus-codegen', output: ['dbus-display1.h', 'dbus-display1.c'], - input: files('dbus-display1.xml'), + input: xml, command: [gdbus_codegen, '@INPUT@', '--glib-min-required', '2.64', '--output-directory', meson.current_build_dir(), diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c index 3ab7e2e..e4f024a 100644 --- a/ui/qemu-pixman.c +++ b/ui/qemu-pixman.c @@ -6,6 +6,7 @@ #include "qemu/osdep.h" #include "ui/console.h" #include "standard-headers/drm/drm_fourcc.h" +#include "trace.h" PixelFormat qemu_pixelformat_from_pixman(pixman_format_code_t format) { diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c index bbfa70e..28d7966 100644 --- a/ui/sdl2-gl.c +++ b/ui/sdl2-gl.c @@ -205,7 +205,8 @@ void sdl2_gl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { struct sdl2_console *scon = container_of(dcl, struct sdl2_console, dcl); @@ -113,11 +113,11 @@ void sdl2_window_create(struct sdl2_console *scon) SDL_SetHint(SDL_HINT_RENDER_DRIVER, driver); SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1"); - } - scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0); - if (scon->opengl) { scon->winctx = SDL_GL_CreateContext(scon->real_window); + } else { + /* The SDL renderer is only used by sdl2-2D, when OpenGL is disabled */ + scon->real_renderer = SDL_CreateRenderer(scon->real_window, -1, 0); } sdl_update_caption(scon); } @@ -128,10 +128,14 @@ void sdl2_window_destroy(struct sdl2_console *scon) return; } - SDL_GL_DeleteContext(scon->winctx); - scon->winctx = NULL; - SDL_DestroyRenderer(scon->real_renderer); - scon->real_renderer = NULL; + if (scon->winctx) { + SDL_GL_DeleteContext(scon->winctx); + scon->winctx = NULL; + } + if (scon->real_renderer) { + SDL_DestroyRenderer(scon->real_renderer); + scon->real_renderer = NULL; + } SDL_DestroyWindow(scon->real_window); scon->real_window = NULL; } diff --git a/ui/spice-display.c b/ui/spice-display.c index 5bee19a..3f3f801 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -935,7 +935,8 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, - uint32_t w, uint32_t h) + uint32_t w, uint32_t h, + void *d3d_tex2d) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); EGLint stride = 0, fourcc = 0; diff --git a/ui/trace-events b/ui/trace-events index 6747361..76b19a2 100644 --- a/ui/trace-events +++ b/ui/trace-events @@ -9,7 +9,7 @@ console_putchar_unhandled(int ch) "unhandled escape character '%c'" console_txt_new(int w, int h) "%dx%d" console_select(int nr) "%d" console_refresh(int interval) "interval %d ms" -displaysurface_create(void *display_surface, int w, int h) "surface=%p, %dx%d" +displaysurface_create(int w, int h) "%dx%d" displaysurface_create_from(void *display_surface, int w, int h, uint32_t format) "surface=%p, %dx%d, format 0x%x" displaysurface_create_pixman(void *display_surface) "surface=%p" displaysurface_free(void *display_surface) "surface=%p" @@ -154,7 +154,14 @@ dbus_mouse_press(unsigned int button) "button %u" dbus_mouse_release(unsigned int button) "button %u" dbus_mouse_set_pos(unsigned int x, unsigned int y) "x=%u, y=%u" dbus_mouse_rel_motion(int dx, int dy) "dx=%d, dy=%d" +dbus_touch_send_event(unsigned int kind, uint32_t num_slot, uint32_t x, uint32_t y) "kind=%u, num_slot=%u, x=%d, y=%d" dbus_update(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d" +dbus_update_gl(int x, int y, int w, int h) "x=%d, y=%d, w=%d, h=%d" dbus_clipboard_grab_failed(void) "" dbus_clipboard_register(const char *bus_name) "peer %s" dbus_clipboard_unregister(const char *bus_name) "peer %s" +dbus_scanout_texture(uint32_t tex_id, bool backing_y_0_top, uint32_t backing_width, uint32_t backing_height, uint32_t x, uint32_t y, uint32_t w, uint32_t h) "tex_id:%u y0top:%d back:%ux%u %u+%u-%ux%u" +dbus_gl_gfx_switch(void *p) "surf: %p" + +# egl-helpers.c +egl_init_d3d11_device(void *p) "d3d device: %p" diff --git a/util/oslib-win32.c b/util/oslib-win32.c index fafbab8..429542f 100644 --- a/util/oslib-win32.c +++ b/util/oslib-win32.c @@ -835,3 +835,36 @@ int qemu_msync(void *addr, size_t length, int fd) */ return qemu_fdatasync(fd); } + +void *qemu_win32_map_alloc(size_t size, HANDLE *h, Error **errp) +{ + void *bits; + + trace_win32_map_alloc(size); + + *h = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, + size, NULL); + if (*h == NULL) { + error_setg_win32(errp, GetLastError(), "Failed to CreateFileMapping"); + return NULL; + } + + bits = MapViewOfFile(*h, FILE_MAP_ALL_ACCESS, 0, 0, size); + if (bits == NULL) { + error_setg_win32(errp, GetLastError(), "Failed to MapViewOfFile"); + CloseHandle(*h); + return NULL; + } + + return bits; +} + +void qemu_win32_map_free(void *ptr, HANDLE h, Error **errp) +{ + trace_win32_map_free(ptr, h); + + if (UnmapViewOfFile(ptr) == 0) { + error_setg_win32(errp, GetLastError(), "Failed to UnmapViewOfFile"); + } + CloseHandle(h); +} diff --git a/util/trace-events b/util/trace-events index 3f7e766..49a4962 100644 --- a/util/trace-events +++ b/util/trace-events @@ -52,6 +52,10 @@ qemu_anon_ram_alloc(size_t size, void *ptr) "size %zu ptr %p" qemu_vfree(void *ptr) "ptr %p" qemu_anon_ram_free(void *ptr, size_t size) "ptr %p size %zu" +# oslib-win32.c +win32_map_alloc(size_t size) "size:%zd" +win32_map_free(void *ptr, void *h) "ptr:%p handle:%p" + # hbitmap.c hbitmap_iter_skip_words(const void *hb, void *hbi, uint64_t pos, unsigned long cur) "hb %p hbi %p pos %"PRId64" cur 0x%lx" hbitmap_reset(void *hb, uint64_t start, uint64_t count, uint64_t sbit, uint64_t ebit) "hb %p items %"PRIu64",%"PRIu64" bits %"PRIu64"..%"PRIu64 |