[Spice-commits] 5 commits - server/dispatcher.c server/dispatcher.h server/Makefile.am server/red_record_qxl.c server/red_record_qxl.h server/red_replay_qxl.c server/red_replay_qxl.h server/red_worker.c server/spice-server.syms server/tests

Frediano Ziglio fziglio at kemper.freedesktop.org
Sat Aug 22 04:39:33 PDT 2015


 server/Makefile.am              |    2 
 server/dispatcher.c             |   10 
 server/dispatcher.h             |   12 
 server/red_record_qxl.c         |  827 ++++++++++++++++++++++++++
 server/red_record_qxl.h         |   34 +
 server/red_replay_qxl.c         | 1243 ++++++++++++++++++++++++++++++++++++++++
 server/red_replay_qxl.h         |   34 +
 server/red_worker.c             |   40 +
 server/spice-server.syms        |    4 
 server/tests/Makefile.am        |   11 
 server/tests/basic_event_loop.c |  285 +++------
 server/tests/replay.c           |  346 +++++++++++
 12 files changed, 2655 insertions(+), 193 deletions(-)

New commits:
commit 622cb433a8f9208ba90cd810877636493c04c687
Author: Alon Levy <alon at pobox.com>
Date:   Fri Aug 21 09:24:34 2015 +0100

    server/tests/spice-server-replay: introduce
    
    usage: spice-server-replay -p <port> -c <client command line> <cmdfile>
    
    will run the commands from cmdfile ignoring timestamps, right after a
    connection is established from the client, and will SIGINT the client
    on end of cmdfile, and exit itself after waiting for the client.
    
    spicy-stats from spice-gtk is useful for testing, it prints the summary
    of the traffic on each channel.
    
    You can also run with no client by doing:
    spice-server-replay <cmdfile>
    
    For example, the 300 MB file (compressed to 4 MB with xz -9) available
    at [1] produces the following output:
    
    spicy-stats total bytes read:
    total bytes read:
    inputs: 214
    display: 1968983
    cursor: 390
    main: 256373
    
    You could run it directly like so:
    curl http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz | \
      xzcat | server/tests/spice-server-replay -p 12345 -c `which spicy-stats` -
    
    Known Problems:
    * Implementation is wrong. Should do a single device->host conversion
     (i.e. get_virt), and then marshall/demarshall that (i.e. RedDrawable).
    * segfault on file read done resulting in the above spicy-stats not
     being reproducable (well, up to 1% yes).
    
    [1] http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz
    
    Now based on glib including using an asyncqueue for reading the playback
    file, and proper freeing of the allocated commands, with --slow,
    --compression and a progress timer, and doesn't use more then nsurfaces.
    
    Signed-off-by: Alon Levy <alon at pobox.com>
    Signed-off-by: Marc-André Lureau <marcandre.lureau at gmail.com>

diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index a52ad59..96eb39c 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -43,6 +43,7 @@ noinst_PROGRAMS =				\
 	test_two_servers			\
 	test_vdagent				\
 	test_display_width_stride		\
+	spice-server-replay			\
 	$(NULL)
 
 test_vdagent_SOURCES =		\
@@ -104,3 +105,11 @@ test_display_width_stride_SOURCES =		\
 	test_display_base.h			\
 	test_display_width_stride.c 		\
 	$(NULL)
