summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAlon Levy <alon@pobox.com>2011-07-01 19:49:42 +0300
committerFrediano Ziglio <fziglio@redhat.com>2015-08-21 09:38:44 +0100
commit3c7026b1802c7e00d30f33c458b67126396ad10a (patch)
tree1b85ceee41b14f719697e80f500910bde68c46fd
parent865455cf321ba7a1d2a7d32065bf40c601cfd494 (diff)
downloadspice-3c7026b1802c7e00d30f33c458b67126396ad10a.tar.gz
spice-3c7026b1802c7e00d30f33c458b67126396ad10a.tar.xz
spice-3c7026b1802c7e00d30f33c458b67126396ad10a.zip
server/red_{record, replay}.[ch]: introduce
Currently hand crafted with some sed scripts and alot of vim macros from red_parse_qxl after considering the logger in qemu/hw/qxl-logger.c and seeing it was incomplete. The only problem with logging from the server and not from qemu is that it requires coordinated shutdown to avoid half a message. Should be automatically generated from a declarative syntax, i.e. qxl.proto. Note: zlib compression is introduced in a disabled state, see ZLIB define Now with a simple versioned header and generated ids by the server instead of based on the recorded file, and doesn't use more then 1024 surfaces (configurable). Signed-off-by: Alon Levy <alon@pobox.com> Signed-off-by: Marc-André Lureau <marcandre.lureau@gmail.com>
-rw-r--r--server/Makefile.am2
-rw-r--r--server/red_record_qxl.c827
-rw-r--r--server/red_record_qxl.h34
-rw-r--r--server/red_replay_qxl.c1243
-rw-r--r--server/red_replay_qxl.h34
-rw-r--r--server/spice-server.syms4
6 files changed, 2144 insertions, 0 deletions
diff --git a/server/Makefile.am b/server/Makefile.am
index 61372499..09b44984 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -99,6 +99,8 @@ libspice_server_la_SOURCES = \
red_memslots.c \
red_memslots.h \
red_parse_qxl.c \
+ red_record_qxl.c \
+ red_replay_qxl.c \
red_parse_qxl.h \
red_time.h \
red_worker.c \
diff --git a/server/red_record_qxl.c b/server/red_record_qxl.c
new file mode 100644
index 00000000..d96fb799
--- /dev/null
+++ b/server/red_record_qxl.c
@@ -0,0 +1,827 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2009,2010 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include "red_worker.h"
+#include "red_common.h"
+#include "red_memslots.h"
+#include "red_parse_qxl.h"
+#include "zlib_encoder.h"
+
+#if 0
+static void hexdump_qxl(RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, uint8_t bytes)
+{
+ uint8_t *hex;
+ int i;
+ int error;
+
+ hex = (uint8_t*)get_virt(slots, addr, bytes, group_id,
+ &error);
+ for (i = 0; i < bytes; i++) {
+ if (0 == i % 16) {
+ fprintf(stderr, "%lx: ", addr+i);
+ }
+ if (0 == i % 4) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " %02x", hex[i]);
+ if (15 == i % 16) {
+ fprintf(stderr, "\n");
+ }
+ }
+}
+#endif
+
+#define WITH_ZLIB 0
+
+/* TODO: make this thread safe (required for two qxl devices) */
+
+#if WITH_ZLIB
+typedef struct RecordEncoderData {
+ ZlibEncoderUsrContext base;
+ uint8_t *buf;
+ int size;
+} RecordEncoderData;
+
+static int record_zlib_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr)
+{
+ return 0;
+}
+
+static int record_zlib_more_input(ZlibEncoderUsrContext *usr, uint8_t **input)
+{
+ RecordEncoderData *data = SPICE_CONTAINEROF(usr, RecordEncoderData, base);
+
+ if (data->buf == NULL) {
+ fprintf(stderr, "%s: error: no more data\n", __FUNCTION__);
+ exit(1);
+ }
+ *input = data->buf;
+ data->buf = 0;
+ return data->size;
+}
+
+RecordEncoderData record_encoder_data = {
+ .base = {
+ record_zlib_more_space,
+ record_zlib_more_input,
+ },
+ .buf = NULL,
+ .size = 0,
+};
+#define RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL 3
+#endif
+
+#if WITH_ZLIB
+static uint8_t output[1024*1024*4]; // static buffer for encoding, 4MB
+#endif
+
+static void write_binary(FILE *fd, const char *prefix, size_t size, const uint8_t *buf)
+{
+ int n;
+
+#if WITH_ZLIB
+ ZlibEncoder *enc;
+ int zlib_size;
+
+ record_encoder_data.buf = buf;
+ record_encoder_data.size = size;
+ enc = zlib_encoder_create(&record_encoder_data.base,
+ RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL);
+ if (!enc) {
+ fprintf(stderr, "%s: zlib encoder creation failed\n", __FUNCTION__);
+ exit(1);
+ }
+#endif
+
+ fprintf(fd, "binary %d %s %ld:", WITH_ZLIB, prefix, size);
+#if WITH_ZLIB
+ zlib_size = zlib_encode(enc, RECORD_ZLIB_DEFAULT_COMPRESSION_LEVEL, size,
+ output, sizeof(output));
+ fprintf(fd, "%d:", zlib_size);
+ n = fwrite(output, zlib_size, 1, fd);
+ zlib_encoder_destroy(enc);
+#else
+ n = fwrite(buf, size, 1, fd);
+#endif
+ (void)n;
+ fprintf(fd, "\n");
+}
+
+static size_t red_record_data_chunks_ptr(FILE *fd, const char *prefix,
+ RedMemSlotInfo *slots, int group_id,
+ int memslot_id, QXLDataChunk *qxl)
+{
+ size_t data_size = qxl->data_size;
+ int count_chunks = 0;
+ QXLDataChunk *cur = qxl;
+ int error;
+
+ while (cur->next_chunk) {
+ cur =
+ (QXLDataChunk*)get_virt(slots, cur->next_chunk, sizeof(*cur), group_id,
+ &error);
+ data_size += cur->data_size;
+ count_chunks++;
+ }
+ fprintf(fd, "data_chunks %d %ld\n", count_chunks, data_size);
+ validate_virt(slots, (intptr_t)qxl->data, memslot_id, qxl->data_size, group_id);
+ write_binary(fd, prefix, qxl->data_size, qxl->data);
+
+ while (qxl->next_chunk) {
+ memslot_id = get_memslot_id(slots, qxl->next_chunk);
+ qxl = (QXLDataChunk*)get_virt(slots, qxl->next_chunk, sizeof(*qxl), group_id,
+ &error);
+
+ validate_virt(slots, (intptr_t)qxl->data, memslot_id, qxl->data_size, group_id);
+ write_binary(fd, prefix, qxl->data_size, qxl->data);
+ }
+
+ return data_size;
+}
+
+static size_t red_record_data_chunks(FILE *fd, const char *prefix,
+ RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLDataChunk *qxl;
+ int memslot_id = get_memslot_id(slots, addr);
+ int error;
+
+ qxl = (QXLDataChunk*)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ return red_record_data_chunks_ptr(fd, prefix, slots, group_id, memslot_id, qxl);
+}
+
+static void red_record_point_ptr(FILE *fd, QXLPoint *qxl)
+{
+ fprintf(fd, "point %d %d\n", qxl->x, qxl->y);
+}
+
+static void red_record_point16_ptr(FILE *fd, QXLPoint16 *qxl)
+{
+ fprintf(fd, "point16 %d %d\n", qxl->x, qxl->y);
+}
+
+static void red_record_rect_ptr(FILE *fd, const char *prefix, QXLRect *qxl)
+{
+ fprintf(fd, "rect %s %d %d %d %d\n", prefix,
+ qxl->top, qxl->left, qxl->bottom, qxl->right);
+}
+
+static void red_record_path(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLPath *qxl;
+ int error;
+
+ qxl = (QXLPath *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ red_record_data_chunks_ptr(fd, "path", slots, group_id,
+ get_memslot_id(slots, addr),
+ &qxl->chunk);
+}
+
+static void red_record_clip_rects(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLClipRects *qxl;
+ int error;
+
+ qxl = (QXLClipRects *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ fprintf(fd, "num_rects %d\n", qxl->num_rects);
+ red_record_data_chunks_ptr(fd, "clip_rects", slots, group_id,
+ get_memslot_id(slots, addr),
+ &qxl->chunk);
+}
+
+static void red_record_virt_data_flat(FILE *fd, const char *prefix,
+ RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, size_t size)
+{
+ int error;
+
+ write_binary(fd, prefix,
+ size, (uint8_t*)get_virt(slots, addr, size, group_id,
+ &error));
+}
+
+static void red_record_image_data_flat(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, size_t size)
+{
+ red_record_virt_data_flat(fd, "image_data_flat", slots, group_id, addr, size);
+}
+
+static void red_record_transform(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ red_record_virt_data_flat(fd, "transform", slots, group_id,
+ addr, sizeof(SpiceTransform));
+}
+
+static void red_record_image(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, uint32_t flags)
+{
+ QXLImage *qxl;
+ size_t bitmap_size, size;
+ uint8_t qxl_flags;
+ int error;
+
+ fprintf(fd, "image %d\n", addr ? 1 : 0);
+ if (addr == 0) {
+ return;
+ }
+
+ qxl = (QXLImage *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ fprintf(fd, "descriptor.id %ld\n", qxl->descriptor.id);
+ fprintf(fd, "descriptor.type %d\n", qxl->descriptor.type);
+ fprintf(fd, "descriptor.flags %d\n", qxl->descriptor.flags);
+ fprintf(fd, "descriptor.width %d\n", qxl->descriptor.width);
+ fprintf(fd, "descriptor.height %d\n", qxl->descriptor.height);
+
+ switch (qxl->descriptor.type) {
+ case SPICE_IMAGE_TYPE_BITMAP:
+ fprintf(fd, "bitmap.format %d\n", qxl->bitmap.format);
+ fprintf(fd, "bitmap.flags %d\n", qxl->bitmap.flags);
+ fprintf(fd, "bitmap.x %d\n", qxl->bitmap.x);
+ fprintf(fd, "bitmap.y %d\n", qxl->bitmap.y);
+ fprintf(fd, "bitmap.stride %d\n", qxl->bitmap.stride);
+ qxl_flags = qxl->bitmap.flags;
+ fprintf(fd, "has_palette %d\n", qxl->bitmap.palette ? 1 : 0);
+ if (qxl->bitmap.palette) {
+ QXLPalette *qp;
+ int i, num_ents;
+ qp = (QXLPalette *)get_virt(slots, qxl->bitmap.palette,
+ sizeof(*qp), group_id, &error);
+ num_ents = qp->num_ents;
+ fprintf(fd, "qp.num_ents %d\n", qp->num_ents);
+ validate_virt(slots, (intptr_t)qp->ents,
+ get_memslot_id(slots, qxl->bitmap.palette),
+ num_ents * sizeof(qp->ents[0]), group_id);
+ fprintf(fd, "unique %ld\n", qp->unique);
+ for (i = 0; i < num_ents; i++) {
+ fprintf(fd, "ents %d\n", qp->ents[i]);
+ }
+ }
+ bitmap_size = qxl->bitmap.y * abs(qxl->bitmap.stride);
+ if (qxl_flags & QXL_BITMAP_DIRECT) {
+ red_record_image_data_flat(fd, slots, group_id,
+ qxl->bitmap.data,
+ bitmap_size);
+ } else {
+ size = red_record_data_chunks(fd, "bitmap.data", slots, group_id,
+ qxl->bitmap.data);
+ spice_assert(size == bitmap_size);
+ }
+ break;
+ case SPICE_IMAGE_TYPE_SURFACE:
+ fprintf(fd, "surface_image.surface_id %d\n", qxl->surface_image.surface_id);
+ break;
+ case SPICE_IMAGE_TYPE_QUIC:
+ fprintf(fd, "quic.data_size %d\n", qxl->quic.data_size);
+ size = red_record_data_chunks_ptr(fd, "quic.data", slots, group_id,
+ get_memslot_id(slots, addr),
+ (QXLDataChunk *)qxl->quic.data);
+ spice_assert(size == qxl->quic.data_size);
+ break;
+ default:
+ spice_error("%s: unknown type %d", __FUNCTION__, qxl->descriptor.type);
+ }
+}
+
+static void red_record_brush_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLBrush *qxl, uint32_t flags)
+{
+ fprintf(fd, "type %d\n", qxl->type);
+ switch (qxl->type) {
+ case SPICE_BRUSH_TYPE_SOLID:
+ fprintf(fd, "u.color %d\n", qxl->u.color);
+ break;
+ case SPICE_BRUSH_TYPE_PATTERN:
+ red_record_image(fd, slots, group_id, qxl->u.pattern.pat, flags);
+ red_record_point_ptr(fd, &qxl->u.pattern.pos);
+ break;
+ }
+}
+
+static void red_record_qmask_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLQMask *qxl, uint32_t flags)
+{
+ fprintf(fd, "flags %d\n", qxl->flags);
+ red_record_point_ptr(fd, &qxl->pos);
+ red_record_image(fd, slots, group_id, qxl->bitmap, flags);
+}
+
+static void red_record_fill_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLFill *qxl, uint32_t flags)
+{
+ red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags);
+ fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor);
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_opaque_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLOpaque *qxl, uint32_t flags)
+{
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+ red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags);
+ fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor);
+ fprintf(fd, "scale_mode %d\n", qxl->scale_mode);
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_copy_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLCopy *qxl, uint32_t flags)
+{
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+ fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor);
+ fprintf(fd, "scale_mode %d\n", qxl->scale_mode);
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_blend_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLBlend *qxl, uint32_t flags)
+{
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+ fprintf(fd, "rop_descriptor %d\n", qxl->rop_descriptor);
+ fprintf(fd, "scale_mode %d\n", qxl->scale_mode);
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_transparent_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLTransparent *qxl,
+ uint32_t flags)
+{
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+ fprintf(fd, "src_color %d\n", qxl->src_color);
+ fprintf(fd, "true_color %d\n", qxl->true_color);
+}
+
+static void red_record_alpha_blend_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLAlphaBlend *qxl,
+ uint32_t flags)
+{
+ fprintf(fd, "alpha_flags %d\n", qxl->alpha_flags);
+ fprintf(fd, "alpha %d\n", qxl->alpha);
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+}
+
+static void red_record_alpha_blend_ptr_compat(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLCompatAlphaBlend *qxl,
+ uint32_t flags)
+{
+ fprintf(fd, "alpha %d\n", qxl->alpha);
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+}
+
+static void red_record_rop3_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLRop3 *qxl, uint32_t flags)
+{
+ red_record_image(fd, slots, group_id, qxl->src_bitmap, flags);
+ red_record_rect_ptr(fd, "src_area", &qxl->src_area);
+ red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags);
+ fprintf(fd, "rop3 %d\n", qxl->rop3);
+ fprintf(fd, "scale_mode %d\n", qxl->scale_mode);
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_stroke_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLStroke *qxl, uint32_t flags)
+{
+ int error;
+
+ red_record_path(fd, slots, group_id, qxl->path);
+ fprintf(fd, "attr.flags %d\n", qxl->attr.flags);
+ if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) {
+ int style_nseg = qxl->attr.style_nseg;
+ uint8_t *buf;
+
+ fprintf(fd, "attr.style_nseg %d\n", qxl->attr.style_nseg);
+ spice_assert(qxl->attr.style);
+ buf = (uint8_t *)get_virt(slots, qxl->attr.style,
+ style_nseg * sizeof(QXLFIXED), group_id,
+ &error);
+ write_binary(fd, "style", style_nseg * sizeof(QXLFIXED), buf);
+ }
+ red_record_brush_ptr(fd, slots, group_id, &qxl->brush, flags);
+ fprintf(fd, "fore_mode %d\n", qxl->fore_mode);
+ fprintf(fd, "back_mode %d\n", qxl->back_mode);
+}
+
+static void red_record_string(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLString *qxl;
+ size_t chunk_size;
+ int error;
+
+ qxl = (QXLString *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ fprintf(fd, "data_size %d\n", qxl->data_size);
+ fprintf(fd, "length %d\n", qxl->length);
+ fprintf(fd, "flags %d\n", qxl->flags);
+ chunk_size = red_record_data_chunks_ptr(fd, "string", slots, group_id,
+ get_memslot_id(slots, addr),
+ &qxl->chunk);
+ spice_assert(chunk_size == qxl->data_size);
+}
+
+static void red_record_text_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLText *qxl, uint32_t flags)
+{
+ red_record_string(fd, slots, group_id, qxl->str);
+ red_record_rect_ptr(fd, "back_area", &qxl->back_area);
+ red_record_brush_ptr(fd, slots, group_id, &qxl->fore_brush, flags);
+ red_record_brush_ptr(fd, slots, group_id, &qxl->back_brush, flags);
+ fprintf(fd, "fore_mode %d\n", qxl->fore_mode);
+ fprintf(fd, "back_mode %d\n", qxl->back_mode);
+}
+
+static void red_record_whiteness_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLWhiteness *qxl, uint32_t flags)
+{
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_blackness_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLBlackness *qxl, uint32_t flags)
+{
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_invers_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLInvers *qxl, uint32_t flags)
+{
+ red_record_qmask_ptr(fd, slots, group_id, &qxl->mask, flags);
+}
+
+static void red_record_clip_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLClip *qxl)
+{
+ fprintf(fd, "type %d\n", qxl->type);
+ switch (qxl->type) {
+ case SPICE_CLIP_TYPE_RECTS:
+ red_record_clip_rects(fd, slots, group_id, qxl->data);
+ break;
+ }
+}
+
+static void red_record_composite_ptr(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLComposite *qxl, uint32_t flags)
+{
+ fprintf(fd, "flags %d\n", qxl->flags);
+
+ red_record_image(fd, slots, group_id, qxl->src, flags);
+ fprintf(fd, "src_transform %d\n", !!qxl->src_transform);
+ if (qxl->src_transform)
+ red_record_transform(fd, slots, group_id, qxl->src_transform);
+ fprintf(fd, "mask %d\n", !!qxl->mask);
+ if (qxl->mask)
+ red_record_image(fd, slots, group_id, qxl->mask, flags);
+ fprintf(fd, "mask_transform %d\n", !!qxl->mask_transform);
+ if (qxl->mask_transform)
+ red_record_transform(fd, slots, group_id, qxl->mask_transform);
+
+ fprintf(fd, "src_origin %d %d\n", qxl->src_origin.x, qxl->src_origin.y);
+ fprintf(fd, "mask_origin %d %d\n", qxl->mask_origin.x, qxl->mask_origin.y);
+}
+
+static void red_record_native_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, uint32_t flags)
+{
+ QXLDrawable *qxl;
+ int i;
+ int error;
+
+ qxl = (QXLDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ red_record_rect_ptr(fd, "bbox", &qxl->bbox);
+ red_record_clip_ptr(fd, slots, group_id, &qxl->clip);
+ fprintf(fd, "effect %d\n", qxl->effect);
+ fprintf(fd, "mm_time %d\n", qxl->mm_time);
+ fprintf(fd, "self_bitmap %d\n", qxl->self_bitmap);
+ red_record_rect_ptr(fd, "self_bitmap_area", &qxl->self_bitmap_area);
+ fprintf(fd, "surface_id %d\n", qxl->surface_id);
+
+ for (i = 0; i < 3; i++) {
+ fprintf(fd, "surfaces_dest %d\n", qxl->surfaces_dest[i]);
+ red_record_rect_ptr(fd, "surfaces_rects", &qxl->surfaces_rects[i]);
+ }
+
+ fprintf(fd, "type %d\n", qxl->type);
+ switch (qxl->type) {
+ case QXL_DRAW_ALPHA_BLEND:
+ red_record_alpha_blend_ptr(fd, slots, group_id,
+ &qxl->u.alpha_blend, flags);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ red_record_blackness_ptr(fd, slots, group_id,
+ &qxl->u.blackness, flags);
+ break;
+ case QXL_DRAW_BLEND:
+ red_record_blend_ptr(fd, slots, group_id, &qxl->u.blend, flags);
+ break;
+ case QXL_DRAW_COPY:
+ red_record_copy_ptr(fd, slots, group_id, &qxl->u.copy, flags);
+ break;
+ case QXL_COPY_BITS:
+ red_record_point_ptr(fd, &qxl->u.copy_bits.src_pos);
+ break;
+ case QXL_DRAW_FILL:
+ red_record_fill_ptr(fd, slots, group_id, &qxl->u.fill, flags);
+ break;
+ case QXL_DRAW_OPAQUE:
+ red_record_opaque_ptr(fd, slots, group_id, &qxl->u.opaque, flags);
+ break;
+ case QXL_DRAW_INVERS:
+ red_record_invers_ptr(fd, slots, group_id, &qxl->u.invers, flags);
+ break;
+ case QXL_DRAW_NOP:
+ break;
+ case QXL_DRAW_ROP3:
+ red_record_rop3_ptr(fd, slots, group_id, &qxl->u.rop3, flags);
+ break;
+ case QXL_DRAW_STROKE:
+ red_record_stroke_ptr(fd, slots, group_id, &qxl->u.stroke, flags);
+ break;
+ case QXL_DRAW_TEXT:
+ red_record_text_ptr(fd, slots, group_id, &qxl->u.text, flags);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ red_record_transparent_ptr(fd, slots, group_id, &qxl->u.transparent, flags);
+ break;
+ case QXL_DRAW_WHITENESS:
+ red_record_whiteness_ptr(fd, slots, group_id, &qxl->u.whiteness, flags);
+ break;
+ case QXL_DRAW_COMPOSITE:
+ red_record_composite_ptr(fd, slots, group_id, &qxl->u.composite, flags);
+ break;
+ default:
+ spice_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+ break;
+ };
+}
+
+static void red_record_compat_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, uint32_t flags)
+{
+ QXLCompatDrawable *qxl;
+ int error;
+
+ qxl = (QXLCompatDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ red_record_rect_ptr(fd, "bbox", &qxl->bbox);
+ red_record_clip_ptr(fd, slots, group_id, &qxl->clip);
+ fprintf(fd, "effect %d\n", qxl->effect);
+ fprintf(fd, "mm_time %d\n", qxl->mm_time);
+
+ fprintf(fd, "bitmap_offset %d\n", qxl->bitmap_offset);
+ red_record_rect_ptr(fd, "bitmap_area", &qxl->bitmap_area);
+
+ fprintf(fd, "type %d\n", qxl->type);
+ switch (qxl->type) {
+ case QXL_DRAW_ALPHA_BLEND:
+ red_record_alpha_blend_ptr_compat(fd, slots, group_id,
+ &qxl->u.alpha_blend, flags);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ red_record_blackness_ptr(fd, slots, group_id,
+ &qxl->u.blackness, flags);
+ break;
+ case QXL_DRAW_BLEND:
+ red_record_blend_ptr(fd, slots, group_id, &qxl->u.blend, flags);
+ break;
+ case QXL_DRAW_COPY:
+ red_record_copy_ptr(fd, slots, group_id, &qxl->u.copy, flags);
+ break;
+ case QXL_COPY_BITS:
+ red_record_point_ptr(fd, &qxl->u.copy_bits.src_pos);
+ break;
+ case QXL_DRAW_FILL:
+ red_record_fill_ptr(fd, slots, group_id, &qxl->u.fill, flags);
+ break;
+ case QXL_DRAW_OPAQUE:
+ red_record_opaque_ptr(fd, slots, group_id, &qxl->u.opaque, flags);
+ break;
+ case QXL_DRAW_INVERS:
+ red_record_invers_ptr(fd, slots, group_id, &qxl->u.invers, flags);
+ break;
+ case QXL_DRAW_NOP:
+ break;
+ case QXL_DRAW_ROP3:
+ red_record_rop3_ptr(fd, slots, group_id, &qxl->u.rop3, flags);
+ break;
+ case QXL_DRAW_STROKE:
+ red_record_stroke_ptr(fd, slots, group_id, &qxl->u.stroke, flags);
+ break;
+ case QXL_DRAW_TEXT:
+ red_record_text_ptr(fd, slots, group_id, &qxl->u.text, flags);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ red_record_transparent_ptr(fd, slots, group_id, &qxl->u.transparent, flags);
+ break;
+ case QXL_DRAW_WHITENESS:
+ red_record_whiteness_ptr(fd, slots, group_id, &qxl->u.whiteness, flags);
+ break;
+ default:
+ spice_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+ break;
+ };
+}
+
+static void red_record_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr, uint32_t flags)
+{
+ fprintf(fd, "drawable\n");
+ if (flags & QXL_COMMAND_FLAG_COMPAT) {
+ red_record_compat_drawable(fd, slots, group_id, addr, flags);
+ } else {
+ red_record_native_drawable(fd, slots, group_id, addr, flags);
+ }
+}
+
+static void red_record_update_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLUpdateCmd *qxl;
+ int error;
+
+ qxl = (QXLUpdateCmd *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ fprintf(fd, "update\n");
+ red_record_rect_ptr(fd, "area", &qxl->area);
+ fprintf(fd, "update_id %d\n", qxl->update_id);
+ fprintf(fd, "surface_id %d\n", qxl->surface_id);
+}
+
+static void red_record_message(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLMessage *qxl;
+ int error;
+
+ /*
+ * security alert:
+ * qxl->data[0] size isn't specified anywhere -> can't verify
+ * luckily this is for debug logging only,
+ * so we can just ignore it by default.
+ */
+ qxl = (QXLMessage *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+ write_binary(fd, "message", strlen((char*)qxl->data), (uint8_t*)qxl->data);
+}
+
+static void red_record_surface_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLSurfaceCmd *qxl;
+ size_t size;
+ int error;
+
+ qxl = (QXLSurfaceCmd *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ fprintf(fd, "surface_cmd\n");
+ fprintf(fd, "surface_id %d\n", qxl->surface_id);
+ fprintf(fd, "type %d\n", qxl->type);
+ fprintf(fd, "flags %d\n", qxl->flags);
+
+ switch (qxl->type) {
+ case QXL_SURFACE_CMD_CREATE:
+ fprintf(fd, "u.surface_create.format %d\n", qxl->u.surface_create.format);
+ fprintf(fd, "u.surface_create.width %d\n", qxl->u.surface_create.width);
+ fprintf(fd, "u.surface_create.height %d\n", qxl->u.surface_create.height);
+ fprintf(fd, "u.surface_create.stride %d\n", qxl->u.surface_create.stride);
+ size = qxl->u.surface_create.height * abs(qxl->u.surface_create.stride);
+ if (qxl->flags && QXL_SURF_FLAG_KEEP_DATA) {
+ write_binary(fd, "data", size,
+ (uint8_t*)get_virt(slots, qxl->u.surface_create.data, size, group_id,
+ &error));
+ }
+ break;
+ }
+}
+
+static void red_record_cursor(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLCursor *qxl;
+ int error;
+
+ qxl = (QXLCursor *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ fprintf(fd, "header.unique %ld\n", qxl->header.unique);
+ fprintf(fd, "header.type %d\n", qxl->header.type);
+ fprintf(fd, "header.width %d\n", qxl->header.width);
+ fprintf(fd, "header.height %d\n", qxl->header.height);
+ fprintf(fd, "header.hot_spot_x %d\n", qxl->header.hot_spot_x);
+ fprintf(fd, "header.hot_spot_y %d\n", qxl->header.hot_spot_y);
+
+ fprintf(fd, "data_size %d\n", qxl->data_size);
+ red_record_data_chunks_ptr(fd, "cursor", slots, group_id,
+ get_memslot_id(slots, addr),
+ &qxl->chunk);
+}
+
+void red_record_cursor_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+ QXLPHYSICAL addr)
+{
+ QXLCursorCmd *qxl;
+ int error;
+
+ qxl = (QXLCursorCmd *)get_virt(slots, addr, sizeof(*qxl), group_id,
+ &error);
+
+ fprintf(fd, "cursor_cmd\n");
+ fprintf(fd, "type %d\n", qxl->type);
+ switch (qxl->type) {
+ case QXL_CURSOR_SET:
+ red_record_point16_ptr(fd, &qxl->u.set.position);
+ fprintf(fd, "u.set.visible %d\n", qxl->u.set.visible);
+ red_record_cursor(fd, slots, group_id, qxl->u.set.shape);
+ break;
+ case QXL_CURSOR_MOVE:
+ red_record_point16_ptr(fd, &qxl->u.position);
+ break;
+ case QXL_CURSOR_TRAIL:
+ fprintf(fd, "u.trail.length %d\n", qxl->u.trail.length);
+ fprintf(fd, "u.trail.frequency %d\n", qxl->u.trail.frequency);
+ break;
+ }
+}
+
+void red_record_dev_input_primary_surface_create(FILE *fd,
+ QXLDevSurfaceCreate* surface, uint8_t *line_0)
+{
+ fprintf(fd, "%d %d %d %d\n", surface->width, surface->height,
+ surface->stride, surface->format);
+ fprintf(fd, "%d %d %d %d\n", surface->position, surface->mouse_mode,
+ surface->flags, surface->type);
+ write_binary(fd, "data", line_0 ? abs(surface->stride)*surface->height : 0,
+ line_0);
+}
+
+void red_record_event(FILE *fd, int what, uint32_t type, unsigned long ts)
+{
+ static int counter = 0;
+
+ // TODO: record the size of the packet in the header. This would make
+ // navigating it much faster (well, I can add an index while I'm at it..)
+ // and make it trivial to get a histogram from a file.
+ // But to implement that I would need some temporary buffer for each event.
+ // (that can be up to VGA_FRAMEBUFFER large)
+ fprintf(fd, "event %d %d %u %lu\n", counter++, what, type, ts);
+}
+
+void red_record_qxl_command(FILE *fd, RedMemSlotInfo *slots,
+ QXLCommandExt ext_cmd, unsigned long ts)
+{
+ red_record_event(fd, 0, ext_cmd.cmd.type, ts);
+
+ switch (ext_cmd.cmd.type) {
+ case QXL_CMD_DRAW:
+ red_record_drawable(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data, ext_cmd.flags);
+ break;
+ case QXL_CMD_UPDATE:
+ red_record_update_cmd(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data);
+ break;
+ case QXL_CMD_MESSAGE:
+ red_record_message(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data);
+ break;
+ case QXL_CMD_SURFACE:
+ red_record_surface_cmd(fd, slots, ext_cmd.group_id, ext_cmd.cmd.data);
+ break;
+ }
+}
diff --git a/server/red_record_qxl.h b/server/red_record_qxl.h
new file mode 100644
index 00000000..b737db89
--- /dev/null
+++ b/server/red_record_qxl.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2009,2010 Red Hat, Inc.
+
+ This program is free software; you can redistribute it and/or
+ modify it under the terms of the GNU General Public License as
+ published by the Free Software Foundation; either version 2 of
+ the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef RED_ABI_RECORD_H
+#define RED_ABI_RECORD_H
+
+#include <spice/qxl_dev.h>
+#include "red_common.h"
+#include "red_memslots.h"
+
+void red_record_dev_input_primary_surface_create(
+ FILE *fd, QXLDevSurfaceCreate *surface, uint8_t *line_0);
+
+void red_record_event(FILE *fd, int what, uint32_t type, unsigned long ts);
+
+void red_record_qxl_command(FILE *fd, RedMemSlotInfo *slots,
+ QXLCommandExt ext_cmd, unsigned long ts);
+
+#endif
diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c
new file mode 100644
index 00000000..a010a58d
--- /dev/null
+++ b/server/red_replay_qxl.c
@@ -0,0 +1,1243 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2009,2010 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <inttypes.h>
+#include <zlib.h>
+#include "reds.h"
+#include "red_worker.h"
+#include "red_common.h"
+#include "red_memslots.h"
+#include "red_parse_qxl.h"
+#include "red_replay_qxl.h"
+#include <glib.h>
+
+typedef enum {
+ REPLAY_OK = 0,
+ REPLAY_EOF,
+} replay_t;
+
+struct SpiceReplay {
+ FILE *fd;
+ int eof;
+ int counter;
+ bool created_primary;
+
+ GArray *id_map; // record id -> replay id
+ GArray *id_map_inv; // replay id -> record id
+ GArray *id_free; // free list
+ int nsurfaces;
+
+ /* FIXME: some API requires 2.32 */
+ GMutex mutex;
+ GCond cond;
+};
+
+static int replay_fread(SpiceReplay *replay, uint8_t *buf, size_t size)
+{
+ if (replay->eof) {
+ return 0;
+ }
+ if (feof(replay->fd)) {
+ replay->eof = 1;
+ return 0;
+ }
+ return fread(buf, size, 1, replay->fd);
+}
+
+__attribute__((format(scanf, 2, 3)))
+static replay_t replay_fscanf(SpiceReplay *replay, const char *fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ if (replay->eof) {
+ return REPLAY_EOF;
+ }
+ if (feof(replay->fd)) {
+ replay->eof = 1;
+ return REPLAY_EOF;
+ }
+ va_start(ap, fmt);
+ ret = vfscanf(replay->fd, fmt, ap);
+ va_end(ap);
+ if (ret == EOF) {
+ replay->eof = 1;
+ }
+ return replay->eof ? REPLAY_EOF : REPLAY_OK;
+}
+
+static uint32_t replay_id_get(SpiceReplay *replay, uint32_t id)
+{
+ uint32_t newid = 0;
+
+ /* TODO: this should be avoided, perhaps in recording? */
+ if (id == -1)
+ return id;
+
+ g_mutex_lock(&replay->mutex);
+ if (replay->id_map->len <= id) {
+ spice_warn_if_reached();
+ } else {
+ newid = g_array_index(replay->id_map, uint32_t, id);
+ }
+ g_mutex_unlock(&replay->mutex);
+
+ return newid;
+}
+
+static uint32_t replay_id_new(SpiceReplay *replay, uint32_t id)
+{
+ uint32_t new_id;
+ uint32_t *map;
+
+ g_mutex_lock(&replay->mutex);
+ while (1) {
+ if (replay->id_free->len > 0) {
+ new_id = g_array_index(replay->id_free, uint32_t, 0);
+ g_array_remove_index_fast(replay->id_free, 0);
+ } else {
+ new_id = replay->id_map_inv->len;
+ }
+
+ if (new_id < replay->nsurfaces)
+ break;
+ g_cond_wait(&replay->cond, &replay->mutex);
+ }
+
+ if (replay->id_map->len <= id)
+ g_array_set_size(replay->id_map, id + 1);
+ if (replay->id_map_inv->len <= new_id)
+ g_array_set_size(replay->id_map_inv, new_id + 1);
+
+ map = &g_array_index(replay->id_map, uint32_t, id);
+ *map = new_id;
+ map = &g_array_index(replay->id_map_inv, uint32_t, new_id);
+ *map = id;
+ g_mutex_unlock(&replay->mutex);
+
+ spice_debug("%u -> %u (map %u, inv %u)", id, new_id,
+ replay->id_map->len, replay->id_map_inv->len);
+
+ return new_id;
+}
+
+static void replay_id_free(SpiceReplay *replay, uint32_t id)
+{
+ uint32_t old_id;
+ uint32_t *map;
+
+ g_mutex_lock(&replay->mutex);
+ map = &g_array_index(replay->id_map_inv, uint32_t, id);
+ old_id = *map;
+ *map = -1;
+
+ if (old_id != -1) {
+ map = &g_array_index(replay->id_map, uint32_t, old_id);
+ if (*map == id)
+ *map = -1;
+
+ g_array_append_val(replay->id_free, id);
+ }
+ g_cond_signal(&replay->cond);
+ g_mutex_unlock(&replay->mutex);
+}
+
+
+#if 0
+static void hexdump(uint8_t *hex, uint8_t bytes)
+{
+ int i;
+
+ for (i = 0; i < bytes; i++) {
+ if (0 == i % 16) {
+ fprintf(stderr, "%lx: ", (size_t)hex+i);
+ }
+ if (0 == i % 4) {
+ fprintf(stderr, " ");
+ }
+ fprintf(stderr, " %02x", hex[i]);
+ if (15 == i % 16) {
+ fprintf(stderr, "\n");
+ }
+ }
+}
+#endif
+
+static replay_t read_binary(SpiceReplay *replay, const char *prefix, size_t *size, uint8_t
+ **buf, size_t base_size)
+{
+ char template[1024];
+ int with_zlib = -1;
+ int zlib_size;
+ uint8_t *zlib_buffer;
+ z_stream strm;
+
+ snprintf(template, sizeof(template), "binary %%d %s %%ld:", prefix);
+ if (replay_fscanf(replay, template, &with_zlib, size) == REPLAY_EOF)
+ return REPLAY_EOF;
+
+ if (*buf == NULL) {
+ *buf = malloc(*size + base_size);
+ if (*buf == NULL) {
+ spice_error("allocation error for %ld", *size);
+ exit(1);
+ }
+ }
+#if 0
+ {
+ int num_read = fread(*buf + base_size, *size, 1, fd);
+ spice_error("num_read = %d", num_read);
+ hexdump(*buf + base_size, *size);
+ }
+#else
+ spice_return_val_if_fail(with_zlib != -1, REPLAY_EOF);
+ if (with_zlib) {
+ int ret;
+
+ replay_fscanf(replay, "%d:", &zlib_size);
+ zlib_buffer = malloc(zlib_size);
+ replay_fread(replay, zlib_buffer, zlib_size);
+ strm.zalloc = Z_NULL;
+ strm.zfree = Z_NULL;
+ strm.opaque = Z_NULL;
+ strm.avail_in = zlib_size;
+ strm.next_in = zlib_buffer;
+ strm.avail_out = *size;
+ strm.next_out = *buf + base_size;
+ if ((ret = inflateInit(&strm)) != Z_OK) {
+ spice_error("inflateInit failed");
+ exit(1);
+ }
+ if ((ret = inflate(&strm, Z_NO_FLUSH)) != Z_STREAM_END) {
+ spice_error("inflate error %d (disc: %ld)", ret, *size - strm.total_out);
+ if (ret == Z_DATA_ERROR) {
+ /* last operation may be wrong. since we do the recording
+ * in red_worker, when there is a shutdown from the vcpu/io thread
+ * it seems it may kill the red_worker thread (so a chunk is
+ * left hanging and the rest of the message is never written).
+ * Let it pass */
+ return REPLAY_EOF;
+ }
+ if (ret != Z_OK) {
+ spice_warn_if_reached();
+ }
+ }
+ (void)inflateEnd(&strm);
+ free(zlib_buffer); // TODO - avoid repeat alloc/dealloc by keeping last
+ } else {
+ replay_fread(replay, *buf + base_size, *size);
+ }
+#endif
+ replay_fscanf(replay, "\n");
+ return REPLAY_OK;
+}
+
+static size_t red_replay_data_chunks(SpiceReplay *replay, const char *prefix,
+ uint8_t **mem, size_t base_size)
+{
+ size_t data_size;
+ int count_chunks;
+ size_t next_data_size;
+ QXLDataChunk *cur;
+
+ replay_fscanf(replay, "data_chunks %d %ld\n", &count_chunks, &data_size);
+ if (base_size == 0) {
+ base_size = sizeof(QXLDataChunk);
+ }
+
+ if (read_binary(replay, prefix, &next_data_size, mem, base_size) == REPLAY_EOF) {
+ return 0;
+ }
+ cur = (QXLDataChunk*)(*mem + base_size - sizeof(QXLDataChunk));
+ cur->data_size = next_data_size;
+ data_size = cur->data_size;
+ cur->next_chunk = cur->prev_chunk = 0;
+ while (count_chunks-- > 0) {
+ if (read_binary(replay, prefix, &next_data_size, (uint8_t**)&cur->next_chunk,
+ sizeof(QXLDataChunk)) == REPLAY_EOF) {
+ return 0;
+ }
+ data_size += next_data_size;
+ ((QXLDataChunk*)cur->next_chunk)->prev_chunk = (QXLPHYSICAL)cur;
+ cur = (QXLDataChunk*)cur->next_chunk;
+ cur->data_size = next_data_size;
+ cur->next_chunk = 0;
+ }
+
+ return data_size;
+}
+
+static void red_replay_data_chunks_free(SpiceReplay *replay, void *data, size_t base_size)
+{
+ QXLDataChunk *cur = (QXLDataChunk *)((uint8_t*)data +
+ (base_size ? base_size - sizeof(QXLDataChunk) : 0));
+
+ cur = (QXLDataChunk *)cur->next_chunk;
+ while (cur) {
+ QXLDataChunk *next = (QXLDataChunk *)cur->next_chunk;
+ free(cur);
+ cur = next;
+ }
+
+ free(data);
+}
+
+static void red_replay_point_ptr(SpiceReplay *replay, QXLPoint *qxl)
+{
+ replay_fscanf(replay, "point %d %d\n", &qxl->x, &qxl->y);
+}
+
+static void red_replay_rect_ptr(SpiceReplay *replay, const char *prefix, QXLRect *qxl)
+{
+ char template[1024];
+
+ snprintf(template, sizeof(template), "rect %s %%d %%d %%d %%d %%d", prefix);
+ replay_fscanf(replay, template, &qxl->top, &qxl->left, &qxl->bottom, &qxl->right);
+}
+
+static QXLPath *red_replay_path(SpiceReplay *replay)
+{
+ QXLPath *qxl = NULL;
+ size_t data_size;
+
+ data_size = red_replay_data_chunks(replay, "path", (uint8_t**)&qxl, sizeof(QXLPath));
+ qxl->data_size = data_size;
+ return qxl;
+}
+
+static void red_replay_path_free(SpiceReplay *replay, QXLPHYSICAL p)
+{
+ QXLPath *qxl = (QXLPath *)p;
+
+ red_replay_data_chunks_free(replay, qxl, sizeof(*qxl));
+}
+
+static QXLClipRects *red_replay_clip_rects(SpiceReplay *replay)
+{
+ QXLClipRects *qxl = NULL;
+ int num_rects;
+
+ replay_fscanf(replay, "num_rects %d\n", &num_rects);
+ red_replay_data_chunks(replay, "clip_rects", (uint8_t**)&qxl, sizeof(QXLClipRects));
+ qxl->num_rects = num_rects;
+ return qxl;
+}
+
+static void red_replay_clip_rects_free(SpiceReplay *replay, QXLClipRects *qxl)
+{
+ red_replay_data_chunks_free(replay, qxl, sizeof(*qxl));
+}
+
+static uint8_t *red_replay_image_data_flat(SpiceReplay *replay, size_t *size)
+{
+ uint8_t *data = NULL;
+
+ read_binary(replay, "image_data_flat", size, &data, 0);
+ return data;
+}
+
+static QXLImage *red_replay_image(SpiceReplay *replay, uint32_t flags)
+{
+ QXLImage* qxl = NULL;
+ size_t bitmap_size, size;
+ uint8_t qxl_flags;
+ int temp;
+ int has_palette;
+ int has_image;
+
+ replay_fscanf(replay, "image %d\n", &has_image);
+ if (!has_image) {
+ return NULL;
+ }
+
+ qxl = (QXLImage*)malloc(sizeof(QXLImage));
+ replay_fscanf(replay, "descriptor.id %ld\n", &qxl->descriptor.id);
+ replay_fscanf(replay, "descriptor.type %d\n", &temp); qxl->descriptor.type = temp;
+ replay_fscanf(replay, "descriptor.flags %d\n", &temp); qxl->descriptor.flags = temp;
+ replay_fscanf(replay, "descriptor.width %d\n", &qxl->descriptor.width);
+ replay_fscanf(replay, "descriptor.height %d\n", &qxl->descriptor.height);
+
+ switch (qxl->descriptor.type) {
+ case SPICE_IMAGE_TYPE_BITMAP:
+ replay_fscanf(replay, "bitmap.format %d\n", &temp); qxl->bitmap.format = temp;
+ replay_fscanf(replay, "bitmap.flags %d\n", &temp); qxl->bitmap.flags = temp;
+ replay_fscanf(replay, "bitmap.x %d\n", &qxl->bitmap.x);
+ replay_fscanf(replay, "bitmap.y %d\n", &qxl->bitmap.y);
+ replay_fscanf(replay, "bitmap.stride %d\n", &qxl->bitmap.stride);
+ qxl_flags = qxl->bitmap.flags;
+ replay_fscanf(replay, "has_palette %d\n", &has_palette);
+ if (has_palette) {
+ QXLPalette *qp;
+ int i, num_ents;
+
+ replay_fscanf(replay, "qp.num_ents %d\n", &num_ents);
+ qp = malloc(sizeof(QXLPalette) + num_ents * sizeof(qp->ents[0]));
+ qp->num_ents = num_ents;
+ qxl->bitmap.palette = (QXLPHYSICAL)qp;
+ replay_fscanf(replay, "unique %ld\n", &qp->unique);
+ for (i = 0; i < num_ents; i++) {
+ replay_fscanf(replay, "ents %d\n", &qp->ents[i]);
+ }
+ } else {
+ qxl->bitmap.palette = 0;
+ }
+ bitmap_size = qxl->bitmap.y * abs(qxl->bitmap.stride);
+ qxl->bitmap.data = 0;
+ if (qxl_flags & QXL_BITMAP_DIRECT) {
+ qxl->bitmap.data = (QXLPHYSICAL)red_replay_image_data_flat(replay, &bitmap_size);
+ } else {
+ size = red_replay_data_chunks(replay, "bitmap.data", (uint8_t**)&qxl->bitmap.data, 0);
+ if (size != bitmap_size) {
+ spice_printerr("bad image, %ld != %ld", size, bitmap_size);
+ return NULL;
+ }
+ }
+ break;
+ case SPICE_IMAGE_TYPE_SURFACE:
+ replay_fscanf(replay, "surface_image.surface_id %d\n", &qxl->surface_image.surface_id);
+ qxl->surface_image.surface_id = replay_id_get(replay, qxl->surface_image.surface_id);
+ break;
+ case SPICE_IMAGE_TYPE_QUIC:
+ // TODO - make this much nicer (precompute size and allocs, store them during
+ // record, then reread into them. and use MPEG-4).
+ replay_fscanf(replay, "quic.data_size %d\n", &qxl->quic.data_size);
+ qxl = realloc(qxl, sizeof(QXLImageDescriptor) + sizeof(QXLQUICData) +
+ qxl->quic.data_size);
+ size = red_replay_data_chunks(replay, "quic.data", (uint8_t**)&qxl->quic.data, 0);
+ spice_assert(size == qxl->quic.data_size);
+ break;
+ default:
+ spice_warn_if_reached();
+ }
+ return qxl;
+}
+
+static void red_replay_image_free(SpiceReplay *replay, QXLPHYSICAL p, uint32_t flags)
+{
+ QXLImage *qxl = (QXLImage *)p;
+ if (!qxl)
+ return;
+
+ switch (qxl->descriptor.type) {
+ case SPICE_IMAGE_TYPE_BITMAP:
+ free((void*)qxl->bitmap.palette);
+ if (qxl->bitmap.flags & QXL_BITMAP_DIRECT) {
+ free((void*)qxl->bitmap.data);
+ } else {
+ red_replay_data_chunks_free(replay, (void*)qxl->bitmap.data, 0);
+ }
+ break;
+ case SPICE_IMAGE_TYPE_SURFACE:
+ break;
+ case SPICE_IMAGE_TYPE_QUIC:
+ red_replay_data_chunks_free(replay, qxl, 0);
+ break;
+ default:
+ spice_warn_if_reached();
+ }
+
+ free(qxl);
+}
+
+static void red_replay_brush_ptr(SpiceReplay *replay, QXLBrush *qxl, uint32_t flags)
+{
+ replay_fscanf(replay, "type %d\n", &qxl->type);
+ switch (qxl->type) {
+ case SPICE_BRUSH_TYPE_SOLID:
+ replay_fscanf(replay, "u.color %d\n", &qxl->u.color);
+ break;
+ case SPICE_BRUSH_TYPE_PATTERN:
+ qxl->u.pattern.pat = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_point_ptr(replay, &qxl->u.pattern.pos);
+ break;
+ }
+}
+
+static void red_replay_brush_free(SpiceReplay *replay, QXLBrush *qxl, uint32_t flags)
+{
+ switch (qxl->type) {
+ case SPICE_BRUSH_TYPE_PATTERN:
+ red_replay_image_free(replay, qxl->u.pattern.pat, flags);
+ break;
+ }
+}
+
+static void red_replay_qmask_ptr(SpiceReplay *replay, QXLQMask *qxl, uint32_t flags)
+{
+ int temp;
+
+ replay_fscanf(replay, "flags %d\n", &temp); qxl->flags = temp;
+ red_replay_point_ptr(replay, &qxl->pos);
+ qxl->bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+}
+
+static void red_replay_qmask_free(SpiceReplay *replay, QXLQMask *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->bitmap, flags);
+}
+
+static void red_replay_fill_ptr(SpiceReplay *replay, QXLFill *qxl, uint32_t flags)
+{
+ int temp;
+
+ red_replay_brush_ptr(replay, &qxl->brush, flags);
+ replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp;
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_fill_free(SpiceReplay *replay, QXLFill *qxl, uint32_t flags)
+{
+ red_replay_brush_free(replay, &qxl->brush, flags);
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_opaque_ptr(SpiceReplay *replay, QXLOpaque *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+ red_replay_brush_ptr(replay, &qxl->brush, flags);
+ replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp;
+ replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp;
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_opaque_free(SpiceReplay *replay, QXLOpaque *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+ red_replay_brush_free(replay, &qxl->brush, flags);
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_copy_ptr(SpiceReplay *replay, QXLCopy *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+ replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp;
+ replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp;
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_copy_free(SpiceReplay *replay, QXLCopy *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_blend_ptr(SpiceReplay *replay, QXLBlend *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+ replay_fscanf(replay, "rop_descriptor %d\n", &temp); qxl->rop_descriptor = temp;
+ replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp;
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_blend_free(SpiceReplay *replay, QXLBlend *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_transparent_ptr(SpiceReplay *replay, QXLTransparent *qxl, uint32_t flags)
+{
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+ replay_fscanf(replay, "src_color %d\n", &qxl->src_color);
+ replay_fscanf(replay, "true_color %d\n", &qxl->true_color);
+}
+
+static void red_replay_transparent_free(SpiceReplay *replay, QXLTransparent *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+}
+
+static void red_replay_alpha_blend_ptr(SpiceReplay *replay, QXLAlphaBlend *qxl, uint32_t flags)
+{
+ int temp;
+
+ replay_fscanf(replay, "alpha_flags %d\n", &temp); qxl->alpha_flags = temp;
+ replay_fscanf(replay, "alpha %d\n", &temp); qxl->alpha = temp;
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+}
+
+static void red_replay_alpha_blend_free(SpiceReplay *replay, QXLAlphaBlend *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+}
+
+static void red_replay_alpha_blend_ptr_compat(SpiceReplay *replay, QXLCompatAlphaBlend *qxl, uint32_t flags)
+{
+ int temp;
+
+ replay_fscanf(replay, "alpha %d\n", &temp); qxl->alpha = temp;
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+}
+
+static void red_replay_rop3_ptr(SpiceReplay *replay, QXLRop3 *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->src_bitmap = (QXLPHYSICAL)red_replay_image(replay, flags);
+ red_replay_rect_ptr(replay, "src_area", &qxl->src_area);
+ red_replay_brush_ptr(replay, &qxl->brush, flags);
+ replay_fscanf(replay, "rop3 %d\n", &temp); qxl->rop3 = temp;
+ replay_fscanf(replay, "scale_mode %d\n", &temp); qxl->scale_mode = temp;
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_rop3_free(SpiceReplay *replay, QXLRop3 *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src_bitmap, flags);
+ red_replay_brush_free(replay, &qxl->brush, flags);
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_stroke_ptr(SpiceReplay *replay, QXLStroke *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->path = (QXLPHYSICAL)red_replay_path(replay);
+ replay_fscanf(replay, "attr.flags %d\n", &temp); qxl->attr.flags = temp;
+ if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) {
+ size_t size;
+
+ replay_fscanf(replay, "attr.style_nseg %d\n", &temp); qxl->attr.style_nseg = temp;
+ read_binary(replay, "style", &size, (uint8_t**)&qxl->attr.style, 0);
+ }
+ red_replay_brush_ptr(replay, &qxl->brush, flags);
+ replay_fscanf(replay, "fore_mode %d\n", &temp); qxl->fore_mode = temp;
+ replay_fscanf(replay, "back_mode %d\n", &temp); qxl->back_mode = temp;
+}
+
+static void red_replay_stroke_free(SpiceReplay *replay, QXLStroke *qxl, uint32_t flags)
+{
+ red_replay_path_free(replay, qxl->path);
+ if (qxl->attr.flags & SPICE_LINE_FLAGS_STYLED) {
+ free((void*)qxl->attr.style);
+ }
+ red_replay_brush_free(replay, &qxl->brush, flags);
+}
+
+static QXLString *red_replay_string(SpiceReplay *replay)
+{
+ int temp;
+ uint32_t data_size;
+ uint16_t length;
+ uint16_t flags;
+ size_t chunk_size;
+ QXLString *qxl = NULL;
+
+ replay_fscanf(replay, "data_size %d\n", &data_size);
+ replay_fscanf(replay, "length %d\n", &temp); length = temp;
+ replay_fscanf(replay, "flags %d\n", &temp); flags = temp;
+ chunk_size = red_replay_data_chunks(replay, "string", (uint8_t**)&qxl, sizeof(QXLString));
+ qxl->data_size = data_size;
+ qxl->length = length;
+ qxl->flags = flags;
+ spice_assert(chunk_size == qxl->data_size);
+ return qxl;
+}
+
+static void red_replay_string_free(SpiceReplay *replay, QXLString *qxl)
+{
+ red_replay_data_chunks_free(replay, qxl, sizeof(*qxl));
+}
+
+static void red_replay_text_ptr(SpiceReplay *replay, QXLText *qxl, uint32_t flags)
+{
+ int temp;
+
+ qxl->str = (QXLPHYSICAL)red_replay_string(replay);
+ red_replay_rect_ptr(replay, "back_area", &qxl->back_area);
+ red_replay_brush_ptr(replay, &qxl->fore_brush, flags);
+ red_replay_brush_ptr(replay, &qxl->back_brush, flags);
+ replay_fscanf(replay, "fore_mode %d\n", &temp); qxl->fore_mode = temp;
+ replay_fscanf(replay, "back_mode %d\n", &temp); qxl->back_mode = temp;
+}
+
+static void red_replay_text_free(SpiceReplay *replay, QXLText *qxl, uint32_t flags)
+{
+ red_replay_string_free(replay, (QXLString *)qxl->str);
+ red_replay_brush_free(replay, &qxl->fore_brush, flags);
+ red_replay_brush_free(replay, &qxl->back_brush, flags);
+}
+
+static void red_replay_whiteness_ptr(SpiceReplay *replay, QXLWhiteness *qxl, uint32_t flags)
+{
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_whiteness_free(SpiceReplay *replay, QXLWhiteness *qxl, uint32_t flags)
+{
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_blackness_ptr(SpiceReplay *replay, QXLBlackness *qxl, uint32_t flags)
+{
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_blackness_free(SpiceReplay *replay, QXLBlackness *qxl, uint32_t flags)
+{
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_invers_ptr(SpiceReplay *replay, QXLInvers *qxl, uint32_t flags)
+{
+ red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_invers_free(SpiceReplay *replay, QXLInvers *qxl, uint32_t flags)
+{
+ red_replay_qmask_free(replay, &qxl->mask, flags);
+}
+
+static void red_replay_clip_ptr(SpiceReplay *replay, QXLClip *qxl)
+{
+ replay_fscanf(replay, "type %d\n", &qxl->type);
+ switch (qxl->type) {
+ case SPICE_CLIP_TYPE_RECTS:
+ qxl->data = (QXLPHYSICAL)red_replay_clip_rects(replay);
+ break;
+ }
+}
+
+static void red_replay_clip_free(SpiceReplay *replay, QXLClip *qxl)
+{
+ switch (qxl->type) {
+ case SPICE_CLIP_TYPE_RECTS:
+ red_replay_clip_rects_free(replay, (QXLClipRects*)qxl->data);
+ break;
+ }
+}
+
+static uint8_t *red_replay_transform(SpiceReplay *replay)
+{
+ uint8_t *data = NULL;
+ size_t size;
+
+ read_binary(replay, "transform", &size, &data, 0);
+ spice_warn_if_fail(size == sizeof(SpiceTransform));
+
+ return data;
+}
+
+static void red_replay_composite_ptr(SpiceReplay *replay, QXLComposite *qxl, uint32_t flags)
+{
+ int enabled;
+
+ replay_fscanf(replay, "flags %d\n", &qxl->flags);
+ qxl->src = (QXLPHYSICAL)red_replay_image(replay, flags);
+
+ replay_fscanf(replay, "src_transform %d\n", &enabled);
+ qxl->src_transform = enabled ? (QXLPHYSICAL)red_replay_transform(replay) : 0;
+
+ replay_fscanf(replay, "mask %d\n", &enabled);
+ qxl->mask = enabled ? (QXLPHYSICAL)red_replay_image(replay, flags) : 0;
+
+ replay_fscanf(replay, "mask_transform %d\n", &enabled);
+ qxl->mask_transform = enabled ? (QXLPHYSICAL)red_replay_transform(replay) : 0;
+
+ replay_fscanf(replay, "src_origin %" SCNi16 " %" SCNi16 "\n", &qxl->src_origin.x, &qxl->src_origin.y);
+ replay_fscanf(replay, "mask_origin %" SCNi16 " %" SCNi16 "\n", &qxl->mask_origin.x, &qxl->mask_origin.y);
+}
+
+static void red_replay_composite_free(SpiceReplay *replay, QXLComposite *qxl, uint32_t flags)
+{
+ red_replay_image_free(replay, qxl->src, flags);
+ free((void*)qxl->src_transform);
+ red_replay_image_free(replay, qxl->mask, flags);
+ free((void*)qxl->mask_transform);
+
+}
+
+static QXLDrawable *red_replay_native_drawable(SpiceReplay *replay, uint32_t flags)
+{
+ QXLDrawable *qxl = malloc(sizeof(QXLDrawable)); // TODO - this is too large usually
+ int i;
+ int temp;
+
+ red_replay_rect_ptr(replay, "bbox", &qxl->bbox);
+ red_replay_clip_ptr(replay, &qxl->clip);
+ replay_fscanf(replay, "effect %d\n", &temp); qxl->effect = temp;
+ replay_fscanf(replay, "mm_time %d\n", &qxl->mm_time);
+ replay_fscanf(replay, "self_bitmap %d\n", &temp); qxl->self_bitmap = temp;
+ red_replay_rect_ptr(replay, "self_bitmap_area", &qxl->self_bitmap_area);
+ replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id);
+ qxl->surface_id = replay_id_get(replay, qxl->surface_id);
+
+ for (i = 0; i < 3; i++) {
+ replay_fscanf(replay, "surfaces_dest %d\n", &qxl->surfaces_dest[i]);
+ qxl->surfaces_dest[i] = replay_id_get(replay, qxl->surfaces_dest[i]);
+ red_replay_rect_ptr(replay, "surfaces_rects", &qxl->surfaces_rects[i]);
+ }
+
+ replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp;
+ switch (qxl->type) {
+ case QXL_DRAW_ALPHA_BLEND:
+ red_replay_alpha_blend_ptr(replay, &qxl->u.alpha_blend, flags);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ red_replay_blackness_ptr(replay, &qxl->u.blackness, flags);
+ break;
+ case QXL_DRAW_BLEND:
+ red_replay_blend_ptr(replay, &qxl->u.blend, flags);
+ break;
+ case QXL_DRAW_COPY:
+ red_replay_copy_ptr(replay, &qxl->u.copy, flags);
+ break;
+ case QXL_COPY_BITS:
+ red_replay_point_ptr(replay, &qxl->u.copy_bits.src_pos);
+ break;
+ case QXL_DRAW_FILL:
+ red_replay_fill_ptr(replay, &qxl->u.fill, flags);
+ break;
+ case QXL_DRAW_OPAQUE:
+ red_replay_opaque_ptr(replay, &qxl->u.opaque, flags);
+ break;
+ case QXL_DRAW_INVERS:
+ red_replay_invers_ptr(replay, &qxl->u.invers, flags);
+ break;
+ case QXL_DRAW_NOP:
+ break;
+ case QXL_DRAW_ROP3:
+ red_replay_rop3_ptr(replay, &qxl->u.rop3, flags);
+ break;
+ case QXL_DRAW_STROKE:
+ red_replay_stroke_ptr(replay, &qxl->u.stroke, flags);
+ break;
+ case QXL_DRAW_TEXT:
+ red_replay_text_ptr(replay, &qxl->u.text, flags);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ red_replay_transparent_ptr(replay, &qxl->u.transparent, flags);
+ break;
+ case QXL_DRAW_WHITENESS:
+ red_replay_whiteness_ptr(replay, &qxl->u.whiteness, flags);
+ break;
+ case QXL_DRAW_COMPOSITE:
+ red_replay_composite_ptr(replay, &qxl->u.composite, flags);
+ break;
+ default:
+ spice_warn_if_reached();
+ break;
+ };
+ return qxl;
+}
+
+static void red_replay_native_drawable_free(SpiceReplay *replay, QXLDrawable *qxl, uint32_t flags)
+{
+ red_replay_clip_free(replay, &qxl->clip);
+
+ switch (qxl->type) {
+ case QXL_DRAW_ALPHA_BLEND:
+ red_replay_alpha_blend_free(replay, &qxl->u.alpha_blend, flags);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ red_replay_blackness_free(replay, &qxl->u.blackness, flags);
+ break;
+ case QXL_DRAW_BLEND:
+ red_replay_blend_free(replay, &qxl->u.blend, flags);
+ break;
+ case QXL_DRAW_COPY:
+ red_replay_copy_free(replay, &qxl->u.copy, flags);
+ break;
+ case QXL_COPY_BITS:
+ break;
+ case QXL_DRAW_FILL:
+ red_replay_fill_free(replay, &qxl->u.fill, flags);
+ break;
+ case QXL_DRAW_OPAQUE:
+ red_replay_opaque_free(replay, &qxl->u.opaque, flags);
+ break;
+ case QXL_DRAW_INVERS:
+ red_replay_invers_free(replay, &qxl->u.invers, flags);
+ break;
+ case QXL_DRAW_NOP:
+ break;
+ case QXL_DRAW_ROP3:
+ red_replay_rop3_free(replay, &qxl->u.rop3, flags);
+ break;
+ case QXL_DRAW_STROKE:
+ red_replay_stroke_free(replay, &qxl->u.stroke, flags);
+ break;
+ case QXL_DRAW_TEXT:
+ red_replay_text_free(replay, &qxl->u.text, flags);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ red_replay_transparent_free(replay, &qxl->u.transparent, flags);
+ break;
+ case QXL_DRAW_WHITENESS:
+ red_replay_whiteness_free(replay, &qxl->u.whiteness, flags);
+ break;
+ case QXL_DRAW_COMPOSITE:
+ red_replay_composite_free(replay, &qxl->u.composite, flags);
+ break;
+ default:
+ spice_warn_if_reached();
+ break;
+ };
+
+ free(qxl);
+}
+
+static QXLCompatDrawable *red_replay_compat_drawable(SpiceReplay *replay, uint32_t flags)
+{
+ int temp;
+ QXLCompatDrawable *qxl = malloc(sizeof(QXLCompatDrawable)); // TODO - too large usually
+
+ red_replay_rect_ptr(replay, "bbox", &qxl->bbox);
+ red_replay_clip_ptr(replay, &qxl->clip);
+ replay_fscanf(replay, "effect %d\n", &temp); qxl->effect = temp;
+ replay_fscanf(replay, "mm_time %d\n", &qxl->mm_time);
+
+ replay_fscanf(replay, "bitmap_offset %d\n", &temp); qxl->bitmap_offset = temp;
+ red_replay_rect_ptr(replay, "bitmap_area", &qxl->bitmap_area);
+
+ replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp;
+ switch (qxl->type) {
+ case QXL_DRAW_ALPHA_BLEND:
+ red_replay_alpha_blend_ptr_compat(replay, &qxl->u.alpha_blend, flags);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ red_replay_blackness_ptr(replay, &qxl->u.blackness, flags);
+ break;
+ case QXL_DRAW_BLEND:
+ red_replay_blend_ptr(replay, &qxl->u.blend, flags);
+ break;
+ case QXL_DRAW_COPY:
+ red_replay_copy_ptr(replay, &qxl->u.copy, flags);
+ break;
+ case QXL_COPY_BITS:
+ red_replay_point_ptr(replay, &qxl->u.copy_bits.src_pos);
+ break;
+ case QXL_DRAW_FILL:
+ red_replay_fill_ptr(replay, &qxl->u.fill, flags);
+ break;
+ case QXL_DRAW_OPAQUE:
+ red_replay_opaque_ptr(replay, &qxl->u.opaque, flags);
+ break;
+ case QXL_DRAW_INVERS:
+ red_replay_invers_ptr(replay, &qxl->u.invers, flags);
+ break;
+ case QXL_DRAW_NOP:
+ break;
+ case QXL_DRAW_ROP3:
+ red_replay_rop3_ptr(replay, &qxl->u.rop3, flags);
+ break;
+ case QXL_DRAW_STROKE:
+ red_replay_stroke_ptr(replay, &qxl->u.stroke, flags);
+ break;
+ case QXL_DRAW_TEXT:
+ red_replay_text_ptr(replay, &qxl->u.text, flags);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ red_replay_transparent_ptr(replay, &qxl->u.transparent, flags);
+ break;
+ case QXL_DRAW_WHITENESS:
+ red_replay_whiteness_ptr(replay, &qxl->u.whiteness, flags);
+ break;
+ default:
+ spice_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+ break;
+ };
+ return qxl;
+}
+
+static QXLPHYSICAL red_replay_drawable(SpiceReplay *replay, uint32_t flags)
+{
+ if (replay->eof) {
+ return 0;
+ }
+ replay_fscanf(replay, "drawable\n");
+ if (flags & QXL_COMMAND_FLAG_COMPAT) {
+ return (QXLPHYSICAL)red_replay_compat_drawable(replay, flags);
+ } else {
+ return (QXLPHYSICAL)red_replay_native_drawable(replay, flags);
+ }
+}
+
+static QXLUpdateCmd *red_replay_update_cmd(SpiceReplay *replay)
+{
+ QXLUpdateCmd *qxl = malloc(sizeof(QXLUpdateCmd));
+
+ replay_fscanf(replay, "update\n");
+ red_replay_rect_ptr(replay, "area", &qxl->area);
+ replay_fscanf(replay, "update_id %d\n", &qxl->update_id);
+ replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id);
+ qxl->surface_id = replay_id_get(replay, qxl->surface_id);
+
+ return qxl;
+}
+
+static QXLMessage *red_replay_message(SpiceReplay *replay)
+{
+ QXLMessage *qxl;
+ size_t size;
+
+ read_binary(replay, "message", &size, (uint8_t**)&qxl, sizeof(QXLMessage));
+ return qxl;
+}
+
+static QXLSurfaceCmd *red_replay_surface_cmd(SpiceReplay *replay)
+{
+ size_t size;
+ size_t read_size;
+ int temp;
+ QXLSurfaceCmd *qxl = calloc(1, sizeof(QXLSurfaceCmd));
+
+ replay_fscanf(replay, "surface_cmd\n");
+ replay_fscanf(replay, "surface_id %d\n", &qxl->surface_id);
+ replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp;
+ replay_fscanf(replay, "flags %d\n", &qxl->flags);
+
+ switch (qxl->type) {
+ case QXL_SURFACE_CMD_CREATE:
+ replay_fscanf(replay, "u.surface_create.format %d\n", &qxl->u.surface_create.format);
+ replay_fscanf(replay, "u.surface_create.width %d\n", &qxl->u.surface_create.width);
+ replay_fscanf(replay, "u.surface_create.height %d\n", &qxl->u.surface_create.height);
+ replay_fscanf(replay, "u.surface_create.stride %d\n", &qxl->u.surface_create.stride);
+ size = qxl->u.surface_create.height * abs(qxl->u.surface_create.stride);
+ if (qxl->flags && QXL_SURF_FLAG_KEEP_DATA) {
+ read_binary(replay, "data", &read_size, (uint8_t**)&qxl->u.surface_create.data, 0);
+ if (read_size != size) {
+ spice_printerr("mismatch %ld != %ld", size, read_size);
+ }
+ } else {
+ qxl->u.surface_create.data = (QXLPHYSICAL)malloc(size);
+ }
+ qxl->surface_id = replay_id_new(replay, qxl->surface_id);
+ break;
+ case QXL_SURFACE_CMD_DESTROY:
+ qxl->u.surface_create.data = (QXLPHYSICAL)NULL;
+ qxl->surface_id = replay_id_get(replay, qxl->surface_id);
+ }
+ return qxl;
+}
+
+static void red_replay_surface_cmd_free(SpiceReplay *replay, QXLSurfaceCmd *qxl)
+{
+ if (qxl->type == QXL_SURFACE_CMD_DESTROY) {
+ replay_id_free(replay, qxl->surface_id);
+ }
+
+ free((void*)qxl->u.surface_create.data);
+ free(qxl);
+}
+
+static void replay_handle_create_primary(QXLWorker *worker, SpiceReplay *replay)
+{
+ QXLDevSurfaceCreate surface = { 0, };
+ size_t size;
+ uint8_t *mem = NULL;
+
+ if (replay->created_primary) {
+ spice_printerr(
+ "WARNING: %d: orignal recording event not preceded by a destroy primary",
+ replay->counter);
+ worker->destroy_primary_surface(worker, 0);
+ }
+ replay->created_primary = TRUE;
+
+ replay_fscanf(replay, "%d %d %d %d\n", &surface.width, &surface.height,
+ &surface.stride, &surface.format);
+ replay_fscanf(replay, "%d %d %d %d\n", &surface.position, &surface.mouse_mode,
+ &surface.flags, &surface.type);
+ read_binary(replay, "data", &size, &mem, 0);
+ surface.group_id = 0;
+ surface.mem = (QXLPHYSICAL)mem;
+ worker->create_primary_surface(worker, 0, &surface);
+}
+
+static void replay_handle_dev_input(QXLWorker *worker, SpiceReplay *replay,
+ RedWorkerMessage message)
+{
+ switch (message) {
+ case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE:
+ case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE_ASYNC:
+ replay_handle_create_primary(worker, replay);
+ break;
+ case RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE:
+ replay->created_primary = FALSE;
+ worker->destroy_primary_surface(worker, 0);
+ break;
+ case RED_WORKER_MESSAGE_DESTROY_SURFACES:
+ replay->created_primary = FALSE;
+ worker->destroy_surfaces(worker);
+ break;
+ case RED_WORKER_MESSAGE_UPDATE:
+ // XXX do anything? we record the correct bitmaps already.
+ case RED_WORKER_MESSAGE_DISPLAY_CONNECT:
+ // we want to ignore this one - it is sent on client connection, we
+ // shall have our own clients
+ case RED_WORKER_MESSAGE_WAKEUP:
+ // safe to ignore
+ break;
+ default:
+ spice_debug("unhandled %d\n", message);
+ }
+}
+
+/*
+ * NOTE: This reads from a saved file and performs all io actions, calling the
+ * dispatcher, until it sees a command, at which point it returns it via the
+ * last parameter [ext_cmd]. Hence you cannot call this from the worker thread
+ * since it will block reading from the dispatcher pipe.
+ */
+SPICE_GNUC_VISIBLE QXLCommandExt* spice_replay_next_cmd(SpiceReplay *replay,
+ QXLWorker *worker)
+{
+ QXLCommandExt* cmd;
+ uint64_t timestamp;
+ int type;
+ int what = -1;
+ int counter;
+
+ while (what != 0) {
+ replay_fscanf(replay, "event %d %d %d %ld\n", &counter,
+ &what, &type, &timestamp);
+ if (replay->eof) {
+ return NULL;
+ }
+ if (what == 1) {
+ replay_handle_dev_input(worker, replay, type);
+ }
+ }
+ cmd = g_slice_new(QXLCommandExt);
+ cmd->cmd.type = type;
+ cmd->group_id = 0;
+ spice_debug("command %ld, %d\r", timestamp, cmd->cmd.type);
+ switch (cmd->cmd.type) {
+ case QXL_CMD_DRAW:
+ cmd->flags = 0;
+ cmd->cmd.data = red_replay_drawable(replay, cmd->flags);
+ break;
+ case QXL_CMD_UPDATE:
+ cmd->cmd.data = (QXLPHYSICAL)red_replay_update_cmd(replay);
+ break;
+ case QXL_CMD_MESSAGE:
+ cmd->cmd.data = (QXLPHYSICAL)red_replay_message(replay);
+ break;
+ case QXL_CMD_SURFACE:
+ cmd->cmd.data = (QXLPHYSICAL)red_replay_surface_cmd(replay);
+ break;
+ }
+
+ QXLReleaseInfo *info;
+ switch (cmd->cmd.type) {
+ case QXL_CMD_DRAW:
+ case QXL_CMD_UPDATE:
+ case QXL_CMD_SURFACE:
+ info = (QXLReleaseInfo *)cmd->cmd.data;
+ info->id = (uint64_t)cmd;
+ }
+
+ replay->counter++;
+
+ return cmd;
+}
+
+SPICE_GNUC_VISIBLE void spice_replay_free_cmd(SpiceReplay *replay, QXLCommandExt *cmd)
+{
+ spice_return_if_fail(replay);
+ spice_return_if_fail(cmd);
+
+ switch (cmd->cmd.type) {
+ case QXL_CMD_DRAW: {
+ // FIXME: compat flag must be save somewhere...
+ spice_return_if_fail(cmd->flags == 0);
+ QXLDrawable *qxl = (QXLDrawable *)cmd->cmd.data;
+ red_replay_native_drawable_free(replay, qxl, cmd->flags);
+ break;
+ }
+ case QXL_CMD_UPDATE: {
+ QXLUpdateCmd *qxl = (QXLUpdateCmd *)cmd->cmd.data;
+ free(qxl);
+ break;
+ }
+ case QXL_CMD_SURFACE: {
+ QXLSurfaceCmd *qxl = (QXLSurfaceCmd *)cmd->cmd.data;
+ red_replay_surface_cmd_free(replay, qxl);
+ break;
+ }
+ default:
+ break;
+ }
+
+ g_slice_free(QXLCommandExt, cmd);
+}
+
+/* caller is incharge of closing the replay when done and releasing the SpiceReplay
+ * memory */
+SPICE_GNUC_VISIBLE
+SpiceReplay *spice_replay_new(FILE *file, int nsurfaces)
+{
+ unsigned int version = 0;
+ SpiceReplay *replay;
+
+ spice_return_val_if_fail(file != NULL, NULL);
+
+ if (fscanf(file, "SPICE_REPLAY %u\n", &version) == 1) {
+ if (version > 1) {
+ spice_warning("Replay file version unsupported");
+ return NULL;
+ }
+ }
+
+ replay = malloc(sizeof(SpiceReplay));
+
+ replay->eof = 0;
+ replay->fd = file;
+ replay->created_primary = FALSE;
+ g_mutex_init(&replay->mutex);
+ g_cond_init(&replay->cond);
+ replay->id_map = g_array_new(FALSE, FALSE, sizeof(uint32_t));
+ replay->id_map_inv = g_array_new(FALSE, FALSE, sizeof(uint32_t));
+ replay->id_free = g_array_new(FALSE, FALSE, sizeof(uint32_t));
+ replay->nsurfaces = nsurfaces;
+
+ /* reserve id 0 */
+ replay_id_new(replay, 0);
+
+ return replay;
+}
+
+SPICE_GNUC_VISIBLE void spice_replay_free(SpiceReplay *replay)
+{
+ spice_return_if_fail(replay != NULL);
+
+ g_mutex_clear(&replay->mutex);
+ g_cond_clear(&replay->cond);
+ g_array_free(replay->id_map, TRUE);
+ g_array_free(replay->id_map_inv, TRUE);
+ g_array_free(replay->id_free, TRUE);
+ fclose(replay->fd);
+ free(replay);
+}
diff --git a/server/red_replay_qxl.h b/server/red_replay_qxl.h
new file mode 100644
index 00000000..a89b3d44
--- /dev/null
+++ b/server/red_replay_qxl.h
@@ -0,0 +1,34 @@
+/* -*- Mode: C; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/*
+ Copyright (C) 2009-2015 Red Hat, Inc.
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Lesser General Public
+ License as published by the Free Software Foundation; either
+ version 2.1 of the License, or (at your option) any later version.
+
+ This library is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public
+ License along with this library; if not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef RED_REPLAY_QXL_H
+#define RED_REPLAY_QXL_H
+
+#include <stdio.h>
+#include <spice/qxl_dev.h>
+#include <spice.h>
+
+typedef struct SpiceReplay SpiceReplay;
+
+/* reads until encountering a cmd, processing any recorded messages (io) on the
+ * way */
+QXLCommandExt* spice_replay_next_cmd(SpiceReplay *replay, QXLWorker *worker);
+void spice_replay_free_cmd(SpiceReplay *replay, QXLCommandExt *cmd);
+void spice_replay_free(SpiceReplay *replay);
+SpiceReplay * spice_replay_new(FILE *file, int nsurfaces);
+
+#endif // RED_REPLAY_QXL_H
diff --git a/server/spice-server.syms b/server/spice-server.syms
index 3c7d9456..d65e14d9 100644
--- a/server/spice-server.syms
+++ b/server/spice-server.syms
@@ -157,4 +157,8 @@ global:
SPICE_SERVER_0.12.6 {
global:
spice_qxl_set_max_monitors;
+ spice_replay_free;
+ spice_replay_new;
+ spice_replay_next_cmd;
+ spice_replay_free_cmd;
} SPICE_SERVER_0.12.5;