diff options
Diffstat (limited to 'ui')
-rw-r--r-- | ui/clipboard.c | 66 | ||||
-rw-r--r-- | ui/console-gl.c | 54 | ||||
-rw-r--r-- | ui/console-vc.c | 6 | ||||
-rw-r--r-- | ui/console.c | 6 | ||||
-rw-r--r-- | ui/dbus-chardev.c | 2 | ||||
-rw-r--r-- | ui/dbus-display1.xml | 45 | ||||
-rw-r--r-- | ui/dbus-listener.c | 129 | ||||
-rw-r--r-- | ui/dbus.c | 6 | ||||
-rw-r--r-- | ui/dmabuf.c | 80 | ||||
-rw-r--r-- | ui/egl-helpers.c | 119 | ||||
-rw-r--r-- | ui/gtk-clipboard.c | 13 | ||||
-rw-r--r-- | ui/gtk-egl.c | 58 | ||||
-rw-r--r-- | ui/gtk-gl-area.c | 53 | ||||
-rw-r--r-- | ui/gtk.c | 301 | ||||
-rw-r--r-- | ui/input-barrier.c | 4 | ||||
-rw-r--r-- | ui/input-linux.c | 5 | ||||
-rw-r--r-- | ui/meson.build | 3 | ||||
-rw-r--r-- | ui/qemu-pixman.c | 15 | ||||
-rw-r--r-- | ui/sdl2-gl.c | 2 | ||||
-rw-r--r-- | ui/sdl2.c | 20 | ||||
-rw-r--r-- | ui/spice-app.c | 2 | ||||
-rw-r--r-- | ui/spice-core.c | 32 | ||||
-rw-r--r-- | ui/spice-display.c | 326 | ||||
-rw-r--r-- | ui/vdagent.c | 207 | ||||
-rw-r--r-- | ui/vnc-enc-tight.c | 476 | ||||
-rw-r--r-- | ui/vnc-enc-zlib.c | 47 | ||||
-rw-r--r-- | ui/vnc-enc-zrle.c | 124 | ||||
-rw-r--r-- | ui/vnc-enc-zrle.c.inc | 20 | ||||
-rw-r--r-- | ui/vnc-jobs.c | 15 | ||||
-rw-r--r-- | ui/vnc.c | 106 | ||||
-rw-r--r-- | ui/vnc.h | 53 |
31 files changed, 1708 insertions, 687 deletions
diff --git a/ui/clipboard.c b/ui/clipboard.c index 132086e..ec00a0b 100644 --- a/ui/clipboard.c +++ b/ui/clipboard.c @@ -1,4 +1,5 @@ #include "qemu/osdep.h" +#include "system/runstate.h" #include "ui/clipboard.h" #include "trace.h" @@ -7,8 +8,62 @@ static NotifierList clipboard_notifiers = static QemuClipboardInfo *cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; +static VMChangeStateEntry *cb_change_state_entry = NULL; + +static bool cb_reset_serial_on_resume = false; + +static const VMStateDescription vmstate_cbcontent = { + .name = "clipboard/content", + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(available, QemuClipboardContent), + VMSTATE_BOOL(requested, QemuClipboardContent), + VMSTATE_UINT32(size, QemuClipboardContent), + VMSTATE_VBUFFER_ALLOC_UINT32(data, QemuClipboardContent, 0, 0, size), + VMSTATE_END_OF_LIST() + } +}; + +const VMStateDescription vmstate_cbinfo = { + .name = "clipboard", + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_INT32(selection, QemuClipboardInfo), + VMSTATE_BOOL(has_serial, QemuClipboardInfo), + VMSTATE_UINT32(serial, QemuClipboardInfo), + VMSTATE_STRUCT_ARRAY(types, QemuClipboardInfo, QEMU_CLIPBOARD_TYPE__COUNT, 0, vmstate_cbcontent, QemuClipboardContent), + VMSTATE_END_OF_LIST() + } +}; + +static void qemu_clipboard_change_state(void *opaque, bool running, RunState state) +{ + int i; + + if (!running) { + return; + } + + if (cb_reset_serial_on_resume) { + qemu_clipboard_reset_serial(); + } + + for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) { + if (cbinfo[i]) { + qemu_clipboard_update(cbinfo[i]); + } + } + +} + void qemu_clipboard_peer_register(QemuClipboardPeer *peer) { + if (cb_change_state_entry == NULL) { + cb_change_state_entry = qemu_add_vm_change_state_handler(qemu_clipboard_change_state, NULL); + } + notifier_list_add(&clipboard_notifiers, &peer->notifier); } @@ -83,7 +138,9 @@ void qemu_clipboard_update(QemuClipboardInfo *info) } } - notifier_list_notify(&clipboard_notifiers, ¬ify); + if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) { + notifier_list_notify(&clipboard_notifiers, ¬ify); + } if (cbinfo[info->selection] != info) { qemu_clipboard_info_unref(cbinfo[info->selection]); @@ -163,7 +220,12 @@ void qemu_clipboard_reset_serial(void) info->serial = 0; } } - notifier_list_notify(&clipboard_notifiers, ¬ify); + + if (runstate_is_running() || runstate_check(RUN_STATE_SUSPENDED)) { + notifier_list_notify(&clipboard_notifiers, ¬ify); + } else { + cb_reset_serial_on_resume = true; + } } void qemu_clipboard_set_data(QemuClipboardPeer *peer, diff --git a/ui/console-gl.c b/ui/console-gl.c index 103b954..403fc36 100644 --- a/ui/console-gl.c +++ b/ui/console-gl.c @@ -25,6 +25,7 @@ * THE SOFTWARE. */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "ui/console.h" #include "ui/shader.h" @@ -96,6 +97,53 @@ void surface_gl_create_texture(QemuGLShader *gls, glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); } +bool surface_gl_create_texture_from_fd(DisplaySurface *surface, + int fd, GLuint *texture, + GLuint *mem_obj) +{ + unsigned long size = surface_stride(surface) * surface_height(surface); + GLenum err = glGetError(); + *texture = 0; + *mem_obj = 0; + + if (!epoxy_has_gl_extension("GL_EXT_memory_object") || + !epoxy_has_gl_extension("GL_EXT_memory_object_fd")) { + error_report("spice: required OpenGL extensions not supported: " + "GL_EXT_memory_object and GL_EXT_memory_object_fd"); + return false; + } + +#ifdef GL_EXT_memory_object_fd + glCreateMemoryObjectsEXT(1, mem_obj); + glImportMemoryFdEXT(*mem_obj, size, GL_HANDLE_TYPE_OPAQUE_FD_EXT, fd); + + err = glGetError(); + if (err != GL_NO_ERROR) { + error_report("spice: cannot import memory object from fd"); + goto cleanup_mem; + } + + glGenTextures(1, texture); + glBindTexture(GL_TEXTURE_2D, *texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_TILING_EXT, GL_LINEAR_TILING_EXT); + glTexStorageMem2DEXT(GL_TEXTURE_2D, 1, GL_RGBA8, surface_width(surface), + surface_height(surface), *mem_obj, 0); + err = glGetError(); + if (err != GL_NO_ERROR) { + error_report("spice: cannot create texture from memory object"); + goto cleanup_tex_and_mem; + } + return true; + +cleanup_tex_and_mem: + glDeleteTextures(1, texture); +cleanup_mem: + glDeleteMemoryObjectsEXT(1, mem_obj); + +#endif + return false; +} + void surface_gl_update_texture(QemuGLShader *gls, DisplaySurface *surface, int x, int y, int w, int h) @@ -136,6 +184,12 @@ void surface_gl_destroy_texture(QemuGLShader *gls, } glDeleteTextures(1, &surface->texture); surface->texture = 0; +#ifdef GL_EXT_memory_object_fd + if (surface->mem_obj) { + glDeleteMemoryObjectsEXT(1, &surface->mem_obj); + surface->mem_obj = 0; + } +#endif } void surface_gl_setup_viewport(QemuGLShader *gls, diff --git a/ui/console-vc.c b/ui/console-vc.c index df13415..8308420 100644 --- a/ui/console-vc.c +++ b/ui/console-vc.c @@ -1036,7 +1036,7 @@ qemu_text_console_finalize(Object *obj) } static void -qemu_text_console_class_init(ObjectClass *oc, void *data) +qemu_text_console_class_init(ObjectClass *oc, const void *data) { if (!cursor_timer) { cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL); @@ -1065,7 +1065,7 @@ qemu_fixed_text_console_finalize(Object *obj) } static void -qemu_fixed_text_console_class_init(ObjectClass *oc, void *data) +qemu_fixed_text_console_class_init(ObjectClass *oc, const void *data) { } @@ -1181,7 +1181,7 @@ static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) } } -static void char_vc_class_init(ObjectClass *oc, void *data) +static void char_vc_class_init(ObjectClass *oc, const void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); diff --git a/ui/console.c b/ui/console.c index 6456e8d..2d00828 100644 --- a/ui/console.c +++ b/ui/console.c @@ -35,7 +35,7 @@ #include "qemu/option.h" #include "chardev/char.h" #include "trace.h" -#include "exec/memory.h" +#include "system/memory.h" #include "qom/object.h" #include "qemu/memfd.h" @@ -401,7 +401,7 @@ qemu_console_finalize(Object *obj) } static void -qemu_console_class_init(ObjectClass *oc, void *data) +qemu_console_class_init(ObjectClass *oc, const void *data) { } @@ -437,7 +437,7 @@ qemu_graphic_console_prop_get_head(Object *obj, Visitor *v, const char *name, } static void -qemu_graphic_console_class_init(ObjectClass *oc, void *data) +qemu_graphic_console_class_init(ObjectClass *oc, const void *data) { object_class_property_add_link(oc, "device", TYPE_DEVICE, offsetof(QemuGraphicConsole, device), diff --git a/ui/dbus-chardev.c b/ui/dbus-chardev.c index bf061cb..d05ddda 100644 --- a/ui/dbus-chardev.c +++ b/ui/dbus-chardev.c @@ -269,7 +269,7 @@ dbus_chr_parse(QemuOpts *opts, ChardevBackend *backend, } static void -char_dbus_class_init(ObjectClass *oc, void *data) +char_dbus_class_init(ObjectClass *oc, const void *data) { DBusChardevClass *klass = DBUS_CHARDEV_CLASS(oc); ChardevClass *cc = CHARDEV_CLASS(oc); diff --git a/ui/dbus-display1.xml b/ui/dbus-display1.xml index 72deefa..4a41a7e 100644 --- a/ui/dbus-display1.xml +++ b/ui/dbus-display1.xml @@ -615,6 +615,51 @@ </interface> <!-- + org.qemu.Display1.Listener.Unix.ScanoutDMABUF2: + + This optional client-side interface can complement + org.qemu.Display1.Listener on ``/org/qemu/Display1/Listener`` for + Unix-specific DMABUF scanout setup which support multi plane. + --> + <?if $(env.HOST_OS) != windows?> + <interface name="org.qemu.Display1.Listener.Unix.ScanoutDMABUF2"> + <!-- + ScanoutDMABUF2: + @dmabuf: DMABUF file descriptor of each plane. + @x: display x offset, in pixels + @y: display y offset, in pixels + @width: display width, in pixels. + @height: display height, in pixels. + @offset: offset of each plane, in bytes. + @stride: stride of each plane, in bytes. + @num_planes: plane number. + @fourcc: DMABUF fourcc. + @backing_width: backing framebuffer width, in pixels + @backing_height: backing framebuffer height, in pixels + @modifier: DMABUF modifier. + @y0_top: whether Y position 0 is the top or not. + + Resize and update the display content with DMABUF. + --> + <method name="ScanoutDMABUF2"> + <arg type="ah" name="dmabuf" 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"/> + <arg type="au" name="offset" direction="in"/> + <arg type="au" name="stride" direction="in"/> + <arg type="u" name="num_planes" direction="in"/> + <arg type="u" name="fourcc" direction="in"/> + <arg type="u" name="backing_width" direction="in"/> + <arg type="u" name="backing_height" direction="in"/> + <arg type="t" name="modifier" direction="in"/> + <arg type="b" name="y0_top" direction="in"/> + </method> + </interface> + <?endif?> + + <!-- org.qemu.Display1.Clipboard: This interface must be implemented by both the client and the server on diff --git a/ui/dbus-listener.c b/ui/dbus-listener.c index 51244c9..42875b8 100644 --- a/ui/dbus-listener.c +++ b/ui/dbus-listener.c @@ -85,6 +85,7 @@ struct _DBusDisplayListener { #endif #else /* !WIN32 */ QemuDBusDisplay1ListenerUnixMap *map_proxy; + QemuDBusDisplay1ListenerUnixScanoutDMABUF2 *scanout_dmabuf_v2_proxy; #endif guint dbus_filter; @@ -288,10 +289,9 @@ static void dbus_call_update_gl(DisplayChangeListener *dcl, } #ifdef CONFIG_GBM -static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, - QemuDmaBuf *dmabuf) +static void dbus_scanout_dmabuf_v1(DBusDisplayListener *ddl, + QemuDmaBuf *dmabuf) { - DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); g_autoptr(GError) err = NULL; g_autoptr(GUnixFDList) fd_list = NULL; int fd; @@ -299,7 +299,7 @@ static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, uint64_t modifier; bool y0_top; - fd = qemu_dmabuf_get_fd(dmabuf); + fd = qemu_dmabuf_get_fds(dmabuf, NULL)[0]; fd_list = g_unix_fd_list_new(); if (g_unix_fd_list_append(fd_list, fd, &err) != 0) { error_report("Failed to setup dmabuf fdlist: %s", err->message); @@ -310,7 +310,7 @@ static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, width = qemu_dmabuf_get_width(dmabuf); height = qemu_dmabuf_get_height(dmabuf); - stride = qemu_dmabuf_get_stride(dmabuf); + stride = qemu_dmabuf_get_strides(dmabuf, NULL)[0]; fourcc = qemu_dmabuf_get_fourcc(dmabuf); modifier = qemu_dmabuf_get_modifier(dmabuf); y0_top = qemu_dmabuf_get_y0_top(dmabuf); @@ -322,6 +322,87 @@ static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, y0_top, G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL, NULL, NULL); } + +static void dbus_scanout_dmabuf_v2(DBusDisplayListener *ddl, + QemuDmaBuf *dmabuf) +{ + g_autoptr(GError) err = NULL; + g_autoptr(GUnixFDList) fd_list = NULL; + int i, fd_index[DMABUF_MAX_PLANES], num_fds; + uint32_t x, y, width, height, fourcc, backing_width, backing_height; + GVariant *fd, *offset, *stride, *fd_handles[DMABUF_MAX_PLANES]; + uint64_t modifier; + bool y0_top; + int nfds, noffsets, nstrides; + const int *fds = qemu_dmabuf_get_fds(dmabuf, &nfds); + const uint32_t *offsets = qemu_dmabuf_get_offsets(dmabuf, &noffsets); + const uint32_t *strides = qemu_dmabuf_get_strides(dmabuf, &nstrides); + uint32_t num_planes = qemu_dmabuf_get_num_planes(dmabuf); + + assert(nfds >= num_planes); + assert(noffsets >= num_planes); + assert(nstrides >= num_planes); + + fd_list = g_unix_fd_list_new(); + + for (num_fds = 0; num_fds < num_planes; num_fds++) { + int plane_fd = fds[num_fds]; + + if (plane_fd < 0) { + break; + } + + fd_index[num_fds] = g_unix_fd_list_append(fd_list, plane_fd, &err); + if (fd_index[num_fds] < 0) { + error_report("Failed to setup dmabuf fdlist: %s", err->message); + return; + } + } + + ddl_discard_display_messages(ddl); + + x = qemu_dmabuf_get_x(dmabuf); + y = qemu_dmabuf_get_y(dmabuf); + width = qemu_dmabuf_get_width(dmabuf); + height = qemu_dmabuf_get_height(dmabuf); + fourcc = qemu_dmabuf_get_fourcc(dmabuf); + backing_width = qemu_dmabuf_get_backing_width(dmabuf); + backing_height = qemu_dmabuf_get_backing_height(dmabuf); + modifier = qemu_dmabuf_get_modifier(dmabuf); + y0_top = qemu_dmabuf_get_y0_top(dmabuf); + + offset = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + offsets, num_planes, sizeof(uint32_t)); + stride = g_variant_new_fixed_array(G_VARIANT_TYPE_UINT32, + strides, num_planes, sizeof(uint32_t)); + + for (i = 0; i < num_fds; i++) { + fd_handles[i] = g_variant_new_handle(fd_index[i]); + } + fd = g_variant_new_array(G_VARIANT_TYPE_HANDLE, fd_handles, num_fds); + + qemu_dbus_display1_listener_unix_scanout_dmabuf2_call_scanout_dmabuf2( + ddl->scanout_dmabuf_v2_proxy, fd, x, y, width, height, offset, stride, + num_planes, fourcc, backing_width, backing_height, modifier, y0_top, + G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL, NULL, NULL); +} + +static void dbus_scanout_dmabuf(DisplayChangeListener *dcl, + QemuDmaBuf *dmabuf) +{ + DBusDisplayListener *ddl = container_of(dcl, DBusDisplayListener, dcl); + + if (ddl->scanout_dmabuf_v2_proxy) { + dbus_scanout_dmabuf_v2(ddl, dmabuf); + } else { + if (qemu_dmabuf_get_num_planes(dmabuf) > 1) { + g_debug("org.qemu.Display1.Listener.ScanoutDMABUF " + "does not support mutli plane"); + return; + } + dbus_scanout_dmabuf_v1(ddl, dmabuf); + } +} #endif /* GBM */ #endif /* OPENGL */ @@ -504,19 +585,18 @@ static void dbus_scanout_texture(DisplayChangeListener *dcl, backing_width, backing_height, x, y, w, h); #ifdef CONFIG_GBM g_autoptr(QemuDmaBuf) dmabuf = NULL; - int fd; - uint32_t stride, fourcc; + int fd[DMABUF_MAX_PLANES], num_planes; + uint32_t offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES], fourcc; uint64_t modifier; assert(tex_id); - fd = egl_get_fd_for_texture(tex_id, (EGLint *)&stride, (EGLint *)&fourcc, - &modifier); - if (fd < 0) { - error_report("%s: failed to get fd for texture", __func__); + if (!egl_dmabuf_export_texture(tex_id, fd, (EGLint *)offset, (EGLint *)stride, + (EGLint *)&fourcc, &num_planes, &modifier)) { + error_report("%s: failed to export dmabuf for texture", __func__); return; } - dmabuf = qemu_dmabuf_new(w, h, stride, x, y, backing_width, - backing_height, fourcc, modifier, fd, + dmabuf = qemu_dmabuf_new(w, h, offset, stride, x, y, backing_width, + backing_height, fourcc, modifier, fd, num_planes, false, backing_y_0_top); dbus_scanout_dmabuf(dcl, dmabuf); @@ -883,6 +963,8 @@ dbus_display_listener_dispose(GObject *object) #ifdef CONFIG_OPENGL egl_fb_destroy(&ddl->fb); #endif +#else /* !WIN32 */ + g_clear_object(&ddl->scanout_dmabuf_v2_proxy); #endif G_OBJECT_CLASS(dbus_display_listener_parent_class)->dispose(object); @@ -1071,6 +1153,26 @@ dbus_display_listener_setup_shared_map(DBusDisplayListener *ddl) #endif } +static void dbus_display_listener_setup_scanout_dmabuf_v2(DBusDisplayListener *ddl) +{ +#ifndef WIN32 + g_autoptr(GError) err = NULL; + + if (!dbus_display_listener_implements( + ddl, "org.qemu.Display1.Listener.Unix.ScanoutDMABUF2")) { + return; + } + ddl->scanout_dmabuf_v2_proxy = + qemu_dbus_display1_listener_unix_scanout_dmabuf2_proxy_new_sync( + ddl->conn, G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START, NULL, + "/org/qemu/Display1/Listener", NULL, &err); + if (!ddl->scanout_dmabuf_v2_proxy) { + g_debug("Failed to setup Unix scanout dmabuf v2 proxy: %s", err->message); + return; + } +#endif +} + static GDBusMessage * dbus_filter(GDBusConnection *connection, GDBusMessage *message, @@ -1159,6 +1261,7 @@ dbus_display_listener_new(const char *bus_name, dbus_display_listener_setup_shared_map(ddl); trace_dbus_can_share_map(ddl->can_share_map); dbus_display_listener_setup_d3d11(ddl); + dbus_display_listener_setup_scanout_dmabuf_v2(ddl); con = qemu_console_lookup_by_index(dbus_display_console_get_index(console)); assert(con); @@ -404,7 +404,7 @@ set_gl_mode(Object *o, int val, Error **errp) } static void -dbus_display_class_init(ObjectClass *oc, void *data) +dbus_display_class_init(ObjectClass *oc, const void *data) { UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); @@ -453,7 +453,7 @@ dbus_vc_parse(QemuOpts *opts, ChardevBackend *backend, } static void -dbus_vc_class_init(ObjectClass *oc, void *data) +dbus_vc_class_init(ObjectClass *oc, const void *data) { DBusVCClass *klass = DBUS_VC_CLASS(oc); ChardevClass *cc = CHARDEV_CLASS(oc); @@ -514,7 +514,7 @@ static const TypeInfo dbus_display_info = { .instance_init = dbus_display_init, .instance_finalize = dbus_display_finalize, .class_init = dbus_display_class_init, - .interfaces = (InterfaceInfo[]) { + .interfaces = (const InterfaceInfo[]) { { TYPE_USER_CREATABLE }, { } } diff --git a/ui/dmabuf.c b/ui/dmabuf.c index df7a097..7433a26 100644 --- a/ui/dmabuf.c +++ b/ui/dmabuf.c @@ -11,10 +11,12 @@ #include "ui/dmabuf.h" struct QemuDmaBuf { - int fd; + int fd[DMABUF_MAX_PLANES]; uint32_t width; uint32_t height; - uint32_t stride; + uint32_t offset[DMABUF_MAX_PLANES]; + uint32_t stride[DMABUF_MAX_PLANES]; + uint32_t num_planes; uint32_t fourcc; uint64_t modifier; uint32_t texture; @@ -30,28 +32,33 @@ struct QemuDmaBuf { }; QemuDmaBuf *qemu_dmabuf_new(uint32_t width, uint32_t height, - uint32_t stride, uint32_t x, - uint32_t y, uint32_t backing_width, - uint32_t backing_height, uint32_t fourcc, - uint64_t modifier, int32_t dmabuf_fd, + const uint32_t *offset, const uint32_t *stride, + uint32_t x, uint32_t y, + uint32_t backing_width, uint32_t backing_height, + uint32_t fourcc, uint64_t modifier, + const int32_t *dmabuf_fd, uint32_t num_planes, bool allow_fences, bool y0_top) { QemuDmaBuf *dmabuf; + assert(num_planes > 0 && num_planes <= DMABUF_MAX_PLANES); + dmabuf = g_new0(QemuDmaBuf, 1); dmabuf->width = width; dmabuf->height = height; - dmabuf->stride = stride; + memcpy(dmabuf->offset, offset, num_planes * sizeof(*offset)); + memcpy(dmabuf->stride, stride, num_planes * sizeof(*stride)); dmabuf->x = x; dmabuf->y = y; dmabuf->backing_width = backing_width; dmabuf->backing_height = backing_height; dmabuf->fourcc = fourcc; dmabuf->modifier = modifier; - dmabuf->fd = dmabuf_fd; + memcpy(dmabuf->fd, dmabuf_fd, num_planes * sizeof(*dmabuf_fd)); dmabuf->allow_fences = allow_fences; dmabuf->y0_top = y0_top; dmabuf->fence_fd = -1; + dmabuf->num_planes = num_planes; return dmabuf; } @@ -65,31 +72,40 @@ void qemu_dmabuf_free(QemuDmaBuf *dmabuf) g_free(dmabuf); } -int qemu_dmabuf_get_fd(QemuDmaBuf *dmabuf) +const int *qemu_dmabuf_get_fds(QemuDmaBuf *dmabuf, int *nfds) { assert(dmabuf != NULL); + if (nfds) { + *nfds = ARRAY_SIZE(dmabuf->fd); + } + return dmabuf->fd; } -int qemu_dmabuf_dup_fd(QemuDmaBuf *dmabuf) +void qemu_dmabuf_dup_fds(QemuDmaBuf *dmabuf, int *fds, int nfds) { + int i; + assert(dmabuf != NULL); + assert(nfds >= dmabuf->num_planes); - if (dmabuf->fd >= 0) { - return dup(dmabuf->fd); - } else { - return -1; + for (i = 0; i < dmabuf->num_planes; i++) { + fds[i] = dmabuf->fd[i] >= 0 ? dup(dmabuf->fd[i]) : -1; } } void qemu_dmabuf_close(QemuDmaBuf *dmabuf) { + int i; + assert(dmabuf != NULL); - if (dmabuf->fd >= 0) { - close(dmabuf->fd); - dmabuf->fd = -1; + for (i = 0; i < dmabuf->num_planes; i++) { + if (dmabuf->fd[i] >= 0) { + close(dmabuf->fd[i]); + dmabuf->fd[i] = -1; + } } } @@ -107,13 +123,35 @@ uint32_t qemu_dmabuf_get_height(QemuDmaBuf *dmabuf) return dmabuf->height; } -uint32_t qemu_dmabuf_get_stride(QemuDmaBuf *dmabuf) +const uint32_t *qemu_dmabuf_get_offsets(QemuDmaBuf *dmabuf, int *noffsets) +{ + assert(dmabuf != NULL); + + if (noffsets) { + *noffsets = ARRAY_SIZE(dmabuf->offset); + } + + return dmabuf->offset; +} + +const uint32_t *qemu_dmabuf_get_strides(QemuDmaBuf *dmabuf, int *nstrides) { assert(dmabuf != NULL); + if (nstrides) { + *nstrides = ARRAY_SIZE(dmabuf->stride); + } + return dmabuf->stride; } +uint32_t qemu_dmabuf_get_num_planes(QemuDmaBuf *dmabuf) +{ + assert(dmabuf != NULL); + + return dmabuf->num_planes; +} + uint32_t qemu_dmabuf_get_fourcc(QemuDmaBuf *dmabuf) { assert(dmabuf != NULL); @@ -221,9 +259,3 @@ void qemu_dmabuf_set_draw_submitted(QemuDmaBuf *dmabuf, bool draw_submitted) assert(dmabuf != NULL); dmabuf->draw_submitted = draw_submitted; } - -void qemu_dmabuf_set_fd(QemuDmaBuf *dmabuf, int32_t fd) -{ - assert(dmabuf != NULL); - dmabuf->fd = fd; -} diff --git a/ui/egl-helpers.c b/ui/egl-helpers.c index d591159..e3f2872 100644 --- a/ui/egl-helpers.c +++ b/ui/egl-helpers.c @@ -23,6 +23,7 @@ #include "system/system.h" #include "qapi/error.h" #include "trace.h" +#include "standard-headers/drm/drm_fourcc.h" EGLDisplay *qemu_egl_display; EGLConfig qemu_egl_config; @@ -92,14 +93,18 @@ void egl_fb_destroy(egl_fb *fb) fb->width = 0; fb->height = 0; + fb->x = 0; + fb->y = 0; fb->texture = 0; fb->framebuffer = 0; } -void egl_fb_setup_default(egl_fb *fb, int width, int height) +void egl_fb_setup_default(egl_fb *fb, int width, int height, int x, int y) { fb->width = width; fb->height = height; + fb->x = x; + fb->y = y; fb->framebuffer = 0; /* default framebuffer */ } @@ -144,6 +149,7 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip) glBindFramebuffer(GL_READ_FRAMEBUFFER, src->framebuffer); glBindFramebuffer(GL_DRAW_FRAMEBUFFER, dst->framebuffer); glViewport(0, 0, dst->width, dst->height); + glClear(GL_COLOR_BUFFER_BIT); if (src->dmabuf) { x1 = qemu_dmabuf_get_x(src->dmabuf); @@ -160,7 +166,8 @@ void egl_fb_blit(egl_fb *dst, egl_fb *src, bool flip) x2 = x1 + w; glBlitFramebuffer(x1, y1, x2, y2, - 0, 0, dst->width, dst->height, + dst->x, dst->y, + dst->x + dst->width, dst->y + dst->height, GL_COLOR_BUFFER_BIT, GL_LINEAR); } @@ -257,6 +264,11 @@ int egl_rendernode_init(const char *rendernode, DisplayGLMode mode) error_report("egl: EGL_MESA_image_dma_buf_export not supported"); goto err; } + if (!epoxy_has_egl_extension(qemu_egl_display, + "EGL_EXT_image_dma_buf_import_modifiers")) { + error_report("egl: EGL_EXT_image_dma_buf_import_modifiers not supported"); + goto err; + } qemu_egl_rn_ctx = qemu_egl_init_ctx(); if (!qemu_egl_rn_ctx) { @@ -277,44 +289,92 @@ err: return -1; } -int egl_get_fd_for_texture(uint32_t tex_id, EGLint *stride, EGLint *fourcc, - EGLuint64KHR *modifier) +bool egl_dmabuf_export_texture(uint32_t tex_id, int *fd, EGLint *offset, + EGLint *stride, EGLint *fourcc, int *num_planes, + EGLuint64KHR *modifier) { EGLImageKHR image; - EGLint num_planes, fd; + EGLuint64KHR modifiers[DMABUF_MAX_PLANES]; + int i; image = eglCreateImageKHR(qemu_egl_display, eglGetCurrentContext(), EGL_GL_TEXTURE_2D_KHR, (EGLClientBuffer)(unsigned long)tex_id, NULL); if (!image) { - return -1; + return false; } eglExportDMABUFImageQueryMESA(qemu_egl_display, image, fourcc, - &num_planes, modifier); - if (num_planes != 1) { - eglDestroyImageKHR(qemu_egl_display, image); - return -1; - } - eglExportDMABUFImageMESA(qemu_egl_display, image, &fd, stride, NULL); + num_planes, modifiers); + eglExportDMABUFImageMESA(qemu_egl_display, image, fd, stride, offset); eglDestroyImageKHR(qemu_egl_display, image); - return fd; + /* Only first modifier matters. */ + if (modifier) { + *modifier = modifiers[0]; + } + + for (i = 0; i < *num_planes; i++) { + if (fd[i] < 0) { + return false; + } + } + return true; } void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf) { EGLImageKHR image = EGL_NO_IMAGE_KHR; EGLint attrs[64]; - int i = 0; - uint64_t modifier; + int i = 0, j; + uint64_t modifier = qemu_dmabuf_get_modifier(dmabuf); uint32_t texture = qemu_dmabuf_get_texture(dmabuf); + int nfds, noffsets, nstrides; + const int *fds = qemu_dmabuf_get_fds(dmabuf, &nfds); + const uint32_t *offsets = qemu_dmabuf_get_offsets(dmabuf, &noffsets); + const uint32_t *strides = qemu_dmabuf_get_strides(dmabuf, &nstrides); + uint32_t num_planes = qemu_dmabuf_get_num_planes(dmabuf); + + EGLint fd_attrs[] = { + EGL_DMA_BUF_PLANE0_FD_EXT, + EGL_DMA_BUF_PLANE1_FD_EXT, + EGL_DMA_BUF_PLANE2_FD_EXT, + EGL_DMA_BUF_PLANE3_FD_EXT, + }; + EGLint offset_attrs[] = { + EGL_DMA_BUF_PLANE0_OFFSET_EXT, + EGL_DMA_BUF_PLANE1_OFFSET_EXT, + EGL_DMA_BUF_PLANE2_OFFSET_EXT, + EGL_DMA_BUF_PLANE3_OFFSET_EXT, + }; + EGLint stride_attrs[] = { + EGL_DMA_BUF_PLANE0_PITCH_EXT, + EGL_DMA_BUF_PLANE1_PITCH_EXT, + EGL_DMA_BUF_PLANE2_PITCH_EXT, + EGL_DMA_BUF_PLANE3_PITCH_EXT, + }; + EGLint modifier_lo_attrs[] = { + EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_LO_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_LO_EXT, + }; + EGLint modifier_hi_attrs[] = { + EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE1_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE2_MODIFIER_HI_EXT, + EGL_DMA_BUF_PLANE3_MODIFIER_HI_EXT, + }; if (texture != 0) { return; } + assert(nfds >= num_planes); + assert(noffsets >= num_planes); + assert(nstrides >= num_planes); + attrs[i++] = EGL_WIDTH; attrs[i++] = qemu_dmabuf_get_backing_width(dmabuf); attrs[i++] = EGL_HEIGHT; @@ -322,21 +382,22 @@ void egl_dmabuf_import_texture(QemuDmaBuf *dmabuf) attrs[i++] = EGL_LINUX_DRM_FOURCC_EXT; attrs[i++] = qemu_dmabuf_get_fourcc(dmabuf); - attrs[i++] = EGL_DMA_BUF_PLANE0_FD_EXT; - attrs[i++] = qemu_dmabuf_get_fd(dmabuf); - attrs[i++] = EGL_DMA_BUF_PLANE0_PITCH_EXT; - attrs[i++] = qemu_dmabuf_get_stride(dmabuf); - attrs[i++] = EGL_DMA_BUF_PLANE0_OFFSET_EXT; - attrs[i++] = 0; -#ifdef EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT - modifier = qemu_dmabuf_get_modifier(dmabuf); - if (modifier) { - attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_LO_EXT; - attrs[i++] = (modifier >> 0) & 0xffffffff; - attrs[i++] = EGL_DMA_BUF_PLANE0_MODIFIER_HI_EXT; - attrs[i++] = (modifier >> 32) & 0xffffffff; + for (j = 0; j < num_planes; j++) { + attrs[i++] = fd_attrs[j]; + /* fd[1-3] may be -1 if using a joint buffer for all planes */ + attrs[i++] = fds[j] >= 0 ? fds[j] : fds[0]; + attrs[i++] = stride_attrs[j]; + attrs[i++] = strides[j]; + attrs[i++] = offset_attrs[j]; + attrs[i++] = offsets[j]; + if (modifier != DRM_FORMAT_MOD_INVALID) { + attrs[i++] = modifier_lo_attrs[j]; + attrs[i++] = (modifier >> 0) & 0xffffffff; + attrs[i++] = modifier_hi_attrs[j]; + attrs[i++] = (modifier >> 32) & 0xffffffff; + } } -#endif + attrs[i++] = EGL_NONE; image = eglCreateImageKHR(qemu_egl_display, diff --git a/ui/gtk-clipboard.c b/ui/gtk-clipboard.c index 8d8a636..65d89ec 100644 --- a/ui/gtk-clipboard.c +++ b/ui/gtk-clipboard.c @@ -19,6 +19,7 @@ */ #include "qemu/osdep.h" +#include "qemu/error-report.h" #include "qemu/main-loop.h" #include "ui/gtk.h" @@ -95,11 +96,13 @@ static void gd_clipboard_update_info(GtkDisplayState *gd, gtk_clipboard_clear(gd->gtkcb[s]); if (targets) { gd->cbowner[s] = true; - gtk_clipboard_set_with_data(gd->gtkcb[s], - targets, n_targets, - gd_clipboard_get_data, - gd_clipboard_clear, - gd); + if (!gtk_clipboard_set_with_data(gd->gtkcb[s], + targets, n_targets, + gd_clipboard_get_data, + gd_clipboard_clear, + gd)) { + warn_report("Failed to set GTK clipboard"); + } gtk_target_table_free(targets, n_targets); } diff --git a/ui/gtk-egl.c b/ui/gtk-egl.c index f7a428c..0b787be 100644 --- a/ui/gtk-egl.c +++ b/ui/gtk-egl.c @@ -70,16 +70,18 @@ void gd_egl_draw(VirtualConsole *vc) QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf; int fence_fd; #endif - int ww, wh, ws; + int ww, wh, pw, ph, gs; if (!vc->gfx.gls) { return; } window = gtk_widget_get_window(vc->gfx.drawing_area); - ws = gdk_window_get_scale_factor(window); - ww = gdk_window_get_width(window) * ws; - wh = gdk_window_get_height(window) * ws; + gs = gdk_window_get_scale_factor(window); + ww = gdk_window_get_width(window); + wh = gdk_window_get_height(window); + pw = ww * gs; + ph = wh * gs; if (vc->gfx.scanout_mode) { #ifdef CONFIG_GBM @@ -93,8 +95,9 @@ void gd_egl_draw(VirtualConsole *vc) #endif gd_egl_scanout_flush(&vc->gfx.dcl, 0, 0, vc->gfx.w, vc->gfx.h); - vc->gfx.scale_x = (double)ww / surface_width(vc->gfx.ds); - vc->gfx.scale_y = (double)wh / surface_height(vc->gfx.ds); + gd_update_scale(vc, ww, wh, + surface_width(vc->gfx.ds), + surface_height(vc->gfx.ds)); glFlush(); #ifdef CONFIG_GBM @@ -115,13 +118,14 @@ void gd_egl_draw(VirtualConsole *vc) eglMakeCurrent(qemu_egl_display, vc->gfx.esurface, vc->gfx.esurface, vc->gfx.ectx); - surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh); + surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph); surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds); eglSwapBuffers(qemu_egl_display, vc->gfx.esurface); - vc->gfx.scale_x = (double)ww / surface_width(vc->gfx.ds); - vc->gfx.scale_y = (double)wh / surface_height(vc->gfx.ds); + gd_update_scale(vc, ww, wh, + surface_width(vc->gfx.ds), + surface_height(vc->gfx.ds)); glFlush(); } @@ -336,7 +340,11 @@ void gd_egl_scanout_flush(DisplayChangeListener *dcl, { VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); GdkWindow *window; - int ww, wh, ws; + int px_offset, py_offset; + int gs; + int pw_widget, ph_widget, pw_surface, ph_surface; + int ww_widget, wh_widget, ww_surface, wh_surface; + int fbw, fbh; if (!vc->gfx.scanout_mode) { return; @@ -349,10 +357,32 @@ void gd_egl_scanout_flush(DisplayChangeListener *dcl, vc->gfx.esurface, vc->gfx.ectx); window = gtk_widget_get_window(vc->gfx.drawing_area); - ws = gdk_window_get_scale_factor(window); - ww = gdk_window_get_width(window) * ws; - wh = gdk_window_get_height(window) * ws; - egl_fb_setup_default(&vc->gfx.win_fb, ww, wh); + gs = gdk_window_get_scale_factor(window); + ww_widget = gdk_window_get_width(window); + wh_widget = gdk_window_get_height(window); + fbw = surface_width(vc->gfx.ds); + fbh = surface_height(vc->gfx.ds); + + gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh); + + ww_surface = fbw * vc->gfx.scale_x; + wh_surface = fbh * vc->gfx.scale_y; + pw_widget = ww_widget * gs; + ph_widget = wh_widget * gs; + pw_surface = ww_surface * gs; + ph_surface = wh_surface * gs; + + px_offset = 0; + py_offset = 0; + if (pw_widget > pw_surface) { + px_offset = (pw_widget - pw_surface) / 2; + } + if (ph_widget > ph_surface) { + py_offset = (ph_widget - ph_surface) / 2; + } + + egl_fb_setup_default(&vc->gfx.win_fb, pw_surface, ph_surface, + px_offset, py_offset); if (vc->gfx.cursor_fb.texture) { egl_texture_blit(vc->gfx.gls, &vc->gfx.win_fb, &vc->gfx.guest_fb, vc->gfx.y0_top); diff --git a/ui/gtk-gl-area.c b/ui/gtk-gl-area.c index 2c9a0db..8151cc4 100644 --- a/ui/gtk-gl-area.c +++ b/ui/gtk-gl-area.c @@ -42,16 +42,37 @@ void gd_gl_area_draw(VirtualConsole *vc) #ifdef CONFIG_GBM QemuDmaBuf *dmabuf = vc->gfx.guest_fb.dmabuf; #endif - int ww, wh, ws, y1, y2; + int pw, ph, gs, y1, y2; + int ww, wh; + int ww_surface, wh_surface; + int fbw, fbh; + int wx_offset, wy_offset; if (!vc->gfx.gls) { return; } gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); - ws = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area)); - ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area) * ws; - wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area) * ws; + gs = gdk_window_get_scale_factor(gtk_widget_get_window(vc->gfx.drawing_area)); + fbw = surface_width(vc->gfx.ds); + fbh = surface_height(vc->gfx.ds); + ww = gtk_widget_get_allocated_width(vc->gfx.drawing_area); + wh = gtk_widget_get_allocated_height(vc->gfx.drawing_area); + pw = ww * gs; + ph = wh * gs; + + gd_update_scale(vc, ww, wh, fbw, fbh); + + ww_surface = fbw * vc->gfx.scale_x; + wh_surface = fbh * vc->gfx.scale_y; + + wx_offset = wy_offset = 0; + if (ww > ww_surface) { + wx_offset = (ww - ww_surface) / 2; + } + if (wh > wh_surface) { + wy_offset = (wh - wh_surface) / 2; + } if (vc->gfx.scanout_mode) { if (!vc->gfx.guest_fb.framebuffer) { @@ -71,11 +92,29 @@ void gd_gl_area_draw(VirtualConsole *vc) glBindFramebuffer(GL_READ_FRAMEBUFFER, vc->gfx.guest_fb.framebuffer); /* GtkGLArea sets GL_DRAW_FRAMEBUFFER for us */ - glViewport(0, 0, ww, wh); + if (wx_offset > 0) { + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, wx_offset * gs, wh * gs); + glClear(GL_COLOR_BUFFER_BIT); + glScissor((ww - wx_offset) * gs, 0, wx_offset * gs, wh * gs); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + } + if (wy_offset > 0) { + glEnable(GL_SCISSOR_TEST); + glScissor(0, 0, ww * gs, wy_offset * gs); + glClear(GL_COLOR_BUFFER_BIT); + glScissor(0, (wh - wy_offset) * gs, ww * gs, wy_offset * gs); + glClear(GL_COLOR_BUFFER_BIT); + glDisable(GL_SCISSOR_TEST); + } + + glViewport(0, 0, pw, ph); y1 = vc->gfx.y0_top ? 0 : vc->gfx.h; y2 = vc->gfx.y0_top ? vc->gfx.h : 0; glBlitFramebuffer(0, y1, vc->gfx.w, y2, - 0, 0, ww, wh, + wx_offset * gs, wy_offset * gs, + (ww - wx_offset) * gs, (wh - wy_offset) * gs, GL_COLOR_BUFFER_BIT, GL_NEAREST); #ifdef CONFIG_GBM if (dmabuf) { @@ -101,7 +140,7 @@ void gd_gl_area_draw(VirtualConsole *vc) } gtk_gl_area_make_current(GTK_GL_AREA(vc->gfx.drawing_area)); - surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, ww, wh); + surface_gl_setup_viewport(vc->gfx.gls, vc->gfx.ds, pw, ph); surface_gl_render_texture(vc->gfx.gls, vc->gfx.ds); } } @@ -67,6 +67,7 @@ #define VC_TERM_X_MIN 80 #define VC_TERM_Y_MIN 25 #define VC_SCALE_MIN 0.25 +#define VC_SCALE_MAX 4 #define VC_SCALE_STEP 0.25 #ifdef GDK_WINDOWING_X11 @@ -272,15 +273,11 @@ static void gd_update_geometry_hints(VirtualConsole *vc) if (!vc->gfx.ds) { return; } - if (s->free_scale) { - geo.min_width = surface_width(vc->gfx.ds) * VC_SCALE_MIN; - geo.min_height = surface_height(vc->gfx.ds) * VC_SCALE_MIN; - mask |= GDK_HINT_MIN_SIZE; - } else { - geo.min_width = surface_width(vc->gfx.ds) * vc->gfx.scale_x; - geo.min_height = surface_height(vc->gfx.ds) * vc->gfx.scale_y; - mask |= GDK_HINT_MIN_SIZE; - } + double scale_x = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_x; + double scale_y = s->free_scale ? VC_SCALE_MIN : vc->gfx.scale_y; + geo.min_width = surface_width(vc->gfx.ds) * scale_x; + geo.min_height = surface_height(vc->gfx.ds) * scale_y; + mask |= GDK_HINT_MIN_SIZE; geo_widget = vc->gfx.drawing_area; gtk_widget_set_size_request(geo_widget, geo.min_width, geo.min_height); @@ -387,16 +384,16 @@ static void *gd_win32_get_hwnd(VirtualConsole *vc) /** DisplayState Callbacks **/ static void gd_update(DisplayChangeListener *dcl, - int x, int y, int w, int h) + int fbx, int fby, int fbw, int fbh) { VirtualConsole *vc = container_of(dcl, VirtualConsole, gfx.dcl); GdkWindow *win; - int x1, x2, y1, y2; - int mx, my; - int fbw, fbh; - int ww, wh; + int wx1, wx2, wy1, wy2; + int wx_offset, wy_offset; + int ww_surface, wh_surface; + int ww_widget, wh_widget; - trace_gd_update(vc->label, x, y, w, h); + trace_gd_update(vc->label, fbx, fby, fbw, fbh); if (!gtk_widget_get_realized(vc->gfx.drawing_area)) { return; @@ -405,35 +402,36 @@ static void gd_update(DisplayChangeListener *dcl, if (vc->gfx.convert) { pixman_image_composite(PIXMAN_OP_SRC, vc->gfx.ds->image, NULL, vc->gfx.convert, - x, y, 0, 0, x, y, w, h); + fbx, fby, 0, 0, fbx, fby, fbw, fbh); } - x1 = floor(x * vc->gfx.scale_x); - y1 = floor(y * vc->gfx.scale_y); + wx1 = floor(fbx * vc->gfx.scale_x); + wy1 = floor(fby * vc->gfx.scale_y); - x2 = ceil(x * vc->gfx.scale_x + w * vc->gfx.scale_x); - y2 = ceil(y * vc->gfx.scale_y + h * vc->gfx.scale_y); + wx2 = ceil(fbx * vc->gfx.scale_x + fbw * vc->gfx.scale_x); + wy2 = ceil(fby * vc->gfx.scale_y + fbh * vc->gfx.scale_y); - fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x; - fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y; + ww_surface = surface_width(vc->gfx.ds) * vc->gfx.scale_x; + wh_surface = surface_height(vc->gfx.ds) * vc->gfx.scale_y; win = gtk_widget_get_window(vc->gfx.drawing_area); if (!win) { return; } - ww = gdk_window_get_width(win); - wh = gdk_window_get_height(win); + ww_widget = gdk_window_get_width(win); + wh_widget = gdk_window_get_height(win); - mx = my = 0; - if (ww > fbw) { - mx = (ww - fbw) / 2; + wx_offset = wy_offset = 0; + if (ww_widget > ww_surface) { + wx_offset = (ww_widget - ww_surface) / 2; } - if (wh > fbh) { - my = (wh - fbh) / 2; + if (wh_widget > wh_surface) { + wy_offset = (wh_widget - wh_surface) / 2; } gtk_widget_queue_draw_area(vc->gfx.drawing_area, - mx + x1, my + y1, (x2 - x1), (y2 - y1)); + wx_offset + wx1, wy_offset + wy1, + (wx2 - wx1), (wy2 - wy1)); } static void gd_refresh(DisplayChangeListener *dcl) @@ -771,8 +769,21 @@ static void gd_resize_event(GtkGLArea *area, gint width, gint height, gpointer *opaque) { VirtualConsole *vc = (void *)opaque; + double pw = width, ph = height; + double sx = vc->gfx.scale_x, sy = vc->gfx.scale_y; + GdkWindow *window = gtk_widget_get_window(GTK_WIDGET(area)); + const int gs = gdk_window_get_scale_factor(window); - gd_set_ui_size(vc, width, height); + if (!vc->s->free_scale && !vc->s->full_screen) { + pw /= sx; + ph /= sy; + } + + /** + * width and height here are in pixel coordinate, so we must divide it + * by global window scale (gs) + */ + gd_set_ui_size(vc, pw / gs, ph / gs); } #endif @@ -800,12 +811,99 @@ void gd_update_monitor_refresh_rate(VirtualConsole *vc, GtkWidget *widget) #endif } +void gd_update_scale(VirtualConsole *vc, int ww, int wh, int fbw, int fbh) +{ + if (!vc) { + return; + } + + if (vc->s->full_screen) { + vc->gfx.scale_x = (double)ww / fbw; + vc->gfx.scale_y = (double)wh / fbh; + } else if (vc->s->free_scale) { + double sx, sy; + + sx = (double)ww / fbw; + sy = (double)wh / fbh; + if (vc->s->keep_aspect_ratio) { + vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy); + } else { + vc->gfx.scale_x = sx; + vc->gfx.scale_y = sy; + } + } +} +/** + * DOC: Coordinate handling. + * + * We are coping with sizes and positions in various coordinates and the + * handling of these coordinates is somewhat confusing. It would benefit us + * all if we define these coordinates explicitly and clearly. Besides, it's + * also helpful to follow the same naming convention for variables + * representing values in different coordinates. + * + * I. Definitions + * + * - (guest) buffer coordinate: this is the coordinates that the guest will + * see. The x/y offsets and width/height specified in commands sent by + * guest is basically in buffer coordinate. + * + * - (host) pixel coordinate: this is the coordinate in pixel level on the + * host destop. A window/widget of width 300 in pixel coordinate means it + * occupies 300 pixels horizontally. + * + * - (host) logical window coordinate: the existence of global scaling + * factor in desktop level makes this kind of coordinate play a role. It + * always holds that (logical window size) * (global scale factor) = + * (pixel size). + * + * - global scale factor: this is specified in desktop level and is + * typically invariant during the life cycle of the process. Users with + * high-DPI monitors might set this scale, for example, to 2, in order to + * make the UI look larger. + * + * - zooming scale: this can be freely controlled by the QEMU user to zoom + * in/out the guest content. + * + * II. Representation + * + * We'd like to use consistent representation for variables in different + * coordinates: + * - buffer coordinate: prefix fb + * - pixel coordinate: prefix p + * - logical window coordinate: prefix w + * + * For scales: + * - global scale factor: prefix gs + * - zooming scale: prefix scale/s + * + * Example: fbw, pw, ww for width in different coordinates + * + * III. Equation + * + * - fbw * gs * scale_x = pw + * - pw = gs * ww + * + * Consequently we have + * + * - fbw * scale_x = ww + * + * Example: assuming we are running QEMU on a 3840x2160 screen and have set + * global scaling factor to 2, if the guest buffer size is 1920x1080 and the + * zooming scale is 0.5, then we have: + * - fbw = 1920, fbh = 1080 + * - pw = 1920, ph = 1080 + * - ww = 960, wh = 540 + * A bonus of this configuration is that we can achieve pixel to pixel + * presentation of the guest content. + */ + static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) { VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - int mx, my; - int ww, wh; + int wx_offset, wy_offset; + int ww_widget, wh_widget, ww_surface, wh_surface; int fbw, fbh; #if defined(CONFIG_OPENGL) @@ -839,46 +937,37 @@ static gboolean gd_draw_event(GtkWidget *widget, cairo_t *cr, void *opaque) fbw = surface_width(vc->gfx.ds); fbh = surface_height(vc->gfx.ds); - ww = gdk_window_get_width(gtk_widget_get_window(widget)); - wh = gdk_window_get_height(gtk_widget_get_window(widget)); - - if (s->full_screen) { - vc->gfx.scale_x = (double)ww / fbw; - vc->gfx.scale_y = (double)wh / fbh; - } else if (s->free_scale) { - double sx, sy; - - sx = (double)ww / fbw; - sy = (double)wh / fbh; + ww_widget = gdk_window_get_width(gtk_widget_get_window(widget)); + wh_widget = gdk_window_get_height(gtk_widget_get_window(widget)); - vc->gfx.scale_x = vc->gfx.scale_y = MIN(sx, sy); - } + gd_update_scale(vc, ww_widget, wh_widget, fbw, fbh); - fbw *= vc->gfx.scale_x; - fbh *= vc->gfx.scale_y; + ww_surface = fbw * vc->gfx.scale_x; + wh_surface = fbh * vc->gfx.scale_y; - mx = my = 0; - if (ww > fbw) { - mx = (ww - fbw) / 2; + wx_offset = wy_offset = 0; + if (ww_widget > ww_surface) { + wx_offset = (ww_widget - ww_surface) / 2; } - if (wh > fbh) { - my = (wh - fbh) / 2; + if (wh_widget > wh_surface) { + wy_offset = (wh_widget - wh_surface) / 2; } - cairo_rectangle(cr, 0, 0, ww, wh); + cairo_rectangle(cr, 0, 0, ww_widget, wh_widget); /* Optionally cut out the inner area where the pixmap will be drawn. This avoids 'flashing' since we're not double-buffering. Note we're using the undocumented behaviour of drawing the rectangle from right to left to cut out the whole */ - cairo_rectangle(cr, mx + fbw, my, - -1 * fbw, fbh); + cairo_rectangle(cr, wx_offset + ww_surface, wy_offset, + -1 * ww_surface, wh_surface); cairo_fill(cr); cairo_scale(cr, vc->gfx.scale_x, vc->gfx.scale_y); cairo_set_source_surface(cr, vc->gfx.surface, - mx / vc->gfx.scale_x, my / vc->gfx.scale_y); + wx_offset / vc->gfx.scale_x, + wy_offset / vc->gfx.scale_y); cairo_paint(cr); return TRUE; @@ -889,19 +978,19 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, { VirtualConsole *vc = opaque; GtkDisplayState *s = vc->s; - int x, y; - int mx, my; - int fbh, fbw; - int ww, wh; + int fbx, fby; + int wx_offset, wy_offset; + int wh_surface, ww_surface; + int ww_widget, wh_widget; if (!vc->gfx.ds) { return TRUE; } - fbw = surface_width(vc->gfx.ds) * vc->gfx.scale_x; - fbh = surface_height(vc->gfx.ds) * vc->gfx.scale_y; - ww = gtk_widget_get_allocated_width(widget); - wh = gtk_widget_get_allocated_height(widget); + ww_surface = surface_width(vc->gfx.ds) * vc->gfx.scale_x; + wh_surface = surface_height(vc->gfx.ds) * vc->gfx.scale_y; + ww_widget = gtk_widget_get_allocated_width(widget); + wh_widget = gtk_widget_get_allocated_height(widget); /* * `widget` may not have the same size with the frame buffer. @@ -909,41 +998,42 @@ static gboolean gd_motion_event(GtkWidget *widget, GdkEventMotion *motion, * To achieve that, `vc` will be displayed at (mx, my) * so that it is displayed at the center of the widget. */ - mx = my = 0; - if (ww > fbw) { - mx = (ww - fbw) / 2; + wx_offset = wy_offset = 0; + if (ww_widget > ww_surface) { + wx_offset = (ww_widget - ww_surface) / 2; } - if (wh > fbh) { - my = (wh - fbh) / 2; + if (wh_widget > wh_surface) { + wy_offset = (wh_widget - wh_surface) / 2; } /* * `motion` is reported in `widget` coordinates * so translating it to the coordinates in `vc`. */ - x = (motion->x - mx) / vc->gfx.scale_x; - y = (motion->y - my) / vc->gfx.scale_y; + fbx = (motion->x - wx_offset) / vc->gfx.scale_x; + fby = (motion->y - wy_offset) / vc->gfx.scale_y; - trace_gd_motion_event(ww, wh, gtk_widget_get_scale_factor(widget), x, y); + trace_gd_motion_event(ww_widget, wh_widget, + gtk_widget_get_scale_factor(widget), fbx, fby); if (qemu_input_is_absolute(vc->gfx.dcl.con)) { - if (x < 0 || y < 0 || - x >= surface_width(vc->gfx.ds) || - y >= surface_height(vc->gfx.ds)) { + if (fbx < 0 || fby < 0 || + fbx >= surface_width(vc->gfx.ds) || + fby >= surface_height(vc->gfx.ds)) { return TRUE; } - qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, x, + qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_X, fbx, 0, surface_width(vc->gfx.ds)); - qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, y, + qemu_input_queue_abs(vc->gfx.dcl.con, INPUT_AXIS_Y, fby, 0, surface_height(vc->gfx.ds)); qemu_input_event_sync(); } else if (s->last_set && s->ptr_owner == vc) { - qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, x - s->last_x); - qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, y - s->last_y); + qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_X, fbx - s->last_x); + qemu_input_queue_rel(vc->gfx.dcl.con, INPUT_AXIS_Y, fby - s->last_y); qemu_input_event_sync(); } - s->last_x = x; - s->last_y = y; + s->last_x = fbx; + s->last_y = fby; s->last_set = TRUE; if (!qemu_input_is_absolute(vc->gfx.dcl.con) && s->ptr_owner == vc) { @@ -1486,8 +1576,8 @@ static void gd_menu_full_screen(GtkMenuItem *item, void *opaque) } s->full_screen = FALSE; if (vc->type == GD_VC_GFX) { - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; gd_update_windowsize(vc); } } @@ -1543,8 +1633,8 @@ static void gd_menu_zoom_fixed(GtkMenuItem *item, void *opaque) GtkDisplayState *s = opaque; VirtualConsole *vc = gd_vc_find_current(s); - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; gd_update_windowsize(vc); } @@ -1558,8 +1648,8 @@ static void gd_menu_zoom_fit(GtkMenuItem *item, void *opaque) s->free_scale = TRUE; } else { s->free_scale = FALSE; - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; } gd_update_windowsize(vc); @@ -1760,8 +1850,16 @@ static gboolean gd_configure(GtkWidget *widget, GdkEventConfigure *cfg, gpointer opaque) { VirtualConsole *vc = opaque; + const double sx = vc->gfx.scale_x, sy = vc->gfx.scale_y; + double width = cfg->width, height = cfg->height; + + if (!vc->s->free_scale && !vc->s->full_screen) { + width /= sx; + height /= sy; + } + + gd_set_ui_size(vc, width, height); - gd_set_ui_size(vc, cfg->width, cfg->height); return FALSE; } @@ -1879,7 +1977,7 @@ static void gd_vc_open(Chardev *chr, *be_opened = false; } -static void char_gd_vc_class_init(ObjectClass *oc, void *data) +static void char_gd_vc_class_init(ObjectClass *oc, const void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); @@ -2142,6 +2240,11 @@ static void gl_area_realize(GtkGLArea *area, VirtualConsole *vc) } #endif +static bool gd_scale_valid(double scale) +{ + return scale >= VC_SCALE_MIN && scale <= VC_SCALE_MAX; +} + static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, QemuConsole *con, int idx, GSList *group, GtkWidget *view_menu) @@ -2151,8 +2254,18 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, vc->label = qemu_console_get_label(con); vc->s = s; - vc->gfx.scale_x = 1.0; - vc->gfx.scale_y = 1.0; + vc->gfx.preferred_scale = 1.0; + if (s->opts->u.gtk.has_scale) { + if (gd_scale_valid(s->opts->u.gtk.scale)) { + vc->gfx.preferred_scale = s->opts->u.gtk.scale; + } else { + error_report("Invalid scale value %lf given, being ignored", + s->opts->u.gtk.scale); + s->opts->u.gtk.has_scale = false; + } + } + vc->gfx.scale_x = vc->gfx.preferred_scale; + vc->gfx.scale_y = vc->gfx.preferred_scale; #if defined(CONFIG_OPENGL) if (display_opengl) { @@ -2231,6 +2344,10 @@ static GSList *gd_vc_gfx_init(GtkDisplayState *s, VirtualConsole *vc, s->free_scale = true; } + s->keep_aspect_ratio = true; + if (s->opts->u.gtk.has_keep_aspect_ratio) + s->keep_aspect_ratio = s->opts->u.gtk.keep_aspect_ratio; + for (i = 0; i < INPUT_EVENT_SLOTS_MAX; i++) { struct touch_slot *slot = &touch_slots[i]; slot->tracking_id = -1; diff --git a/ui/input-barrier.c b/ui/input-barrier.c index c86e5d6..9793258 100644 --- a/ui/input-barrier.c +++ b/ui/input-barrier.c @@ -696,7 +696,7 @@ static void input_barrier_instance_init(Object *obj) ib->height = 1080; } -static void input_barrier_class_init(ObjectClass *oc, void *data) +static void input_barrier_class_init(ObjectClass *oc, const void *data) { UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); @@ -732,7 +732,7 @@ static const TypeInfo input_barrier_info = { .instance_size = sizeof(InputBarrier), .instance_init = input_barrier_instance_init, .instance_finalize = input_barrier_instance_finalize, - .interfaces = (InterfaceInfo[]) { + .interfaces = (const InterfaceInfo[]) { { TYPE_USER_CREATABLE }, { } } diff --git a/ui/input-linux.c b/ui/input-linux.c index 381148e..92e1a1a 100644 --- a/ui/input-linux.c +++ b/ui/input-linux.c @@ -412,7 +412,6 @@ err_read_event_bits: err_close: close(il->fd); - return; } static void input_linux_instance_finalize(Object *obj) @@ -495,7 +494,7 @@ static void input_linux_instance_init(Object *obj) { } -static void input_linux_class_init(ObjectClass *oc, void *data) +static void input_linux_class_init(ObjectClass *oc, const void *data) { UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); @@ -523,7 +522,7 @@ static const TypeInfo input_linux_info = { .instance_size = sizeof(InputLinux), .instance_init = input_linux_instance_init, .instance_finalize = input_linux_instance_finalize, - .interfaces = (InterfaceInfo[]) { + .interfaces = (const InterfaceInfo[]) { { TYPE_USER_CREATABLE }, { } } diff --git a/ui/meson.build b/ui/meson.build index 35fb04c..6371422 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -1,7 +1,4 @@ system_ss.add(pixman) -specific_ss.add(when: ['CONFIG_SYSTEM_ONLY'], if_true: pixman) # for the include path -specific_ss.add(when: ['CONFIG_SYSTEM_ONLY'], if_true: opengl) # for the include path - system_ss.add(png) system_ss.add(files( 'clipboard.c', diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c index 6ef4376..ef4e71d 100644 --- a/ui/qemu-pixman.c +++ b/ui/qemu-pixman.c @@ -126,33 +126,34 @@ uint32_t qemu_pixman_to_drm_format(pixman_format_code_t pixman_format) return 0; } -int qemu_pixman_get_type(int rshift, int gshift, int bshift) +int qemu_pixman_get_type(int rshift, int gshift, int bshift, int endian) { int type = PIXMAN_TYPE_OTHER; + bool native_endian = (endian == G_BYTE_ORDER); if (rshift > gshift && gshift > bshift) { if (bshift == 0) { - type = PIXMAN_TYPE_ARGB; + type = native_endian ? PIXMAN_TYPE_ARGB : PIXMAN_TYPE_BGRA; } else { - type = PIXMAN_TYPE_RGBA; + type = native_endian ? PIXMAN_TYPE_RGBA : PIXMAN_TYPE_ABGR; } } else if (rshift < gshift && gshift < bshift) { if (rshift == 0) { - type = PIXMAN_TYPE_ABGR; + type = native_endian ? PIXMAN_TYPE_ABGR : PIXMAN_TYPE_RGBA; } else { - type = PIXMAN_TYPE_BGRA; + type = native_endian ? PIXMAN_TYPE_BGRA : PIXMAN_TYPE_ARGB; } } return type; } #ifdef CONFIG_PIXMAN -pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf) +pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf, int endian) { pixman_format_code_t format; int type; - type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift); + type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift, endian); format = PIXMAN_FORMAT(pf->bits_per_pixel, type, pf->abits, pf->rbits, pf->gbits, pf->bbits); if (!pixman_format_supported_source(format)) { diff --git a/ui/sdl2-gl.c b/ui/sdl2-gl.c index e01d9ab..3be17d1 100644 --- a/ui/sdl2-gl.c +++ b/ui/sdl2-gl.c @@ -241,7 +241,7 @@ void sdl2_gl_scanout_flush(DisplayChangeListener *dcl, SDL_GL_MakeCurrent(scon->real_window, scon->winctx); SDL_GetWindowSize(scon->real_window, &ww, &wh); - egl_fb_setup_default(&scon->win_fb, ww, wh); + egl_fb_setup_default(&scon->win_fb, ww, wh, 0, 0); egl_fb_blit(&scon->win_fb, &scon->guest_fb, !scon->y0_top); SDL_GL_SwapWindow(scon->real_window); @@ -488,14 +488,14 @@ static void handle_mousemotion(SDL_Event *ev) { int max_x, max_y; struct sdl2_console *scon = get_scon_from_window(ev->motion.windowID); + int scr_w, scr_h, surf_w, surf_h, x, y, dx, dy; if (!scon || !qemu_console_is_graphic(scon->dcl.con)) { return; } + SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); if (qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { - int scr_w, scr_h; - SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); max_x = scr_w - 1; max_y = scr_h - 1; if (gui_grab && !gui_fullscreen @@ -509,9 +509,14 @@ static void handle_mousemotion(SDL_Event *ev) sdl_grab_start(scon); } } + surf_w = surface_width(scon->surface); + surf_h = surface_height(scon->surface); + x = (int64_t)ev->motion.x * surf_w / scr_w; + y = (int64_t)ev->motion.y * surf_h / scr_h; + dx = (int64_t)ev->motion.xrel * surf_w / scr_w; + dy = (int64_t)ev->motion.yrel * surf_h / scr_h; if (gui_grab || qemu_input_is_absolute(scon->dcl.con) || absolute_enabled) { - sdl_send_mouse_event(scon, ev->motion.xrel, ev->motion.yrel, - ev->motion.x, ev->motion.y, ev->motion.state); + sdl_send_mouse_event(scon, dx, dy, x, y, ev->motion.state); } } @@ -520,12 +525,17 @@ static void handle_mousebutton(SDL_Event *ev) int buttonstate = SDL_GetMouseState(NULL, NULL); SDL_MouseButtonEvent *bev; struct sdl2_console *scon = get_scon_from_window(ev->button.windowID); + int scr_w, scr_h, x, y; if (!scon || !qemu_console_is_graphic(scon->dcl.con)) { return; } bev = &ev->button; + SDL_GetWindowSize(scon->real_window, &scr_w, &scr_h); + x = (int64_t)bev->x * surface_width(scon->surface) / scr_w; + y = (int64_t)bev->y * surface_height(scon->surface) / scr_h; + if (!gui_grab && !qemu_input_is_absolute(scon->dcl.con)) { if (ev->type == SDL_MOUSEBUTTONUP && bev->button == SDL_BUTTON_LEFT) { /* start grabbing all events */ @@ -537,7 +547,7 @@ static void handle_mousebutton(SDL_Event *ev) } else { buttonstate &= ~SDL_BUTTON(bev->button); } - sdl_send_mouse_event(scon, 0, 0, bev->x, bev->y, buttonstate); + sdl_send_mouse_event(scon, 0, 0, x, y, buttonstate); } } diff --git a/ui/spice-app.c b/ui/spice-app.c index 91e258a..24f78f3 100644 --- a/ui/spice-app.c +++ b/ui/spice-app.c @@ -101,7 +101,7 @@ static void vc_chr_parse(QemuOpts *opts, ChardevBackend *backend, Error **errp) /* fqdn is dealt with in vc_chr_open() */ } -static void char_vc_class_init(ObjectClass *oc, void *data) +static void char_vc_class_init(ObjectClass *oc, const void *data) { VCChardevClass *vc = CHARDEV_VC_CLASS(oc); ChardevClass *cc = CHARDEV_CLASS(oc); diff --git a/ui/spice-core.c b/ui/spice-core.c index 0326c63..5992f9d 100644 --- a/ui/spice-core.c +++ b/ui/spice-core.c @@ -56,6 +56,8 @@ struct SpiceTimer { QEMUTimer *timer; }; +#define DEFAULT_MAX_REFRESH_RATE 30 + static SpiceTimer *timer_add(SpiceTimerFunc func, void *opaque) { SpiceTimer *timer; @@ -489,6 +491,12 @@ static QemuOptsList qemu_spice_opts = { .name = "streaming-video", .type = QEMU_OPT_STRING, },{ + .name = "video-codec", + .type = QEMU_OPT_STRING, + },{ + .name = "max-refresh-rate", + .type = QEMU_OPT_NUMBER, + },{ .name = "agent-mouse", .type = QEMU_OPT_BOOL, },{ @@ -801,6 +809,13 @@ static void qemu_spice_init(void) spice_server_set_streaming_video(spice_server, SPICE_STREAM_VIDEO_OFF); } + spice_max_refresh_rate = qemu_opt_get_number(opts, "max-refresh-rate", + DEFAULT_MAX_REFRESH_RATE); + if (spice_max_refresh_rate <= 0) { + error_report("max refresh rate/fps is invalid"); + exit(1); + } + spice_server_set_agent_mouse (spice_server, qemu_opt_get_bool(opts, "agent-mouse", 1)); spice_server_set_playback_compression @@ -836,9 +851,26 @@ static void qemu_spice_init(void) #ifdef HAVE_SPICE_GL if (qemu_opt_get_bool(opts, "gl", 0)) { if ((port != 0) || (tls_port != 0)) { +#if SPICE_SERVER_VERSION >= 0x000f03 /* release 0.15.3 */ + const char *video_codec = NULL; + g_autofree char *enc_codec = NULL; + + spice_remote_client = 1; + + video_codec = qemu_opt_get(opts, "video-codec"); + if (video_codec) { + enc_codec = g_strconcat("gstreamer:", video_codec, NULL); + } + if (spice_server_set_video_codecs(spice_server, + enc_codec ?: "gstreamer:h264")) { + error_report("invalid video codec"); + exit(1); + } +#else error_report("SPICE GL support is local-only for now and " "incompatible with -spice port/tls-port"); exit(1); +#endif } egl_init(qemu_opt_get(opts, "rendernode"), DISPLAY_GL_MODE_ON, &error_fatal); spice_opengl = 1; diff --git a/ui/spice-display.c b/ui/spice-display.c index c794ae0..9ce622c 100644 --- a/ui/spice-display.c +++ b/ui/spice-display.c @@ -28,7 +28,11 @@ #include "ui/spice-display.h" +#include "standard-headers/drm/drm_fourcc.h" + bool spice_opengl; +bool spice_remote_client; +int spice_max_refresh_rate; int qemu_spice_rect_is_empty(const QXLRect* r) { @@ -841,12 +845,32 @@ static void qemu_spice_gl_block_timer(void *opaque) warn_report("spice: no gl-draw-done within one second"); } +static void spice_gl_draw(SimpleSpiceDisplay *ssd, + uint32_t x, uint32_t y, uint32_t w, uint32_t h) +{ + uint64_t cookie; + + cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); + spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie); +} + static void spice_gl_refresh(DisplayChangeListener *dcl) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); - uint64_t cookie; - if (!ssd->ds || qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (!ssd->ds) { + return; + } + + if (qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) { + glFlush(); + spice_gl_draw(ssd, 0, 0, + surface_width(ssd->ds), surface_height(ssd->ds)); + ssd->gl_updates = 0; + /* E.g, to achieve 60 FPS, update_interval needs to be ~16.66 ms */ + dcl->update_interval = 1000 / spice_max_refresh_rate; + } return; } @@ -854,11 +878,8 @@ static void spice_gl_refresh(DisplayChangeListener *dcl) if (ssd->gl_updates && ssd->have_surface) { qemu_spice_gl_block(ssd, true); glFlush(); - cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); - spice_qxl_gl_draw_async(&ssd->qxl, 0, 0, - surface_width(ssd->ds), - surface_height(ssd->ds), - cookie); + spice_gl_draw(ssd, 0, 0, + surface_width(ssd->ds), surface_height(ssd->ds)); ssd->gl_updates = 0; } } @@ -872,23 +893,130 @@ static void spice_gl_update(DisplayChangeListener *dcl, ssd->gl_updates++; } +static bool spice_gl_replace_fd_texture(SimpleSpiceDisplay *ssd, + int *fds, uint64_t *modifier, + int *num_planes) +{ + uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES]; + GLuint texture; + GLuint mem_obj; + int fourcc; + bool ret; + + if (!spice_remote_client) { + return true; + } + + if (*modifier == DRM_FORMAT_MOD_LINEAR) { + return true; + } + + if (*num_planes > 1) { + error_report("spice: cannot replace texture with multiple planes"); + return false; + } + + /* + * We really want to ensure that the memory layout of the texture + * is linear; otherwise, the encoder's output may show corruption. + */ + if (!surface_gl_create_texture_from_fd(ssd->ds, fds[0], &texture, + &mem_obj)) { + error_report("spice: cannot create new texture from fd"); + return false; + } + + /* + * A successful return after glImportMemoryFdEXT() means that + * the ownership of fd has been passed to GL. In other words, + * the fd we got above should not be used anymore. + */ + ret = egl_dmabuf_export_texture(texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + num_planes, + modifier); + if (!ret) { + glDeleteTextures(1, &texture); +#ifdef GL_EXT_memory_object_fd + glDeleteMemoryObjectsEXT(1, &mem_obj); +#endif + + /* + * Since we couldn't export our newly create texture (or create, + * an fd associated with it) we need to backtrack and try to + * recreate the fd associated with the original texture. + */ + ret = egl_dmabuf_export_texture(ssd->ds->texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + num_planes, + modifier); + if (!ret) { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + warn_report("spice: no texture available to display"); + } + } else { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + ssd->ds->texture = texture; + ssd->ds->mem_obj = mem_obj; + } + return ret; +} + +static void spice_server_gl_scanout(QXLInstance *qxl, + const int *fd, + uint32_t width, uint32_t height, + const uint32_t *offset, + const uint32_t *stride, + uint32_t num_planes, uint32_t format, + uint64_t modifier, int y_0_top) +{ +#ifdef HAVE_SPICE_QXL_GL_SCANOUT2 + spice_qxl_gl_scanout2(qxl, fd, width, height, offset, stride, + num_planes, format, modifier, y_0_top); +#else + if (num_planes <= 1) { + spice_qxl_gl_scanout(qxl, fd[0], width, height, stride[0], format, y_0_top); + } else { + error_report("SPICE server does not support multi plane GL scanout"); + } +#endif +} + static void spice_gl_switch(DisplayChangeListener *dcl, struct DisplaySurface *new_surface) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); - EGLint stride, fourcc; - int fd; + bool ret; if (ssd->ds) { surface_gl_destroy_texture(ssd->gls, ssd->ds); } ssd->ds = new_surface; if (ssd->ds) { + uint32_t offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES]; + int fd[DMABUF_MAX_PLANES], num_planes, fourcc; + uint64_t modifier; + surface_gl_create_texture(ssd->gls, ssd->ds); - fd = egl_get_fd_for_texture(ssd->ds->texture, - &stride, &fourcc, - NULL); - if (fd < 0) { + if (!egl_dmabuf_export_texture(ssd->ds->texture, + fd, + (EGLint *)offset, + (EGLint *)stride, + &fourcc, + &num_planes, + &modifier)) { + surface_gl_destroy_texture(ssd->gls, ssd->ds); + return; + } + + ret = spice_gl_replace_fd_texture(ssd, fd, &modifier, &num_planes); + if (!ret) { surface_gl_destroy_texture(ssd->gls, ssd->ds); return; } @@ -899,10 +1027,11 @@ static void spice_gl_switch(DisplayChangeListener *dcl, fourcc); /* note: spice server will close the fd */ - spice_qxl_gl_scanout(&ssd->qxl, fd, - surface_width(ssd->ds), - surface_height(ssd->ds), - stride, fourcc, false); + spice_server_gl_scanout(&ssd->qxl, fd, + surface_width(ssd->ds), + surface_height(ssd->ds), + offset, stride, num_planes, + fourcc, modifier, false); ssd->have_surface = true; ssd->have_scanout = false; @@ -925,7 +1054,22 @@ static void qemu_spice_gl_scanout_disable(DisplayChangeListener *dcl) SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); trace_qemu_spice_gl_scanout_disable(ssd->qxl.id); - spice_qxl_gl_scanout(&ssd->qxl, -1, 0, 0, 0, 0, false); + + /* + * We need to check for the case of "lost" updates, where a gl_draw + * was not submitted because the timer did not get a chance to run. + * One case where this happens is when the Guest VM is getting + * rebooted. If the console is blocked in this situation, we need + * to unblock it. Otherwise, newer updates would not take effect. + */ + if (qemu_console_is_gl_blocked(ssd->dcl.con)) { + if (spice_remote_client && ssd->gl_updates && ssd->have_scanout) { + ssd->gl_updates = 0; + qemu_spice_gl_block(ssd, false); + } + } + spice_server_gl_scanout(&ssd->qxl, NULL, 0, 0, NULL, NULL, 0, DRM_FORMAT_INVALID, + DRM_FORMAT_MOD_INVALID, false); qemu_spice_gl_monitor_config(ssd, 0, 0, 0, 0); ssd->have_surface = false; ssd->have_scanout = false; @@ -941,21 +1085,39 @@ static void qemu_spice_gl_scanout_texture(DisplayChangeListener *dcl, void *d3d_tex2d) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); - EGLint stride = 0, fourcc = 0; - int fd = -1; + EGLint offset[DMABUF_MAX_PLANES], stride[DMABUF_MAX_PLANES], fourcc = 0; + int fd[DMABUF_MAX_PLANES], num_planes, i; + uint64_t modifier; assert(tex_id); - fd = egl_get_fd_for_texture(tex_id, &stride, &fourcc, NULL); - if (fd < 0) { - fprintf(stderr, "%s: failed to get fd for texture\n", __func__); + if (!egl_dmabuf_export_texture(tex_id, fd, offset, stride, &fourcc, + &num_planes, &modifier)) { + fprintf(stderr, "%s: failed to export dmabuf for texture\n", __func__); return; } + trace_qemu_spice_gl_scanout_texture(ssd->qxl.id, w, h, fourcc); - /* note: spice server will close the fd */ - spice_qxl_gl_scanout(&ssd->qxl, fd, backing_width, backing_height, - stride, fourcc, y_0_top); - qemu_spice_gl_monitor_config(ssd, x, y, w, h); + if (spice_remote_client && modifier != DRM_FORMAT_MOD_LINEAR) { + egl_fb_destroy(&ssd->guest_fb); + egl_fb_setup_for_tex(&ssd->guest_fb, + backing_width, backing_height, + tex_id, false); + ssd->backing_y_0_top = y_0_top; + ssd->blit_scanout_texture = true; + ssd->new_scanout_texture = true; + + for (i = 0; i < num_planes; i++) { + close(fd[i]); + } + } else { + /* note: spice server will close the fd */ + spice_server_gl_scanout(&ssd->qxl, fd, backing_width, backing_height, + (uint32_t *)offset, (uint32_t *)stride, + num_planes, fourcc, modifier, y_0_top); + qemu_spice_gl_monitor_config(ssd, x, y, w, h); + } + ssd->have_surface = false; ssd->have_scanout = true; } @@ -1021,15 +1183,58 @@ static void qemu_spice_gl_release_dmabuf(DisplayChangeListener *dcl, egl_dmabuf_release_texture(dmabuf); } +static bool spice_gl_blit_scanout_texture(SimpleSpiceDisplay *ssd, + egl_fb *scanout_tex_fb) +{ + uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES]; + int fds[DMABUF_MAX_PLANES], num_planes, fourcc; + uint64_t modifier; + bool ret; + + egl_fb_destroy(scanout_tex_fb); + egl_fb_setup_for_tex(scanout_tex_fb, + surface_width(ssd->ds), surface_height(ssd->ds), + ssd->ds->texture, false); + egl_fb_blit(scanout_tex_fb, &ssd->guest_fb, false); + glFlush(); + + if (!ssd->new_scanout_texture) { + return true; + } + + ret = egl_dmabuf_export_texture(ssd->ds->texture, + fds, + (EGLint *)offsets, + (EGLint *)strides, + &fourcc, + &num_planes, + &modifier); + if (!ret) { + error_report("spice: failed to get fd for texture"); + return false; + } + + spice_server_gl_scanout(&ssd->qxl, fds, + surface_width(ssd->ds), + surface_height(ssd->ds), + (uint32_t *)offsets, (uint32_t *)strides, + num_planes, fourcc, modifier, + ssd->backing_y_0_top); + qemu_spice_gl_monitor_config(ssd, 0, 0, + surface_width(ssd->ds), + surface_height(ssd->ds)); + ssd->new_scanout_texture = false; + return true; +} + static void qemu_spice_gl_update(DisplayChangeListener *dcl, uint32_t x, uint32_t y, uint32_t w, uint32_t h) { SimpleSpiceDisplay *ssd = container_of(dcl, SimpleSpiceDisplay, dcl); - EGLint stride = 0, fourcc = 0; + EGLint fourcc = 0; bool render_cursor = false; bool y_0_top = false; /* FIXME */ - uint64_t cookie; - int fd; + bool ret; uint32_t width, height, texture; if (!ssd->have_scanout) { @@ -1064,26 +1269,47 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl, /* dest framebuffer */ if (ssd->blit_fb.width != width || ssd->blit_fb.height != height) { + int fds[DMABUF_MAX_PLANES], num_planes; + uint32_t offsets[DMABUF_MAX_PLANES], strides[DMABUF_MAX_PLANES]; + uint64_t modifier; + trace_qemu_spice_gl_render_dmabuf(ssd->qxl.id, width, height); egl_fb_destroy(&ssd->blit_fb); egl_fb_setup_new_tex(&ssd->blit_fb, width, height); - fd = egl_get_fd_for_texture(ssd->blit_fb.texture, - &stride, &fourcc, NULL); - spice_qxl_gl_scanout(&ssd->qxl, fd, width, height, - stride, fourcc, false); + if (!egl_dmabuf_export_texture(ssd->blit_fb.texture, fds, + (EGLint *)offsets, (EGLint *)strides, + &fourcc, &num_planes, &modifier)) { + fprintf(stderr, + "%s: failed to export dmabuf for texture\n", __func__); + return; + } + + spice_server_gl_scanout(&ssd->qxl, fds, width, height, offsets, strides, + num_planes, fourcc, modifier, false); } } else { - stride = qemu_dmabuf_get_stride(dmabuf); + int fds[DMABUF_MAX_PLANES]; + int noffsets, nstrides; + const uint32_t *offsets = qemu_dmabuf_get_offsets(dmabuf, &noffsets); + const uint32_t *strides = qemu_dmabuf_get_strides(dmabuf, &nstrides); + uint32_t num_planes = qemu_dmabuf_get_num_planes(dmabuf); + + assert(noffsets >= num_planes); + assert(nstrides >= num_planes); + fourcc = qemu_dmabuf_get_fourcc(dmabuf); y_0_top = qemu_dmabuf_get_y0_top(dmabuf); - fd = qemu_dmabuf_dup_fd(dmabuf); + qemu_dmabuf_dup_fds(dmabuf, fds, DMABUF_MAX_PLANES); trace_qemu_spice_gl_forward_dmabuf(ssd->qxl.id, width, height); /* note: spice server will close the fd, so hand over a dup */ - spice_qxl_gl_scanout(&ssd->qxl, fd, width, height, - stride, fourcc, y_0_top); + spice_server_gl_scanout(&ssd->qxl, fds, width, height, + offsets, strides, num_planes, + fourcc, + qemu_dmabuf_get_modifier(dmabuf), + y_0_top); } qemu_spice_gl_monitor_config(ssd, 0, 0, width, height); ssd->guest_dmabuf_refresh = false; @@ -1103,11 +1329,31 @@ static void qemu_spice_gl_update(DisplayChangeListener *dcl, glFlush(); } + if (spice_remote_client && ssd->blit_scanout_texture) { + egl_fb scanout_tex_fb; + + ret = spice_gl_blit_scanout_texture(ssd, &scanout_tex_fb); + if (!ret) { + return; + } + } + trace_qemu_spice_gl_update(ssd->qxl.id, w, h, x, y); qemu_spice_gl_block(ssd, true); glFlush(); - cookie = (uintptr_t)qxl_cookie_new(QXL_COOKIE_TYPE_GL_DRAW_DONE, 0); - spice_qxl_gl_draw_async(&ssd->qxl, x, y, w, h, cookie); + + /* + * In the case of remote clients, the submission of gl_draw request is + * deferred here, so that it can be submitted later (to spice server) + * from spice_gl_refresh() timer callback. This is done to ensure that + * Guest updates are submitted at a steady rate (e.g. 60 FPS) instead + * of submitting them arbitrarily. + */ + if (spice_remote_client) { + ssd->gl_updates++; + } else { + spice_gl_draw(ssd, x, y, w, h); + } } static const DisplayChangeListenerOps display_listener_gl_ops = { diff --git a/ui/vdagent.c b/ui/vdagent.c index 724eff9..c0746fe 100644 --- a/ui/vdagent.c +++ b/ui/vdagent.c @@ -6,10 +6,10 @@ #include "qemu/option.h" #include "qemu/units.h" #include "hw/qdev-core.h" -#include "migration/blocker.h" #include "ui/clipboard.h" #include "ui/console.h" #include "ui/input.h" +#include "migration/vmstate.h" #include "trace.h" #include "qapi/qapi-types-char.h" @@ -32,14 +32,12 @@ struct VDAgentChardev { Chardev parent; - /* TODO: migration isn't yet supported */ - Error *migration_blocker; - /* config */ bool mouse; bool clipboard; /* guest vdagent */ + bool connected; uint32_t caps; VDIChunkHeader chunk; uint32_t chunksize; @@ -47,7 +45,7 @@ struct VDAgentChardev { uint32_t msgsize; uint8_t *xbuf; uint32_t xoff, xsize; - Buffer outbuf; + GByteArray *outbuf; /* mouse */ DeviceState mouse_dev; @@ -142,16 +140,16 @@ static void vdagent_send_buf(VDAgentChardev *vd) { uint32_t len; - while (!buffer_empty(&vd->outbuf)) { + while (vd->outbuf->len) { len = qemu_chr_be_can_write(CHARDEV(vd)); if (len == 0) { return; } - if (len > vd->outbuf.offset) { - len = vd->outbuf.offset; + if (len > vd->outbuf->len) { + len = vd->outbuf->len; } - qemu_chr_be_write(CHARDEV(vd), vd->outbuf.buffer, len); - buffer_advance(&vd->outbuf, len); + qemu_chr_be_write(CHARDEV(vd), vd->outbuf->data, len); + g_byte_array_remove_range(vd->outbuf, 0, len); } } @@ -166,7 +164,7 @@ static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg) msg->protocol = VD_AGENT_PROTOCOL; - if (vd->outbuf.offset + msgsize > VDAGENT_BUFFER_LIMIT) { + if (vd->outbuf->len + msgsize > VDAGENT_BUFFER_LIMIT) { error_report("buffer full, dropping message"); return; } @@ -177,9 +175,8 @@ static void vdagent_send_msg(VDAgentChardev *vd, VDAgentMessage *msg) if (chunk.size > 1024) { chunk.size = 1024; } - buffer_reserve(&vd->outbuf, sizeof(chunk) + chunk.size); - buffer_append(&vd->outbuf, &chunk, sizeof(chunk)); - buffer_append(&vd->outbuf, msgbuf + msgoff, chunk.size); + g_byte_array_append(vd->outbuf, (void *)&chunk, sizeof(chunk)); + g_byte_array_append(vd->outbuf, msgbuf + msgoff, chunk.size); msgoff += chunk.size; } vdagent_send_buf(vd); @@ -672,10 +669,6 @@ static void vdagent_chr_open(Chardev *chr, return; #endif - if (migrate_add_blocker(&vd->migration_blocker, errp) != 0) { - return; - } - vd->mouse = VDAGENT_MOUSE_DEFAULT; if (cfg->has_mouse) { vd->mouse = cfg->mouse; @@ -694,6 +687,18 @@ static void vdagent_chr_open(Chardev *chr, *be_opened = true; } +static void vdagent_clipboard_peer_register(VDAgentChardev *vd) +{ + if (vd->cbpeer.notifier.notify != NULL) { + return; + } + + vd->cbpeer.name = "vdagent"; + vd->cbpeer.notifier.notify = vdagent_clipboard_notify; + vd->cbpeer.request = vdagent_clipboard_request; + qemu_clipboard_peer_register(&vd->cbpeer); +} + static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) { VDAgentAnnounceCapabilities *caps = (void *)msg->data; @@ -720,13 +725,9 @@ static void vdagent_chr_recv_caps(VDAgentChardev *vd, VDAgentMessage *msg) memset(vd->last_serial, 0, sizeof(vd->last_serial)); - if (have_clipboard(vd) && vd->cbpeer.notifier.notify == NULL) { + if (have_clipboard(vd)) { qemu_clipboard_reset_serial(); - - vd->cbpeer.name = "vdagent"; - vd->cbpeer.notifier.notify = vdagent_clipboard_notify; - vd->cbpeer.request = vdagent_clipboard_request; - qemu_clipboard_peer_register(&vd->cbpeer); + vdagent_clipboard_peer_register(vd); } } @@ -859,7 +860,8 @@ static void vdagent_disconnect(VDAgentChardev *vd) { trace_vdagent_disconnect(); - buffer_reset(&vd->outbuf); + vd->connected = false; + g_byte_array_set_size(vd->outbuf, 0); vdagent_reset_bufs(vd); vd->caps = 0; if (vd->mouse_hs) { @@ -877,6 +879,10 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) trace_vdagent_fe_open(fe_open); + if (vd->connected == fe_open) { + return; + } + if (!fe_open) { trace_vdagent_close(); vdagent_disconnect(vd); @@ -886,6 +892,7 @@ static void vdagent_chr_set_fe_open(struct Chardev *chr, int fe_open) return; } + vd->connected = true; vdagent_send_caps(vd, true); } @@ -905,7 +912,7 @@ static void vdagent_chr_parse(QemuOpts *opts, ChardevBackend *backend, /* ------------------------------------------------------------------ */ -static void vdagent_chr_class_init(ObjectClass *oc, void *data) +static void vdagent_chr_class_init(ObjectClass *oc, const void *data) { ChardevClass *cc = CHARDEV_CLASS(oc); @@ -916,25 +923,163 @@ static void vdagent_chr_class_init(ObjectClass *oc, void *data) cc->chr_accept_input = vdagent_chr_accept_input; } +static int post_load(void *opaque, int version_id) +{ + VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(opaque); + + if (have_mouse(vd) && vd->mouse_hs) { + qemu_input_handler_activate(vd->mouse_hs); + } + + if (have_clipboard(vd)) { + vdagent_clipboard_peer_register(vd); + } + + return 0; +} + +static const VMStateDescription vmstate_chunk = { + .name = "vdagent/chunk", + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(port, VDIChunkHeader), + VMSTATE_UINT32(size, VDIChunkHeader), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_vdba = { + .name = "vdagent/bytearray", + .version_id = 0, + .minimum_version_id = 0, + .fields = (const VMStateField[]) { + VMSTATE_UINT32(len, GByteArray), + VMSTATE_VBUFFER_ALLOC_UINT32(data, GByteArray, 0, 0, len), + VMSTATE_END_OF_LIST() + } +}; + +struct CBInfoArray { + uint32_t n; + QemuClipboardInfo cbinfo[QEMU_CLIPBOARD_SELECTION__COUNT]; +}; + +static const VMStateDescription vmstate_cbinfo_array = { + .name = "cbinfoarray", + .fields = (const VMStateField[]) { + VMSTATE_UINT32(n, struct CBInfoArray), + VMSTATE_STRUCT_VARRAY_UINT32(cbinfo, struct CBInfoArray, n, + 0, vmstate_cbinfo, QemuClipboardInfo), + VMSTATE_END_OF_LIST() + } +}; + +static int put_cbinfo(QEMUFile *f, void *pv, size_t size, + const VMStateField *field, JSONWriter *vmdesc) +{ + VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv); + struct CBInfoArray cbinfo = {}; + int i; + + if (!have_clipboard(vd)) { + return 0; + } + + for (i = 0; i < QEMU_CLIPBOARD_SELECTION__COUNT; i++) { + if (qemu_clipboard_peer_owns(&vd->cbpeer, i)) { + cbinfo.cbinfo[cbinfo.n++] = *qemu_clipboard_info(i); + } + } + + return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc); +} + +static int get_cbinfo(QEMUFile *f, void *pv, size_t size, + const VMStateField *field) +{ + VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv); + struct CBInfoArray cbinfo = {}; + int i, ret; + + if (!have_clipboard(vd)) { + return 0; + } + + vdagent_clipboard_peer_register(vd); + + ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0); + if (ret) { + return ret; + } + + for (i = 0; i < cbinfo.n; i++) { + g_autoptr(QemuClipboardInfo) info = + qemu_clipboard_info_new(&vd->cbpeer, cbinfo.cbinfo[i].selection); + /* this will steal clipboard data pointer from cbinfo.types */ + memcpy(info->types, cbinfo.cbinfo[i].types, sizeof(cbinfo.cbinfo[i].types)); + qemu_clipboard_update(info); + } + + return 0; +} + +static const VMStateInfo vmstate_cbinfos = { + .name = "vdagent/cbinfos", + .get = get_cbinfo, + .put = put_cbinfo, +}; + +static const VMStateDescription vmstate_vdagent = { + .name = "vdagent", + .version_id = 0, + .minimum_version_id = 0, + .post_load = post_load, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(connected, VDAgentChardev), + VMSTATE_UINT32(caps, VDAgentChardev), + VMSTATE_STRUCT(chunk, VDAgentChardev, 0, vmstate_chunk, VDIChunkHeader), + VMSTATE_UINT32(chunksize, VDAgentChardev), + VMSTATE_UINT32(msgsize, VDAgentChardev), + VMSTATE_VBUFFER_ALLOC_UINT32(msgbuf, VDAgentChardev, 0, 0, msgsize), + VMSTATE_UINT32(xsize, VDAgentChardev), + VMSTATE_UINT32(xoff, VDAgentChardev), + VMSTATE_VBUFFER_ALLOC_UINT32(xbuf, VDAgentChardev, 0, 0, xsize), + VMSTATE_STRUCT_POINTER(outbuf, VDAgentChardev, vmstate_vdba, GByteArray), + VMSTATE_UINT32(mouse_x, VDAgentChardev), + VMSTATE_UINT32(mouse_y, VDAgentChardev), + VMSTATE_UINT32(mouse_btn, VDAgentChardev), + VMSTATE_UINT32(mouse_display, VDAgentChardev), + VMSTATE_UINT32_ARRAY(last_serial, VDAgentChardev, + QEMU_CLIPBOARD_SELECTION__COUNT), + VMSTATE_UINT32_ARRAY(cbpending, VDAgentChardev, + QEMU_CLIPBOARD_SELECTION__COUNT), + { + .name = "cbinfos", + .info = &vmstate_cbinfos, + .flags = VMS_SINGLE, + }, + VMSTATE_END_OF_LIST() + } +}; + static void vdagent_chr_init(Object *obj) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); - buffer_init(&vd->outbuf, "vdagent-outbuf"); - error_setg(&vd->migration_blocker, - "The vdagent chardev doesn't yet support migration"); + vd->outbuf = g_byte_array_new(); + vmstate_register_any(NULL, &vmstate_vdagent, vd); } static void vdagent_chr_fini(Object *obj) { VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(obj); - migrate_del_blocker(&vd->migration_blocker); vdagent_disconnect(vd); if (vd->mouse_hs) { qemu_input_handler_unregister(vd->mouse_hs); } - buffer_free(&vd->outbuf); + g_clear_pointer(&vd->outbuf, g_byte_array_unref); } static const TypeInfo vdagent_chr_type_info = { diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c index 41f559e..9dfe6ae 100644 --- a/ui/vnc-enc-tight.c +++ b/ui/vnc-enc-tight.c @@ -72,8 +72,8 @@ static const struct { }; -static int tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h); +static int tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); #ifdef CONFIG_VNC_JPEG static const struct { @@ -111,12 +111,12 @@ static const struct { { 9, PNG_ALL_FILTERS }, }; -static int send_png_rect(VncState *vs, int x, int y, int w, int h, - VncPalette *palette); +static int send_png_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, VncPalette *palette); -static bool tight_can_send_png_rect(VncState *vs, int w, int h) +static bool tight_can_send_png_rect(VncState *vs, VncTight *tight, int w, int h) { - if (vs->tight->type != VNC_ENCODING_TIGHT_PNG) { + if (tight->type != VNC_ENCODING_TIGHT_PNG) { return false; } @@ -135,7 +135,7 @@ static bool tight_can_send_png_rect(VncState *vs, int w, int h) */ static unsigned int -tight_detect_smooth_image24(VncState *vs, int w, int h) +tight_detect_smooth_image24(VncState *vs, VncTight *tight, int w, int h) { int off; int x, y, d, dx; @@ -144,13 +144,13 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) int pixels = 0; int pix, left[3]; unsigned int errors; - unsigned char *buf = vs->tight->tight.buffer; + unsigned char *buf = tight->tight.buffer; /* * If client is big-endian, color samples begin from the second * byte (offset 1) of a 32-bit pixel value. */ - off = vs->client_be; + off = vs->client_endian == G_BIG_ENDIAN ? 1 : 0; memset(stats, 0, sizeof (stats)); @@ -205,7 +205,8 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) #define DEFINE_DETECT_FUNCTION(bpp) \ \ static unsigned int \ - tight_detect_smooth_image##bpp(VncState *vs, int w, int h) { \ + tight_detect_smooth_image##bpp(VncState *vs, VncTight *tight, \ + int w, int h) { \ bool endian; \ uint##bpp##_t pix; \ int max[3], shift[3]; \ @@ -215,7 +216,7 @@ tight_detect_smooth_image24(VncState *vs, int w, int h) int pixels = 0; \ int sample, sum, left[3]; \ unsigned int errors; \ - unsigned char *buf = vs->tight->tight.buffer; \ + unsigned char *buf = tight->tight.buffer; \ \ endian = 0; /* FIXME */ \ \ @@ -293,11 +294,11 @@ DEFINE_DETECT_FUNCTION(16) DEFINE_DETECT_FUNCTION(32) static int -tight_detect_smooth_image(VncState *vs, int w, int h) +tight_detect_smooth_image(VncState *vs, VncTight *tight, int w, int h) { unsigned int errors; - int compression = vs->tight->compression; - int quality = vs->tight->quality; + int compression = tight->compression; + int quality = tight->quality; if (!vs->vd->lossy) { return 0; @@ -309,7 +310,7 @@ tight_detect_smooth_image(VncState *vs, int w, int h) return 0; } - if (vs->tight->quality != (uint8_t)-1) { + if (tight->quality != (uint8_t)-1) { if (w * h < VNC_TIGHT_JPEG_MIN_RECT_SIZE) { return 0; } @@ -320,17 +321,17 @@ tight_detect_smooth_image(VncState *vs, int w, int h) } if (vs->client_pf.bytes_per_pixel == 4) { - if (vs->tight->pixel24) { - errors = tight_detect_smooth_image24(vs, w, h); - if (vs->tight->quality != (uint8_t)-1) { + if (tight->pixel24) { + errors = tight_detect_smooth_image24(vs, tight, w, h); + if (tight->quality != (uint8_t)-1) { return (errors < tight_conf[quality].jpeg_threshold24); } return (errors < tight_conf[compression].gradient_threshold24); } else { - errors = tight_detect_smooth_image32(vs, w, h); + errors = tight_detect_smooth_image32(vs, tight, w, h); } } else { - errors = tight_detect_smooth_image16(vs, w, h); + errors = tight_detect_smooth_image16(vs, tight, w, h); } if (quality != (uint8_t)-1) { return (errors < tight_conf[quality].jpeg_threshold); @@ -344,15 +345,15 @@ tight_detect_smooth_image(VncState *vs, int w, int h) #define DEFINE_FILL_PALETTE_FUNCTION(bpp) \ \ static int \ - tight_fill_palette##bpp(VncState *vs, int x, int y, \ - int max, size_t count, \ + tight_fill_palette##bpp(VncState *vs, VncTight *tight, \ + int x, int y, int max, size_t count, \ uint32_t *bg, uint32_t *fg, \ VncPalette *palette) { \ uint##bpp##_t *data; \ uint##bpp##_t c0, c1, ci; \ int i, n0, n1; \ \ - data = (uint##bpp##_t *)vs->tight->tight.buffer; \ + data = (uint##bpp##_t *)tight->tight.buffer; \ \ c0 = data[0]; \ i = 1; \ @@ -417,15 +418,15 @@ DEFINE_FILL_PALETTE_FUNCTION(8) DEFINE_FILL_PALETTE_FUNCTION(16) DEFINE_FILL_PALETTE_FUNCTION(32) -static int tight_fill_palette(VncState *vs, int x, int y, +static int tight_fill_palette(VncState *vs, VncTight *tight, int x, int y, size_t count, uint32_t *bg, uint32_t *fg, VncPalette *palette) { int max; - max = count / tight_conf[vs->tight->compression].idx_max_colors_divisor; + max = count / tight_conf[tight->compression].idx_max_colors_divisor; if (max < 2 && - count >= tight_conf[vs->tight->compression].mono_min_rect_size) { + count >= tight_conf[tight->compression].mono_min_rect_size) { max = 2; } if (max >= 256) { @@ -434,12 +435,15 @@ static int tight_fill_palette(VncState *vs, int x, int y, switch (vs->client_pf.bytes_per_pixel) { case 4: - return tight_fill_palette32(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette32(vs, tight, x, y, max, count, bg, fg, + palette); case 2: - return tight_fill_palette16(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette16(vs, tight, x, y, max, count, bg, fg, + palette); default: max = 2; - return tight_fill_palette8(vs, x, y, max, count, bg, fg, palette); + return tight_fill_palette8(vs, tight, x, y, max, count, bg, fg, + palette); } return 0; } @@ -547,7 +551,8 @@ DEFINE_MONO_ENCODE_FUNCTION(32) */ static void -tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) +tight_filter_gradient24(VncState *vs, VncTight *tight, uint8_t *buf, + int w, int h) { uint32_t *buf32; uint32_t pix32; @@ -558,7 +563,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) int x, y, c; buf32 = (uint32_t *)buf; - memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); + memset(tight->gradient.buffer, 0, w * 3 * sizeof(int)); if (1 /* FIXME */) { shift[0] = vs->client_pf.rshift; @@ -575,7 +580,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) upper[c] = 0; here[c] = 0; } - prev = (int *)vs->tight->gradient.buffer; + prev = (int *)tight->gradient.buffer; for (x = 0; x < w; x++) { pix32 = *buf32++; for (c = 0; c < 3; c++) { @@ -605,8 +610,8 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) #define DEFINE_GRADIENT_FILTER_FUNCTION(bpp) \ \ static void \ - tight_filter_gradient##bpp(VncState *vs, uint##bpp##_t *buf, \ - int w, int h) { \ + tight_filter_gradient##bpp(VncState *vs, VncTight *tight, \ + uint##bpp##_t *buf, int w, int h) { \ uint##bpp##_t pix, diff; \ bool endian; \ int *prev; \ @@ -615,7 +620,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) int prediction; \ int x, y, c; \ \ - memset(vs->tight->gradient.buffer, 0, w * 3 * sizeof(int)); \ + memset(tight->gradient.buffer, 0, w * 3 * sizeof(int)); \ \ endian = 0; /* FIXME */ \ \ @@ -631,7 +636,7 @@ tight_filter_gradient24(VncState *vs, uint8_t *buf, int w, int h) upper[c] = 0; \ here[c] = 0; \ } \ - prev = (int *)vs->tight->gradient.buffer; \ + prev = (int *)tight->gradient.buffer; \ for (x = 0; x < w; x++) { \ pix = *buf; \ if (endian) { \ @@ -782,10 +787,10 @@ static void extend_solid_area(VncState *vs, int x, int y, int w, int h, *w_ptr += cx - (*x_ptr + *w_ptr); } -static int tight_init_stream(VncState *vs, int stream_id, +static int tight_init_stream(VncState *vs, VncTight *tight, int stream_id, int level, int strategy) { - z_streamp zstream = &vs->tight->stream[stream_id]; + z_streamp zstream = &tight->stream[stream_id]; if (zstream->opaque == NULL) { int err; @@ -803,15 +808,15 @@ static int tight_init_stream(VncState *vs, int stream_id, return -1; } - vs->tight->levels[stream_id] = level; + tight->levels[stream_id] = level; zstream->opaque = vs; } - if (vs->tight->levels[stream_id] != level) { + if (tight->levels[stream_id] != level) { if (deflateParams(zstream, level, strategy) != Z_OK) { return -1; } - vs->tight->levels[stream_id] = level; + tight->levels[stream_id] = level; } return 0; } @@ -836,29 +841,29 @@ static void tight_send_compact_size(VncState *vs, size_t len) } } -static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, - int level, int strategy) +static int tight_compress_data(VncState *vs, VncTight *tight, int stream_id, + size_t bytes, int level, int strategy) { - z_streamp zstream = &vs->tight->stream[stream_id]; + z_streamp zstream = &tight->stream[stream_id]; int previous_out; if (bytes < VNC_TIGHT_MIN_TO_COMPRESS) { - vnc_write(vs, vs->tight->tight.buffer, vs->tight->tight.offset); + vnc_write(vs, tight->tight.buffer, tight->tight.offset); return bytes; } - if (tight_init_stream(vs, stream_id, level, strategy)) { + if (tight_init_stream(vs, tight, stream_id, level, strategy)) { return -1; } /* reserve memory in output buffer */ - buffer_reserve(&vs->tight->zlib, bytes + 64); + buffer_reserve(&tight->zlib, bytes + 64); /* set pointers */ - zstream->next_in = vs->tight->tight.buffer; - zstream->avail_in = vs->tight->tight.offset; - zstream->next_out = vs->tight->zlib.buffer + vs->tight->zlib.offset; - zstream->avail_out = vs->tight->zlib.capacity - vs->tight->zlib.offset; + zstream->next_in = tight->tight.buffer; + zstream->avail_in = tight->tight.offset; + zstream->next_out = tight->zlib.buffer + tight->zlib.offset; + zstream->avail_out = tight->zlib.capacity - tight->zlib.offset; previous_out = zstream->avail_out; zstream->data_type = Z_BINARY; @@ -868,14 +873,14 @@ static int tight_compress_data(VncState *vs, int stream_id, size_t bytes, return -1; } - vs->tight->zlib.offset = vs->tight->zlib.capacity - zstream->avail_out; + tight->zlib.offset = tight->zlib.capacity - zstream->avail_out; /* ...how much data has actually been produced by deflate() */ bytes = previous_out - zstream->avail_out; tight_send_compact_size(vs, bytes); - vnc_write(vs, vs->tight->zlib.buffer, bytes); + vnc_write(vs, tight->zlib.buffer, bytes); - buffer_reset(&vs->tight->zlib); + buffer_reset(&tight->zlib); return bytes; } @@ -891,7 +896,7 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret) buf8 = buf; - if (1 /* FIXME */) { + if (vs->client_endian == G_BYTE_ORDER) { rshift = vs->client_pf.rshift; gshift = vs->client_pf.gshift; bshift = vs->client_pf.bshift; @@ -914,67 +919,69 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret) } } -static int send_full_color_rect(VncState *vs, int x, int y, int w, int h) +static int send_full_color_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; + int level = tight_conf[tight->compression].raw_zlib_level; int stream = 0; ssize_t bytes; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { - return send_png_rect(vs, x, y, w, h, NULL); + if (tight_can_send_png_rect(vs, tight, w, h)) { + return send_png_rect(vs, worker, x, y, w, h, NULL); } #endif vnc_write_u8(vs, stream << 4); /* no flushing, no filter */ - if (vs->tight->pixel24) { - tight_pack24(vs, vs->tight->tight.buffer, w * h, - &vs->tight->tight.offset); + if (tight->pixel24) { + tight_pack24(vs, tight->tight.buffer, w * h, &tight->tight.offset); bytes = 3; } else { bytes = vs->client_pf.bytes_per_pixel; } - bytes = tight_compress_data(vs, stream, w * h * bytes, - tight_conf[vs->tight->compression].raw_zlib_level, + bytes = tight_compress_data(vs, tight, stream, w * h * bytes, level, Z_DEFAULT_STRATEGY); return (bytes >= 0); } -static int send_solid_rect(VncState *vs) +static int send_solid_rect(VncState *vs, VncWorker *worker) { + VncTight *tight = &worker->tight; size_t bytes; vnc_write_u8(vs, VNC_TIGHT_FILL << 4); /* no flushing, no filter */ - if (vs->tight->pixel24) { - tight_pack24(vs, vs->tight->tight.buffer, 1, &vs->tight->tight.offset); + if (tight->pixel24) { + tight_pack24(vs, tight->tight.buffer, 1, &tight->tight.offset); bytes = 3; } else { bytes = vs->client_pf.bytes_per_pixel; } - vnc_write(vs, vs->tight->tight.buffer, bytes); + vnc_write(vs, tight->tight.buffer, bytes); return 1; } -static int send_mono_rect(VncState *vs, int x, int y, +static int send_mono_rect(VncState *vs, VncWorker *worker, int x, int y, int w, int h, uint32_t bg, uint32_t fg) { ssize_t bytes; int stream = 1; - int level = tight_conf[vs->tight->compression].mono_zlib_level; + int level = tight_conf[worker->tight.compression].mono_zlib_level; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { + if (tight_can_send_png_rect(vs, &worker->tight, w, h)) { int ret; int bpp = vs->client_pf.bytes_per_pixel * 8; VncPalette *palette = palette_new(2, bpp); palette_put(palette, bg); palette_put(palette, fg); - ret = send_png_rect(vs, x, y, w, h, palette); + ret = send_png_rect(vs, worker, x, y, w, h, palette); palette_destroy(palette); return ret; } @@ -992,33 +999,43 @@ static int send_mono_rect(VncState *vs, int x, int y, uint32_t buf[2] = {bg, fg}; size_t ret = sizeof (buf); - if (vs->tight->pixel24) { + if (worker->tight.pixel24) { tight_pack24(vs, (unsigned char*)buf, 2, &ret); } vnc_write(vs, buf, ret); - tight_encode_mono_rect32(vs->tight->tight.buffer, w, h, bg, fg); + tight_encode_mono_rect32(worker->tight.tight.buffer, w, h, bg, fg); break; } case 2: - vnc_write(vs, &bg, 2); - vnc_write(vs, &fg, 2); - tight_encode_mono_rect16(vs->tight->tight.buffer, w, h, bg, fg); + { + uint16_t bg16 = bg; + uint16_t fg16 = fg; + vnc_write(vs, &bg16, 2); + vnc_write(vs, &fg16, 2); + tight_encode_mono_rect16(worker->tight.tight.buffer, w, h, bg, fg); break; + } default: - vnc_write_u8(vs, bg); - vnc_write_u8(vs, fg); - tight_encode_mono_rect8(vs->tight->tight.buffer, w, h, bg, fg); + { + uint8_t bg8 = bg; + uint8_t fg8 = fg; + vnc_write_u8(vs, bg8); + vnc_write_u8(vs, fg8); + tight_encode_mono_rect8(worker->tight.tight.buffer, w, h, bg, fg); break; } - vs->tight->tight.offset = bytes; + } + worker->tight.tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, level, Z_DEFAULT_STRATEGY); + bytes = tight_compress_data(vs, &worker->tight, stream, bytes, level, + Z_DEFAULT_STRATEGY); return (bytes >= 0); } struct palette_cb_priv { VncState *vs; + VncTight *tight; uint8_t *header; #ifdef CONFIG_PNG png_colorp png_palette; @@ -1038,53 +1055,58 @@ static void write_palette(int idx, uint32_t color, void *opaque) } } -static bool send_gradient_rect(VncState *vs, int x, int y, int w, int h) +static bool send_gradient_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; int stream = 3; - int level = tight_conf[vs->tight->compression].gradient_zlib_level; + int level = tight_conf[tight->compression].gradient_zlib_level; ssize_t bytes; if (vs->client_pf.bytes_per_pixel == 1) { - return send_full_color_rect(vs, x, y, w, h); + return send_full_color_rect(vs, worker, x, y, w, h); } vnc_write_u8(vs, (stream | VNC_TIGHT_EXPLICIT_FILTER) << 4); vnc_write_u8(vs, VNC_TIGHT_FILTER_GRADIENT); - buffer_reserve(&vs->tight->gradient, w * 3 * sizeof(int)); + buffer_reserve(&tight->gradient, w * 3 * sizeof(int)); - if (vs->tight->pixel24) { - tight_filter_gradient24(vs, vs->tight->tight.buffer, w, h); + if (tight->pixel24) { + tight_filter_gradient24(vs, tight, tight->tight.buffer, w, h); bytes = 3; } else if (vs->client_pf.bytes_per_pixel == 4) { - tight_filter_gradient32(vs, (uint32_t *)vs->tight->tight.buffer, w, h); + tight_filter_gradient32(vs, tight, (uint32_t *)tight->tight.buffer, + w, h); bytes = 4; } else { - tight_filter_gradient16(vs, (uint16_t *)vs->tight->tight.buffer, w, h); + tight_filter_gradient16(vs, tight, (uint16_t *)tight->tight.buffer, + w, h); bytes = 2; } - buffer_reset(&vs->tight->gradient); + buffer_reset(&tight->gradient); bytes = w * h * bytes; - vs->tight->tight.offset = bytes; + tight->tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, + bytes = tight_compress_data(vs, tight, stream, bytes, level, Z_FILTERED); return (bytes >= 0); } -static int send_palette_rect(VncState *vs, int x, int y, +static int send_palette_rect(VncState *vs, VncWorker *worker, int x, int y, int w, int h, VncPalette *palette) { + VncTight *tight = &worker->tight; int stream = 2; - int level = tight_conf[vs->tight->compression].idx_zlib_level; + int level = tight_conf[tight->compression].idx_zlib_level; int colors; ssize_t bytes; #ifdef CONFIG_PNG - if (tight_can_send_png_rect(vs, w, h)) { - return send_png_rect(vs, x, y, w, h, palette); + if (tight_can_send_png_rect(vs, tight, w, h)) { + return send_png_rect(vs, worker, x, y, w, h, palette); } #endif @@ -1099,38 +1121,38 @@ static int send_palette_rect(VncState *vs, int x, int y, { size_t old_offset, offset, palette_sz = palette_size(palette); g_autofree uint32_t *header = g_new(uint32_t, palette_sz); - struct palette_cb_priv priv = { vs, (uint8_t *)header }; + struct palette_cb_priv priv = { vs, tight, (uint8_t *)header }; old_offset = vs->output.offset; palette_iter(palette, write_palette, &priv); vnc_write(vs, header, palette_sz * sizeof(uint32_t)); - if (vs->tight->pixel24) { + if (tight->pixel24) { tight_pack24(vs, vs->output.buffer + old_offset, colors, &offset); vs->output.offset = old_offset + offset; } - tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, palette); + tight_encode_indexed_rect32(tight->tight.buffer, w * h, palette); break; } case 2: { size_t palette_sz = palette_size(palette); g_autofree uint16_t *header = g_new(uint16_t, palette_sz); - struct palette_cb_priv priv = { vs, (uint8_t *)header }; + struct palette_cb_priv priv = { vs, tight, (uint8_t *)header }; palette_iter(palette, write_palette, &priv); vnc_write(vs, header, palette_sz * sizeof(uint16_t)); - tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, palette); + tight_encode_indexed_rect16(tight->tight.buffer, w * h, palette); break; } default: return -1; /* No palette for 8bits colors */ } bytes = w * h; - vs->tight->tight.offset = bytes; + tight->tight.offset = bytes; - bytes = tight_compress_data(vs, stream, bytes, + bytes = tight_compress_data(vs, tight, stream, bytes, level, Z_DEFAULT_STRATEGY); return (bytes >= 0); } @@ -1146,8 +1168,8 @@ static int send_palette_rect(VncState *vs, int x, int y, /* This is called once per encoding */ static void jpeg_init_destination(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; cinfo->dest->next_output_byte = (JOCTET *)buffer->buffer + buffer->offset; cinfo->dest->free_in_buffer = (size_t)(buffer->capacity - buffer->offset); @@ -1156,8 +1178,8 @@ static void jpeg_init_destination(j_compress_ptr cinfo) /* This is called when we ran out of buffer (shouldn't happen!) */ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; buffer->offset = buffer->capacity; buffer_reserve(buffer, 2048); @@ -1168,13 +1190,14 @@ static boolean jpeg_empty_output_buffer(j_compress_ptr cinfo) /* This is called when we are done processing data */ static void jpeg_term_destination(j_compress_ptr cinfo) { - VncState *vs = cinfo->client_data; - Buffer *buffer = &vs->tight->jpeg; + VncTight *tight = cinfo->client_data; + Buffer *buffer = &tight->jpeg; buffer->offset = buffer->capacity - cinfo->dest->free_in_buffer; } -static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) +static int send_jpeg_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int quality) { struct jpeg_compress_struct cinfo; struct jpeg_error_mgr jerr; @@ -1185,15 +1208,15 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) int dy; if (surface_bytes_per_pixel(vs->vd->ds) == 1) { - return send_full_color_rect(vs, x, y, w, h); + return send_full_color_rect(vs, worker, x, y, w, h); } - buffer_reserve(&vs->tight->jpeg, 2048); + buffer_reserve(&worker->tight.jpeg, 2048); cinfo.err = jpeg_std_error(&jerr); jpeg_create_compress(&cinfo); - cinfo.client_data = vs; + cinfo.client_data = &worker->tight; cinfo.image_width = w; cinfo.image_height = h; cinfo.input_components = 3; @@ -1223,9 +1246,9 @@ static int send_jpeg_rect(VncState *vs, int x, int y, int w, int h, int quality) vnc_write_u8(vs, VNC_TIGHT_JPEG << 4); - tight_send_compact_size(vs, vs->tight->jpeg.offset); - vnc_write(vs, vs->tight->jpeg.buffer, vs->tight->jpeg.offset); - buffer_reset(&vs->tight->jpeg); + tight_send_compact_size(vs, worker->tight.jpeg.offset); + vnc_write(vs, worker->tight.jpeg.buffer, worker->tight.jpeg.offset); + buffer_reset(&worker->tight.jpeg); return 1; } @@ -1241,7 +1264,7 @@ static void write_png_palette(int idx, uint32_t pix, void *opaque) VncState *vs = priv->vs; png_colorp color = &priv->png_palette[idx]; - if (vs->tight->pixel24) + if (priv->tight->pixel24) { color->red = (pix >> vs->client_pf.rshift) & vs->client_pf.rmax; color->green = (pix >> vs->client_pf.gshift) & vs->client_pf.gmax; @@ -1266,12 +1289,12 @@ static void write_png_palette(int idx, uint32_t pix, void *opaque) static void png_write_data(png_structp png_ptr, png_bytep data, png_size_t length) { - VncState *vs = png_get_io_ptr(png_ptr); + VncWorker *worker = png_get_io_ptr(png_ptr); - buffer_reserve(&vs->tight->png, vs->tight->png.offset + length); - memcpy(vs->tight->png.buffer + vs->tight->png.offset, data, length); + buffer_reserve(&worker->tight.png, worker->tight.png.offset + length); + memcpy(worker->tight.png.buffer + worker->tight.png.offset, data, length); - vs->tight->png.offset += length; + worker->tight.png.offset += length; } static void png_flush_data(png_structp png_ptr) @@ -1288,16 +1311,16 @@ static void vnc_png_free(png_structp png_ptr, png_voidp ptr) g_free(ptr); } -static int send_png_rect(VncState *vs, int x, int y, int w, int h, - VncPalette *palette) +static int send_png_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, VncPalette *palette) { png_byte color_type; png_structp png_ptr; png_infop info_ptr; png_colorp png_palette = NULL; pixman_image_t *linebuf; - int level = tight_png_conf[vs->tight->compression].png_zlib_level; - int filters = tight_png_conf[vs->tight->compression].png_filters; + int level = tight_png_conf[worker->tight.compression].png_zlib_level; + int filters = tight_png_conf[worker->tight.compression].png_filters; uint8_t *buf; int dy; @@ -1314,7 +1337,7 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, return -1; } - png_set_write_fn(png_ptr, (void *) vs, png_write_data, png_flush_data); + png_set_write_fn(png_ptr, worker, png_write_data, png_flush_data); png_set_compression_level(png_ptr, level); png_set_filter(png_ptr, PNG_FILTER_TYPE_DEFAULT, filters); @@ -1335,29 +1358,30 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, palette_size(palette)); priv.vs = vs; + priv.tight = &worker->tight; priv.png_palette = png_palette; palette_iter(palette, write_png_palette, &priv); png_set_PLTE(png_ptr, info_ptr, png_palette, palette_size(palette)); if (vs->client_pf.bytes_per_pixel == 4) { - tight_encode_indexed_rect32(vs->tight->tight.buffer, w * h, + tight_encode_indexed_rect32(worker->tight.tight.buffer, w * h, palette); } else { - tight_encode_indexed_rect16(vs->tight->tight.buffer, w * h, + tight_encode_indexed_rect16(worker->tight.tight.buffer, w * h, palette); } } png_write_info(png_ptr, info_ptr); - buffer_reserve(&vs->tight->png, 2048); + buffer_reserve(&worker->tight.png, 2048); linebuf = qemu_pixman_linebuf_create(PIXMAN_BE_r8g8b8, w); buf = (uint8_t *)pixman_image_get_data(linebuf); for (dy = 0; dy < h; dy++) { if (color_type == PNG_COLOR_TYPE_PALETTE) { - memcpy(buf, vs->tight->tight.buffer + (dy * w), w); + memcpy(buf, worker->tight.tight.buffer + (dy * w), w); } else { qemu_pixman_linebuf_fill(linebuf, vs->vd->server, w, x, y + dy); } @@ -1375,46 +1399,47 @@ static int send_png_rect(VncState *vs, int x, int y, int w, int h, vnc_write_u8(vs, VNC_TIGHT_PNG << 4); - tight_send_compact_size(vs, vs->tight->png.offset); - vnc_write(vs, vs->tight->png.buffer, vs->tight->png.offset); - buffer_reset(&vs->tight->png); + tight_send_compact_size(vs, worker->tight.png.offset); + vnc_write(vs, worker->tight.png.buffer, worker->tight.png.offset); + buffer_reset(&worker->tight.png); return 1; } #endif /* CONFIG_PNG */ -static void vnc_tight_start(VncState *vs) +static void vnc_tight_start(VncState *vs, VncTight *tight) { - buffer_reset(&vs->tight->tight); + buffer_reset(&tight->tight); // make the output buffer be the zlib buffer, so we can compress it later - vs->tight->tmp = vs->output; - vs->output = vs->tight->tight; + tight->tmp = vs->output; + vs->output = tight->tight; } -static void vnc_tight_stop(VncState *vs) +static void vnc_tight_stop(VncState *vs, VncTight *tight) { // switch back to normal output/zlib buffers - vs->tight->tight = vs->output; - vs->output = vs->tight->tmp; + tight->tight = vs->output; + vs->output = tight->tmp; } -static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h, +static int send_sub_rect_nojpeg(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int bg, int fg, int colors, VncPalette *palette) { int ret; if (colors == 0) { - if (tight_detect_smooth_image(vs, w, h)) { - ret = send_gradient_rect(vs, x, y, w, h); + if (tight_detect_smooth_image(vs, &worker->tight, w, h)) { + ret = send_gradient_rect(vs, worker, x, y, w, h); } else { - ret = send_full_color_rect(vs, x, y, w, h); + ret = send_full_color_rect(vs, worker, x, y, w, h); } } else if (colors == 1) { - ret = send_solid_rect(vs); + ret = send_solid_rect(vs, worker); } else if (colors == 2) { - ret = send_mono_rect(vs, x, y, w, h, bg, fg); + ret = send_mono_rect(vs, worker, x, y, w, h, bg, fg); } else if (colors <= 256) { - ret = send_palette_rect(vs, x, y, w, h, palette); + ret = send_palette_rect(vs, worker, x, y, w, h, palette); } else { ret = 0; } @@ -1422,34 +1447,35 @@ static int send_sub_rect_nojpeg(VncState *vs, int x, int y, int w, int h, } #ifdef CONFIG_VNC_JPEG -static int send_sub_rect_jpeg(VncState *vs, int x, int y, int w, int h, +static int send_sub_rect_jpeg(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int bg, int fg, int colors, VncPalette *palette, bool force) { int ret; if (colors == 0) { - if (force || (tight_jpeg_conf[vs->tight->quality].jpeg_full && - tight_detect_smooth_image(vs, w, h))) { - int quality = tight_conf[vs->tight->quality].jpeg_quality; + if (force || (tight_jpeg_conf[worker->tight.quality].jpeg_full && + tight_detect_smooth_image(vs, &worker->tight, w, h))) { + int quality = tight_conf[worker->tight.quality].jpeg_quality; - ret = send_jpeg_rect(vs, x, y, w, h, quality); + ret = send_jpeg_rect(vs, worker, x, y, w, h, quality); } else { - ret = send_full_color_rect(vs, x, y, w, h); + ret = send_full_color_rect(vs, worker, x, y, w, h); } } else if (colors == 1) { - ret = send_solid_rect(vs); + ret = send_solid_rect(vs, worker); } else if (colors == 2) { - ret = send_mono_rect(vs, x, y, w, h, bg, fg); + ret = send_mono_rect(vs, worker, x, y, w, h, bg, fg); } else if (colors <= 256) { if (force || (colors > 96 && - tight_jpeg_conf[vs->tight->quality].jpeg_idx && - tight_detect_smooth_image(vs, w, h))) { - int quality = tight_conf[vs->tight->quality].jpeg_quality; + tight_jpeg_conf[worker->tight.quality].jpeg_idx && + tight_detect_smooth_image(vs, &worker->tight, w, h))) { + int quality = tight_conf[worker->tight.quality].jpeg_quality; - ret = send_jpeg_rect(vs, x, y, w, h, quality); + ret = send_jpeg_rect(vs, worker, x, y, w, h, quality); } else { - ret = send_palette_rect(vs, x, y, w, h, palette); + ret = send_palette_rect(vs, worker, x, y, w, h, palette); } } else { ret = 0; @@ -1467,8 +1493,10 @@ static void vnc_tight_cleanup(Notifier *n, void *value) color_count_palette = NULL; } -static int send_sub_rect(VncState *vs, int x, int y, int w, int h) +static int send_sub_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { + VncTight *tight = &worker->tight; uint32_t bg = 0, fg = 0; int colors; int ret = 0; @@ -1483,57 +1511,59 @@ static int send_sub_rect(VncState *vs, int x, int y, int w, int h) qemu_thread_atexit_add(&vnc_tight_cleanup_notifier); } - vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type); + vnc_framebuffer_update(vs, x, y, w, h, tight->type); - vnc_tight_start(vs); + vnc_tight_start(vs, tight); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vnc_tight_stop(vs); + vnc_tight_stop(vs, tight); #ifdef CONFIG_VNC_JPEG - if (!vs->vd->non_adaptive && vs->tight->quality != (uint8_t)-1) { + if (!vs->vd->non_adaptive && tight->quality != (uint8_t)-1) { double freq = vnc_update_freq(vs, x, y, w, h); - if (freq < tight_jpeg_conf[vs->tight->quality].jpeg_freq_min) { + if (freq < tight_jpeg_conf[tight->quality].jpeg_freq_min) { allow_jpeg = false; } - if (freq >= tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) { + if (freq >= tight_jpeg_conf[tight->quality].jpeg_freq_threshold) { force_jpeg = true; - vnc_sent_lossy_rect(vs, x, y, w, h); + vnc_sent_lossy_rect(worker, x, y, w, h); } } #endif - colors = tight_fill_palette(vs, x, y, w * h, &bg, &fg, color_count_palette); + colors = tight_fill_palette(vs, tight, x, y, w * h, &bg, &fg, + color_count_palette); #ifdef CONFIG_VNC_JPEG - if (allow_jpeg && vs->tight->quality != (uint8_t)-1) { - ret = send_sub_rect_jpeg(vs, x, y, w, h, bg, fg, colors, + if (allow_jpeg && tight->quality != (uint8_t)-1) { + ret = send_sub_rect_jpeg(vs, worker, x, y, w, h, bg, fg, colors, color_count_palette, force_jpeg); } else { - ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, - color_count_palette); + ret = send_sub_rect_nojpeg(vs, worker, x, y, w, h, bg, fg, + colors, color_count_palette); } #else - ret = send_sub_rect_nojpeg(vs, x, y, w, h, bg, fg, colors, + ret = send_sub_rect_nojpeg(vs, worker, x, y, w, h, bg, fg, colors, color_count_palette); #endif return ret; } -static int send_sub_rect_solid(VncState *vs, int x, int y, int w, int h) +static int send_sub_rect_solid(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vnc_framebuffer_update(vs, x, y, w, h, vs->tight->type); + vnc_framebuffer_update(vs, x, y, w, h, worker->tight.type); - vnc_tight_start(vs); + vnc_tight_start(vs, &worker->tight); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vnc_tight_stop(vs); + vnc_tight_stop(vs, &worker->tight); - return send_solid_rect(vs); + return send_solid_rect(vs, worker); } -static int send_rect_simple(VncState *vs, int x, int y, int w, int h, - bool split) +static int send_rect_simple(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, bool split) { int max_size, max_width; int max_sub_width, max_sub_height; @@ -1541,8 +1571,8 @@ static int send_rect_simple(VncState *vs, int x, int y, int w, int h, int rw, rh; int n = 0; - max_size = tight_conf[vs->tight->compression].max_rect_size; - max_width = tight_conf[vs->tight->compression].max_rect_width; + max_size = tight_conf[worker->tight.compression].max_rect_size; + max_width = tight_conf[worker->tight.compression].max_rect_width; if (split && (w > max_width || w * h > max_size)) { max_sub_width = (w > max_width) ? max_width : w; @@ -1552,18 +1582,18 @@ static int send_rect_simple(VncState *vs, int x, int y, int w, int h, for (dx = 0; dx < w; dx += max_width) { rw = MIN(max_sub_width, w - dx); rh = MIN(max_sub_height, h - dy); - n += send_sub_rect(vs, x+dx, y+dy, rw, rh); + n += send_sub_rect(vs, worker, x + dx, y + dy, rw, rh); } } } else { - n += send_sub_rect(vs, x, y, w, h); + n += send_sub_rect(vs, worker, x, y, w, h); } return n; } -static int find_large_solid_color_rect(VncState *vs, int x, int y, - int w, int h, int max_rows) +static int find_large_solid_color_rect(VncState *vs, VncWorker *worker, + int x, int y, int w, int h, int max_rows) { int dx, dy, dw, dh; int n = 0; @@ -1575,7 +1605,7 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, /* If a rectangle becomes too large, send its upper part now. */ if (dy - y >= max_rows) { - n += send_rect_simple(vs, x, y, w, max_rows, true); + n += send_rect_simple(vs, worker, x, y, w, max_rows, true); y += max_rows; h -= max_rows; } @@ -1614,26 +1644,28 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, /* Send rectangles at top and left to solid-color area. */ if (y_best != y) { - n += send_rect_simple(vs, x, y, w, y_best-y, true); + n += send_rect_simple(vs, worker, x, y, w, y_best - y, true); } if (x_best != x) { - n += tight_send_framebuffer_update(vs, x, y_best, + n += tight_send_framebuffer_update(vs, worker, x, y_best, x_best-x, h_best); } /* Send solid-color rectangle. */ - n += send_sub_rect_solid(vs, x_best, y_best, w_best, h_best); + n += send_sub_rect_solid(vs, worker, + x_best, y_best, w_best, h_best); /* Send remaining rectangles (at right and bottom). */ if (x_best + w_best != x + w) { - n += tight_send_framebuffer_update(vs, x_best+w_best, + n += tight_send_framebuffer_update(vs, worker, x_best + w_best, y_best, w-(x_best-x)-w_best, h_best); } if (y_best + h_best != y + h) { - n += tight_send_framebuffer_update(vs, x, y_best+h_best, + n += tight_send_framebuffer_update(vs, worker, + x, y_best + h_best, w, h-(y_best-y)-h_best); } @@ -1641,73 +1673,73 @@ static int find_large_solid_color_rect(VncState *vs, int x, int y, return n; } } - return n + send_rect_simple(vs, x, y, w, h, true); + return n + send_rect_simple(vs, worker, x, y, w, h, true); } -static int tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +static int tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int max_rows; if (vs->client_pf.bytes_per_pixel == 4 && vs->client_pf.rmax == 0xFF && vs->client_pf.bmax == 0xFF && vs->client_pf.gmax == 0xFF) { - vs->tight->pixel24 = true; + worker->tight.pixel24 = true; } else { - vs->tight->pixel24 = false; + worker->tight.pixel24 = false; } #ifdef CONFIG_VNC_JPEG - if (vs->tight->quality != (uint8_t)-1) { + if (worker->tight.quality != (uint8_t)-1) { double freq = vnc_update_freq(vs, x, y, w, h); - if (freq > tight_jpeg_conf[vs->tight->quality].jpeg_freq_threshold) { - return send_rect_simple(vs, x, y, w, h, false); + if (freq > tight_jpeg_conf[worker->tight.quality].jpeg_freq_threshold) { + return send_rect_simple(vs, worker, x, y, w, h, false); } } #endif if (w * h < VNC_TIGHT_MIN_SPLIT_RECT_SIZE) { - return send_rect_simple(vs, x, y, w, h, true); + return send_rect_simple(vs, worker, x, y, w, h, true); } /* Calculate maximum number of rows in one non-solid rectangle. */ - max_rows = tight_conf[vs->tight->compression].max_rect_size; - max_rows /= MIN(tight_conf[vs->tight->compression].max_rect_width, w); + max_rows = tight_conf[worker->tight.compression].max_rect_size; + max_rows /= MIN(tight_conf[worker->tight.compression].max_rect_width, w); - return find_large_solid_color_rect(vs, x, y, w, h, max_rows); + return find_large_solid_color_rect(vs, worker, x, y, w, h, max_rows); } -int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +int vnc_tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->tight->type = VNC_ENCODING_TIGHT; - return tight_send_framebuffer_update(vs, x, y, w, h); + worker->tight.type = VNC_ENCODING_TIGHT; + return tight_send_framebuffer_update(vs, worker, x, y, w, h); } -int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +int vnc_tight_png_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->tight->type = VNC_ENCODING_TIGHT_PNG; - return tight_send_framebuffer_update(vs, x, y, w, h); + worker->tight.type = VNC_ENCODING_TIGHT_PNG; + return tight_send_framebuffer_update(vs, worker, x, y, w, h); } -void vnc_tight_clear(VncState *vs) +void vnc_tight_clear(VncWorker *worker) { int i; - for (i = 0; i < ARRAY_SIZE(vs->tight->stream); i++) { - if (vs->tight->stream[i].opaque) { - deflateEnd(&vs->tight->stream[i]); + for (i = 0; i < ARRAY_SIZE(worker->tight.stream); i++) { + if (worker->tight.stream[i].opaque) { + deflateEnd(&worker->tight.stream[i]); } } - buffer_free(&vs->tight->tight); - buffer_free(&vs->tight->zlib); - buffer_free(&vs->tight->gradient); + buffer_free(&worker->tight.tight); + buffer_free(&worker->tight.zlib); + buffer_free(&worker->tight.gradient); #ifdef CONFIG_VNC_JPEG - buffer_free(&vs->tight->jpeg); + buffer_free(&worker->tight.jpeg); #endif #ifdef CONFIG_PNG - buffer_free(&vs->tight->png); + buffer_free(&worker->tight.png); #endif } diff --git a/ui/vnc-enc-zlib.c b/ui/vnc-enc-zlib.c index 900ae5b..a6d2871 100644 --- a/ui/vnc-enc-zlib.c +++ b/ui/vnc-enc-zlib.c @@ -46,23 +46,23 @@ void vnc_zlib_zfree(void *x, void *addr) g_free(addr); } -static void vnc_zlib_start(VncState *vs) +static void vnc_zlib_start(VncState *vs, VncWorker *worker) { - buffer_reset(&vs->zlib.zlib); + buffer_reset(&worker->zlib.zlib); // make the output buffer be the zlib buffer, so we can compress it later - vs->zlib.tmp = vs->output; - vs->output = vs->zlib.zlib; + worker->zlib.tmp = vs->output; + vs->output = worker->zlib.zlib; } -static int vnc_zlib_stop(VncState *vs) +static int vnc_zlib_stop(VncState *vs, VncWorker *worker) { - z_streamp zstream = &vs->zlib.stream; + z_streamp zstream = &worker->zlib.stream; int previous_out; // switch back to normal output/zlib buffers - vs->zlib.zlib = vs->output; - vs->output = vs->zlib.tmp; + worker->zlib.zlib = vs->output; + vs->output = worker->zlib.tmp; // compress the zlib buffer @@ -76,7 +76,7 @@ static int vnc_zlib_stop(VncState *vs) zstream->zalloc = vnc_zlib_zalloc; zstream->zfree = vnc_zlib_zfree; - err = deflateInit2(zstream, vs->tight->compression, Z_DEFLATED, + err = deflateInit2(zstream, worker->tight.compression, Z_DEFLATED, MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY); @@ -85,24 +85,24 @@ static int vnc_zlib_stop(VncState *vs) return -1; } - vs->zlib.level = vs->tight->compression; + worker->zlib.level = worker->tight.compression; zstream->opaque = vs; } - if (vs->tight->compression != vs->zlib.level) { - if (deflateParams(zstream, vs->tight->compression, + if (worker->tight.compression != worker->zlib.level) { + if (deflateParams(zstream, worker->tight.compression, Z_DEFAULT_STRATEGY) != Z_OK) { return -1; } - vs->zlib.level = vs->tight->compression; + worker->zlib.level = worker->tight.compression; } // reserve memory in output buffer - buffer_reserve(&vs->output, vs->zlib.zlib.offset + 64); + buffer_reserve(&vs->output, worker->zlib.zlib.offset + 64); // set pointers - zstream->next_in = vs->zlib.zlib.buffer; - zstream->avail_in = vs->zlib.zlib.offset; + zstream->next_in = worker->zlib.zlib.buffer; + zstream->avail_in = worker->zlib.zlib.offset; zstream->next_out = vs->output.buffer + vs->output.offset; zstream->avail_out = vs->output.capacity - vs->output.offset; previous_out = zstream->avail_out; @@ -118,7 +118,8 @@ static int vnc_zlib_stop(VncState *vs) return previous_out - zstream->avail_out; } -int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int old_offset, new_offset, bytes_written; @@ -129,9 +130,9 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) vnc_write_s32(vs, 0); // compress the stream - vnc_zlib_start(vs); + vnc_zlib_start(vs, worker); vnc_raw_send_framebuffer_update(vs, x, y, w, h); - bytes_written = vnc_zlib_stop(vs); + bytes_written = vnc_zlib_stop(vs, worker); if (bytes_written == -1) return 0; @@ -145,10 +146,10 @@ int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) return 1; } -void vnc_zlib_clear(VncState *vs) +void vnc_zlib_clear(VncWorker *worker) { - if (vs->zlib.stream.opaque) { - deflateEnd(&vs->zlib.stream); + if (worker->zlib.stream.opaque) { + deflateEnd(&worker->zlib.stream); } - buffer_free(&vs->zlib.zlib); + buffer_free(&worker->zlib.zlib); } diff --git a/ui/vnc-enc-zrle.c b/ui/vnc-enc-zrle.c index bd33b89..7679014 100644 --- a/ui/vnc-enc-zrle.c +++ b/ui/vnc-enc-zrle.c @@ -35,45 +35,45 @@ static const int bits_per_packed_pixel[] = { }; -static void vnc_zrle_start(VncState *vs) +static void vnc_zrle_start(VncState *vs, VncZrle *zrle) { - buffer_reset(&vs->zrle->zrle); + buffer_reset(&zrle->zrle); /* make the output buffer be the zlib buffer, so we can compress it later */ - vs->zrle->tmp = vs->output; - vs->output = vs->zrle->zrle; + zrle->tmp = vs->output; + vs->output = zrle->zrle; } -static void vnc_zrle_stop(VncState *vs) +static void vnc_zrle_stop(VncState *vs, VncZrle *zrle) { /* switch back to normal output/zlib buffers */ - vs->zrle->zrle = vs->output; - vs->output = vs->zrle->tmp; + zrle->zrle = vs->output; + vs->output = zrle->tmp; } -static void *zrle_convert_fb(VncState *vs, int x, int y, int w, int h, - int bpp) +static void *zrle_convert_fb(VncState *vs, VncZrle *zrle, + int x, int y, int w, int h, int bpp) { Buffer tmp; - buffer_reset(&vs->zrle->fb); - buffer_reserve(&vs->zrle->fb, w * h * bpp + bpp); + buffer_reset(&zrle->fb); + buffer_reserve(&zrle->fb, w * h * bpp + bpp); tmp = vs->output; - vs->output = vs->zrle->fb; + vs->output = zrle->fb; vnc_raw_send_framebuffer_update(vs, x, y, w, h); - vs->zrle->fb = vs->output; + zrle->fb = vs->output; vs->output = tmp; - return vs->zrle->fb.buffer; + return zrle->fb.buffer; } -static int zrle_compress_data(VncState *vs, int level) +static int zrle_compress_data(VncState *vs, VncZrle *zrle, int level) { - z_streamp zstream = &vs->zrle->stream; + z_streamp zstream = &zrle->stream; - buffer_reset(&vs->zrle->zlib); + buffer_reset(&zrle->zlib); if (zstream->opaque != vs) { int err; @@ -93,13 +93,13 @@ static int zrle_compress_data(VncState *vs, int level) } /* reserve memory in output buffer */ - buffer_reserve(&vs->zrle->zlib, vs->zrle->zrle.offset + 64); + buffer_reserve(&zrle->zlib, zrle->zrle.offset + 64); /* set pointers */ - zstream->next_in = vs->zrle->zrle.buffer; - zstream->avail_in = vs->zrle->zrle.offset; - zstream->next_out = vs->zrle->zlib.buffer; - zstream->avail_out = vs->zrle->zlib.capacity; + zstream->next_in = zrle->zrle.buffer; + zstream->avail_in = zrle->zrle.offset; + zstream->next_out = zrle->zlib.buffer; + zstream->avail_out = zrle->zlib.capacity; zstream->data_type = Z_BINARY; /* start encoding */ @@ -108,8 +108,8 @@ static int zrle_compress_data(VncState *vs, int level) return -1; } - vs->zrle->zlib.offset = vs->zrle->zlib.capacity - zstream->avail_out; - return vs->zrle->zlib.offset; + zrle->zlib.offset = zrle->zlib.capacity - zstream->avail_out; + return zrle->zlib.offset; } /* Try to work out whether to use RLE and/or a palette. We do this by @@ -252,21 +252,21 @@ static void zrle_write_u8(VncState *vs, uint8_t value) #undef ZRLE_COMPACT_PIXEL #undef ZRLE_BPP -static int zrle_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h) +static int zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - bool be = vs->client_be; + bool be = vs->client_endian == G_BIG_ENDIAN; size_t bytes; int zywrle_level; - if (vs->zrle->type == VNC_ENCODING_ZYWRLE) { - if (!vs->vd->lossy || vs->tight->quality == (uint8_t)-1 - || vs->tight->quality == 9) { + if (worker->zrle.type == VNC_ENCODING_ZYWRLE) { + if (!vs->vd->lossy || worker->tight.quality == (uint8_t)-1 + || worker->tight.quality == 9) { zywrle_level = 0; - vs->zrle->type = VNC_ENCODING_ZRLE; - } else if (vs->tight->quality < 3) { + worker->zrle.type = VNC_ENCODING_ZRLE; + } else if (worker->tight.quality < 3) { zywrle_level = 3; - } else if (vs->tight->quality < 6) { + } else if (worker->tight.quality < 6) { zywrle_level = 2; } else { zywrle_level = 1; @@ -275,25 +275,25 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y, zywrle_level = 0; } - vnc_zrle_start(vs); + vnc_zrle_start(vs, &worker->zrle); switch (vs->client_pf.bytes_per_pixel) { case 1: - zrle_encode_8ne(vs, x, y, w, h, zywrle_level); + zrle_encode_8ne(vs, &worker->zrle, x, y, w, h, zywrle_level); break; case 2: if (vs->client_pf.gmax > 0x1F) { if (be) { - zrle_encode_16be(vs, x, y, w, h, zywrle_level); + zrle_encode_16be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_16le(vs, x, y, w, h, zywrle_level); + zrle_encode_16le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else { if (be) { - zrle_encode_15be(vs, x, y, w, h, zywrle_level); + zrle_encode_15be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_15le(vs, x, y, w, h, zywrle_level); + zrle_encode_15le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } break; @@ -314,53 +314,55 @@ static int zrle_send_framebuffer_update(VncState *vs, int x, int y, if ((fits_in_ls3bytes && !be) || (fits_in_ms3bytes && be)) { if (be) { - zrle_encode_24abe(vs, x, y, w, h, zywrle_level); + zrle_encode_24abe(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_24ale(vs, x, y, w, h, zywrle_level); + zrle_encode_24ale(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else if ((fits_in_ls3bytes && be) || (fits_in_ms3bytes && !be)) { if (be) { - zrle_encode_24bbe(vs, x, y, w, h, zywrle_level); + zrle_encode_24bbe(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_24ble(vs, x, y, w, h, zywrle_level); + zrle_encode_24ble(vs, &worker->zrle, x, y, w, h, zywrle_level); } } else { if (be) { - zrle_encode_32be(vs, x, y, w, h, zywrle_level); + zrle_encode_32be(vs, &worker->zrle, x, y, w, h, zywrle_level); } else { - zrle_encode_32le(vs, x, y, w, h, zywrle_level); + zrle_encode_32le(vs, &worker->zrle, x, y, w, h, zywrle_level); } } } break; } - vnc_zrle_stop(vs); - bytes = zrle_compress_data(vs, Z_DEFAULT_COMPRESSION); - vnc_framebuffer_update(vs, x, y, w, h, vs->zrle->type); + vnc_zrle_stop(vs, &worker->zrle); + bytes = zrle_compress_data(vs, &worker->zrle, Z_DEFAULT_COMPRESSION); + vnc_framebuffer_update(vs, x, y, w, h, worker->zrle.type); vnc_write_u32(vs, bytes); - vnc_write(vs, vs->zrle->zlib.buffer, vs->zrle->zlib.offset); + vnc_write(vs, worker->zrle.zlib.buffer, worker->zrle.zlib.offset); return 1; } -int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->zrle->type = VNC_ENCODING_ZRLE; - return zrle_send_framebuffer_update(vs, x, y, w, h); + worker->zrle.type = VNC_ENCODING_ZRLE; + return zrle_send_framebuffer_update(vs, worker, x, y, w, h); } -int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { - vs->zrle->type = VNC_ENCODING_ZYWRLE; - return zrle_send_framebuffer_update(vs, x, y, w, h); + worker->zrle.type = VNC_ENCODING_ZYWRLE; + return zrle_send_framebuffer_update(vs, worker, x, y, w, h); } -void vnc_zrle_clear(VncState *vs) +void vnc_zrle_clear(VncWorker *worker) { - if (vs->zrle->stream.opaque) { - deflateEnd(&vs->zrle->stream); + if (worker->zrle.stream.opaque) { + deflateEnd(&worker->zrle.stream); } - buffer_free(&vs->zrle->zrle); - buffer_free(&vs->zrle->fb); - buffer_free(&vs->zrle->zlib); + buffer_free(&worker->zrle.zrle); + buffer_free(&worker->zrle.fb); + buffer_free(&worker->zrle.zlib); } diff --git a/ui/vnc-enc-zrle.c.inc b/ui/vnc-enc-zrle.c.inc index 2ef7501..68d28f5 100644 --- a/ui/vnc-enc-zrle.c.inc +++ b/ui/vnc-enc-zrle.c.inc @@ -62,16 +62,16 @@ #define ZRLE_ENCODE_TILE ZRLE_CONCAT2(zrle_encode_tile, ZRLE_ENCODE_SUFFIX) #define ZRLE_WRITE_PALETTE ZRLE_CONCAT2(zrle_write_palette,ZRLE_ENCODE_SUFFIX) -static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, - int zywrle_level); +static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data, + int w, int h, int zywrle_level); #if ZRLE_BPP != 8 #include "vnc-enc-zywrle-template.c" #endif -static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h, - int zywrle_level) +static void ZRLE_ENCODE(VncState *vs, VncZrle *zrle, + int x, int y, int w, int h, int zywrle_level) { int ty; @@ -87,16 +87,16 @@ static void ZRLE_ENCODE(VncState *vs, int x, int y, int w, int h, tw = MIN(VNC_ZRLE_TILE_WIDTH, x + w - tx); - buf = zrle_convert_fb(vs, tx, ty, tw, th, ZRLE_BPP); - ZRLE_ENCODE_TILE(vs, buf, tw, th, zywrle_level); + buf = zrle_convert_fb(vs, zrle, tx, ty, tw, th, ZRLE_BPP); + ZRLE_ENCODE_TILE(vs, zrle, buf, tw, th, zywrle_level); } } } -static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, - int zywrle_level) +static void ZRLE_ENCODE_TILE(VncState *vs, VncZrle *zrle, ZRLE_PIXEL *data, + int w, int h, int zywrle_level) { - VncPalette *palette = &vs->zrle->palette; + VncPalette *palette = &zrle->palette; int runs = 0; int single_pixels = 0; @@ -236,7 +236,7 @@ static void ZRLE_ENCODE_TILE(VncState *vs, ZRLE_PIXEL *data, int w, int h, #if ZRLE_BPP != 8 if (zywrle_level > 0 && !(zywrle_level & 0x80)) { ZYWRLE_ANALYZE(data, data, w, h, w, zywrle_level, vs->zywrle.buf); - ZRLE_ENCODE_TILE(vs, data, w, h, zywrle_level | 0x80); + ZRLE_ENCODE_TILE(vs, zrle, data, w, h, zywrle_level | 0x80); } else #endif diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c index fcca7ec..bed3395 100644 --- a/ui/vnc-jobs.c +++ b/ui/vnc-jobs.c @@ -185,14 +185,10 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local) local->vnc_encoding = orig->vnc_encoding; local->features = orig->features; local->vd = orig->vd; - local->lossy_rect = orig->lossy_rect; local->write_pixels = orig->write_pixels; local->client_pf = orig->client_pf; - local->client_be = orig->client_be; - local->tight = orig->tight; - local->zlib = orig->zlib; + local->client_endian = orig->client_endian; local->hextile = orig->hextile; - local->zrle = orig->zrle; local->client_width = orig->client_width; local->client_height = orig->client_height; } @@ -200,11 +196,7 @@ static void vnc_async_encoding_start(VncState *orig, VncState *local) static void vnc_async_encoding_end(VncState *orig, VncState *local) { buffer_free(&local->output); - orig->tight = local->tight; - orig->zlib = local->zlib; orig->hextile = local->hextile; - orig->zrle = local->zrle; - orig->lossy_rect = local->lossy_rect; } static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect) @@ -237,6 +229,7 @@ static bool vnc_worker_clamp_rect(VncState *vs, VncJob *job, VncRect *rect) static int vnc_worker_thread_loop(VncJobQueue *queue) { + VncConnection *vc; VncJob *job; VncRectEntry *entry, *tmp; VncState vs = {}; @@ -256,6 +249,7 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) } assert(job->vs->magic == VNC_MAGIC); + vc = container_of(job->vs, VncConnection, vs); vnc_lock_output(job->vs); if (job->vs->ioc == NULL || job->vs->abort == true) { @@ -295,7 +289,8 @@ static int vnc_worker_thread_loop(VncJobQueue *queue) } if (vnc_worker_clamp_rect(&vs, job, &entry->rect)) { - n = vnc_send_framebuffer_update(&vs, entry->rect.x, entry->rect.y, + n = vnc_send_framebuffer_update(&vs, &vc->worker, + entry->rect.x, entry->rect.y, entry->rect.w, entry->rect.h); if (n >= 0) { @@ -146,8 +146,6 @@ static void vnc_init_basic_info(SocketAddress *addr, default: abort(); } - - return; } static void vnc_init_basic_info_from_server_addr(QIOChannelSocket *ioc, @@ -893,7 +891,7 @@ void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) buf[0] = v; break; case 2: - if (vs->client_be) { + if (vs->client_endian == G_BIG_ENDIAN) { buf[0] = v >> 8; buf[1] = v; } else { @@ -903,7 +901,7 @@ void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v) break; default: case 4: - if (vs->client_be) { + if (vs->client_endian == G_BIG_ENDIAN) { buf[0] = v >> 24; buf[1] = v >> 16; buf[2] = v >> 8; @@ -948,29 +946,30 @@ int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) return 1; } -int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h) { int n = 0; switch(vs->vnc_encoding) { case VNC_ENCODING_ZLIB: - n = vnc_zlib_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zlib_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_HEXTILE: vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_HEXTILE); n = vnc_hextile_send_framebuffer_update(vs, x, y, w, h); break; case VNC_ENCODING_TIGHT: - n = vnc_tight_send_framebuffer_update(vs, x, y, w, h); + n = vnc_tight_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_TIGHT_PNG: - n = vnc_tight_png_send_framebuffer_update(vs, x, y, w, h); + n = vnc_tight_png_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_ZRLE: - n = vnc_zrle_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zrle_send_framebuffer_update(vs, worker, x, y, w, h); break; case VNC_ENCODING_ZYWRLE: - n = vnc_zywrle_send_framebuffer_update(vs, x, y, w, h); + n = vnc_zywrle_send_framebuffer_update(vs, worker, x, y, w, h); break; default: vnc_framebuffer_update(vs, x, y, w, h, VNC_ENCODING_RAW); @@ -1308,7 +1307,7 @@ static void vnc_disconnect_start(VncState *vs) void vnc_disconnect_finish(VncState *vs) { - int i; + VncConnection *vc = container_of(vs, VncConnection, vs); trace_vnc_client_disconnect_finish(vs, vs->ioc); @@ -1322,9 +1321,9 @@ void vnc_disconnect_finish(VncState *vs) qapi_free_VncClientInfo(vs->info); - vnc_zlib_clear(vs); - vnc_tight_clear(vs); - vnc_zrle_clear(vs); + vnc_zlib_clear(&vc->worker); + vnc_tight_clear(&vc->worker); + vnc_zrle_clear(&vc->worker); #ifdef CONFIG_VNC_SASL vnc_sasl_client_cleanup(vs); @@ -1352,19 +1351,12 @@ void vnc_disconnect_finish(VncState *vs) } buffer_free(&vs->jobs_buffer); - for (i = 0; i < VNC_STAT_ROWS; ++i) { - g_free(vs->lossy_rect[i]); - } - g_free(vs->lossy_rect); - object_unref(OBJECT(vs->ioc)); vs->ioc = NULL; object_unref(OBJECT(vs->sioc)); vs->sioc = NULL; vs->magic = 0; - g_free(vs->zrle); - g_free(vs->tight); - g_free(vs); + g_free(vc); } size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error *err) @@ -2128,13 +2120,14 @@ static void send_xvp_message(VncState *vs, int code) static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) { + VncConnection *vc = container_of(vs, VncConnection, vs); int i; unsigned int enc = 0; vs->features = 0; vs->vnc_encoding = 0; - vs->tight->compression = 9; - vs->tight->quality = -1; /* Lossless by default */ + vc->worker.tight.compression = 9; + vc->worker.tight.quality = -1; /* Lossless by default */ vs->absolute = -1; /* @@ -2222,11 +2215,11 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) vnc_server_cut_text_caps(vs); break; case VNC_ENCODING_COMPRESSLEVEL0 ... VNC_ENCODING_COMPRESSLEVEL0 + 9: - vs->tight->compression = (enc & 0x0F); + vc->worker.tight.compression = (enc & 0x0F); break; case VNC_ENCODING_QUALITYLEVEL0 ... VNC_ENCODING_QUALITYLEVEL0 + 9: if (vs->vd->lossy) { - vs->tight->quality = (enc & 0x0F); + vc->worker.tight.quality = (enc & 0x0F); } break; default: @@ -2242,7 +2235,8 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) static void set_pixel_conversion(VncState *vs) { - pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf); + pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf, + vs->client_endian); if (fmt == VNC_SERVER_FB_FORMAT) { vs->write_pixels = vnc_write_pixels_copy; @@ -2314,7 +2308,7 @@ static void set_pixel_format(VncState *vs, int bits_per_pixel, vs->client_pf.bits_per_pixel = bits_per_pixel; vs->client_pf.bytes_per_pixel = bits_per_pixel / 8; vs->client_pf.depth = bits_per_pixel == 32 ? 24 : bits_per_pixel; - vs->client_be = big_endian_flag; + vs->client_endian = big_endian_flag ? G_BIG_ENDIAN : G_LITTLE_ENDIAN; if (!true_color_flag) { send_color_map(vs); @@ -2954,7 +2948,7 @@ static VncRectStat *vnc_stat_rect(VncDisplay *vd, int x, int y) return &vs->stats[y / VNC_STAT_RECT][x / VNC_STAT_RECT]; } -void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h) +void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h) { int i, j; @@ -2965,7 +2959,7 @@ void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h) for (j = y; j <= h; j++) { for (i = x; i <= w; i++) { - vs->lossy_rect[j][i] = 1; + worker->lossy_rect[j][i] = 1; } } } @@ -2981,6 +2975,7 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) x = QEMU_ALIGN_DOWN(x, VNC_STAT_RECT); QTAILQ_FOREACH(vs, &vd->clients, next) { + VncConnection *vc = container_of(vs, VncConnection, vs); int j; /* kernel send buffers are full -> refresh later */ @@ -2988,11 +2983,11 @@ static int vnc_refresh_lossy_rect(VncDisplay *vd, int x, int y) continue; } - if (!vs->lossy_rect[sty][stx]) { + if (!vc->worker.lossy_rect[sty][stx]) { continue; } - vs->lossy_rect[sty][stx] = 0; + vc->worker.lossy_rect[sty][stx] = 0; for (j = 0; j < VNC_STAT_RECT; ++j) { bitmap_set(vs->dirty[y + j], x / VNC_DIRTY_PIXELS_PER_BIT, @@ -3242,13 +3237,11 @@ static void vnc_refresh(DisplayChangeListener *dcl) static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, bool skipauth, bool websocket) { - VncState *vs = g_new0(VncState, 1); + VncConnection *vc = g_new0(VncConnection, 1); + VncState *vs = &vc->vs; bool first_client = QTAILQ_EMPTY(&vd->clients); - int i; trace_vnc_client_connect(vs, sioc); - vs->zrle = g_new0(VncZrle, 1); - vs->tight = g_new0(VncTight, 1); vs->magic = VNC_MAGIC; vs->sioc = sioc; object_ref(OBJECT(vs->sioc)); @@ -3256,23 +3249,23 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, object_ref(OBJECT(vs->ioc)); vs->vd = vd; - buffer_init(&vs->input, "vnc-input/%p", sioc); - buffer_init(&vs->output, "vnc-output/%p", sioc); - buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); + buffer_init(&vs->input, "vnc-input/%p", sioc); + buffer_init(&vs->output, "vnc-output/%p", sioc); + buffer_init(&vs->jobs_buffer, "vnc-jobs_buffer/%p", sioc); - buffer_init(&vs->tight->tight, "vnc-tight/%p", sioc); - buffer_init(&vs->tight->zlib, "vnc-tight-zlib/%p", sioc); - buffer_init(&vs->tight->gradient, "vnc-tight-gradient/%p", sioc); + buffer_init(&vc->worker.tight.tight, "vnc-tight/%p", sioc); + buffer_init(&vc->worker.tight.zlib, "vnc-tight-zlib/%p", sioc); + buffer_init(&vc->worker.tight.gradient, "vnc-tight-gradient/%p", sioc); #ifdef CONFIG_VNC_JPEG - buffer_init(&vs->tight->jpeg, "vnc-tight-jpeg/%p", sioc); + buffer_init(&vc->worker.tight.jpeg, "vnc-tight-jpeg/%p", sioc); #endif #ifdef CONFIG_PNG - buffer_init(&vs->tight->png, "vnc-tight-png/%p", sioc); + buffer_init(&vc->worker.tight.png, "vnc-tight-png/%p", sioc); #endif - buffer_init(&vs->zlib.zlib, "vnc-zlib/%p", sioc); - buffer_init(&vs->zrle->zrle, "vnc-zrle/%p", sioc); - buffer_init(&vs->zrle->fb, "vnc-zrle-fb/%p", sioc); - buffer_init(&vs->zrle->zlib, "vnc-zrle-zlib/%p", sioc); + buffer_init(&vc->worker.zlib.zlib, "vnc-zlib/%p", sioc); + buffer_init(&vc->worker.zrle.zrle, "vnc-zrle/%p", sioc); + buffer_init(&vc->worker.zrle.fb, "vnc-zrle-fb/%p", sioc); + buffer_init(&vc->worker.zrle.zlib, "vnc-zrle-zlib/%p", sioc); if (skipauth) { vs->auth = VNC_AUTH_NONE; @@ -3289,11 +3282,6 @@ static void vnc_connect(VncDisplay *vd, QIOChannelSocket *sioc, VNC_DEBUG("Client sioc=%p ws=%d auth=%d subauth=%d\n", sioc, websocket, vs->auth, vs->subauth); - vs->lossy_rect = g_malloc0(VNC_STAT_ROWS * sizeof (*vs->lossy_rect)); - for (i = 0; i < VNC_STAT_ROWS; ++i) { - vs->lossy_rect[i] = g_new0(uint8_t, VNC_STAT_COLS); - } - VNC_DEBUG("New client on socket %p\n", vs->sioc); update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); qio_channel_set_blocking(vs->ioc, false, NULL); @@ -3386,6 +3374,16 @@ static const DisplayChangeListenerOps dcl_ops = { .dpy_cursor_define = vnc_dpy_cursor_define, }; +static void vmstate_change_handler(void *opaque, bool running, RunState state) +{ + VncDisplay *vd = opaque; + + if (state != RUN_STATE_RUNNING) { + return; + } + update_displaychangelistener(&vd->dcl, VNC_REFRESH_INTERVAL_BASE); +} + void vnc_display_init(const char *id, Error **errp) { VncDisplay *vd; @@ -3422,6 +3420,8 @@ void vnc_display_init(const char *id, Error **errp) vd->dcl.ops = &dcl_ops; register_displaychangelistener(&vd->dcl); vd->kbd = qkbd_state_init(vd->dcl.con); + vd->vmstate_handler_entry = qemu_add_vm_change_state_handler( + &vmstate_change_handler, vd); } @@ -185,6 +185,8 @@ struct VncDisplay #endif AudioState *audio_state; + + VMChangeStateEntry *vmstate_handler_entry; }; typedef struct VncTight { @@ -270,8 +272,6 @@ struct VncState gboolean disconnecting; DECLARE_BITMAP(dirty[VNC_MAX_HEIGHT], VNC_DIRTY_BITS); - uint8_t **lossy_rect; /* Not an Array to avoid costly memcpy in - * vnc-jobs-async.c */ VncDisplay *vd; VncStateUpdate update; /* Most recent pending request from client */ @@ -323,7 +323,7 @@ struct VncState VncWritePixels *write_pixels; PixelFormat client_pf; pixman_format_code_t client_format; - bool client_be; + int client_endian; /* G_LITTLE_ENDIAN or G_BIG_ENDIAN */ CaptureVoiceOut *audio_cap; struct audsettings as; @@ -339,10 +339,7 @@ struct VncState /* Encoding specific, if you add something here, don't forget to * update vnc_async_encoding_start() */ - VncTight *tight; - VncZlib zlib; VncHextile hextile; - VncZrle *zrle; VncZywrle zywrle; Notifier mouse_mode_notifier; @@ -354,6 +351,19 @@ struct VncState QTAILQ_ENTRY(VncState) next; }; +typedef struct VncWorker { + uint8_t lossy_rect[VNC_STAT_ROWS][VNC_STAT_COLS]; + + VncTight tight; + VncZlib zlib; + VncZrle zrle; +} VncWorker; + +typedef struct VncConnection { + VncState vs; + VncWorker worker; +} VncConnection; + /***************************************************************************** * @@ -600,10 +610,11 @@ int vnc_server_fb_stride(VncDisplay *vd); void vnc_convert_pixel(VncState *vs, uint8_t *buf, uint32_t v); double vnc_update_freq(VncState *vs, int x, int y, int w, int h); -void vnc_sent_lossy_rect(VncState *vs, int x, int y, int w, int h); +void vnc_sent_lossy_rect(VncWorker *worker, int x, int y, int w, int h); /* Encodings */ -int vnc_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); +int vnc_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); int vnc_raw_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); @@ -613,17 +624,21 @@ void vnc_hextile_set_pixel_conversion(VncState *vs, int generic); void *vnc_zlib_zalloc(void *x, unsigned items, unsigned size); void vnc_zlib_zfree(void *x, void *addr); -int vnc_zlib_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -void vnc_zlib_clear(VncState *vs); - -int vnc_tight_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -int vnc_tight_png_send_framebuffer_update(VncState *vs, int x, int y, - int w, int h); -void vnc_tight_clear(VncState *vs); - -int vnc_zrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -int vnc_zywrle_send_framebuffer_update(VncState *vs, int x, int y, int w, int h); -void vnc_zrle_clear(VncState *vs); +int vnc_zlib_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_zlib_clear(VncWorker *worker); + +int vnc_tight_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +int vnc_tight_png_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_tight_clear(VncWorker *worker); + +int vnc_zrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +int vnc_zywrle_send_framebuffer_update(VncState *vs, VncWorker *worker, + int x, int y, int w, int h); +void vnc_zrle_clear(VncWorker *worker); /* vnc-clipboard.c */ void vnc_server_cut_text_caps(VncState *vs); |