+
+spice_server_replay_SOURCES = 			\
+	replay.c				\
+	test_display_base.h			\
+	basic_event_loop.c			\
+	basic_event_loop.h			\
+	test_util.h				\
+	$(NULL)
diff --git a/server/tests/replay.c b/server/tests/replay.c
new file mode 100644
index 0000000..01590c0
--- /dev/null
+++ b/server/tests/replay.c
@@ -0,0 +1,346 @@
+/* Replay a previously recorded file (via SPICE_WORKER_RECORD_FILENAME)
+ */
+
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <sys/types.h>
+#include <signal.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <glib.h>
+
+#include <spice/macros.h>
+#include "red_replay_qxl.h"
+#include "test_display_base.h"
+#include "common/log.h"
+
+static SpiceCoreInterface *core;
+static SpiceServer *server;
+static SpiceReplay *replay;
+static QXLWorker *qxl_worker = NULL;
+static gboolean started = FALSE;
+static QXLInstance display_sin = { 0, };
+static gint slow = 0;
+static pid_t client_pid;
+static GMainLoop *loop = NULL;
+static GAsyncQueue *aqueue = NULL;
+static long total_size;
+
+static GMutex mutex;
+static guint fill_source_id = 0;
+
+
+#define MEM_SLOT_GROUP_ID 0
+
+/* Parts cribbed from spice-display.h/.c/qxl.c */
+
+static QXLDevMemSlot slot = {
+.slot_group_id = MEM_SLOT_GROUP_ID,
+.slot_id = 0,
+.generation = 0,
+.virt_start = 0,
+.virt_end = ~0,
+.addr_delta = 0,
+.qxl_ram_size = ~0,
+};
+
+static void attach_worker(QXLInstance *qin, QXLWorker *_qxl_worker)
+{
+    static int count = 0;
+    if (++count > 1) {
+        g_warning("%s ignored\n", __func__);
+        return;
+    }
+    g_debug("%s\n", __func__);
+    qxl_worker = _qxl_worker;
+    spice_qxl_add_memslot(qin, &slot);
+    spice_server_vm_start(server);
+}
+
+static void set_compression_level(QXLInstance *qin, int level)
+{
+    g_debug("%s\n", __func__);
+}
+
+static void set_mm_time(QXLInstance *qin, uint32_t mm_time)
+{
+}
+
+// same as qemu/ui/spice-display.h
+#define MAX_SURFACE_NUM 1024
+
+static void get_init_info(QXLInstance *qin, QXLDevInitInfo *info)
+{
+    bzero(info, sizeof(*info));
+    info->num_memslots = 1;
+    info->num_memslots_groups = 1;
+    info->memslot_id_bits = 1;
+    info->memslot_gen_bits = 1;
+    info->n_surfaces = MAX_SURFACE_NUM;
+}
+
+static gboolean fill_queue_idle(gpointer user_data)
+{
+    gboolean keep = FALSE;
+
+    while (g_async_queue_length(aqueue) < 50) {
+        QXLCommandExt *cmd = spice_replay_next_cmd(replay, qxl_worker);
+        if (!cmd) {
+            g_async_queue_push(aqueue, GINT_TO_POINTER(-1));
+            goto end;
+        }
+
+        if (slow)
+            g_usleep(slow);
+
+        g_async_queue_push(aqueue, cmd);
+    }
+
+end:
+    if (!keep) {
+        g_mutex_lock(&mutex);
+        fill_source_id = 0;
+        g_mutex_unlock(&mutex);
+    }
+    spice_qxl_wakeup(&display_sin);
+
+    return keep;
+}
+
+static void fill_queue(void)
+{
+    g_mutex_lock(&mutex);
+
+    if (!started)
+        goto end;
+
+    if (fill_source_id != 0)
+        goto end;
+
+    fill_source_id = g_idle_add(fill_queue_idle, NULL);
+
+end:
+    g_mutex_unlock(&mutex);
+}
+
+
+// called from spice_server thread (i.e. red_worker thread)
+static int get_command(QXLInstance *qin, QXLCommandExt *ext)
+{
+    QXLCommandExt *cmd;
+
+    if (g_async_queue_length(aqueue) == 0) {
+        /* could use a gcondition ? */
+        fill_queue();
+        return FALSE;
+    }
+
+    cmd = g_async_queue_try_pop(aqueue);
+    if (GPOINTER_TO_INT(cmd) == -1) {
+        g_main_loop_quit(loop);
+        return FALSE;
+    }
+
+    *ext = *cmd;
+
+    return TRUE;
+}
+
+static int req_cmd_notification(QXLInstance *qin)
+{
+    if (!started)
+        return TRUE;
+
+    g_printerr("id: %d, queue length: %d",
+                   fill_source_id, g_async_queue_length(aqueue));
+
+    return TRUE;
+}
+
+static void end_replay(void)
+{
+    int child_status;
+
+    /* FIXME: wait threads and end cleanly */
+    spice_replay_free(replay);
+
+    if (client_pid) {
+        g_debug("kill %d", client_pid);
+        kill(client_pid, SIGINT);
+        waitpid(client_pid, &child_status, 0);
+    }
+    exit(0);
+}
+
+static void release_resource(QXLInstance *qin, struct QXLReleaseInfoExt release_info)
+{
+    spice_replay_free_cmd(replay, (QXLCommandExt *)release_info.info->id);
+}
+
+static int get_cursor_command(QXLInstance *qin, struct QXLCommandExt *ext)
+{
+    return FALSE;
+}
+
+static int req_cursor_notification(QXLInstance *qin)
+{
+    return TRUE;
+}
+
+static void notify_update(QXLInstance *qin, uint32_t update_id)
+{
+}
+
+static int flush_resources(QXLInstance *qin)
+{
+    return TRUE;
+}
+
+static QXLInterface display_sif = {
+    .base = {
+        .type = SPICE_INTERFACE_QXL,
+        .description = "replay",
+        .major_version = SPICE_INTERFACE_QXL_MAJOR,
+        .minor_version = SPICE_INTERFACE_QXL_MINOR
+    },
+    .attache_worker = attach_worker,
+    .set_compression_level = set_compression_level,
+    .set_mm_time = set_mm_time,
+    .get_init_info = get_init_info,
+    .get_command = get_command,
+    .req_cmd_notification = req_cmd_notification,
+    .release_resource = release_resource,
+    .get_cursor_command = get_cursor_command,
+    .req_cursor_notification = req_cursor_notification,
+    .notify_update = notify_update,
+    .flush_resources = flush_resources,
+};
+
+static void replay_channel_event(int event, SpiceChannelEventInfo *info)
+{
+    g_printerr("");
+
+    if (info->type == SPICE_CHANNEL_DISPLAY &&
+        event == SPICE_CHANNEL_EVENT_INITIALIZED) {
+        started = TRUE;
+    }
+}
+
+static gboolean start_client(gchar *cmd, GError **error)
+{
+    gboolean retval;
+    gint argc;
+    gchar **argv = NULL;
+
+
+    if (!g_shell_parse_argv(cmd, &argc, &argv, error))
+        return FALSE;
+
+    retval = g_spawn_async(NULL, argv, NULL, G_SPAWN_SEARCH_PATH,
+                           NULL, NULL, &client_pid, error);
+    g_strfreev(argv);
+
+    return retval;
+}
+
+static gboolean progress_timer(gpointer user_data)
+{
+    FILE *fd = user_data;
+    /* it seems somehow thread safe, move to worker thread? */
+    double pos = (double)ftell(fd);
+
+    g_debug("%.2f%%", pos/total_size * 100);
+    return TRUE;
+}
+
+int main(int argc, char **argv)
+{
+    GError *error = NULL;
+    GOptionContext *context = NULL;
+    gchar *client = NULL, **file = NULL;
+    gint port = 5000, compression = SPICE_IMAGE_COMPRESSION_AUTO_GLZ;
+    gboolean wait = FALSE;
+    FILE *fd;
+
+    GOptionEntry entries[] = {
+        { "client", 'c', 0, G_OPTION_ARG_STRING, &client, "Client", "CMD" },
+        { "compression", 'C', 0, G_OPTION_ARG_INT, &compression, "Compression (default 2)", "INT" },
+        { "port", 'p', 0, G_OPTION_ARG_INT, &port, "Server port (default 5000)", "PORT" },
+        { "wait", 'w', 0, G_OPTION_ARG_NONE, &wait, "Wait for client", NULL },
+        { "slow", 's', 0, G_OPTION_ARG_INT, &slow, "Slow down replay", NULL },
+        { G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &file, "replay file", "FILE" },
+        { NULL }
+    };
+
+    context = g_option_context_new("- replay spice server recording");
+    g_option_context_add_main_entries(context, entries, NULL);
+    if (!g_option_context_parse(context, &argc, &argv, &error)) {
+        g_printerr("Option parsing failed: %s\n", error->message);
+        exit(1);
+    }
+    if (!file) {
+        g_printerr("%s\n", g_option_context_get_help(context, TRUE, NULL));
+        exit(1);
+    }
+
+    if (strncmp(file[0], "-", 1) == 0) {
+        fd = stdin;
+    } else {
+        fd = fopen(file[0], "r");
+    }
+    if (fd == NULL) {
+        g_printerr("error opening %s\n", argv[1]);
+        return 1;
+    }
+    if (fcntl(fileno(fd), FD_CLOEXEC) < 0) {
+        perror("fcntl failed");
+        exit(1);
+    }
+    fseek(fd, 0L, SEEK_END);
+    total_size = ftell(fd);
+    fseek(fd, 0L, SEEK_SET);
+    if (total_size > 0)
+        g_timeout_add_seconds(1, progress_timer, fd);
+    replay = spice_replay_new(fd, MAX_SURFACE_NUM);
+
+    aqueue = g_async_queue_new();
+    core = basic_event_loop_init();
+    core->channel_event = replay_channel_event;
+
+    server = spice_server_new();
+    spice_server_set_image_compression(server, compression);
+    spice_server_set_port(server, port);
+    spice_server_set_noauth(server);
+
+    g_print("listening on port %d (insecure)\n", port);
+    spice_server_init(server, core);
+
+    display_sin.base.sif = &display_sif.base;
+    spice_server_add_interface(server, &display_sin.base);
+
+    if (client) {
+        start_client(client, &error);
+        wait = TRUE;
+    }
+
+    if (!wait) {
+        started = TRUE;
+        fill_queue();
+    }
+
+    loop = g_main_loop_new(NULL, FALSE);
+    g_main_loop_run(loop);
+
+    end_replay();
+    g_async_queue_unref(aqueue);
+
+    /* FIXME: there should be a way to join server threads before:
+     * g_main_loop_unref(loop);
+     */
+
+    return 0;
+}
commit 510a6b8dca0ca3e153dd29658005f30093db32a2
Author: Alon Levy <alon at pobox.com>
Date:   Fri Aug 21 09:30:21 2015 +0100

    server/red_worker: record to SPICE_WORKER_RECORD_FILENAME
    
    if the environment variable in the title is set and can be
    opened for writing a log of all display commands (no cursor
    commands yet) and any QXLWorker calls (particularily primary
    create and destroy) will be logged to that file, and possible
    to replay using the replay utility introduced later.
    
    For an example file (4 MB download, 300 MB after unpack with xz,
    these 300 MB are themselves reduced from 1.2GB using zlib compression
    for any chunk):
    
    (old file without a header)
    http://annarchy.freedesktop.org/~alon/win7_boot_shutdown.cmd.xz
    
    Signed-off-by: Alon Levy <alon at pobox.com>
    Signed-off-by: Marc-André Lureau <marcandre.lureau at gmail.com>

