/* -*- 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 .
*/
#ifdef HAVE_CONFIG_H
#include
#endif
#include
#include
#include
#include
#include "reds.h"
#include "red-worker.h"
#include "red-common.h"
#include "memslot.h"
#include "red-parse-qxl.h"
#include "red-replay-qxl.h"
#include
#define QXLPHYSICAL_FROM_PTR(ptr) ((QXLPHYSICAL)(intptr_t)(ptr))
#define QXLPHYSICAL_TO_PTR(phy) ((void*)(intptr_t)(phy))
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;
pthread_mutex_t mutex;
pthread_cond_t 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;
pthread_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);
}
pthread_mutex_unlock(&replay->mutex);
return newid;
}
static uint32_t replay_id_new(SpiceReplay *replay, uint32_t id)
{
uint32_t new_id;
uint32_t *map;
pthread_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;
pthread_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;
pthread_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;
pthread_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);
}
pthread_cond_signal(&replay->cond);
pthread_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 = spice_malloc(*size + base_size);
}
#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 = spice_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, *next;
replay_fscanf(replay, "data_chunks %d %zu\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;
next = QXLPHYSICAL_TO_PTR(cur->next_chunk);
next->prev_chunk = QXLPHYSICAL_FROM_PTR(cur);
next->data_size = next_data_size;
next->next_chunk = 0;
cur = next;
}
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 = QXLPHYSICAL_TO_PTR(cur->next_chunk);
while (cur) {
QXLDataChunk *next = QXLPHYSICAL_TO_PTR(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\n", 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 = QXLPHYSICAL_TO_PTR(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*)spice_malloc0(sizeof(QXLImage));
replay_fscanf(replay, "descriptor.id %"PRIu64"\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 = spice_malloc(sizeof(QXLPalette) + num_ents * sizeof(qp->ents[0]));
qp->num_ents = num_ents;
qxl->bitmap.palette = QXLPHYSICAL_FROM_PTR(qp);
replay_fscanf(replay, "unique %"PRIu64"\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_FROM_PTR(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, %zu != %zu", 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 = QXLPHYSICAL_TO_PTR(p);
if (!qxl)
return;
switch (qxl->descriptor.type) {
case SPICE_IMAGE_TYPE_BITMAP:
free(QXLPHYSICAL_TO_PTR(qxl->bitmap.palette));
if (qxl->bitmap.flags & QXL_BITMAP_DIRECT) {
free(QXLPHYSICAL_TO_PTR(qxl->bitmap.data));
} else {
red_replay_data_chunks_free(replay, QXLPHYSICAL_TO_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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_FROM_PTR(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(QXLPHYSICAL_TO_PTR(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_FROM_PTR(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, QXLPHYSICAL_TO_PTR(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_FROM_PTR(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, QXLPHYSICAL_TO_PTR(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_FROM_PTR(red_replay_image(replay, flags));
replay_fscanf(replay, "src_transform %d\n", &enabled);
qxl->src_transform = enabled ? QXLPHYSICAL_FROM_PTR(red_replay_transform(replay)) : 0;
replay_fscanf(replay, "mask %d\n", &enabled);
qxl->mask = enabled ? QXLPHYSICAL_FROM_PTR(red_replay_image(replay, flags)) : 0;
replay_fscanf(replay, "mask_transform %d\n", &enabled);
qxl->mask_transform = enabled ? QXLPHYSICAL_FROM_PTR(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(QXLPHYSICAL_TO_PTR(qxl->src_transform));
red_replay_image_free(replay, qxl->mask, flags);
free(QXLPHYSICAL_TO_PTR(qxl->mask_transform));
}
static QXLDrawable *red_replay_native_drawable(SpiceReplay *replay, uint32_t flags)
{
QXLDrawable *qxl = spice_malloc0(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 = spice_malloc0(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_FROM_PTR(red_replay_compat_drawable(replay, flags));
} else {
return QXLPHYSICAL_FROM_PTR(red_replay_native_drawable(replay, flags));
}
}
static QXLUpdateCmd *red_replay_update_cmd(SpiceReplay *replay)
{
QXLUpdateCmd *qxl = spice_malloc0(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 = spice_malloc0(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) != 0) {
read_binary(replay, "data", &read_size, (uint8_t**)&qxl->u.surface_create.data, 0);
if (read_size != size) {
spice_printerr("mismatch %zu != %zu", size, read_size);
}
} else {
qxl->u.surface_create.data = QXLPHYSICAL_FROM_PTR(spice_malloc(size));
}
qxl->surface_id = replay_id_new(replay, qxl->surface_id);
break;
case QXL_SURFACE_CMD_DESTROY:
qxl->u.surface_create.data = 0;
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(QXLPHYSICAL_TO_PTR(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: original 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_FROM_PTR(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 %"PRIu64"\n", &counter,
&what, &type, ×tamp);
if (replay->eof) {
return NULL;
}
if (what == 1) {
replay_handle_dev_input(worker, replay, type);
}
}
cmd = g_new(QXLCommandExt, 1);
cmd->cmd.type = type;
cmd->group_id = 0;
spice_debug("command %"PRIu64", %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_FROM_PTR(red_replay_update_cmd(replay));
break;
case QXL_CMD_MESSAGE:
cmd->cmd.data = QXLPHYSICAL_FROM_PTR(red_replay_message(replay));
break;
case QXL_CMD_SURFACE:
cmd->cmd.data = QXLPHYSICAL_FROM_PTR(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 = QXLPHYSICAL_TO_PTR(cmd->cmd.data);
info->id = (uintptr_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 = QXLPHYSICAL_TO_PTR(cmd->cmd.data);
red_replay_native_drawable_free(replay, qxl, cmd->flags);
break;
}
case QXL_CMD_UPDATE: {
QXLUpdateCmd *qxl = QXLPHYSICAL_TO_PTR(cmd->cmd.data);
free(qxl);
break;
}
case QXL_CMD_SURFACE: {
QXLSurfaceCmd *qxl = QXLPHYSICAL_TO_PTR(cmd->cmd.data);
red_replay_surface_cmd_free(replay, qxl);
break;
}
default:
break;
}
g_free(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;
}
} else {
spice_warning("This doesn't look like a valid replay file");
return NULL;
}
replay = spice_malloc0(sizeof(SpiceReplay));
replay->eof = 0;
replay->fd = file;
replay->created_primary = FALSE;
pthread_mutex_init(&replay->mutex, NULL);
pthread_cond_init(&replay->cond, NULL);
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);
pthread_mutex_destroy(&replay->mutex);
pthread_cond_destroy(&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);
}