[Spice-devel] [RfC 2/4] server/red_{record, replay}.[ch]: introduce

Alon Levy alevy at redhat.com
Fri Jul 1 19:49:43 PDT 2011


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.
---
 server/Makefile.am      |    2 +
 server/make_recorder.sh |   13 +
 server/red_record_qxl.c |  715 +++++++++++++++++++++++++++++++++++++++
 server/red_record_qxl.h |   44 +++
 server/red_replay_qxl.c |  849 +++++++++++++++++++++++++++++++++++++++++++++++
 server/red_replay_qxl.h |   23 ++
 6 files changed, 1646 insertions(+), 0 deletions(-)
 create mode 100755 server/make_recorder.sh
 create mode 100644 server/red_record_qxl.c
 create mode 100644 server/red_record_qxl.h
 create mode 100644 server/red_replay_qxl.c
 create mode 100644 server/red_replay_qxl.h

diff --git a/server/Makefile.am b/server/Makefile.am
index 601840a..cc35cfe 100644
--- a/server/Makefile.am
+++ b/server/Makefile.am
@@ -82,6 +82,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_worker.c				\
 	red_worker.h				\
diff --git a/server/make_recorder.sh b/server/make_recorder.sh
new file mode 100755
index 0000000..e7a9234
--- /dev/null
+++ b/server/make_recorder.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+if [ ! -e red_parse_qxl.c ]; then
+    echo run in server subdir
+    exit 1
+fi
+if [ -e red_record_qxl.c ]; then
+    echo not overwriting red_record_qxl.c
+    exit 1
+fi
+cp red_parse_qxl.c red_record_qxl.c.base
+sed -e 's/\(static.*\)red_get\([a-z_]*(\)/\1red_record\2int fd, /' -e 's/^void red_get_\(.*\)(/void red_record_\1(int fd, /' < red_record_qxl.c.base > red_record_qxl.1.c
+sed -e 's/red_get\(.*\)(/red_record\1(fd, /' < red_record_qxl.1.c > red_record_qxl.c
+diff -u red_parse_qxl.c red_record_qxl.c
diff --git a/server/red_record_qxl.c b/server/red_record_qxl.c
new file mode 100644
index 0000000..b6c1326
--- /dev/null
+++ b/server/red_record_qxl.c
@@ -0,0 +1,715 @@
+/* -*- 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;
+
+    hex = (uint8_t*)get_virt(slots, addr, bytes, group_id);
+    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 1
+
+/* TODO: make this thread safe (required for two qxl devices) */
+
+#if WITH_ZLIB
+typedef struct RecordEncoderData {
+    ZlibEncoderUsrContext base;
+    uint8_t *buf;
+    int size;
+} RecordEncoderData;
+
+int record_zlib_more_space(ZlibEncoderUsrContext *usr, uint8_t **io_ptr)
+{
+    return 0;
+}
+
+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
+
+static void write_binary(FILE *fd, const char *prefix, size_t size, uint8_t *buf)
+{
+#if WITH_ZLIB
+    uint8_t output[1024*1024*4]; // static buffer for encoding, 4MB
+    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);
+    fwrite(output, zlib_size, 1, fd);
+#else
+    fwrite(buf, size, 1, fd);
+#endif
+    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;
+
+    while (cur->next_chunk) {
+        cur =
+            (QXLDataChunk*)get_virt(slots, cur->next_chunk, sizeof(*cur), group_id);
+        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);
+
+        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);
+
+    qxl = (QXLDataChunk*)get_virt(slots, addr, sizeof(*qxl), group_id);
+    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;
+
+    qxl = (QXLPath *)get_virt(slots, addr, sizeof(*qxl), group_id);
+    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;
+
+    qxl = (QXLClipRects *)get_virt(slots, addr, sizeof(*qxl), group_id);
+    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_image_data_flat(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                                       QXLPHYSICAL addr, size_t size)
+{
+    write_binary(fd, "image_data_flat",
+                 size, (uint8_t*)get_virt(slots, addr, size, group_id));
+}
+
+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;
+
+    fprintf(fd, "image %d\n", addr ? 1 : 0);
+    if (addr == 0) {
+        return;
+    }
+
+    qxl = (QXLImage *)get_virt(slots, addr, sizeof(*qxl), group_id);
+    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);
+            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);
+            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);
+        ASSERT(size == qxl->quic.data_size);
+        break;
+    default:
+        red_error("%s: unknown type %d", __FUNCTION__, qxl->descriptor.type);
+        abort();
+    }
+}
+
+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)
+{
+    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);
+        ASSERT(qxl->attr.style);
+        buf = (uint8_t *)get_virt(slots, qxl->attr.style,
+                                  style_nseg * sizeof(QXLFIXED), group_id);
+        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;
+
+    qxl = (QXLString *)get_virt(slots, addr, sizeof(*qxl), group_id);
+    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);
+    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_native_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                                       QXLPHYSICAL addr, uint32_t flags)
+{
+    QXLDrawable *qxl;
+    int i;
+
+    qxl = (QXLDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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;
+    default:
+        red_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;
+
+    qxl = (QXLCompatDrawable *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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:
+        red_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+        break;
+    };
+}
+
+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);
+    }
+}
+
+void red_record_update_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                           QXLPHYSICAL addr)
+{
+    QXLUpdateCmd *qxl;
+
+    qxl = (QXLUpdateCmd *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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);
+}
+
+void red_record_message(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                        QXLPHYSICAL addr)
+{
+    QXLMessage *qxl;
+
+    /*
+     * 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);
+    write_binary(fd, "message", strlen((char*)qxl->data), (uint8_t*)qxl->data);
+}
+
+void red_record_surface_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                            QXLPHYSICAL addr)
+{
+    QXLSurfaceCmd *qxl;
+    size_t size;
+
+    qxl = (QXLSurfaceCmd *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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));
+        }
+        break;
+    }
+}
+
+static void red_record_cursor(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                              QXLPHYSICAL addr)
+{
+    QXLCursor *qxl;
+
+    qxl = (QXLCursor *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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;
+
+    qxl = (QXLCursorCmd *)get_virt(slots, addr, sizeof(*qxl), group_id);
+
+    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);
+}
diff --git a/server/red_record_qxl.h b/server/red_record_qxl.h
new file mode 100644
index 0000000..1be9e41
--- /dev/null
+++ b/server/red_record_qxl.h
@@ -0,0 +1,44 @@
+/* -*- 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_drawable(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                         QXLPHYSICAL addr, uint32_t flags);
+
+void red_record_update_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                           QXLPHYSICAL addr);
+
+void red_record_message(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                        QXLPHYSICAL addr);
+
+void red_record_surface_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                            QXLPHYSICAL addr);
+
+void red_record_cursor_cmd(FILE *fd, RedMemSlotInfo *slots, int group_id,
+                           QXLPHYSICAL addr);
+
+void red_record_dev_input_primary_surface_create(
+                           FILE *fd, QXLDevSurfaceCreate *surface, uint8_t *line_0);
+
+#endif
diff --git a/server/red_replay_qxl.c b/server/red_replay_qxl.c
new file mode 100644
index 0000000..81810f3
--- /dev/null
+++ b/server/red_replay_qxl.c
@@ -0,0 +1,849 @@
+/* -*- 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"
+
+int replay_fread(Replay *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);
+}
+
+replay_t replay_fscanf(Replay *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;
+}
+
+#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(Replay *replay, const char *prefix, size_t *size, uint8_t
+                            **buf, size_t base_size)
+{
+    char template[1024];
+    int with_zlib;
+    int zlib_size;
+    uint8_t *zlib_buffer;
+    z_stream strm;
+
+    snprintf(template, sizeof(template), "binary %%d %s %%ld:", prefix);
+    replay_fscanf(replay, template, &with_zlib, size);
+    if (*buf == NULL) {
+        *buf = malloc(*size + base_size);
+        if (*buf == NULL) {
+            fprintf(stderr, "allocation error for %ld\n", *size);
+            exit(1);
+        }
+    }
+#if 0
+    {
+        int num_read = fread(*buf + base_size, *size, 1, fd);
+        fprintf(stderr, "%s: num_read = %d\n", __FUNCTION__, num_read);
+        hexdump(*buf + base_size, *size);
+    }
+#else
+    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) {
+            fprintf(stderr, "inflateInit failed\n");
+            exit(1);
+        }
+        if ((ret = inflate(&strm, Z_NO_FLUSH)) != Z_STREAM_END) {
+            fprintf(stderr, "inflate error %d (disc: %ld)\n", 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) {
+                abort();
+            }
+        }
+        (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(Replay *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_point_ptr(Replay *replay, QXLPoint *qxl)
+{
+    replay_fscanf(replay, "point %d %d\n", &qxl->x, &qxl->y);
+}
+
+static void red_replay_point16_ptr(Replay *replay, QXLPoint16 *qxl)
+{
+    replay_fscanf(replay, "point16 %hd %hd\n", &qxl->x, &qxl->y);
+}
+
+static void red_replay_rect_ptr(Replay *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(Replay *replay)
+{
+    QXLPath *qxl;
+    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 QXLClipRects *red_replay_clip_rects(Replay *replay)
+{
+    QXLClipRects *qxl;
+    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 uint8_t *red_replay_image_data_flat(Replay *replay, size_t *size)
+{
+    uint8_t *data = NULL;
+
+    read_binary(replay, "image_data_flat", size, &data, 0);
+    return data;
+}
+
+static QXLImage *red_replay_image(Replay *replay, uint32_t flags)
+{
+    QXLImage* qxl;
+    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) {
+                fprintf(stderr, "bad image, %ld != %ld\n", 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);
+        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);
+        ASSERT(size == qxl->quic.data_size);
+        break;
+    default:
+        red_error("%s: unknown type %d", __FUNCTION__, qxl->descriptor.type);
+        abort();
+    }
+    return qxl;
+}
+
+static void red_replay_brush_ptr(Replay *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_qmask_ptr(Replay *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_fill_ptr(Replay *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_opaque_ptr(Replay *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_copy_ptr(Replay *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_blend_ptr(Replay *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_transparent_ptr(Replay *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_alpha_blend_ptr(Replay *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_ptr_compat(Replay *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(Replay *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_stroke_ptr(Replay *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 QXLString *red_replay_string(Replay *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;
+    ASSERT(chunk_size == qxl->data_size);
+    return qxl;
+}
+
+static void red_replay_text_ptr(Replay *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_whiteness_ptr(Replay *replay, QXLWhiteness *qxl, uint32_t flags)
+{
+    red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_blackness_ptr(Replay *replay, QXLBlackness *qxl, uint32_t flags)
+{
+    red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_invers_ptr(Replay *replay, QXLInvers *qxl, uint32_t flags)
+{
+    red_replay_qmask_ptr(replay, &qxl->mask, flags);
+}
+
+static void red_replay_clip_ptr(Replay *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 QXLDrawable *red_replay_native_drawable(Replay *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);
+
+    for (i = 0; i < 3; i++) {
+        replay_fscanf(replay, "surfaces_dest %d\n", &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;
+    default:
+        red_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+        break;
+    };
+    return qxl;
+}
+
+static QXLCompatDrawable *red_replay_compat_drawable(Replay *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:
+        red_error("%s: unknown type %d", __FUNCTION__, qxl->type);
+        break;
+    };
+    return qxl;
+}
+
+static QXLPHYSICAL red_replay_drawable(Replay *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(Replay *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);
+    return qxl;
+}
+
+static QXLMessage *red_replay_message(Replay *replay)
+{
+    QXLMessage *qxl;
+    size_t size;
+
+    read_binary(replay, "message", &size, (uint8_t**)&qxl, sizeof(QXLMessage));
+    return qxl;
+}
+
+// keep pointers around for surfaces backing stores. why? dunno.
+uint8_t *surfaces[1024];
+
+static QXLSurfaceCmd *red_replay_surface_cmd(Replay *replay)
+{
+    size_t size;
+    size_t read_size;
+    int temp;
+    QXLSurfaceCmd *qxl = malloc(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);
+
+    if (qxl->surface_id < 0 || qxl->surface_id > sizeof(surfaces)) {
+        fprintf(stderr, "%s: out of bounds surface_id %d\n",
+                __FUNCTION__, qxl->surface_id);
+        exit(1);
+    }
+
+    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) {
+                fprintf(stderr, "%s: mismatch %ld != %ld\n", __FUNCTION__, size, read_size);
+            }
+        } else {
+            if (surfaces[qxl->surface_id]) {
+                free(surfaces[qxl->surface_id]);
+            }
+            surfaces[qxl->surface_id] = malloc(size);
+            qxl->u.surface_create.data = (QXLPHYSICAL)surfaces[qxl->surface_id];
+        }
+        break;
+    }
+    return qxl;
+}
+
+static QXLCursor *red_replay_cursor(Replay *replay)
+{
+    int temp;
+    QXLCursor *qxl;
+    QXLCursorHeader header;
+
+    replay_fscanf(replay, "header.unique %ld\n", &header.unique);
+    replay_fscanf(replay, "header.type %d\n", &temp); header.type = temp;
+    replay_fscanf(replay, "header.width %d\n", &temp); header.width = temp;
+    replay_fscanf(replay, "header.height %d\n", &temp); header.height = temp;
+    replay_fscanf(replay, "header.hot_spot_x %d\n", &temp); header.hot_spot_x = temp;
+    replay_fscanf(replay, "header.hot_spot_y %d\n", &temp); header.hot_spot_y = temp;
+
+    replay_fscanf(replay, "data_size %d\n", &qxl->data_size);
+    red_replay_data_chunks(replay, "cursor", (uint8_t**)&qxl, sizeof(QXLCursor));
+    qxl->header = header;
+    return qxl;
+}
+
+QXLCursorCmd *red_replay_cursor_cmd(Replay *replay)
+{
+    int temp;
+    QXLCursorCmd *qxl = malloc(sizeof(QXLCursorCmd));
+
+    replay_fscanf(replay, "cursor_cmd\n");
+    replay_fscanf(replay, "type %d\n", &temp); qxl->type = temp;
+    switch (qxl->type) {
+    case QXL_CURSOR_SET:
+        red_replay_point16_ptr(replay, &qxl->u.set.position);
+        replay_fscanf(replay, "u.set.visible %d\n", &temp); qxl->u.set.visible = temp;
+        qxl->u.set.shape = (QXLPHYSICAL)red_replay_cursor(replay);
+        break;
+    case QXL_CURSOR_MOVE:
+        red_replay_point16_ptr(replay, &qxl->u.position);
+        break;
+    case QXL_CURSOR_TRAIL:
+        replay_fscanf(replay, "u.trail.length %d\n", &temp); qxl->u.trail.length = temp;
+        replay_fscanf(replay, "u.trail.frequency %d\n", &temp); qxl->u.trail.frequency = temp;
+        break;
+    }
+    return qxl;
+}
+
+#if 1
+#define debug_fprintf fprintf
+#else
+#define debug_fprintf(...)
+#endif
+
+static void replay_handle_create_primary(QXLWorker *worker, Replay *replay)
+{
+    QXLDevSurfaceCreate surface;
+    size_t size;
+    uint8_t *mem = NULL;
+
+    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;
+    if (surface.stride > 0) {
+        fprintf(stderr, "%s: unsupported positive primary surface stride\n",
+            __FUNCTION__);
+        exit(1);
+    }
+    surface.mem = (QXLPHYSICAL)mem;
+    worker->create_primary_surface(worker, 0, &surface);
+}
+
+static void replay_handle_dev_input(QXLWorker *worker, Replay *replay,
+                                    RedWorkerMessage message)
+{
+    switch (message) {
+    case RED_WORKER_MESSAGE_CREATE_PRIMARY_SURFACE:
+        replay_handle_create_primary(worker, replay);
+        break;
+    case RED_WORKER_MESSAGE_DESTROY_PRIMARY_SURFACE:
+        worker->destroy_primary_surface(worker, 0);
+        break;
+    case RED_WORKER_MESSAGE_DESTROY_SURFACES:
+        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:
+        debug_fprintf(stderr, "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 void replay_next_cmd(
+    Replay *replay, QXLWorker *worker, struct QXLCommandExt *ext_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) {
+            ext_cmd->cmd.data = 0;
+            fprintf(stderr, "error reading line from file\n");
+            return;
+        }
+        if (what == 1) {
+            replay_handle_dev_input(worker, replay, type);
+        }
+    }
+    ext_cmd->cmd.type = type;
+    ext_cmd->group_id = 0;
+    debug_fprintf(stderr, "command %ld, %d\r", timestamp, ext_cmd->cmd.type);
+    switch (ext_cmd->cmd.type) {
+    case QXL_CMD_DRAW:
+        ext_cmd->flags = 0;
+        ext_cmd->cmd.data = red_replay_drawable(replay, ext_cmd->flags);
+        break;
+    case QXL_CMD_UPDATE:
+        ext_cmd->cmd.data = (QXLPHYSICAL)red_replay_update_cmd(replay);
+        break;
+    case QXL_CMD_MESSAGE:
+        ext_cmd->cmd.data = (QXLPHYSICAL)red_replay_message(replay);
+        break;
+    case QXL_CMD_SURFACE:
+        ext_cmd->cmd.data = (QXLPHYSICAL)red_replay_surface_cmd(replay);
+        break;
+     }
+}
+
+/* caller is incharge of closing the replay when done and releasing the Replay
+ * memory */
+SPICE_GNUC_VISIBLE Replay *replay_from_fd(FILE *fd)
+{
+    Replay *replay = malloc(sizeof(Replay));
+    replay->eof = 0;
+    replay->fd = fd;
+    return replay;
+}
diff --git a/server/red_replay_qxl.h b/server/red_replay_qxl.h
new file mode 100644
index 0000000..bc1d491
--- /dev/null
+++ b/server/red_replay_qxl.h
@@ -0,0 +1,23 @@
+#ifndef RED_REPLAY_QXL_H
+#define RED_REPLAY_QXL_H
+
+#include <stdio.h>
+#include <spice/qxl_dev.h>
+#include <spice.h>
+
+typedef enum {
+    REPLAY_OK = 0,
+    REPLAY_EOF,
+} replay_t;
+
+typedef struct {
+    FILE *fd;
+    int eof;
+} Replay;
+
+/* reads until encountering a cmd, processing any recorded messages (io) on the
+ * way */
+void replay_next_cmd(Replay *replay, QXLWorker *worker, QXLCommandExt *ext);
+Replay *replay_from_fd(FILE *fd);
+
+#endif // RED_REPLAY_QXL_H
-- 
1.7.5.4



More information about the Spice-devel mailing list