diff --git a/server/red_worker.c b/server/red_worker.c
index 1655bbe..7d7858e 100644
--- a/server/red_worker.c
+++ b/server/red_worker.c
@@ -73,6 +73,7 @@
 #include "mjpeg_encoder.h"
 #include "red_memslots.h"
 #include "red_parse_qxl.h"
+#include "red_record_qxl.h"
 #include "jpeg_encoder.h"
 #ifdef USE_LZ4
 #include "lz4_encoder.h"
@@ -166,7 +167,6 @@ static inline red_time_t timespec_to_red_time(struct timespec *time)
     return time->tv_sec * (1000 * 1000 * 1000) + time->tv_nsec;
 }
 
-#if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT)
 static clockid_t clock_id;
 
 typedef unsigned long stat_time_t;
@@ -179,6 +179,7 @@ static stat_time_t stat_now(void)
     return ts.tv_nsec + ts.tv_sec * 1000 * 1000 * 1000;
 }
 
+#if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT)
 double stat_cpu_time_to_sec(stat_time_t time)
 {
     return (double)time / (1000 * 1000 * 1000);
@@ -1034,6 +1035,8 @@ typedef struct RedWorker {
 
     int driver_cap_monitors_config;
     int set_client_capabilities_pending;
+
+    FILE *record_fd;
 } RedWorker;
 
 typedef enum {
@@ -5045,6 +5048,11 @@ static int red_process_commands(RedWorker *worker, uint32_t max_pipe_size, int *
             }
             continue;
         }
+
+        if (worker->record_fd)
+            red_record_qxl_command(worker->record_fd, &worker->mem_slots, ext_cmd,
+                                   stat_now());
+
         stat_inc_counter(worker->command_counter, 1);
         worker->repoll_cmd_ring = 0;
         switch (ext_cmd.cmd.type) {
@@ -11337,6 +11345,11 @@ static void dev_create_primary_surface(RedWorker *worker, uint32_t surface_id,
     if (error) {
         return;
     }
+    if (worker->record_fd) {
+        red_record_dev_input_primary_surface_create(worker->record_fd,
+                    &surface, line_0);
+    }
+
     if (surface.stride < 0) {
         line_0 -= (int32_t)(surface.stride * (surface.height -1));
     }
@@ -11882,6 +11895,13 @@ static void worker_handle_dispatcher_async_done(void *opaque,
     red_dispatcher_async_complete(worker->red_dispatcher, msg_async->cmd);
 }
 
+static void worker_dispatcher_record(void *opaque, uint32_t message_type, void *payload)
+{
+    RedWorker *worker = opaque;
+
+    red_record_event(worker->record_fd, 1, message_type, stat_now());
+}
+
 static void register_callbacks(Dispatcher *dispatcher)
 {
     dispatcher_register_async_done_callback(
@@ -12078,10 +12098,23 @@ static void red_init(RedWorker *worker, WorkerInitData *init_data)
     RedWorkerMessage message;
     Dispatcher *dispatcher;
     int i;
+    const char *record_filename;
 
     spice_assert(sizeof(CursorItem) <= QXL_CURSUR_DEVICE_DATA_SIZE);
 
     memset(worker, 0, sizeof(RedWorker));
+    record_filename = getenv("SPICE_WORKER_RECORD_FILENAME");
+    if (record_filename) {
+        static const char header[] = "SPICE_REPLAY 1\n";
+
+        worker->record_fd = fopen(record_filename, "w+");
+        if (worker->record_fd == NULL) {
+            spice_error("failed to open recording file %s\n", record_filename);
+        }
+	if (fwrite(header, sizeof(header)-1, 1, worker->record_fd) != 1) {
+            spice_error("failed to write replay header");
+        }
+    }
     dispatcher = red_dispatcher_get_dispatcher(init_data->red_dispatcher);
     dispatcher_set_opaque(dispatcher, worker);
     worker->red_dispatcher = init_data->red_dispatcher;
@@ -12089,6 +12122,9 @@ static void red_init(RedWorker *worker, WorkerInitData *init_data)
     worker->id = init_data->id;
     worker->channel = dispatcher_get_recv_fd(dispatcher);
     register_callbacks(dispatcher);
+    if (worker->record_fd) {
+        dispatcher_register_universal_handler(dispatcher, worker_dispatcher_record);
+    }
     worker->pending = init_data->pending;
     worker->cursor_visible = TRUE;
     spice_assert(init_data->num_renderers > 0);
@@ -12170,11 +12206,9 @@ SPICE_GNUC_NORETURN void *red_worker_main(void *arg)
     spice_assert(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW &&
            MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message
 
-#if  defined(RED_WORKER_STAT) || defined(COMPRESS_STAT)
     if (pthread_getcpuclockid(pthread_self(), &clock_id)) {
         spice_error("pthread_getcpuclockid failed");
     }
-#endif
 
     red_init(worker, (WorkerInitData *)arg);
 
commit 3c7026b1802c7e00d30f33c458b67126396ad10a
Author: Alon Levy <alon at pobox.com>
Date:   Fri Jul 1 19:49:42 2011 +0300

    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 at pobox.com>
    Signed-off-by: Marc-André Lureau <marcandre.lureau at gmail.com>

diff --git a/server/Makefile.am b/server/Makefile.am
index 6137249..09b4498 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 0000000..d96fb79
--- /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 0000000..b737db8
--- /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 0000000..a010a58
--- /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 0000000..a89b3d4
--- /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 3c7d945..d65e14d 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;
commit 865455cf321ba7a1d2a7d32065bf40c601cfd494
Author: Alon Levy <alon at pobox.com>
Date:   Wed Jun 6 11:52:53 2012 +0300

    server/dispatcher: add extra_dispatcher, hack for red_record
    
    Signed-off-by: Alon Levy <alon at pobox.com>

diff --git a/server/dispatcher.c b/server/dispatcher.c
index 298f5f9..d6c03ca 100644
--- a/server/dispatcher.c
+++ b/server/dispatcher.c
@@ -135,6 +135,9 @@ static int dispatcher_handle_single_read(Dispatcher *dispatcher)
         /* TODO: close socketpair? */
         return 0;
     }
+    if (dispatcher->any_handler) {
+        dispatcher->any_handler(dispatcher->opaque, type, payload);
+    }
     if (msg->handler) {
         msg->handler(dispatcher->opaque, (void *)payload);
     } else {
@@ -223,6 +226,13 @@ void dispatcher_register_handler(Dispatcher *dispatcher, uint32_t message_type,
     }
 }
 
+void dispatcher_register_universal_handler(
+                               Dispatcher *dispatcher,
+                               dispatcher_handle_any_message any_handler)
+{
+    dispatcher->any_handler = any_handler;
+}
+
 #ifdef DEBUG_DISPATCHER
 static void dummy_handler(int bla)
 {
diff --git a/server/dispatcher.h b/server/dispatcher.h
index 1b389bd..8cfa1d4 100644
--- a/server/dispatcher.h
+++ b/server/dispatcher.h
@@ -8,6 +8,10 @@ typedef struct Dispatcher Dispatcher;
 typedef void (*dispatcher_handle_message)(void *opaque,
                                           void *payload);
 
+typedef void (*dispatcher_handle_any_message)(void *opaque,
+                                              uint32_t message_type,
+                                              void *payload);
+
 typedef void (*dispatcher_handle_async_done)(void *opaque,
                                              uint32_t message_type,
                                              void *payload);
@@ -32,6 +36,7 @@ struct Dispatcher {
     size_t payload_size; /* used to track realloc calls */
     void *opaque;
     dispatcher_handle_async_done handle_async_done;
+    dispatcher_handle_any_message any_handler;
 };
 
 /*
@@ -85,6 +90,13 @@ void dispatcher_register_async_done_callback(
                                     dispatcher_handle_async_done handler);
 
 /*
+ * Hack to allow red_record to see the message being sent so it can record
+ * it to file.
+ */
+void dispatcher_register_universal_handler(Dispatcher *dispatcher,
+                                    dispatcher_handle_any_message handler);
+
+/*
  *  dispatcher_handle_recv_read
  *  @dispatcher: Dispatcher instance
  */
commit f3179ef7913192d7b928cdba3a48bd2b9923cbb6
Author: Marc-André Lureau <marcandre.lureau at redhat.com>
Date:   Tue Aug 27 14:06:42 2013 +0200

    tests: use glib main loop

diff --git a/server/tests/Makefile.am b/server/tests/Makefile.am
index 8b08d1c..a52ad59 100644
--- a/server/tests/Makefile.am
+++ b/server/tests/Makefile.am
@@ -1,6 +1,7 @@
 NULL =
 
 AM_CPPFLAGS =					\
+	$(GLIB2_CFLAGS)				\
 	-I$(top_srcdir)				\
 	-I$(top_srcdir)/common			\
 	-I$(top_srcdir)/server			\
@@ -18,6 +19,7 @@ AM_CPPFLAGS += -DAUTOMATED_TESTS
 endif
 
 LDADD =								\
+	$(GLIB2_LIBS)						\
 	$(top_builddir)/spice-common/common/libspice-common.la	\
 	$(top_builddir)/server/libspice-server.la		\
 	$(GLIB2_LIBS)						\
diff --git a/server/tests/basic_event_loop.c b/server/tests/basic_event_loop.c
index 79a4523..e692f3a 100644
--- a/server/tests/basic_event_loop.c
+++ b/server/tests/basic_event_loop.c
@@ -4,6 +4,7 @@
 #include <sys/time.h>
 #include <signal.h>
 #include <string.h>
+#include <glib.h>
 
 #include "spice/macros.h"
 #include "common/ring.h"
@@ -20,17 +21,12 @@ int debug = 0;
 
 #define NOT_IMPLEMENTED printf("%s not implemented\n", __func__);
 
-static SpiceCoreInterface core;
 
 typedef struct SpiceTimer {
-    RingItem link;
     SpiceTimerFunc func;
-    struct timeval tv_start;
-    int ms;
     void *opaque;
-} Timer;
-
-Ring timers;
+    guint source_id;
+} SpiceTimer;
 
 static SpiceTimer* timer_add(SpiceTimerFunc func, void *opaque)
 {
@@ -38,79 +34,113 @@ static SpiceTimer* timer_add(SpiceTimerFunc func, void *opaque)
 
     timer->func = func;
     timer->opaque = opaque;
-    ring_add(&timers, &timer->link);
+
     return timer;
 }
 
-static void add_ms_to_timeval(struct timeval *tv, int ms)
+static gboolean timer_func(gpointer user_data)
 {
-    tv->tv_usec += 1000 * ms;
-    while (tv->tv_usec >= 1000000) {
-        tv->tv_sec++;
-        tv->tv_usec -= 1000000;
-    }
+    SpiceTimer *timer = user_data;
+
+    timer->source_id = 0;
+    timer->func(timer->opaque);
+    /* timer might be free after func(), don't touch */
+
+    return FALSE;
 }
 
-static void timer_start(SpiceTimer *timer, uint32_t ms)
+static void timer_cancel(SpiceTimer *timer)
 {
-    ASSERT(ms);
-    gettimeofday(&timer->tv_start, NULL);
-    timer->ms = ms;
-    // already add ms to timer value
-    add_ms_to_timeval(&timer->tv_start, ms);
+    if (timer->source_id == 0)
+        return;
+
+    g_source_remove(timer->source_id);
+    timer->source_id = 0;
 }
 
-static void timer_cancel(SpiceTimer *timer)
+static void timer_start(SpiceTimer *timer, uint32_t ms)
 {
-    timer->ms = 0;
+    timer_cancel(timer);
+
+    timer->source_id = g_timeout_add(ms, timer_func, timer);
 }
 
 static void timer_remove(SpiceTimer *timer)
 {
-    ring_remove(&timer->link);
+    timer_cancel(timer);
+    g_free(timer);
 }
 
-struct SpiceWatch {
-    RingItem link;
-    int fd;
-    int event_mask;
-    SpiceWatchFunc func;
-    int removed;
+typedef struct SpiceWatch {
     void *opaque;
-};
+    guint source_id;
+    GIOChannel *channel;
+    SpiceWatchFunc func;
+} SpiceWatch;
+
+static GIOCondition spice_event_to_condition(int event_mask)
+{
+    GIOCondition condition = 0;
+
+    if (event_mask & SPICE_WATCH_EVENT_READ)
+        condition |= G_IO_IN;
+    if (event_mask & SPICE_WATCH_EVENT_WRITE)
+        condition |= G_IO_OUT;
+
+    return condition;
+}
+
+static int condition_to_spice_event(GIOCondition condition)
+{
+    int event = 0;
+
+    if (condition & G_IO_IN)
+        event |= SPICE_WATCH_EVENT_READ;
+    if (condition & G_IO_OUT)
+        event |= SPICE_WATCH_EVENT_WRITE;
+
+    return event;
+}
 
-Ring watches;
+static gboolean watch_func(GIOChannel *source, GIOCondition condition,
+                           gpointer data)
+{
+    SpiceWatch *watch = data;
+    int fd = g_io_channel_unix_get_fd(source);
+
+    watch->func(fd, condition_to_spice_event(condition), watch->opaque);
 
-int watch_count = 0;
+    return TRUE;
+}
 
 static SpiceWatch *watch_add(int fd, int event_mask, SpiceWatchFunc func, void *opaque)
 {
-    SpiceWatch *watch = malloc(sizeof(SpiceWatch));
+    SpiceWatch *watch;
+    GIOCondition condition = spice_event_to_condition(event_mask);
 
-    DPRINTF(0, "adding %p, fd=%d at %d", watch,
-        fd, watch_count);
-    watch->fd = fd;
-    watch->event_mask = event_mask;
+    watch = g_new(SpiceWatch, 1);
+    watch->channel = g_io_channel_unix_new(fd);
+    watch->source_id = g_io_add_watch(watch->channel, condition, watch_func, watch);
     watch->func = func;
     watch->opaque = opaque;
-    watch->removed = FALSE;
-    ring_item_init(&watch->link);
-    ring_add(&watches, &watch->link);
-    watch_count++;
+
     return watch;
 }
 
 static void watch_update_mask(SpiceWatch *watch, int event_mask)
 {
-    DPRINTF(0, "fd %d to %d", watch->fd, event_mask);
-    watch->event_mask = event_mask;
+    GIOCondition condition = spice_event_to_condition(event_mask);
+
+    g_source_remove(watch->source_id);
+    if (condition != 0)
+        watch->source_id = g_io_add_watch(watch->channel, condition, watch_func, watch);
 }
 
 static void watch_remove(SpiceWatch *watch)
 {
-    DPRINTF(0, "remove %p (fd %d)", watch, watch->fd);
-    watch_count--;
-    watch->removed = TRUE;
+    g_source_remove(watch->source_id);
+    g_io_channel_unref(watch->channel);
+    g_free(watch);
 }
 
 static void channel_event(int event, SpiceChannelEventInfo *info)
@@ -119,139 +149,12 @@ static void channel_event(int event, SpiceChannelEventInfo *info)
             info->connection_id, info->type, info->id, event);
 }
 
-SpiceTimer *get_next_timer(void)
-{
-    SpiceTimer *next, *min;
-
-    if (ring_is_empty(&timers)) {
-        return NULL;
-    }
-    min = next = (SpiceTimer*)ring_get_head(&timers);
-    while ((next=(SpiceTimer*)ring_next(&timers, &next->link)) != NULL) {
-        if (next->ms &&
-            (next->tv_start.tv_sec < min->tv_start.tv_sec ||
-             (next->tv_start.tv_sec == min->tv_start.tv_sec &&
-              next->tv_start.tv_usec < min->tv_start.tv_usec))) {
-             min = next;
-        }
-    }
-    return min;
-}
-
-struct timeval now;
-
-void tv_b_minus_a_return_le_zero(struct timeval *a, struct timeval *b, struct timeval *dest)
-{
-    dest->tv_usec = b->tv_usec - a->tv_usec;
-    dest->tv_sec = b->tv_sec - a->tv_sec;
-    while (dest->tv_usec < 0) {
-        dest->tv_usec += 1000000;
-        dest->tv_sec--;
-    }
-    if (dest->tv_sec < 0) {
-        dest->tv_sec = 0;
-        dest->tv_usec = 0;
-    }
-}
-
-void calc_next_timeout(SpiceTimer *next, struct timeval *timeout)
-{
-    gettimeofday(&now, NULL);
-    tv_b_minus_a_return_le_zero(&now, &next->tv_start, timeout);
-}
-
-void timeout_timers(void)
-{
-    SpiceTimer *next;
-    struct timeval left;
-    int count = 0;
-
-    next = (SpiceTimer*)ring_get_head(&timers);
-    while (next != NULL) {
-        tv_b_minus_a_return_le_zero(&now, &next->tv_start, &left);
-        if (next->ms && left.tv_usec == 0 && left.tv_sec == 0) {
-            count++;
-            DPRINTF(2, "calling timer");
-            next->ms = 0;
-            next->func(next->opaque);
-        }
-        next = (SpiceTimer*)ring_next(&timers, &next->link);
-    }
-    DPRINTF(2, "called %d timers", count);
-}
-
 void basic_event_loop_mainloop(void)
 {
-    fd_set rfds, wfds;
-    int max_fd = -1;
-    int i;
-    int retval;
-    SpiceWatch *watch;
-    SpiceTimer *next_timer;
-    RingItem *link;
-    RingItem *next;
-    struct timeval next_timer_timeout;
-    struct timeval *timeout;
-
-    while (1) {
-        FD_ZERO(&rfds);
-        FD_ZERO(&wfds);
-        i = 0;
-        RING_FOREACH_SAFE(link, next, &watches) {
-            watch = (SpiceWatch*)link;
-            if (watch->removed) {
-                continue;
-            }
-            if (watch->event_mask & SPICE_WATCH_EVENT_READ) {
-                FD_SET(watch->fd, &rfds);
-                max_fd = watch->fd > max_fd ? watch->fd : max_fd;
-            }
-            if (watch->event_mask & SPICE_WATCH_EVENT_WRITE) {
-                FD_SET(watch->fd, &wfds);
-                max_fd = watch->fd > max_fd ? watch->fd : max_fd;
-            }
-            i++;
-        }
-        if ((next_timer = get_next_timer()) != NULL) {
-            calc_next_timeout(next_timer, &next_timer_timeout);
-            timeout = &next_timer_timeout;
-            DPRINTF(2, "timeout of %zd.%06zd",
-                    timeout->tv_sec, timeout->tv_usec);
-        } else {
-            timeout = NULL;
-        }
-        DPRINTF(1, "watching %d fds", i);
-        retval = select(max_fd + 1, &rfds, &wfds, NULL, timeout);
-        if (timeout != NULL) {
-            calc_next_timeout(next_timer, &next_timer_timeout);
-            if (next_timer_timeout.tv_sec == 0 &&
-                next_timer_timeout.tv_usec == 0) {
-                timeout_timers();
-            }
-        }
-        if (retval == -1) {
-            printf("error in select - exiting\n");
-            abort();
-        }
-        if (retval) {
-            RING_FOREACH_SAFE(link, next, &watches) {
-                watch = SPICE_CONTAINEROF(link, SpiceWatch, link);
-                if (!watch->removed && (watch->event_mask & SPICE_WATCH_EVENT_READ)
-                     && FD_ISSET(watch->fd, &rfds)) {
-                    watch->func(watch->fd, SPICE_WATCH_EVENT_READ, watch->opaque);
-                }
-                if (!watch->removed && (watch->event_mask & SPICE_WATCH_EVENT_WRITE)
-                     && FD_ISSET(watch->fd, &wfds)) {
-                    watch->func(watch->fd, SPICE_WATCH_EVENT_WRITE, watch->opaque);
-                }
-                if (watch->removed) {
-                    printf("freeing watch %p\n", watch);
-                    ring_remove(&watch->link);
-                    free(watch);
-                }
-            }
-        }
-    }
+    GMainLoop *loop = g_main_loop_new(NULL, FALSE);
+
+    g_main_loop_run(loop);
+    g_main_loop_unref(loop);
 }
 
 static void ignore_sigpipe(void)
@@ -264,21 +167,23 @@ static void ignore_sigpipe(void)
     sigaction(SIGPIPE, &act, NULL);
 }
 
+static SpiceCoreInterface core = {
+    .base = {
+        .major_version = SPICE_INTERFACE_CORE_MAJOR,
+        .minor_version = SPICE_INTERFACE_CORE_MINOR,
+    },
+    .timer_add = timer_add,
+    .timer_start = timer_start,
+    .timer_cancel = timer_cancel,
+    .timer_remove = timer_remove,
+    .watch_add = watch_add,
+    .watch_update_mask = watch_update_mask,
+    .watch_remove = watch_remove,
+    .channel_event = channel_event,
+};
+
 SpiceCoreInterface *basic_event_loop_init(void)
 {
-    ring_init(&watches);
-    ring_init(&timers);
-    memset(&core, 0, sizeof(core));
-    core.base.major_version = SPICE_INTERFACE_CORE_MAJOR;
-    core.base.minor_version = SPICE_INTERFACE_CORE_MINOR; // anything less then 3 and channel_event isn't called
-    core.timer_add = timer_add;
-    core.timer_start = timer_start;
-    core.timer_cancel = timer_cancel;
-    core.timer_remove = timer_remove;
-    core.watch_add = watch_add;
-    core.watch_update_mask = watch_update_mask;
-    core.watch_remove = watch_remove;
-    core.channel_event = channel_event;
     ignore_sigpipe();
     return &core;
 }


More information about the Spice-commits mailing list