summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorYaniv Kamay <ykamay@redhat.com>2009-09-19 21:25:46 +0300
committerYaniv Kamay <ykamay@redhat.com>2009-10-14 15:06:41 +0200
commitc1b79eb035fa158fb2ac3bc8e559809611070016 (patch)
tree3348dd749a700dedf87c9b16fe8be77c62928df8 /server
downloadspice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.gz
spice-c1b79eb035fa158fb2ac3bc8e559809611070016.tar.xz
spice-c1b79eb035fa158fb2ac3bc8e559809611070016.zip
fresh start
Diffstat (limited to 'server')
-rw-r--r--server/.gitignore8
-rw-r--r--server/Makefile.am81
-rw-r--r--server/glz_encode_match_tmpl.c151
-rw-r--r--server/glz_encode_tmpl.c572
-rw-r--r--server/glz_encoder.c308
-rw-r--r--server/glz_encoder.h56
-rw-r--r--server/glz_encoder_config.h64
-rw-r--r--server/glz_encoder_dictionary.c618
-rw-r--r--server/glz_encoder_dictionary.h70
-rw-r--r--server/glz_encoder_dictionary_protected.h187
-rw-r--r--server/red_bitmap_utils.h162
-rw-r--r--server/red_client_cache.h136
-rw-r--r--server/red_client_shared_cache.h216
-rw-r--r--server/red_common.h99
-rw-r--r--server/red_dispatcher.c479
-rw-r--r--server/red_dispatcher.h33
-rw-r--r--server/red_worker.c8492
-rw-r--r--server/red_worker.h134
-rw-r--r--server/red_yuv.h154
-rw-r--r--server/reds.c4996
-rw-r--r--server/reds.h70
-rw-r--r--server/snd_worker.c1300
-rw-r--r--server/snd_worker.h33
-rw-r--r--server/spice.h29
-rw-r--r--server/stat.h48
-rw-r--r--server/vd_interface.h334
26 files changed, 18830 insertions, 0 deletions
diff --git a/server/.gitignore b/server/.gitignore
new file mode 100644
index 00000000..07491dd4
--- /dev/null
+++ b/server/.gitignore
@@ -0,0 +1,8 @@
+*.la
+*.lo
+*.loT
+*.o
+.deps
+.libs
+Makefile
+Makefile.in
diff --git a/server/Makefile.am b/server/Makefile.am
new file mode 100644
index 00000000..e6ffab40
--- /dev/null
+++ b/server/Makefile.am
@@ -0,0 +1,81 @@
+NULL =
+
+INCLUDES = \
+ -I. \
+ -I$(top_srcdir)/common \
+ $(FFMPEG_CFLAGS) \
+ $(QCAIRO_CFLAGS) \
+ $(GL_CFLAGS) \
+ $(LOG4CPP_CFLAGS) \
+ $(SSL_CFLAGS) \
+ $(CELT051_CFLAGS) \
+ -DCAIRO_CANVAS_IMAGE_CACHE \
+ -DRED_STATISTICS \
+ $(WARN_CFLAGS) \
+ $(VISIBILITY_HIDDEN_CFLAGS) \
+ $(NULL)
+
+COMMON_SRCS = \
+ $(top_srcdir)/common/cairo_canvas.c \
+ $(top_srcdir)/common/gl_canvas.c \
+ $(top_srcdir)/common/region.c \
+ $(top_srcdir)/common/glc.c \
+ $(top_srcdir)/common/ogl_ctx.c \
+ $(top_srcdir)/common/rop3.c \
+ $(top_srcdir)/common/quic.c \
+ $(top_srcdir)/common/lz.c \
+ $(top_srcdir)/common/canvas_utils.c \
+ $(NULL)
+
+lib_LTLIBRARIES = libspice.la
+
+libspice_la_LDFLAGS = \
+ -version-number $(SPICE_LT_VERSION) \
+ -no-undefined \
+ $(NULL)
+
+libspice_la_LIBADD = \
+ $(GL_LIBS) \
+ $(FFMPEG_LIBS) \
+ $(QCAIRO_LIBS) \
+ $(SSL_LIBS) \
+ $(CELT051_LIBS) \
+ $(LIBRT) \
+ $(NULL)
+
+libspice_la_SOURCES = \
+ glz_encoder.c \
+ glz_encoder_config.h \
+ glz_encoder_dictionary.c \
+ glz_encoder_dictionary.h \
+ glz_encoder_dictionary_protected.h \
+ glz_encoder.h \
+ red_bitmap_utils.h \
+ red_client_cache.h \
+ red_client_shared_cache.h \
+ red_common.h \
+ red_dispatcher.c \
+ red_dispatcher.h \
+ reds.c \
+ reds.h \
+ stat.h \
+ red_worker.c \
+ red_worker.h \
+ red_yuv.h \
+ snd_worker.c \
+ snd_worker.h \
+ spice.h \
+ vd_interface.h \
+ $(COMMON_SRCS) \
+ $(NULL)
+
+libspiceincludedir = $(includedir)/libspice
+libspiceinclude_HEADERS = \
+ spice.h \
+ vd_interface.h \
+ $(NULL)
+
+EXTRA_DIST = \
+ glz_encode_match_tmpl.c \
+ glz_encode_tmpl.c \
+ $(NULL)
diff --git a/server/glz_encode_match_tmpl.c b/server/glz_encode_match_tmpl.c
new file mode 100644
index 00000000..01427173
--- /dev/null
+++ b/server/glz_encode_match_tmpl.c
@@ -0,0 +1,151 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#define SHORT_PIX_IMAGE_DIST_LEVEL_1 64 //(1 << 6)
+#define SHORT_PIX_IMAGE_DIST_LEVEL_2 16384 // (1 << 14)
+#define SHORT_PIX_IMAGE_DIST_LEVEL_3 4194304 // (1 << 22)
+#define FAR_PIX_IMAGE_DIST_LEVEL_1 256 // (1 << 8)
+#define FAR_PIX_IMAGE_DIST_LEVEL_2 65536 // (1 << 16)
+#define FAR_PIX_IMAGE_DIST_LEVEL_3 16777216 // (1 << 24)
+
+/* if image_distance = 0, pixel_distance is the distance between the matching pixels.
+ Otherwise, it is the offset from the beginning of the referred image */
+#if defined(GLZ_ENCODE_MATCH) /* actually perfroming the encoding */
+static INLINE void encode_match(Encoder *encoder, uint32_t image_distance,
+ size_t pixel_distance, size_t len)
+#elif defined(GLZ_ENCODE_SIZE) /* compute the size of the encoding except for the match length*/
+static INLINE int get_encode_ref_size(uint32_t image_distance, size_t pixel_distance)
+#endif
+{
+#if defined(GLZ_ENCODE_SIZE)
+ int encode_size;
+#endif
+
+#if defined(GLZ_ENCODE_MATCH)
+ /* encoding the match length + Long/Short dist bit + 12 LSB pixels of pixel_distance*/
+ if (len < 7) {
+ if (pixel_distance < MAX_PIXEL_SHORT_DISTANCE) {
+ encode(encoder, (uint8_t)((len << 5) + (pixel_distance & 0x0f)));
+ } else {
+ encode(encoder, (uint8_t)((len << 5) + 16 + (pixel_distance & 0x0f)));
+ }
+ encode(encoder, (uint8_t)((pixel_distance >> 4) & 255));
+ } else {
+ if (pixel_distance < MAX_PIXEL_SHORT_DISTANCE) {
+ encode(encoder, (uint8_t)((7 << 5) + (pixel_distance & 0x0f)));
+ } else {
+ encode(encoder, (uint8_t)((7 << 5) + 16 + (pixel_distance & 0x0f)));
+ }
+ for (len -= 7; len >= 255; len -= 255) {
+ encode(encoder, 255);
+ }
+ encode(encoder, (uint8_t)len);
+ encode(encoder, (uint8_t)((pixel_distance >> 4) & 255));
+ }
+#endif
+
+
+ /* encoding the rest of the pixel ditsance and the image_dist and its 2 control bits */
+
+ /* The first 2 MSB bits indicate how many more bytes should be read for image dist */
+ if (pixel_distance < MAX_PIXEL_SHORT_DISTANCE) {
+ if (image_distance < SHORT_PIX_IMAGE_DIST_LEVEL_1) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)(image_distance & 0x3f));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 3;
+#endif
+ } else if (image_distance < SHORT_PIX_IMAGE_DIST_LEVEL_2) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)((1 << 6) + (image_distance & 0x3f)));
+ encode(encoder, (uint8_t)((image_distance >> 6) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 4;
+#endif
+ } else if (image_distance < SHORT_PIX_IMAGE_DIST_LEVEL_3) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)((1 << 7) + (image_distance & 0x3f)));
+ encode(encoder, (uint8_t)((image_distance >> 6) & 255));
+ encode(encoder, (uint8_t)((image_distance >> 14) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 5;
+#endif
+ } else {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)((1 << 7) + (1 << 6) + (image_distance & 0x3f)));
+ encode(encoder, (uint8_t)((image_distance >> 6) & 255));
+ encode(encoder, (uint8_t)((image_distance >> 14) & 255));
+ encode(encoder, (uint8_t)((image_distance >> 22) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 6;
+#endif
+ }
+ } else {
+ /* the third MSB bit indicates if the pixel_distance is medium/long*/
+ uint8_t long_dist_control = (pixel_distance < MAX_PIXEL_MEDIUM_DISTANCE) ? 0 : 32;
+ if (image_distance == 0) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)(long_dist_control + ((pixel_distance >> 12) & 31)));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 3;
+#endif
+ } else if (image_distance < FAR_PIX_IMAGE_DIST_LEVEL_1) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder,
+ (uint8_t)(long_dist_control + (1 << 6) + ((pixel_distance >> 12) & 31)));
+ encode(encoder, (uint8_t)(image_distance & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 4;
+#endif
+ } else if (image_distance < FAR_PIX_IMAGE_DIST_LEVEL_2) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder,
+ (uint8_t)(long_dist_control + (1 << 7) + ((pixel_distance >> 12) & 31)));
+ encode(encoder, (uint8_t)(image_distance & 255));
+ encode(encoder, (uint8_t)((image_distance >> 8) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 5;
+#endif
+ } else {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder,
+ (uint8_t)(long_dist_control + (1 << 7) + (1 << 6) +
+ ((pixel_distance >> 12) & 31)));
+ encode(encoder, (uint8_t)(image_distance & 255));
+ encode(encoder, (uint8_t)((image_distance >> 8) & 255));
+ encode(encoder, (uint8_t)((image_distance >> 16) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size = 6;
+#endif
+ }
+
+ if (long_dist_control) {
+#if defined(GLZ_ENCODE_MATCH)
+ encode(encoder, (uint8_t)((pixel_distance >> 17) & 255));
+#elif defined(GLZ_ENCODE_SIZE)
+ encode_size++;
+#endif
+ }
+ }
+
+#if defined(GLZ_ENCODE_SIZE)
+ return encode_size;
+#endif
+}
+
+#undef GLZ_ENCODE_SIZE
+#undef GLZ_ENCODE_MATCH
diff --git a/server/glz_encode_tmpl.c b/server/glz_encode_tmpl.c
new file mode 100644
index 00000000..32b68fea
--- /dev/null
+++ b/server/glz_encode_tmpl.c
@@ -0,0 +1,572 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#define DJB2_START 5381;
+#define DJB2_HASH(hash, c) (hash = ((hash << 5) + hash) ^ (c)) //|{hash = ((hash << 5) + hash) + c;}
+
+/*
+ For each pixel type the following macros are defined:
+ PIXEL : input type
+ FNAME(name)
+ ENCODE_PIXEL(encoder, pixel) : writing a pixel to the compressed buffer (byte by byte)
+ SAME_PIXEL(pix1, pix2) : comparing two pixels
+ HASH_FUNC(value, pix_ptr) : hash func of 3 consecutive pixels
+*/
+
+#ifdef LZ_PLT
+#define PIXEL one_byte_pixel_t
+#define FNAME(name) glz_plt_##name
+#define ENCODE_PIXEL(e, pix) encode(e, (pix).a) // gets the pixel and write only the needed bytes
+ // from the pixel
+#define SAME_PIXEL(pix1, pix2) ((pix1).a == (pix2).a)
+#define MIN_REF_ENCODE_SIZE 4
+#define MAX_REF_ENCODE_SIZE 7
+#define HASH_FUNC(v, p) { \
+ v = DJB2_START; \
+ DJB2_HASH(v, p[0].a); \
+ DJB2_HASH(v, p[1].a); \
+ DJB2_HASH(v, p[2].a); \
+ v &= HASH_MASK; \
+ }
+#endif
+
+#ifdef LZ_RGB_ALPHA
+//#undef LZ_RGB_ALPHA
+#define PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb_alpha_##name
+#define ENCODE_PIXEL(e, pix) {encode(e, (pix).pad);}
+#define SAME_PIXEL(pix1, pix2) ((pix1).pad == (pix2).pad)
+#define MIN_REF_ENCODE_SIZE 4
+#define MAX_REF_ENCODE_SIZE 7
+#define HASH_FUNC(v, p) { \
+ v = DJB2_START; \
+ DJB2_HASH(v, p[0].pad); \
+ DJB2_HASH(v, p[1].pad); \
+ DJB2_HASH(v, p[2].pad); \
+ v &= HASH_MASK; \
+ }
+#endif
+
+
+#ifdef LZ_RGB16
+#define PIXEL rgb16_pixel_t
+#define FNAME(name) glz_rgb16_##name
+#define GET_r(pix) (((pix) >> 10) & 0x1f)
+#define GET_g(pix) (((pix) >> 5) & 0x1f)
+#define GET_b(pix) ((pix) & 0x1f)
+#define ENCODE_PIXEL(e, pix) {encode(e, (pix) >> 8); encode(e, (pix) & 0xff);}
+#define MIN_REF_ENCODE_SIZE 2
+#define MAX_REF_ENCODE_SIZE 3
+#define HASH_FUNC(v, p) { \
+ v = DJB2_START; \
+ DJB2_HASH(v, p[0] & (0x00ff)); \
+ DJB2_HASH(v, (p[0] >> 8) & (0x007f)); \
+ DJB2_HASH(v, p[1] & (0x00ff)); \
+ DJB2_HASH(v, (p[1] >> 8) & (0x007f)); \
+ DJB2_HASH(v, p[2] & (0x00ff)); \
+ DJB2_HASH(v, (p[2] >> 8) & (0x007f)); \
+ v &= HASH_MASK; \
+}
+#endif
+
+#ifdef LZ_RGB24
+#define PIXEL rgb24_pixel_t
+#define FNAME(name) glz_rgb24_##name
+#define ENCODE_PIXEL(e, pix) {encode(e, (pix).b); encode(e, (pix).g); encode(e, (pix).r);}
+#define MIN_REF_ENCODE_SIZE 2
+#define MAX_REF_ENCODE_SIZE 2
+#endif
+
+#ifdef LZ_RGB32
+#define PIXEL rgb32_pixel_t
+#define FNAME(name) glz_rgb32_##name
+#define ENCODE_PIXEL(e, pix) {encode(e, (pix).b); encode(e, (pix).g); encode(e, (pix).r);}
+#define MIN_REF_ENCODE_SIZE 2
+#define MAX_REF_ENCODE_SIZE 2
+#endif
+
+
+#if defined(LZ_RGB24) || defined(LZ_RGB32)
+#define GET_r(pix) ((pix).r)
+#define GET_g(pix) ((pix).g)
+#define GET_b(pix) ((pix).b)
+#define HASH_FUNC(v, p) { \
+ v = DJB2_START; \
+ DJB2_HASH(v, p[0].r); \
+ DJB2_HASH(v, p[0].g); \
+ DJB2_HASH(v, p[0].b); \
+ DJB2_HASH(v, p[1].r); \
+ DJB2_HASH(v, p[1].g); \
+ DJB2_HASH(v, p[1].b); \
+ DJB2_HASH(v, p[2].r); \
+ DJB2_HASH(v, p[2].g); \
+ DJB2_HASH(v, p[2].b); \
+ v &= HASH_MASK; \
+ }
+#endif
+
+#if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32)
+#define SAME_PIXEL(p1, p2) (GET_r(p1) == GET_r(p2) && GET_g(p1) == GET_g(p2) && \
+ GET_b(p1) == GET_b(p2))
+
+#endif
+
+#ifndef LZ_PLT
+#define PIXEL_ID(pix_ptr, seg_ptr) \
+ ((pix_ptr) - ((PIXEL *)(seg_ptr)->lines) + (seg_ptr)->pixels_so_far)
+#define PIXEL_DIST(src_pix_ptr, src_seg_ptr, ref_pix_ptr, ref_seg_ptr) \
+ (PIXEL_ID(src_pix_ptr,src_seg_ptr) - PIXEL_ID(ref_pix_ptr, ref_seg_ptr))
+#else
+#define PIXEL_ID(pix_ptr, seg_ptr, pix_per_byte) \
+ (((pix_ptr) - ((PIXEL *)(seg_ptr)->lines)) * pix_per_byte + (seg_ptr)->pixels_so_far)
+#define PIXEL_DIST(src_pix_ptr, src_seg_ptr, ref_pix_ptr, ref_seg_ptr, pix_per_byte) \
+ ((PIXEL_ID(src_pix_ptr,src_seg_ptr, pix_per_byte) - \
+ PIXEL_ID(ref_pix_ptr, ref_seg_ptr, pix_per_byte)) / pix_per_byte)
+#endif
+
+/* returns the length of the match. 0 if no match.
+ if image_distance = 0, pixel_distance is the distance between the matching pixels.
+ Otherwise, it is the offset from the beginning of the referred image */
+static INLINE size_t FNAME(do_match)(SharedDictionary *dict,
+ WindowImageSegment *ref_seg, const PIXEL *ref,
+ const PIXEL *ref_limit,
+ WindowImageSegment *ip_seg, const PIXEL *ip,
+ const PIXEL *ip_limit,
+#ifdef LZ_PLT
+ int pix_per_byte,
+#endif
+ size_t *o_image_dist, size_t *o_pix_distance)
+{
+ int encode_size;
+ const PIXEL *tmp_ip = ip;
+ const PIXEL *tmp_ref = ref;
+
+ if (ref > (ref_limit - MIN_REF_ENCODE_SIZE)) {
+ return 0; // in case the hash entry is not relvant
+ }
+
+
+ /* min match lenght == MIN_REF_ENCODE_SIZE (depends on pixel type) */
+
+ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) {
+ return 0;
+ } else {
+ tmp_ref++;
+ tmp_ip++;
+ }
+
+
+ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) {
+ return 0;
+ } else {
+ tmp_ref++;
+ tmp_ip++;
+ }
+
+#if defined(LZ_PLT) || defined(LZ_RGB_ALPHA)
+ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) {
+ return 0;
+ } else {
+ tmp_ref++;
+ tmp_ip++;
+ }
+
+
+ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) {
+ return 0;
+ } else {
+ tmp_ref++;
+ tmp_ip++;
+ }
+
+#endif
+
+
+ *o_image_dist = ip_seg->image->id - ref_seg->image->id;
+
+ if (!(*o_image_dist)) { // the ref is inside the same image - encode distance
+#ifndef LZ_PLT
+ *o_pix_distance = PIXEL_DIST(ip, ip_seg, ref, ref_seg);
+#else
+ // in bytes
+ *o_pix_distance = PIXEL_DIST(ip, ip_seg, ref, ref_seg, pix_per_byte);
+#endif
+ } else { // the ref is at different image - encode offset from the image start
+#ifndef LZ_PLT
+ *o_pix_distance = PIXEL_DIST(ref, ref_seg,
+ (PIXEL *)(dict->window.segs[ref_seg->image->first_seg].lines),
+ &dict->window.segs[ref_seg->image->first_seg]
+ );
+#else
+ // in bytes
+ *o_pix_distance = PIXEL_DIST(ref, ref_seg,
+ (PIXEL *)(dict->window.segs[ref_seg->image->first_seg].lines),
+ &dict->window.segs[ref_seg->image->first_seg],
+ pix_per_byte);
+#endif
+ }
+
+ if ((*o_pix_distance == 0) || (*o_pix_distance >= MAX_PIXEL_LONG_DISTANCE) ||
+ (*o_image_dist > MAX_IMAGE_DIST)) {
+ return 0;
+ }
+
+
+ /* continue the match*/
+ while ((tmp_ip < ip_limit) && (tmp_ref < ref_limit)) {
+ if (!SAME_PIXEL(*tmp_ref, *tmp_ip)) {
+ break;
+ } else {
+ tmp_ref++;
+ tmp_ip++;
+ }
+ }
+
+
+ if ((tmp_ip - ip) > MAX_REF_ENCODE_SIZE) {
+ return (tmp_ip - ip);
+ }
+
+ encode_size = get_encode_ref_size(*o_image_dist, *o_pix_distance);
+
+ // min number of identical pixels for a match
+#if defined(LZ_RGB16)
+ encode_size /= 2;
+#elif defined(LZ_RGB24) || defined(LZ_RGB32)
+ encode_size /= 3;
+#endif
+
+ encode_size++; // the minimum match
+ // match len is smaller than the encoding - not worth encoding
+ if ((tmp_ip - ip) < encode_size) {
+ return 0;
+ }
+ return (tmp_ip - ip);
+}
+
+/* compresses one segment starting from 'from'.
+ In order to encode a match, we use pixels resolution when we encode RGB image,
+ and bytes count when we encode PLT.
+*/
+static void FNAME(compress_seg)(Encoder *encoder, uint32_t seg_idx, PIXEL *from, int copied)
+{
+ WindowImageSegment *seg = &encoder->dict->window.segs[seg_idx];
+ const PIXEL *ip = from;
+ const PIXEL *ip_bound = (PIXEL *)(seg->lines_end) - BOUND_OFFSET;
+ const PIXEL *ip_limit = (PIXEL *)(seg->lines_end) - LIMIT_OFFSET;
+ int hval;
+ int copy = copied;
+#ifdef LZ_PLT
+ int pix_per_byte = PLT_PIXELS_PER_BYTE[encoder->cur_image.type];
+#endif
+
+#ifdef DEBUG_ENCODE
+ int n_encoded = 0;
+#endif
+
+ if (copy == 0) {
+ encode_copy_count(encoder, MAX_COPY - 1);
+ }
+
+
+ while (LZ_EXPECT_CONDITIONAL(ip < ip_limit)) {
+ const PIXEL *ref;
+ const PIXEL *ref_limit;
+ WindowImageSegment *ref_seg;
+ uint32_t ref_seg_idx;
+ size_t pix_dist;
+ size_t image_dist;
+ /* minimum match length */
+ size_t len = 0;
+
+ /* comparison starting-point */
+ const PIXEL *anchor = ip;
+#ifdef CHAINED_HASH
+ int hash_id = 0;
+ size_t best_len = 0;
+ size_t best_pix_dist = 0;
+ size_t best_image_dist = 0;
+#endif
+
+ /* check for a run */
+
+ if (LZ_EXPECT_CONDITIONAL(ip > (PIXEL *)(seg->lines))) {
+ if (SAME_PIXEL(ip[-1], ip[0]) && SAME_PIXEL(ip[0], ip[1]) && SAME_PIXEL(ip[1], ip[2])) {
+ PIXEL x;
+ pix_dist = 1;
+ image_dist = 0;
+
+ ip += 3;
+ ref = anchor + 2;
+ ref_limit = (PIXEL *)(seg->lines_end);
+ len = 3;
+
+ x = *ref;
+
+ while (ip < ip_bound) { // TODO: maybe separate a run from the same seg or from
+ // different ones in order to spare ref < ref_limit
+ if (!SAME_PIXEL(*ip, x)) {
+ ip++;
+ break;
+ } else {
+ ip++;
+ len++;
+ }
+ }
+
+ goto match;
+ } // END RLE MATCH
+ }
+
+ /* find potential match */
+ HASH_FUNC(hval, ip);
+
+#ifdef CHAINED_HASH
+ for (hash_id = 0; hash_id < HASH_CHAIN_SIZE; hash_id++) {
+ ref_seg_idx = encoder->dict->htab[hval][hash_id].image_seg_idx;
+#else
+ ref_seg_idx = encoder->dict->htab[hval].image_seg_idx;
+#endif
+ ref_seg = encoder->dict->window.segs + ref_seg_idx;
+ if (REF_SEG_IS_VALID(encoder->dict, encoder->id,
+ ref_seg, seg)) {
+#ifdef CHAINED_HASH
+ ref = ((PIXEL *)ref_seg->lines) + encoder->dict->htab[hval][hash_id].ref_pix_idx;
+#else
+ ref = ((PIXEL *)ref_seg->lines) + encoder->dict->htab[hval].ref_pix_idx;
+#endif
+ ref_limit = (PIXEL *)ref_seg->lines_end;
+
+ len = FNAME(do_match)(encoder->dict, ref_seg, ref, ref_limit, seg, ip, ip_bound,
+#ifdef LZ_PLT
+ pix_per_byte,
+#endif
+ &image_dist, &pix_dist);
+
+#ifdef CHAINED_HASH
+ // TODO. not compare len but rather len - encode_size
+ if (len > best_len) {
+ best_len = len;
+ best_pix_dist = pix_dist;
+ best_image_dist = image_dist;
+ }
+#endif
+ }
+
+#ifdef CHAINED_HASH
+ } // end chain loop
+ len = best_len;
+ pix_dist = best_pix_dist;
+ image_dist = best_image_dist;
+#endif
+
+ /* update hash table */
+ UPDATE_HASH(encoder->dict, hval, seg_idx, anchor - ((PIXEL *)seg->lines));
+
+ if (!len) {
+ goto literal;
+ }
+
+match: // RLE or dictionary (both are encoded by distance from ref (-1) and length)
+#ifdef DEBUG_ENCODE
+ printf(", match(%d, %d, %d)", image_dist, pix_dist, len);
+ n_encoded += len;
+#endif
+
+ /* distance is biased */
+ if (!image_dist) {
+ pix_dist--;
+ }
+
+ /* if we have copied something, adjust the copy count */
+ if (copy) {
+ /* copy is biased, '0' means 1 byte copy */
+ update_copy_count(encoder, copy - 1);
+ } else {
+ /* back, to overwrite the copy count */
+ compress_output_prev(encoder);
+ }
+
+ /* reset literal counter */
+ copy = 0;
+
+ /* length is biased, '1' means a match of 3 pixels for PLT and alpha*/
+ /* for RGB 16 1 means 2 */
+ /* for RGB24/32 1 means 1...*/
+ ip = anchor + len - 2;
+
+#if defined(LZ_RGB16)
+ len--;
+#elif defined(LZ_PLT) || defined(LZ_RGB_ALPHA)
+ len -= 2;
+#endif
+ GLZ_ASSERT(encoder->usr, len > 0);
+ encode_match(encoder, image_dist, pix_dist, len);
+
+ /* update the hash at match boundary */
+#if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32)
+ if (ip > anchor) {
+#endif
+ HASH_FUNC(hval, ip);
+ UPDATE_HASH(encoder->dict, hval, seg_idx, ip - ((PIXEL *)seg->lines));
+ ip++;
+#if defined(LZ_RGB16) || defined(LZ_RGB24) || defined(LZ_RGB32)
+ } else {ip++;
+ }
+#endif
+#if defined(LZ_RGB24) || defined(LZ_RGB32)
+ if (ip > anchor) {
+#endif
+ HASH_FUNC(hval, ip);
+ UPDATE_HASH(encoder->dict, hval, seg_idx, ip - ((PIXEL *)seg->lines));
+ ip++;
+#if defined(LZ_RGB24) || defined(LZ_RGB32)
+ } else {
+ ip++;
+ }
+#endif
+ /* assuming literal copy */
+ encode_copy_count(encoder, MAX_COPY - 1);
+ continue;
+
+literal:
+#ifdef DEBUG_ENCODE
+ printf(", copy");
+ n_encoded++;
+#endif
+ ENCODE_PIXEL(encoder, *anchor);
+ anchor++;
+ ip = anchor;
+ copy++;
+
+ if (LZ_UNEXPECT_CONDITIONAL(copy == MAX_COPY)) {
+ copy = 0;
+ encode_copy_count(encoder, MAX_COPY - 1);
+ }
+ } // END LOOP (ip < ip_limit)
+
+
+ /* left-over as literal copy */
+ ip_bound++;
+ while (ip <= ip_bound) {
+#ifdef DEBUG_ENCODE
+ printf(", copy");
+ n_encoded++;
+#endif
+ ENCODE_PIXEL(encoder, *ip);
+ ip++;
+ copy++;
+ if (copy == MAX_COPY) {
+ copy = 0;
+ encode_copy_count(encoder, MAX_COPY - 1);
+ }
+ }
+
+ /* if we have copied something, adjust the copy length */
+ if (copy) {
+ update_copy_count(encoder, copy - 1);
+ } else {
+ compress_output_prev(encoder);
+ }
+#ifdef DEBUG_ENCODE
+ printf("\ntotal encoded=%d\n", n_encoded);
+#endif
+}
+
+
+/* If the file is very small, copies it.
+ copies the first two pixels of the first segment, and sends the segments
+ one by one to compress_seg.
+ the number of bytes compressed are stored inside encoder. */
+static void FNAME(compress)(Encoder *encoder)
+{
+ uint32_t seg_id = encoder->cur_image.first_win_seg;
+ PIXEL *ip;
+ SharedDictionary *dict = encoder->dict;
+ int hval;
+
+ // fetch the first image segment that is not too small
+ while ((seg_id != NULL_IMAGE_SEG_ID) &&
+ (dict->window.segs[seg_id].image->id == encoder->cur_image.id) &&
+ ((((PIXEL *)dict->window.segs[seg_id].lines_end) -
+ ((PIXEL *)dict->window.segs[seg_id].lines)) < 4)) {
+ // coping the segment
+ if (dict->window.segs[seg_id].lines != dict->window.segs[seg_id].lines_end) {
+ ip = (PIXEL *)dict->window.segs[seg_id].lines;
+ // Note: we assume MAX_COPY > 3
+ encode_copy_count(encoder, (uint8_t)(
+ (((PIXEL *)dict->window.segs[seg_id].lines_end) -
+ ((PIXEL *)dict->window.segs[seg_id].lines)) - 1));
+ while (ip < (PIXEL *)dict->window.segs[seg_id].lines_end) {
+ ENCODE_PIXEL(encoder, *ip);
+ ip++;
+ }
+ }
+ seg_id = dict->window.segs[seg_id].next;
+ }
+
+ if ((seg_id == NULL_IMAGE_SEG_ID) ||
+ (dict->window.segs[seg_id].image->id != encoder->cur_image.id)) {
+ return;
+ }
+
+ ip = (PIXEL *)dict->window.segs[seg_id].lines;
+
+
+ encode_copy_count(encoder, MAX_COPY - 1);
+
+ HASH_FUNC(hval, ip);
+ UPDATE_HASH(encoder->dict, hval, seg_id, 0);
+
+ ENCODE_PIXEL(encoder, *ip);
+ ip++;
+ ENCODE_PIXEL(encoder, *ip);
+ ip++;
+#ifdef DEBUG_ENCODE
+ printf("copy, copy");
+#endif
+ // compressing the first segment
+ FNAME(compress_seg)(encoder, seg_id, ip, 2);
+
+ // compressing the next segments
+ for (seg_id = dict->window.segs[seg_id].next;
+ seg_id != NULL_IMAGE_SEG_ID && (
+ dict->window.segs[seg_id].image->id == encoder->cur_image.id);
+ seg_id = dict->window.segs[seg_id].next) {
+ FNAME(compress_seg)(encoder, seg_id, (PIXEL *)dict->window.segs[seg_id].lines, 0);
+ }
+}
+
+#undef FNAME
+#undef PIXEL_ID
+#undef PIXEL_DIST
+#undef PIXEL
+#undef ENCODE_PIXEL
+#undef SAME_PIXEL
+#undef HASH_FUNC
+#undef GET_r
+#undef GET_g
+#undef GET_b
+#undef GET_CODE
+#undef LZ_PLT
+#undef LZ_RGB_ALPHA
+#undef LZ_RGB16
+#undef LZ_RGB24
+#undef LZ_RGB32
+#undef MIN_REF_ENCODE_SIZE
+#undef MAX_REF_ENCODE_SIZE
+
diff --git a/server/glz_encoder.c b/server/glz_encoder.c
new file mode 100644
index 00000000..f0caf897
--- /dev/null
+++ b/server/glz_encoder.c
@@ -0,0 +1,308 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <pthread.h>
+#include <stdio.h>
+#include "glz_encoder.h"
+#include "glz_encoder_dictionary_protected.h"
+
+
+/* Holds a specific data for one encoder, and data that is relevant for the current image encoded */
+typedef struct Encoder {
+ GlzEncoderUsrContext *usr;
+ uint8_t id;
+ SharedDictionary *dict;
+
+ struct {
+ LzImageType type;
+ uint32_t id;
+ uint32_t first_win_seg;
+ } cur_image;
+
+ struct {
+ uint8_t *start;
+ uint8_t *now;
+ uint8_t *end;
+ size_t bytes_count;
+ uint8_t *last_copy; // pointer to the last byte in which copy count was written
+ } io;
+} Encoder;
+
+
+/**************************************************************************
+* Handling writing the encoded image to the output buffer
+***************************************************************************/
+static INLINE int more_io_bytes(Encoder *encoder)
+{
+ uint8_t *io_ptr;
+ int num_io_bytes = encoder->usr->more_space(encoder->usr, &io_ptr);
+ encoder->io.bytes_count += num_io_bytes;
+ encoder->io.now = io_ptr;
+ encoder->io.end = encoder->io.now + num_io_bytes;
+ return num_io_bytes;
+}
+
+static INLINE void encode(Encoder *encoder, uint8_t byte)
+{
+ if (encoder->io.now == encoder->io.end) {
+ if (more_io_bytes(encoder) <= 0) {
+ encoder->usr->error(encoder->usr, "%s: no more bytes\n", __FUNCTION__);
+ }
+ GLZ_ASSERT(encoder->usr, encoder->io.now);
+ }
+
+ GLZ_ASSERT(encoder->usr, encoder->io.now < encoder->io.end);
+ *(encoder->io.now++) = byte;
+}
+
+static INLINE void encode_32(Encoder *encoder, unsigned int word)
+{
+ encode(encoder, (uint8_t)(word >> 24));
+ encode(encoder, (uint8_t)(word >> 16) & 0x0000ff);
+ encode(encoder, (uint8_t)(word >> 8) & 0x0000ff);
+ encode(encoder, (uint8_t)(word & 0x0000ff));
+}
+
+static INLINE void encode_64(Encoder *encoder, uint64_t word)
+{
+ encode_32(encoder, (uint32_t)(word >> 32));
+ encode_32(encoder, (uint32_t)(word & 0xffffff));
+}
+
+static INLINE void encode_copy_count(Encoder *encoder, uint8_t copy_count)
+{
+ encode(encoder, copy_count);
+ encoder->io.last_copy = encoder->io.now - 1; // io_now cannot be the first byte of the buffer
+}
+
+static INLINE void update_copy_count(Encoder *encoder, uint8_t copy_count)
+{
+ GLZ_ASSERT(encoder->usr, encoder->io.last_copy);
+ *(encoder->io.last_copy) = copy_count;
+}
+
+// decrease the io ptr by 1
+static INLINE void compress_output_prev(Encoder *encoder)
+{
+ // io_now cannot be the first byte of the buffer
+ encoder->io.now--;
+ // the function should be called only when copy count is written unnecessarily by glz_compress
+ GLZ_ASSERT(encoder->usr, encoder->io.now == encoder->io.last_copy)
+}
+
+static int encoder_reset(Encoder *encoder, uint8_t *io_ptr, uint8_t *io_ptr_end)
+{
+ GLZ_ASSERT(encoder->usr, io_ptr <= io_ptr_end);
+ encoder->io.bytes_count = io_ptr_end - io_ptr;
+ encoder->io.start = io_ptr;
+ encoder->io.now = io_ptr;
+ encoder->io.end = io_ptr_end;
+ encoder->io.last_copy = NULL;
+
+ return TRUE;
+}
+
+/**********************************************************
+* Encoding
+***********************************************************/
+
+GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
+ GlzEncoderUsrContext *usr)
+{
+ Encoder *encoder;
+
+ if (!usr || !usr->error || !usr->warn || !usr->info || !usr->malloc ||
+ !usr->free || !usr->more_space) {
+ return NULL;
+ }
+
+ if (!(encoder = (Encoder *)usr->malloc(usr, sizeof(Encoder)))) {
+ return NULL;
+ }
+
+ encoder->id = id;
+ encoder->usr = usr;
+ encoder->dict = (SharedDictionary *)dictionary;
+
+ return (GlzEncoderContext *)encoder;
+}
+
+void glz_encoder_destroy(GlzEncoderContext *opaque_encoder)
+{
+ Encoder *encoder = (Encoder *)opaque_encoder;
+
+ if (!opaque_encoder) {
+ return;
+ }
+
+ encoder->usr->free(encoder->usr, encoder);
+}
+
+/*
+ * Give hints to the compiler for branch prediction optimization.
+ */
+#if defined(__GNUC__) && (__GNUC__ > 2)
+#define LZ_EXPECT_CONDITIONAL(c) (__builtin_expect((c), 1))
+#define LZ_UNEXPECT_CONDITIONAL(c) (__builtin_expect((c), 0))
+#else
+#define LZ_EXPECT_CONDITIONAL(c) (c)
+#define LZ_UNEXPECT_CONDITIONAL(c) (c)
+#endif
+
+
+typedef uint8_t BYTE;
+
+typedef struct __attribute__ ((__packed__)) one_byte_pixel_t {
+ BYTE a;
+} one_byte_pixel_t;
+
+typedef struct __attribute__ ((__packed__)) rgb32_pixel_t {
+ BYTE b;
+ BYTE g;
+ BYTE r;
+ BYTE pad;
+} rgb32_pixel_t;
+
+typedef struct __attribute__ ((__packed__)) rgb24_pixel_t {
+ BYTE b;
+ BYTE g;
+ BYTE r;
+} rgb24_pixel_t;
+
+typedef uint16_t rgb16_pixel_t;
+
+#define BOUND_OFFSET 2
+#define LIMIT_OFFSET 6
+#define MIN_FILE_SIZE 4
+
+#define MAX_PIXEL_SHORT_DISTANCE 4096 // (1 << 12)
+#define MAX_PIXEL_MEDIUM_DISTANCE 131072 // (1 << 17) 2 ^ (12 + 5)
+#define MAX_PIXEL_LONG_DISTANCE 33554432 // (1 << 25) 2 ^ (12 + 5 + 8)
+#define MAX_IMAGE_DIST 16777215 // (1 << 24 - 1)
+
+
+//#define DEBUG_ENCODE
+
+
+#define GLZ_ENCODE_SIZE
+#include "glz_encode_match_tmpl.c"
+#define GLZ_ENCODE_MATCH
+#include "glz_encode_match_tmpl.c"
+
+#define LZ_PLT
+#include "glz_encode_tmpl.c"
+
+#define LZ_RGB16
+#include "glz_encode_tmpl.c"
+
+#define LZ_RGB24
+#include "glz_encode_tmpl.c"
+
+#define LZ_RGB32
+#include "glz_encode_tmpl.c"
+
+#define LZ_RGB_ALPHA
+#include "glz_encode_tmpl.c"
+
+
+int glz_encode(GlzEncoderContext *opaque_encoder,
+ LzImageType type, int width, int height, int top_down,
+ uint8_t *lines, unsigned int num_lines, int stride,
+ uint8_t *io_ptr, unsigned int num_io_bytes,
+ GlzUsrImageContext *usr_context, GlzEncDictImageContext **o_enc_dict_context)
+{
+ Encoder *encoder = (Encoder *)opaque_encoder;
+ WindowImage *dict_image;
+ uint8_t *io_ptr_end = io_ptr + num_io_bytes;
+ uint32_t win_head_image_dist;
+
+ if (IS_IMAGE_TYPE_PLT[type]) {
+ if (stride > (width / PLT_PIXELS_PER_BYTE[type])) {
+ if (((width % PLT_PIXELS_PER_BYTE[type]) == 0) || (
+ (stride - (width / PLT_PIXELS_PER_BYTE[type])) > 1)) {
+ encoder->usr->error(encoder->usr, "sride overflows (plt)\n");
+ }
+ }
+ } else {
+ if (stride != width * RGB_BYTES_PER_PIXEL[type]) {
+ encoder->usr->error(encoder->usr, "sride != width*bytes_per_pixel (rgb)\n");
+ }
+ }
+
+ // assign the output buffer
+ if (!encoder_reset(encoder, io_ptr, io_ptr_end)) {
+ encoder->usr->error(encoder->usr, "lz encoder io reset failed\n");
+ }
+
+ // first read the list of the image segments into the dictionary window
+ dict_image = glz_dictionary_pre_encode(encoder->id, encoder->usr,
+ encoder->dict, type, width, height, stride,
+ lines, num_lines, usr_context, &win_head_image_dist);
+ *o_enc_dict_context = (GlzEncDictImageContext *)dict_image;
+
+ encoder->cur_image.type = type;
+ encoder->cur_image.id = dict_image->id;
+ encoder->cur_image.first_win_seg = dict_image->first_seg;
+
+ encode_32(encoder, LZ_MAGIC);
+ encode_32(encoder, LZ_VERSION);
+ if (top_down) {
+ encode(encoder, (type & LZ_IMAGE_TYPE_MASK) | (1 << LZ_IMAGE_TYPE_LOG));
+ } else {
+ encode(encoder, (type & LZ_IMAGE_TYPE_MASK));
+ }
+
+ encode_32(encoder, width);
+ encode_32(encoder, height);
+ encode_32(encoder, stride);
+ encode_64(encoder, dict_image->id);
+ encode_32(encoder, win_head_image_dist);
+
+ switch (encoder->cur_image.type) {
+ case LZ_IMAGE_TYPE_PLT1_BE:
+ case LZ_IMAGE_TYPE_PLT1_LE:
+ case LZ_IMAGE_TYPE_PLT4_BE:
+ case LZ_IMAGE_TYPE_PLT4_LE:
+ case LZ_IMAGE_TYPE_PLT8:
+ glz_plt_compress(encoder);
+ break;
+ case LZ_IMAGE_TYPE_RGB16:
+ glz_rgb16_compress(encoder);
+ break;
+ case LZ_IMAGE_TYPE_RGB24:
+ glz_rgb24_compress(encoder);
+ break;
+ case LZ_IMAGE_TYPE_RGB32:
+ glz_rgb32_compress(encoder);
+ break;
+ case LZ_IMAGE_TYPE_RGBA:
+ glz_rgb32_compress(encoder);
+ glz_rgb_alpha_compress(encoder);
+ break;
+ case LZ_IMAGE_TYPE_INVALID:
+ default:
+ encoder->usr->error(encoder->usr, "bad image type\n");
+ }
+
+ glz_dictionary_post_encode(encoder->id, encoder->usr, encoder->dict);
+
+ // move all the used segments to the free ones
+ encoder->io.bytes_count -= (encoder->io.end - encoder->io.now);
+
+ return encoder->io.bytes_count;
+}
+
diff --git a/server/glz_encoder.h b/server/glz_encoder.h
new file mode 100644
index 00000000..b324a68d
--- /dev/null
+++ b/server/glz_encoder.h
@@ -0,0 +1,56 @@
+/*
+ Copyright (C) 2009 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 _H_GLZ_ENCODER
+#define _H_GLZ_ENCODER
+
+/* Manging the lz encoding using a dictionary that is shared among encoders */
+
+#include <stdint.h>
+#include "lz_common.h"
+#include "glz_encoder_dictionary.h"
+#include "glz_encoder_config.h"
+
+typedef void GlzEncoderContext;
+
+GlzEncoderContext *glz_encoder_create(uint8_t id, GlzEncDictContext *dictionary,
+ GlzEncoderUsrContext *usr);
+
+void glz_encoder_destroy(GlzEncoderContext *opaque_encoder);
+
+/*
+ assumes width is in pixels and stride is in bytes
+ usr_context : when an image is released from the window due to capicity overflow,
+ usr_context is given as a parmater to the free_image callback.
+ o_enc_dict_context: if glz_enc_dictionary_remove_image is called, it should be
+ called with the o_enc_dict_context that is associated with
+ the image.
+
+ return: the number of bytes in the compressed data and sets o_enc_dict_context
+
+ NOTE : currently supports only rgb images in which width*bytes_per_pixel = stride OR
+ palette images in which stride eqauls the min number of bytes to hold a line.
+ The stride should be > 0
+*/
+int glz_encode(GlzEncoderContext *opaque_encoder, LzImageType type, int width, int height,
+ int top_down, uint8_t *lines, unsigned int num_lines, int stride,
+ uint8_t *io_ptr, unsigned int num_io_bytes, GlzUsrImageContext *usr_context,
+ GlzEncDictImageContext **o_enc_dict_context);
+
+
+#endif // _H_GLZ_ENCODER
+
diff --git a/server/glz_encoder_config.h b/server/glz_encoder_config.h
new file mode 100644
index 00000000..a2b9b580
--- /dev/null
+++ b/server/glz_encoder_config.h
@@ -0,0 +1,64 @@
+/*
+ Copyright (C) 2009 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 _H_GLZ_ENCODER_CONFIG
+#define _H_GLZ_ENCODER_CONFIG
+
+#include "lz_common.h"
+
+typedef void GlzUsrImageContext;
+typedef struct GlzEncoderUsrContext GlzEncoderUsrContext;
+
+struct GlzEncoderUsrContext {
+ void (*error)(GlzEncoderUsrContext *usr, const char *fmt, ...);
+ void (*warn)(GlzEncoderUsrContext *usr, const char *fmt, ...);
+ void (*info)(GlzEncoderUsrContext *usr, const char *fmt, ...);
+ void *(*malloc)(GlzEncoderUsrContext *usr, int size);
+ void (*free)(GlzEncoderUsrContext *usr, void *ptr);
+
+ // get the next chunk of the image which is entered to the dictionary. If the image is down to
+ // top, return it from the last line to the first one (stride should always be positive)
+ int (*more_lines)(GlzEncoderUsrContext *usr, uint8_t **lines);
+
+ // get the next chunk of the compressed buffer.return number of bytes in the chunk.
+ int (*more_space)(GlzEncoderUsrContext *usr, uint8_t **io_ptr);
+
+ // called when an image is removed from the dictionary, due to the window size limit
+ void (*free_image)(GlzEncoderUsrContext *usr, GlzUsrImageContext *image);
+
+};
+
+
+#ifdef DEBUG
+
+#define GLZ_ASSERT(usr, x) \
+ if (!(x)) (usr)->error(usr, "%s: ASSERT %s failed\n", __FUNCTION__, #x);
+
+#else
+
+#define GLZ_ASSERT(usr, x)
+
+#endif
+
+#define INLINE inline
+
+#define FALSE 0
+#define TRUE 1
+
+
+#endif
+
diff --git a/server/glz_encoder_dictionary.c b/server/glz_encoder_dictionary.c
new file mode 100644
index 00000000..8ea8065f
--- /dev/null
+++ b/server/glz_encoder_dictionary.c
@@ -0,0 +1,618 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <pthread.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "glz_encoder_dictionary.h"
+#include "glz_encoder_dictionary_protected.h"
+
+/* turning all used images to free ones. If they are alive, calling the free_image callback for
+ each one */
+static INLINE void __glz_dictionary_window_reset_images(SharedDictionary *dict)
+{
+ WindowImage *tmp;
+
+ while (dict->window.used_images_head) {
+ tmp = dict->window.used_images_head;
+ dict->window.used_images_head = dict->window.used_images_head->next;
+ if (tmp->is_alive) {
+ dict->cur_usr->free_image(dict->cur_usr, tmp->usr_context);
+ }
+ tmp->next = dict->window.free_images;
+ tmp->is_alive = FALSE;
+ dict->window.free_images = tmp;
+ }
+ dict->window.used_images_tail = NULL;
+}
+
+/* allocate window fields (no reset)*/
+static int glz_dictionary_window_create(SharedDictionary *dict, uint32_t size)
+{
+ if (size > LZ_MAX_WINDOW_SIZE) {
+ return FALSE;
+ }
+
+ dict->window.size_limit = size;
+ dict->window.segs = (WindowImageSegment *)(
+ dict->cur_usr->malloc(dict->cur_usr, sizeof(WindowImageSegment) * INIT_IMAGE_SEGS_NUM));
+
+ if (!dict->window.segs) {
+ return FALSE;
+ }
+
+ dict->window.segs_quota = INIT_IMAGE_SEGS_NUM;
+
+ dict->window.encoders_heads = (uint32_t *)dict->cur_usr->malloc(dict->cur_usr,
+ sizeof(uint32_t) * dict->max_encdoers);
+
+ if (!dict->window.encoders_heads) {
+ dict->cur_usr->free(dict->cur_usr, dict->window.segs);
+ return FALSE;
+ }
+
+ dict->window.used_images_head = NULL;
+ dict->window.used_images_tail = NULL;
+ dict->window.free_images = NULL;
+ dict->window.pixels_so_far = 0;
+
+ return TRUE;
+}
+
+/* initializes an empty window (segs and encoder_heads should be pre allocated.
+ resets the image infos, and calls the free_image usr callback*/
+static void glz_dictionary_window_reset(SharedDictionary *dict)
+{
+ uint32_t i;
+ WindowImageSegment *seg, *last_seg;
+
+ last_seg = dict->window.segs + dict->window.segs_quota;
+ /* reset free segs list */
+ dict->window.free_segs_head = 0;
+ for (seg = dict->window.segs, i = 0; seg < last_seg; seg++, i++) {
+ seg->next = i + 1;
+ seg->image = NULL;
+ seg->lines = NULL;
+ seg->lines_end = NULL;
+ seg->pixels_num = 0;
+ seg->pixels_so_far = 0;
+ }
+ dict->window.segs[dict->window.segs_quota - 1].next = NULL_IMAGE_SEG_ID;
+
+ dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
+ dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
+
+ // reset encoders heads
+ for (i = 0; i < dict->max_encdoers; i++) {
+ dict->window.encoders_heads[i] = NULL_IMAGE_SEG_ID;
+ }
+
+ __glz_dictionary_window_reset_images(dict);
+}
+
+static INLINE void glz_dictionary_reset_hash(SharedDictionary *dict)
+{
+ memset(dict->htab, 0, sizeof(HashEntry) * HASH_SIZE * HASH_CHAIN_SIZE);
+#ifdef CHAINED_HASH
+ memset(dict->htab_counter, 0, HASH_SIZE * sizeof(uint8_t));
+#endif
+}
+
+static INLINE void glz_dictionary_window_destroy(SharedDictionary *dict)
+{
+ __glz_dictionary_window_reset_images(dict);
+
+ if (dict->window.segs) {
+ dict->cur_usr->free(dict->cur_usr, dict->window.segs);
+ dict->window.segs = NULL;
+ }
+
+ while (dict->window.free_images) {
+ WindowImage *tmp = dict->window.free_images;
+ dict->window.free_images = tmp->next;
+
+ dict->cur_usr->free(dict->cur_usr, tmp);
+ }
+
+ if (dict->window.encoders_heads) {
+ dict->cur_usr->free(dict->cur_usr, dict->window.encoders_heads);
+ dict->window.encoders_heads = NULL;
+ }
+}
+
+/* logic removal only */
+static INLINE void glz_dictionary_window_kill_image(SharedDictionary *dict, WindowImage *image)
+{
+ image->is_alive = FALSE;
+}
+
+GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
+ GlzEncoderUsrContext *usr)
+{
+ SharedDictionary *dict;
+
+ if (!(dict = (SharedDictionary *)usr->malloc(usr,
+ sizeof(SharedDictionary)))) {
+ return NULL;
+ }
+
+ dict->cur_usr = usr;
+ dict->last_image_id = 0;
+ dict->max_encdoers = max_encoders;
+
+ pthread_mutex_init(&dict->lock, NULL);
+ pthread_rwlock_init(&dict->rw_alloc_lock, NULL);
+
+ dict->window.encoders_heads = NULL;
+
+ // alloc window fields and reset
+ if (!glz_dictionary_window_create(dict, size)) {
+ dict->cur_usr->free(usr, dict);
+ return NULL;
+ }
+
+ // reset window and hash
+ glz_enc_dictionary_reset((GlzEncDictContext *)dict, usr);
+
+ return (GlzEncDictContext *)dict;
+}
+
+void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
+ GlzEncDictRestoreData *out_data, GlzEncoderUsrContext *usr)
+{
+ SharedDictionary *dict = (SharedDictionary *)opaque_dict;
+ dict->cur_usr = usr;
+ GLZ_ASSERT(dict->cur_usr, opaque_dict);
+ GLZ_ASSERT(dict->cur_usr, out_data);
+
+ out_data->last_image_id = dict->last_image_id;
+ out_data->max_encoders = dict->max_encdoers;
+ out_data->size = dict->window.size_limit;
+}
+
+GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
+ GlzEncoderUsrContext *usr)
+{
+ if (!restore_data) {
+ return NULL;
+ }
+ SharedDictionary *ret = (SharedDictionary *)glz_enc_dictionary_create(
+ restore_data->size, restore_data->max_encoders, usr);
+ ret->last_image_id = restore_data->last_image_id;
+ return ((GlzEncDictContext *)ret);
+}
+
+void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
+{
+ SharedDictionary *dict = (SharedDictionary *)opaque_dict;
+ dict->cur_usr = usr;
+ GLZ_ASSERT(dict->cur_usr, opaque_dict);
+
+ dict->last_image_id = 0;
+ glz_dictionary_window_reset(dict);
+ glz_dictionary_reset_hash(dict);
+}
+
+void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr)
+{
+ SharedDictionary *dict = (SharedDictionary *)opaque_dict;
+
+ if (!opaque_dict) {
+ return;
+ }
+
+ dict->cur_usr = usr;
+ glz_dictionary_window_destroy(dict);
+
+ pthread_mutex_destroy(&dict->lock);
+ pthread_rwlock_destroy(&dict->rw_alloc_lock);
+
+ dict->cur_usr->free(dict->cur_usr, dict);
+}
+
+uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *opaque_dict)
+{
+ SharedDictionary *dict = (SharedDictionary *)opaque_dict;
+
+ if (!opaque_dict) {
+ return 0;
+ }
+ return dict->window.size_limit;
+}
+
+/* doesn't call the remove image callback */
+void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
+ GlzEncDictImageContext *opaque_image,
+ GlzEncoderUsrContext *usr)
+{
+ SharedDictionary *dict = (SharedDictionary *)opaque_dict;
+ WindowImage *image = (WindowImage *)opaque_image;
+ dict->cur_usr = usr;
+ GLZ_ASSERT(dict->cur_usr, opaque_image && opaque_dict);
+
+ glz_dictionary_window_kill_image(dict, image);
+}
+
+/***********************************************************************************
+ Mutators of the window. Should be called by the encoder before and after encoding.
+ ***********************************************************************************/
+
+static INLINE int __get_pixels_num(LzImageType image_type, unsigned int num_lines, int stride)
+{
+ if (IS_IMAGE_TYPE_RGB[image_type]) {
+ return num_lines * stride / RGB_BYTES_PER_PIXEL[image_type];
+ } else {
+ return num_lines * stride * PLT_PIXELS_PER_BYTE[image_type];
+ }
+}
+
+static void __glz_dictionary_window_segs_realloc(SharedDictionary *dict)
+{
+ WindowImageSegment *new_segs;
+ uint32_t new_quota = (MAX_IMAGE_SEGS_NUM < (dict->window.segs_quota * 2)) ?
+ MAX_IMAGE_SEGS_NUM : (dict->window.segs_quota * 2);
+ WindowImageSegment *seg;
+ uint32_t i;
+
+ pthread_rwlock_wrlock(&dict->rw_alloc_lock);
+
+ if (dict->window.segs_quota == MAX_IMAGE_SEGS_NUM) {
+ dict->cur_usr->error(dict->cur_usr, "overflow in image segments window\n");
+ }
+
+ new_segs = (WindowImageSegment*)dict->cur_usr->malloc(
+ dict->cur_usr, sizeof(WindowImageSegment) * new_quota);
+
+ if (!new_segs) {
+ dict->cur_usr->error(dict->cur_usr,
+ "realloc of dictionary window failed\n");
+ }
+
+ memcpy(new_segs, dict->window.segs,
+ sizeof(WindowImageSegment) * dict->window.segs_quota);
+
+ // reseting the new elements
+ for (i = dict->window.segs_quota, seg = new_segs + i; i < new_quota; i++, seg++) {
+ seg->image = NULL;
+ seg->lines = NULL;
+ seg->lines_end = NULL;
+ seg->pixels_num = 0;
+ seg->pixels_so_far = 0;
+ seg->next = i + 1;
+ }
+ new_segs[new_quota - 1].next = dict->window.free_segs_head;
+ dict->window.free_segs_head = dict->window.segs_quota;
+
+ dict->cur_usr->free(dict->cur_usr, dict->window.segs);
+ dict->window.segs = new_segs;
+ dict->window.segs_quota = new_quota;
+
+ pthread_rwlock_unlock(&dict->rw_alloc_lock);
+}
+
+/* NOTE - it also updates the used_images_list*/
+static WindowImage *__glz_dictionary_window_alloc_image(SharedDictionary *dict)
+{
+ WindowImage *ret;
+
+ if (dict->window.free_images) {
+ ret = dict->window.free_images;
+ dict->window.free_images = ret->next;
+ } else {
+ if (!(ret = (WindowImage *)dict->cur_usr->malloc(dict->cur_usr,
+ sizeof(*ret)))) {
+ return NULL;
+ }
+ }
+
+ ret->next = NULL;
+ if (dict->window.used_images_tail) {
+ dict->window.used_images_tail->next = ret;
+ }
+ dict->window.used_images_tail = ret;
+
+ if (!dict->window.used_images_head) {
+ dict->window.used_images_head = ret;
+ }
+ return ret;
+}
+
+/* NOTE - it also updates the used_segs_list*/
+static uint32_t __glz_dictionary_window_alloc_image_seg(SharedDictionary *dict)
+{
+ uint32_t seg_id;
+ WindowImageSegment *seg;
+
+ // TODO: when is it best to realloc? when full or when half full?
+ if (dict->window.free_segs_head == NULL_IMAGE_SEG_ID) {
+ __glz_dictionary_window_segs_realloc(dict);
+ }
+
+ GLZ_ASSERT(dict->cur_usr, dict->window.free_segs_head != NULL_IMAGE_SEG_ID);
+
+ seg_id = dict->window.free_segs_head;
+ seg = dict->window.segs + seg_id;
+ dict->window.free_segs_head = seg->next;
+
+ // first segment
+ if (dict->window.used_segs_tail == NULL_IMAGE_SEG_ID) {
+ dict->window.used_segs_head = seg_id;
+ dict->window.used_segs_tail = seg_id;
+ } else {
+ int prev_tail = dict->window.used_segs_tail;
+ dict->window.segs[prev_tail].next = seg_id;
+ dict->window.used_segs_tail = seg_id;
+ }
+
+ seg->next = NULL_IMAGE_SEG_ID;
+
+ return seg_id;
+}
+
+/* moves image to free list and "kill" it. Calls the free_image callback if was alive. */
+static INLINE void __glz_dictionary_window_free_image(SharedDictionary *dict, WindowImage *image)
+{
+ if (image->is_alive) {
+ dict->cur_usr->free_image(dict->cur_usr, image->usr_context);
+ }
+ image->is_alive = FALSE;
+ image->next = dict->window.free_images;
+ dict->window.free_images = image;
+}
+
+/* moves all the segments that were associaed with the images to the free segments */
+static INLINE void __glz_dictionary_window_free_image_segs(SharedDictionary *dict,
+ WindowImage *image)
+{
+ uint32_t old_free_head = dict->window.free_segs_head;
+ uint32_t seg_id, next_seg_id;
+
+ GLZ_ASSERT(dict->cur_usr, image->first_seg != NULL_IMAGE_SEG_ID);
+ dict->window.free_segs_head = image->first_seg;
+
+ // retrieving the last segment of the image
+ for (seg_id = image->first_seg, next_seg_id = dict->window.segs[seg_id].next;
+ (next_seg_id != NULL_IMAGE_SEG_ID) && (dict->window.segs[next_seg_id].image == image);
+ seg_id = next_seg_id, next_seg_id = dict->window.segs[seg_id].next) {
+ }
+
+ // concatenate the free list
+ dict->window.segs[seg_id].next = old_free_head;
+}
+
+/* Returns the logical head of the window after we add an image with the give size to its tail.
+ Returns NULL when the window is empty, of when we have to empty the window in order
+ to insert the new image. */
+static WindowImage *glz_dictionary_window_get_new_head(SharedDictionary *dict, int new_image_size)
+{
+ uint32_t cur_win_size;
+ WindowImage *cur_head;
+
+ if ((uint32_t)new_image_size > dict->window.size_limit) {
+ dict->cur_usr->error(dict->cur_usr, "image is bigger than window\n");
+ }
+
+ GLZ_ASSERT(dict->cur_usr, new_image_size < dict->window.size_limit)
+
+ // the window is empty
+ if (!dict->window.used_images_head) {
+ return NULL;
+ }
+
+ GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_head != NULL_IMAGE_SEG_ID);
+ GLZ_ASSERT(dict->cur_usr, dict->window.used_segs_tail != NULL_IMAGE_SEG_ID);
+
+ // used_segs_head is the latest logical head (the physical head may preceed it)
+ cur_head = dict->window.segs[dict->window.used_segs_head].image;
+ cur_win_size = dict->window.segs[dict->window.used_segs_tail].pixels_num +
+ dict->window.segs[dict->window.used_segs_tail].pixels_so_far -
+ dict->window.segs[dict->window.used_segs_head].pixels_so_far;
+
+ while ((cur_win_size + new_image_size) > dict->window.size_limit) {
+ GLZ_ASSERT(dict->cur_usr, cur_head);
+ cur_win_size -= cur_head->size;
+ cur_head = cur_head->next;
+ }
+
+ return cur_head;
+}
+
+static INLINE int glz_dictionary_is_in_use(SharedDictionary *dict)
+{
+ uint32_t i = 0;
+ for (i = 0; i < dict->max_encdoers; i++) {
+ if (dict->window.encoders_heads[i] != NULL_IMAGE_SEG_ID) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+/* remove from the window (and free relevant data) the images between the oldest physical head
+ (inclusive) and the end_image (exclusive). If end_image is NULL, empties the window*/
+static void glz_dictionary_window_remove_head(SharedDictionary *dict, uint32_t encoder_id,
+ WindowImage *end_image)
+{
+ // note that the segs list heads (one per econder) may be different than the
+ // used_segs_head and it is updated somewhere else
+ while (dict->window.used_images_head != end_image) {
+ WindowImage *image = dict->window.used_images_head;
+
+ __glz_dictionary_window_free_image_segs(dict, image);
+ dict->window.used_images_head = image->next;
+ __glz_dictionary_window_free_image(dict, image);
+ }
+
+ if (!dict->window.used_images_head) {
+ dict->window.used_segs_head = NULL_IMAGE_SEG_ID;
+ dict->window.used_segs_tail = NULL_IMAGE_SEG_ID;
+ dict->window.used_images_tail = NULL;
+ } else {
+ dict->window.used_segs_head = end_image->first_seg;
+ }
+}
+
+static uint32_t glz_dictionary_window_add_image_seg(SharedDictionary *dict, WindowImage* image,
+ int size, int stride,
+ uint8_t *lines, unsigned int num_lines)
+{
+ uint32_t seg_id = __glz_dictionary_window_alloc_image_seg(dict);
+ WindowImageSegment *seg = &dict->window.segs[seg_id];
+
+ seg->image = image;
+ seg->lines = lines;
+ seg->lines_end = lines + num_lines * stride;
+ seg->pixels_num = size;
+ seg->pixels_so_far = dict->window.pixels_so_far;
+ dict->window.pixels_so_far += seg->pixels_num;
+
+ return seg_id;
+}
+
+static WindowImage *glz_dictionary_window_add_image(SharedDictionary *dict, LzImageType image_type,
+ int image_size, int image_height,
+ int image_stride, uint8_t *first_lines,
+ unsigned int num_first_lines,
+ GlzUsrImageContext *usr_image_context)
+{
+ unsigned int num_lines = num_first_lines;
+ unsigned int row;
+ uint32_t seg_id;
+ uint8_t* lines = first_lines;
+ // alloc image info,update used head tail, if used_head null - update head
+ WindowImage *image = __glz_dictionary_window_alloc_image(dict);
+ image->id = dict->last_image_id++;
+ image->size = image_size;
+ image->type = image_type;
+ image->usr_context = usr_image_context;
+
+ if (num_lines <= 0) {
+ num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
+ if (num_lines <= 0) {
+ dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
+ }
+ }
+
+ for (row = 0;;) {
+ seg_id = glz_dictionary_window_add_image_seg(dict, image,
+ image_size * num_lines / image_height,
+ image_stride,
+ lines, num_lines);
+ if (row == 0) {
+ image->first_seg = seg_id;
+ }
+ row += num_lines;
+ if (row < (uint32_t)image_height) {
+ num_lines = dict->cur_usr->more_lines(dict->cur_usr, &lines);
+ if (num_lines <= 0) {
+ dict->cur_usr->error(dict->cur_usr, "more lines failed\n");
+ }
+ } else {
+ break;
+ }
+ }
+ image->is_alive = TRUE;
+
+ return image;
+}
+
+WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
+ SharedDictionary *dict, LzImageType image_type,
+ int image_width, int image_height, int image_stride,
+ uint8_t *first_lines, unsigned int num_first_lines,
+ GlzUsrImageContext *usr_image_context,
+ uint32_t *image_head_dist)
+{
+ WindowImage *new_win_head, *ret;
+ int image_size;
+
+
+ pthread_mutex_lock(&dict->lock);
+
+ dict->cur_usr = usr;
+ GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] == NULL_IMAGE_SEG_ID);
+
+ image_size = __get_pixels_num(image_type, image_height, image_stride);
+ new_win_head = glz_dictionary_window_get_new_head(dict, image_size);
+
+ if (!glz_dictionary_is_in_use(dict)) {
+ glz_dictionary_window_remove_head(dict, encoder_id, new_win_head);
+ }
+
+ ret = glz_dictionary_window_add_image(dict, image_type, image_size, image_height, image_stride,
+ first_lines, num_first_lines, usr_image_context);
+
+ if (new_win_head) {
+ dict->window.encoders_heads[encoder_id] = new_win_head->first_seg;
+ *image_head_dist = (uint32_t)(ret->id - new_win_head->id); // shouldn't be greater than 32
+ // bit because the window size is
+ // limited to 2^25
+ } else {
+ dict->window.encoders_heads[encoder_id] = ret->first_seg;
+ *image_head_dist = 0;
+ }
+
+
+ // update encoders head (the other heads were already updated)
+ pthread_mutex_unlock(&dict->lock);
+ pthread_rwlock_rdlock(&dict->rw_alloc_lock);
+ return ret;
+}
+
+void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
+ SharedDictionary *dict)
+{
+ uint32_t i;
+ uint32_t early_head_seg = NULL_IMAGE_SEG_ID;
+ uint32_t this_encoder_head_seg;
+
+ pthread_rwlock_unlock(&dict->rw_alloc_lock);
+ pthread_mutex_lock(&dict->lock);
+ dict->cur_usr = usr;
+
+ GLZ_ASSERT(dict->cur_usr, dict->window.encoders_heads[encoder_id] != NULL_IMAGE_SEG_ID);
+ // get the earliest head in use (not including this encoder head)
+ for (i = 0; i < dict->max_encdoers; i++) {
+ if (i != encoder_id) {
+ if (IMAGE_SEG_IS_EARLIER(dict, dict->window.encoders_heads[i], early_head_seg)) {
+ early_head_seg = dict->window.encoders_heads[i];
+ }
+ }
+ }
+
+ // possible only if early_head_seg == NULL
+ if (IMAGE_SEG_IS_EARLIER(dict, dict->window.used_segs_head, early_head_seg)) {
+ early_head_seg = dict->window.used_segs_head;
+ }
+
+ this_encoder_head_seg = dict->window.encoders_heads[encoder_id];
+
+ GLZ_ASSERT(dict->cur_usr, early_head_seg != NULL_IMAGE_SEG_ID);
+
+ if (IMAGE_SEG_IS_EARLIER(dict, this_encoder_head_seg, early_head_seg)) {
+ GLZ_ASSERT(dict->cur_usr,
+ this_encoder_head_seg == dict->window.used_images_head->first_seg);
+ glz_dictionary_window_remove_head(dict, encoder_id,
+ dict->window.segs[early_head_seg].image);
+ }
+
+
+ dict->window.encoders_heads[encoder_id] = NULL_IMAGE_SEG_ID;
+ pthread_mutex_unlock(&dict->lock);
+}
+
diff --git a/server/glz_encoder_dictionary.h b/server/glz_encoder_dictionary.h
new file mode 100644
index 00000000..dbc4f4b5
--- /dev/null
+++ b/server/glz_encoder_dictionary.h
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2009 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 _H_GLZ_ENCODER_DICTIONARY
+#define _H_GLZ_ENCODER_DICTIONARY
+
+#include <stdint.h>
+#include "glz_encoder_config.h"
+
+/*
+ Interface for maintaining lz dictionary that is shared among several encoders.
+ The interface for accessing the dictionary for encoding purposes is located in
+ glz_encoder_diciotnary_protected.h
+*/
+
+typedef void GlzEncDictContext;
+typedef void GlzEncDictImageContext;
+
+/* NOTE: DISPLAY_MIGRATE_DATA_VERSION should change in case GlzEncDictRestoreData changes*/
+typedef struct GlzEncDictRestoreData {
+ uint32_t size;
+ uint32_t max_encoders;
+ uint64_t last_image_id;
+} GlzEncDictRestoreData;
+
+/* size : maximal number of pixels occupying the window
+ max_encoders: maximal number of encoders that use the dicitionary
+ usr : callbacks */
+GlzEncDictContext *glz_enc_dictionary_create(uint32_t size, uint32_t max_encoders,
+ GlzEncoderUsrContext *usr);
+
+void glz_enc_dictionary_destroy(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
+
+/* returns the window capacity in pixels */
+uint32_t glz_enc_dictionary_get_size(GlzEncDictContext *);
+
+/* returns the current state of the dictionary.
+ NOTE - you should use it only when no encoder uses the dicitonary. */
+void glz_enc_dictionary_get_restore_data(GlzEncDictContext *opaque_dict,
+ GlzEncDictRestoreData *out_data,
+ GlzEncoderUsrContext *usr);
+
+/* creates a dictionary and initialized it by use the given info */
+GlzEncDictContext *glz_enc_dictionary_restore(GlzEncDictRestoreData *restore_data,
+ GlzEncoderUsrContext *usr);
+
+/* NOTE - you should use this routine only when no encoder uses the dicitonary. */
+void glz_enc_dictionary_reset(GlzEncDictContext *opaque_dict, GlzEncoderUsrContext *usr);
+
+/* image: the context returned by the encoder when the image was encoded.
+ NOTE - you should use this routine only when no encoder uses the dicitonary.*/
+void glz_enc_dictionary_remove_image(GlzEncDictContext *opaque_dict,
+ GlzEncDictImageContext *image, GlzEncoderUsrContext *usr);
+
+#endif // _H_GLZ_ENCODER_DICTIONARY
+
diff --git a/server/glz_encoder_dictionary_protected.h b/server/glz_encoder_dictionary_protected.h
new file mode 100644
index 00000000..f3f4315c
--- /dev/null
+++ b/server/glz_encoder_dictionary_protected.h
@@ -0,0 +1,187 @@
+/*
+ Copyright (C) 2009 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 _H_GLZ_ENCODER_DICTIONARY_PROTECTED
+#define _H_GLZ_ENCODER_DICTIONARY_PROTECTED
+
+/* Interface for using the dictionary for encoding.
+ Data structures are exposed for the encoder for efficiency
+ purposes. */
+typedef struct WindowImage WindowImage;
+typedef struct WindowImageSegment WindowImageSegment;
+
+
+//#define CHAINED_HASH
+
+#ifdef CHAINED_HASH
+#define HASH_SIZE_LOG 16
+#define HASH_CHAIN_SIZE 4
+#else
+#define HASH_SIZE_LOG 20
+#define HASH_CHAIN_SIZE 1
+#endif
+
+#define HASH_SIZE (1 << HASH_SIZE_LOG)
+#define HASH_MASK (HASH_SIZE - 1)
+
+typedef struct HashEntry HashEntry;
+
+typedef struct SharedDictionary SharedDictionary;
+
+struct WindowImage {
+ uint64_t id;
+ LzImageType type;
+ int size; // in pixels
+ uint32_t first_seg;
+ GlzUsrImageContext *usr_context;
+ WindowImage* next;
+ uint8_t is_alive;
+};
+
+#define MAX_IMAGE_SEGS_NUM (0xffffffff)
+#define NULL_IMAGE_SEG_ID MAX_IMAGE_SEGS_NUM
+#define INIT_IMAGE_SEGS_NUM 1000
+
+/* Images can be seperated into several chunks. The basic unit of the
+ dictionary window is one image segment. Each segment is encoded separately.
+ An encoded match can refer to only one segment.*/
+struct WindowImageSegment {
+ WindowImage *image;
+ uint8_t *lines;
+ uint8_t *lines_end;
+ uint32_t pixels_num; // Number of pixels in the segment
+ uint64_t pixels_so_far; // Total no. pixels passed through the window till this segment.
+ // NOTE - never use size delta independently. It should
+ // always be used with respect to a previouse size delta
+ uint32_t next;
+};
+
+
+struct __attribute__ ((__packed__)) HashEntry {
+ uint32_t image_seg_idx;
+ uint32_t ref_pix_idx;
+};
+
+
+struct SharedDictionary {
+ struct {
+ /* The segments storage. A dynamic array.
+ By reffering to a segment by its index, insetad of address,
+ we save space in the hash entries (32bit instead of 64bit) */
+ WindowImageSegment *segs;
+ uint32_t segs_quota;
+
+ /* The window is manged as a linked list rather than as a cyclic
+ array in order to keep the indices of the segments consistent
+ after reallocation */
+
+ /* the window in a resultion of image segments */
+ uint32_t used_segs_head; // the lateset head
+ uint32_t used_segs_tail;
+ uint32_t free_segs_head;
+
+ uint32_t *encoders_heads; // Holds for each encoder (by id), the window head when
+ // it started the encoding.
+ // The head is NULL_IMAGE_SEG_ID when the encoder is
+ // not encoding.
+
+ /* the window in a resultion of images. But here the head contains the oldest head*/
+ WindowImage* used_images_tail;
+ WindowImage* used_images_head;
+ WindowImage* free_images;
+
+ uint64_t pixels_so_far;
+ uint32_t size_limit; // max number of pixels in a window (per encoder)
+ } window;
+
+ /* Concurrency issues: the reading/wrting of each entry field should be atomic.
+ It is allowed that the reading/writing of the whole entry won't be atomic,
+ since before we access a reference we check its validity*/
+#ifdef CHAINED_HASH
+ HashEntry htab[HASH_SIZE][HASH_CHAIN_SIZE];
+ uint8_t htab_counter[HASH_SIZE]; //cyclic counter for the next entry in a chain to be assigned
+#else
+ HashEntry htab[HASH_SIZE];
+#endif
+
+ uint64_t last_image_id;
+ uint32_t max_encdoers;
+ pthread_mutex_t lock;
+ pthread_rwlock_t rw_alloc_lock;
+ GlzEncoderUsrContext *cur_usr; // each encoder has other context.
+};
+
+/*
+ Add the image to the tail of the window.
+ If possible, release images from the head of the window.
+ Also perform concurrency related operations.
+
+ usr_image_context: when an image is released from the window due to capicity overflow,
+ usr_image_context is given as a parmater to the free_image callback.
+
+ image_head_dist : the number of images between the current image and the head of the
+ window that is associated with the encoder.
+*/
+WindowImage *glz_dictionary_pre_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
+ SharedDictionary *dict, LzImageType image_type,
+ int image_width, int image_height, int image_stride,
+ uint8_t *first_lines, unsigned int num_first_lines,
+ GlzUsrImageContext *usr_image_context,
+ uint32_t *image_head_dist);
+
+/*
+ Performs concurrency related operations.
+ If possible, release images from the head of the window.
+*/
+void glz_dictionary_post_encode(uint32_t encoder_id, GlzEncoderUsrContext *usr,
+ SharedDictionary *dict);
+
+#define IMAGE_SEG_IS_EARLIER(dict, dst_seg, src_seg) ( \
+ ((src_seg) == NULL_IMAGE_SEG_ID) || (((dst_seg) != NULL_IMAGE_SEG_ID) \
+ && ((dict)->window.segs[(dst_seg)].pixels_so_far < \
+ (dict)->window.segs[(src_seg)].pixels_so_far)))
+
+
+#ifdef CHAINED_HASH
+#define UPDATE_HASH(dict, hval, seg, pix) { \
+ uint8_t tmp_count = (dict)->htab_counter[hval]; \
+ (dict)->htab[hval][tmp_count].image_seg_idx = seg; \
+ (dict)->htab[hval][tmp_count].ref_pix_idx = pix; \
+ tmp_count = ((tmp_count) + 1) & (HASH_CHAIN_SIZE - 1); \
+ dict->htab_counter[hval] = tmp_count; \
+}
+#else
+#define UPDATE_HASH(dict, hval, seg, pix) { \
+ (dict)->htab[hval].image_seg_idx = seg; \
+ (dict)->htab[hval].ref_pix_idx = pix; \
+}
+#endif
+
+/* checks if the reference segment is located in the range of the window
+ of the current encoder */
+#define REF_SEG_IS_VALID(dict, enc_id, ref_seg, src_seg) ( \
+ ((ref_seg) == (src_seg)) || \
+ ((ref_seg)->image && \
+ (ref_seg)->image->is_alive && \
+ (src_seg->image->type == ref_seg->image->type) && \
+ (ref_seg->pixels_so_far <= src_seg->pixels_so_far) && \
+ ((dict)->window.segs[ \
+ (dict)->window.encoders_heads[enc_id]].pixels_so_far <= \
+ ref_seg->pixels_so_far)))
+
+#endif // _H_GLZ_ENCODER_DICTIONARY_PROTECTED
+
diff --git a/server/red_bitmap_utils.h b/server/red_bitmap_utils.h
new file mode 100644
index 00000000..6153ea2f
--- /dev/null
+++ b/server/red_bitmap_utils.h
@@ -0,0 +1,162 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#ifdef RED_BITMAP_UTILS_RGB16
+#define PIXEL rgb16_pixel_t
+#define FNAME(name) name##_rgb16
+#define GET_r(pix) (((pix) >> 10) & 0x1f)
+#define GET_g(pix) (((pix) >> 5) & 0x1f)
+#define GET_b(pix) ((pix) & 0x1f)
+#endif
+
+#if defined(RED_BITMAP_UTILS_RGB24) || defined(RED_BITMAP_UTILS_RGB32)
+#define GET_r(pix) ((pix).r)
+#define GET_g(pix) ((pix).g)
+#define GET_b(pix) ((pix).b)
+#endif
+
+#ifdef RED_BITMAP_UTILS_RGB24
+#define PIXEL rgb24_pixel_t
+#define FNAME(name) name##_rgb24
+#endif
+
+#ifdef RED_BITMAP_UTILS_RGB32
+#define PIXEL rgb32_pixel_t
+#define FNAME(name) name##_rgb32
+#endif
+
+
+#define SAME_PIXEL_WEIGHT 0.5
+#define NOT_CONTRAST_PIXELS_WEIGHT -0.25
+#define CONTRAST_PIXELS_WEIGHT 1.0
+
+#ifndef RED_BITMAP_UTILS_RGB16
+#define CONTRAST_TH 60
+#else
+#define CONTRAST_TH 8
+#endif
+
+
+#define SAMPLE_JUMP 15
+#define SAME_PIXEL(p1, p2) (GET_r(p1) == GET_r(p2) && GET_g(p1) == GET_g(p2) && \
+ GET_b(p1) == GET_b(p2))
+
+static const double FNAME(PIX_PAIR_SCORE)[] = {
+ SAME_PIXEL_WEIGHT,
+ CONTRAST_PIXELS_WEIGHT,
+ NOT_CONTRAST_PIXELS_WEIGHT,
+};
+
+// return 0 - equal, 1 - for contrast, 2 for no contrast (PIX_PAIR_SCORE is defined accordingly)
+static inline int FNAME(pixelcmp)(PIXEL p1, PIXEL p2)
+{
+ int diff = ABS(GET_r(p1) - GET_r(p2));
+ int equal;
+
+ if (diff >= CONTRAST_TH) {
+ return 1;
+ }
+ equal = !diff;
+
+ diff = ABS(GET_g(p1) - GET_g(p2));
+
+ if (diff >= CONTRAST_TH) {
+ return 1;
+ }
+
+ equal = equal && !diff;
+
+ diff = ABS(GET_b(p1) - GET_b(p2));
+ if (diff >= CONTRAST_TH) {
+ return 1;
+ }
+ equal = equal && !diff;
+
+ if (equal) {
+ return 0;
+ } else {
+ return 2;
+ }
+}
+
+static inline double FNAME(pixels_square_score)(PIXEL *line1, PIXEL *line2)
+{
+ double ret = 0.0;
+ int all_ident = TRUE;
+ int cmp_res;
+ cmp_res = FNAME(pixelcmp)(*line1, line1[1]);
+ all_ident = all_ident && (!cmp_res);
+ ret += FNAME(PIX_PAIR_SCORE)[cmp_res];
+ cmp_res = FNAME(pixelcmp)(*line1, *line2);
+ all_ident = all_ident && (!cmp_res);
+ ret += FNAME(PIX_PAIR_SCORE)[cmp_res];
+ cmp_res = FNAME(pixelcmp)(*line1, line2[1]);
+ all_ident = all_ident && (!cmp_res);
+ ret += FNAME(PIX_PAIR_SCORE)[cmp_res];
+
+ // ignore squares where al pixels are identical
+ if (all_ident) {
+ ret -= (FNAME(PIX_PAIR_SCORE)[0]) * 3;
+ }
+
+ return ret;
+}
+
+static void FNAME(compute_lines_gradual_score)(PIXEL *lines, int width, int num_lines,
+ double *o_samples_sum_score, int *o_num_samples)
+{
+ int jump = (SAMPLE_JUMP % width) ? SAMPLE_JUMP : SAMPLE_JUMP - 1;
+ PIXEL *cur_pix = lines + width / 2;
+ PIXEL *bottom_pix;
+ PIXEL *last_line = lines + (num_lines - 1) * width;
+
+ if ((width <= 1) || (num_lines <= 1)) {
+ *o_num_samples = 1;
+ *o_samples_sum_score = 1.0;
+ return;
+ }
+
+ *o_samples_sum_score = 0;
+ *o_num_samples = 0;
+
+ while (cur_pix < last_line) {
+ if ((cur_pix + 1 - lines) % width == 0) { // last pixel in the row
+ cur_pix--; // jump is bigger than 1 so we will not enter endless loop
+ }
+ bottom_pix = cur_pix + width;
+ (*o_samples_sum_score) += FNAME(pixels_square_score)(cur_pix, bottom_pix);
+ (*o_num_samples)++;
+ cur_pix += jump;
+ }
+
+ (*o_num_samples) *= 3;
+}
+
+#undef PIXEL
+#undef FNAME
+#undef SAME_PIXEL
+#undef GET_r
+#undef GET_g
+#undef GET_b
+#undef RED_BITMAP_UTILS_RGB16
+#undef RED_BITMAP_UTILS_RGB24
+#undef RED_BITMAP_UTILS_RGB32
+#undef SAMPLE_JUMP
+#undef CONTRAST_TH
+#undef SAME_PIXEL_WEIGHT
+#undef NOT_CONTRAST_PIXELS_WEIGHT
+
diff --git a/server/red_client_cache.h b/server/red_client_cache.h
new file mode 100644
index 00000000..c7b40e2f
--- /dev/null
+++ b/server/red_client_cache.h
@@ -0,0 +1,136 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#if defined(CLIENT_CURSOR_CACHE)
+
+#define CACHE_NAME cursor_cache
+#define CACHE_HASH_KEY CURSOR_CACHE_HASH_KEY
+#define CACHE_HASH_SIZE CURSOR_CACHE_HASH_SIZE
+#define CACHE_INVAL_TYPE RED_CURSOR_INVAL_ONE
+#define FUNC_NAME(name) red_cursor_cache_##name
+#define VAR_NAME(name) cursor_cache_##name
+#define CHANNEL CursorChannel
+
+#elif defined(CLIENT_PALETTE_CACHE)
+
+#define CACHE_NAME palette_cache
+#define CACHE_HASH_KEY PALETTE_CACHE_HASH_KEY
+#define CACHE_HASH_SIZE PALETTE_CACHE_HASH_SIZE
+#define CACHE_INVAL_TYPE RED_DISPLAY_INVAL_PALETTE
+#define FUNC_NAME(name) red_palette_cache_##name
+#define VAR_NAME(name) palette_cache_##name
+#define CHANNEL DisplayChannel
+#else
+
+#error "no cache type."
+
+#endif
+
+static CacheItem *FUNC_NAME(find)(CHANNEL *channel, uint64_t id)
+{
+ CacheItem *item = channel->CACHE_NAME[CACHE_HASH_KEY(id)];
+
+ while (item) {
+ if (item->id == id) {
+ ring_remove(&item->u.cache_data.lru_link);
+ ring_add(&channel->VAR_NAME(lru), &item->u.cache_data.lru_link);
+ break;
+ }
+ item = item->u.cache_data.next;
+ }
+ return item;
+}
+
+static void FUNC_NAME(remove)(CHANNEL *channel, CacheItem *item)
+{
+ CacheItem **now;
+ ASSERT(item);
+
+ now = &channel->CACHE_NAME[CACHE_HASH_KEY(item->id)];
+ for (;;) {
+ ASSERT(*now);
+ if (*now == item) {
+ *now = item->u.cache_data.next;
+ break;
+ }
+ now = &(*now)->u.cache_data.next;
+ }
+ ring_remove(&item->u.cache_data.lru_link);
+ channel->VAR_NAME(items)--;
+ channel->VAR_NAME(available) += item->size;
+
+ red_pipe_item_init(&item->u.pipe_data, PIPE_ITEM_TYPE_INVAL_ONE);
+ red_pipe_add_tail(&channel->base, &item->u.pipe_data); // for now
+}
+
+static int FUNC_NAME(add)(CHANNEL *channel, uint64_t id, size_t size)
+{
+ CacheItem *item;
+ int key;
+
+ item = malloc(sizeof(*item));
+ if (!item) {
+ return FALSE;
+ }
+
+ channel->VAR_NAME(available) -= size;
+ while (channel->VAR_NAME(available) < 0) {
+ CacheItem *tail = (CacheItem *)ring_get_tail(&channel->VAR_NAME(lru));
+ if (!tail) {
+ channel->VAR_NAME(available) += size;
+ free(item);
+ return FALSE;
+ }
+ FUNC_NAME(remove)(channel, tail);
+ }
+ ++channel->VAR_NAME(items);
+ item->u.cache_data.next = channel->CACHE_NAME[(key = CACHE_HASH_KEY(id))];
+ channel->CACHE_NAME[key] = item;
+ ring_item_init(&item->u.cache_data.lru_link);
+ ring_add(&channel->VAR_NAME(lru), &item->u.cache_data.lru_link);
+ item->id = id;
+ item->size = size;
+ item->inval_type = CACHE_INVAL_TYPE;
+ return TRUE;
+}
+
+static void FUNC_NAME(reset)(CHANNEL *channel, long size)
+{
+ int i;
+
+ for (i = 0; i < CACHE_HASH_SIZE; i++) {
+ while (channel->CACHE_NAME[i]) {
+ CacheItem *item = channel->CACHE_NAME[i];
+ channel->CACHE_NAME[i] = item->u.cache_data.next;
+ free(item);
+ }
+ }
+ ring_init(&channel->VAR_NAME(lru));
+ channel->VAR_NAME(available) = size;
+ channel->VAR_NAME(items) = 0;
+}
+
+
+#undef CACHE_NAME
+#undef CACHE_HASH_KEY
+#undef CACHE_HASH_SIZE
+#undef CACHE_INVAL_TYPE
+#undef CACHE_MAX_CLIENT_SIZE
+#undef FUNC_NAME
+#undef VAR_NAME
+#undef CHANNEL
+
diff --git a/server/red_client_shared_cache.h b/server/red_client_shared_cache.h
new file mode 100644
index 00000000..1d3c4528
--- /dev/null
+++ b/server/red_client_shared_cache.h
@@ -0,0 +1,216 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#if defined(CLIENT_PIXMAPS_CACHE)
+
+#define CACHE PixmapCache
+
+#define CACHE_NAME bits_cache
+#define CACHE_HASH_KEY BITS_CACHE_HASH_KEY
+#define CACHE_HASH_SIZE BITS_CACHE_HASH_SIZE
+#define PIPE_ITEM_TYPE PIPE_ITEM_TYPE_INVAL_PIXMAP
+#define FUNC_NAME(name) pixmap_cache_##name
+#define PRIVATE_FUNC_NAME(name) __pixmap_cache_##name
+#define CHANNEL DisplayChannel
+#define CACH_GENERATION pixmap_cache_generation
+#define INVAL_ALL_VERB RED_DISPLAY_INVAL_ALL_PIXMAPS
+#else
+
+#error "no cache type."
+
+#endif
+
+
+static int FUNC_NAME(hit)(CACHE *cache, uint64_t id, CHANNEL *channel)
+{
+ NewCacheItem *item;
+ uint64_t serial;
+
+ serial = channel_message_serial((RedChannel *)channel);
+ pthread_mutex_lock(&cache->lock);
+ item = cache->hash_table[CACHE_HASH_KEY(id)];
+
+ while (item) {
+ if (item->id == id) {
+ ring_remove(&item->lru_link);
+ ring_add(&cache->lru, &item->lru_link);
+ ASSERT(channel->base.id < MAX_CACHE_CLIENTS)
+ item->sync[channel->base.id] = serial;
+ cache->sync[channel->base.id] = serial;
+ break;
+ }
+ item = item->next;
+ }
+ pthread_mutex_unlock(&cache->lock);
+
+ return !!item;
+}
+
+static int FUNC_NAME(add)(CACHE *cache, uint64_t id, uint32_t size, CHANNEL *channel)
+{
+ NewCacheItem *item;
+ uint64_t serial;
+ int key;
+
+ ASSERT(size > 0);
+
+ item = malloc(sizeof(*item));
+ if (!item) {
+ return FALSE;
+ }
+ serial = channel_message_serial((RedChannel *)channel);
+
+ pthread_mutex_lock(&cache->lock);
+
+ if (cache->generation != channel->CACH_GENERATION) {
+ if (!channel->pending_pixmaps_sync) {
+ red_pipe_add_type((RedChannel *)channel, PIPE_ITEM_TYPE_PIXMAP_SYNC);
+ channel->pending_pixmaps_sync = TRUE;
+ }
+ pthread_mutex_unlock(&cache->lock);
+ free(item);
+ return FALSE;
+ }
+
+ cache->available -= size;
+ while (cache->available < 0) {
+ NewCacheItem *tail;
+ NewCacheItem **now;
+
+ if (!(tail = (NewCacheItem *)ring_get_tail(&cache->lru)) ||
+ tail->sync[channel->base.id] == serial) {
+ cache->available += size;
+ pthread_mutex_unlock(&cache->lock);
+ free(item);
+ return FALSE;
+ }
+
+ now = &cache->hash_table[CACHE_HASH_KEY(tail->id)];
+ for (;;) {
+ ASSERT(*now);
+ if (*now == tail) {
+ *now = tail->next;
+ break;
+ }
+ now = &(*now)->next;
+ }
+ ring_remove(&tail->lru_link);
+ cache->items--;
+ cache->available += tail->size;
+ cache->sync[channel->base.id] = serial;
+ display_channel_push_release(channel, RED_RES_TYPE_PIXMAP, tail->id, tail->sync);
+ free(tail);
+ }
+ ++cache->items;
+ item->next = cache->hash_table[(key = CACHE_HASH_KEY(id))];
+ cache->hash_table[key] = item;
+ ring_item_init(&item->lru_link);
+ ring_add(&cache->lru, &item->lru_link);
+ item->id = id;
+ item->size = size;
+ memset(item->sync, 0, sizeof(item->sync));
+ item->sync[channel->base.id] = serial;
+ cache->sync[channel->base.id] = serial;
+ pthread_mutex_unlock(&cache->lock);
+ return TRUE;
+}
+
+static void PRIVATE_FUNC_NAME(clear)(CACHE *cache)
+{
+ NewCacheItem *item;
+
+ if (cache->freezed) {
+ cache->lru.next = cache->freezed_head;
+ cache->lru.prev = cache->freezed_tail;
+ cache->freezed = FALSE;
+ }
+
+ while ((item = (NewCacheItem *)ring_get_head(&cache->lru))) {
+ ring_remove(&item->lru_link);
+ free(item);
+ }
+ memset(cache->hash_table, 0, sizeof(*cache->hash_table) * CACHE_HASH_SIZE);
+
+ cache->available = cache->size;
+ cache->items = 0;
+}
+
+static void FUNC_NAME(reset)(CACHE *cache, CHANNEL *channel, RedWaitForChannels* sync_data)
+{
+ uint8_t wait_count;
+ uint64_t serial;
+ uint32_t i;
+
+ serial = channel_message_serial((RedChannel *)channel);
+ pthread_mutex_lock(&cache->lock);
+ PRIVATE_FUNC_NAME(clear)(cache);
+
+ channel->CACH_GENERATION = ++cache->generation;
+ cache->generation_initiator.client = channel->base.id;
+ cache->generation_initiator.message = serial;
+ cache->sync[channel->base.id] = serial;
+
+ wait_count = 0;
+ for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
+ if (cache->sync[i] && i != channel->base.id) {
+ sync_data->wait_list[wait_count].channel_type = RED_CHANNEL_DISPLAY;
+ sync_data->wait_list[wait_count].channel_id = i;
+ sync_data->wait_list[wait_count++].message_serial = cache->sync[i];
+ }
+ }
+ sync_data->wait_count = wait_count;
+ pthread_mutex_unlock(&cache->lock);
+}
+
+static int FUNC_NAME(freeze)(CACHE *cache)
+{
+ pthread_mutex_lock(&cache->lock);
+
+ if (cache->freezed) {
+ pthread_mutex_unlock(&cache->lock);
+ return FALSE;
+ }
+
+ cache->freezed_head = cache->lru.next;
+ cache->freezed_tail = cache->lru.prev;
+ ring_init(&cache->lru);
+ memset(cache->hash_table, 0, sizeof(*cache->hash_table) * CACHE_HASH_SIZE);
+ cache->available = -1;
+ cache->freezed = TRUE;
+
+ pthread_mutex_unlock(&cache->lock);
+ return TRUE;
+}
+
+static void FUNC_NAME(destroy)(CACHE *cache)
+{
+ ASSERT(cache);
+
+ pthread_mutex_lock(&cache->lock);
+ PRIVATE_FUNC_NAME(clear)(cache);
+ pthread_mutex_unlock(&cache->lock);
+}
+
+#undef CACHE_NAME
+#undef CACHE_HASH_KEY
+#undef CACHE_HASH_SIZE
+#undef CACHE_INVAL_TYPE
+#undef CACHE_MAX_CLIENT_SIZE
+#undef FUNC_NAME
+#undef VAR_NAME
+#undef CHANNEL
+
diff --git a/server/red_common.h b/server/red_common.h
new file mode 100644
index 00000000..b397be51
--- /dev/null
+++ b/server/red_common.h
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2009 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 _H_RED_COMMON
+#define _H_RED_COMMON
+
+#include <sys/uio.h>
+#include <openssl/ssl.h>
+
+#include "red.h"
+
+#ifndef MIN
+#define MIN(x, y) (((x) <= (y)) ? (x) : (y))
+#endif
+#ifndef MAX
+#define MAX(x, y) (((x) >= (y)) ? (x) : (y))
+#endif
+
+#ifndef ABS
+#define ABS(a) ((a) >= 0 ? (a) : -(a))
+#endif
+
+#ifndef ALIGN
+#define ALIGN(a, b) (((a) + ((b) - 1)) & ~((b) - 1))
+#endif
+
+#define ASSERT(x) if (!(x)) { \
+ printf("%s: ASSERT %s failed\n", __FUNCTION__, #x); \
+ abort(); \
+}
+
+#define PANIC(str) { \
+ printf("%s: panic: %s\n", __FUNCTION__, str); \
+ abort(); \
+}
+
+#define TRUE 1
+#define FALSE 0
+
+#define red_error(format, ...) { \
+ printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__ ); \
+ abort(); \
+}
+
+#define red_printf(format, ...) \
+ printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__ )
+
+#define red_printf_once(format, ...) { \
+ static int do_print = TRUE; \
+ if (do_print) { \
+ do_print = FALSE; \
+ printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__ ); \
+ } \
+}
+
+#define red_printf_some(every, format, ...) { \
+ static int count = 0; \
+ if (count++ % (every) == 0) { \
+ printf("%s: " format "\n", __FUNCTION__, ## __VA_ARGS__ ); \
+ } \
+}
+
+#define OFFSETOF(type, member) ((unsigned long)&((type *)0)->member)
+#define CONTAINEROF(ptr, type, member) \
+ ((type *)((uint8_t *)(ptr) - OFFSETOF(type, member)))
+
+typedef enum {
+ IMAGE_COMPRESS_INVALID,
+ IMAGE_COMPRESS_AUTO_GLZ,
+ IMAGE_COMPRESS_AUTO_LZ,
+ IMAGE_COMPRESS_QUIC,
+ IMAGE_COMPRESS_GLZ,
+ IMAGE_COMPRESS_LZ,
+ IMAGE_COMPRESS_OFF,
+} image_compression_t;
+
+static inline uint64_t get_time_stamp()
+{
+ struct timespec time_space;
+ clock_gettime(CLOCK_MONOTONIC, &time_space);
+ return time_space.tv_sec * 1000 * 1000 * 1000 + time_space.tv_nsec;
+}
+
+#endif
+
diff --git a/server/red_dispatcher.c b/server/red_dispatcher.c
new file mode 100644
index 00000000..58de5a33
--- /dev/null
+++ b/server/red_dispatcher.c
@@ -0,0 +1,479 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <pthread.h>
+#include <sys/socket.h>
+#include <signal.h>
+
+#include "qxl_dev.h"
+#include "vd_interface.h"
+#include "red_worker.h"
+#include "quic.h"
+#include "cairo_canvas.h"
+#include "gl_canvas.h"
+#include "reds.h"
+#include "red_dispatcher.h"
+
+static int num_active_workers = 0;
+
+//volatile
+
+typedef struct RedDispatcher RedDispatcher;
+struct RedDispatcher {
+ QXLWorker base;
+ QXLInterface *qxl_interface;
+ int channel;
+ pthread_t worker_thread;
+ uint32_t pending;
+ int active;
+ int x_res;
+ int y_res;
+ int use_hardware_cursor;
+ RedDispatcher *next;
+};
+
+typedef struct RedWorkeState {
+ uint8_t *io_base;
+ unsigned long phys_delta;
+
+ uint32_t x_res;
+ uint32_t y_res;
+ uint32_t bits;
+ uint32_t stride;
+} RedWorkeState;
+
+extern uint32_t streaming_video;
+extern image_compression_t image_compression;
+
+static RedDispatcher *dispatchers = NULL;
+
+static void red_dispatcher_set_peer(Channel *channel, RedsStreamContext *peer, int migration,
+ int num_common_caps, uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ RedDispatcher *dispatcher;
+
+ red_printf("");
+ dispatcher = (RedDispatcher *)channel->data;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_DISPLAY_CONNECT;
+ write_message(dispatcher->channel, &message);
+ send_data(dispatcher->channel, &peer, sizeof(RedsStreamContext *));
+ send_data(dispatcher->channel, &migration, sizeof(int));
+}
+
+static void red_dispatcher_shutdown_peer(Channel *channel)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)channel->data;
+ red_printf("");
+ RedWorkeMessage message = RED_WORKER_MESSAGE_DISPLAY_DISCONNECT;
+ write_message(dispatcher->channel, &message);
+}
+
+static void red_dispatcher_migrate(Channel *channel)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)channel->data;
+ red_printf("channel type %u id %u", channel->type, channel->id);
+ RedWorkeMessage message = RED_WORKER_MESSAGE_DISPLAY_MIGRATE;
+ write_message(dispatcher->channel, &message);
+}
+
+static void red_dispatcher_set_cursor_peer(Channel *channel, RedsStreamContext *peer,
+ int migration, int num_common_caps,
+ uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)channel->data;
+ red_printf("");
+ RedWorkeMessage message = RED_WORKER_MESSAGE_CURSOR_CONNECT;
+ write_message(dispatcher->channel, &message);
+ send_data(dispatcher->channel, &peer, sizeof(RedsStreamContext *));
+ send_data(dispatcher->channel, &migration, sizeof(int));
+}
+
+static void red_dispatcher_shutdown_cursor_peer(Channel *channel)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)channel->data;
+ red_printf("");
+ RedWorkeMessage message = RED_WORKER_MESSAGE_CURSOR_DISCONNECT;
+ write_message(dispatcher->channel, &message);
+}
+
+static void red_dispatcher_cursor_migrate(Channel *channel)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)channel->data;
+ red_printf("channel type %u id %u", channel->type, channel->id);
+ RedWorkeMessage message = RED_WORKER_MESSAGE_CURSOR_MIGRATE;
+ write_message(dispatcher->channel, &message);
+}
+
+typedef struct RendererInfo {
+ int id;
+ const char *name;
+} RendererInfo;
+
+static RendererInfo renderers_info[] = {
+ {RED_RENDERER_CAIRO, "cairo"},
+ {RED_RENDERER_OGL_PBUF, "oglpbuf"},
+ {RED_RENDERER_OGL_PIXMAP, "oglpixmap"},
+ {RED_RENDERER_INVALID, NULL},
+};
+
+static uint32_t renderers[RED_MAX_RENDERERS];
+static uint32_t num_renderers = 0;
+
+static RendererInfo *find_renderer(const char *name)
+{
+ RendererInfo *inf = renderers_info;
+ while (inf->name) {
+ if (strcmp(name, inf->name) == 0) {
+ return inf;
+ }
+ inf++;
+ }
+ return NULL;
+}
+
+int red_dispatcher_add_renderer(const char *name)
+{
+ RendererInfo *inf;
+
+ if (num_renderers == RED_MAX_RENDERERS || !(inf = find_renderer(name))) {
+ return FALSE;
+ }
+ renderers[num_renderers++] = inf->id;
+ return TRUE;
+}
+
+int red_dispatcher_qxl_count()
+{
+ return num_active_workers;
+}
+
+static void update_client_mouse_allowed()
+{
+ static int allowed = FALSE;
+ int allow_now = FALSE;
+ int x_res = 0;
+ int y_res = 0;
+
+ if (num_active_workers > 0) {
+ allow_now = TRUE;
+ RedDispatcher *now = dispatchers;
+ while (now && allow_now) {
+ if (now->active) {
+ allow_now = now->use_hardware_cursor;
+ if (num_active_workers == 1) {
+ if (allow_now) {
+ x_res = now->x_res;
+ y_res = now->y_res;
+ }
+ break;
+ }
+ }
+ now = now->next;
+ }
+ }
+
+ if (allow_now || allow_now != allowed) {
+ allowed = allow_now;
+ reds_set_client_mouse_allowed(allowed, x_res, y_res);
+ }
+}
+
+static void qxl_worker_attach(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_ATTACH;
+ QXLDevInfo info;
+
+ dispatcher->qxl_interface->get_info(dispatcher->qxl_interface, &info);
+ dispatcher->x_res = info.x_res;
+ dispatcher->y_res = info.y_res;
+ dispatcher->use_hardware_cursor = info.use_hardware_cursor;
+ dispatcher->active = TRUE;
+
+ write_message(dispatcher->channel, &message);
+ send_data(dispatcher->channel, &info, sizeof(QXLDevInfo));
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+
+ num_active_workers++;
+ update_client_mouse_allowed();
+}
+
+static void qxl_worker_detach(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_DETACH;
+
+ write_message(dispatcher->channel, &message);
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+
+ dispatcher->x_res = 0;
+ dispatcher->y_res = 0;
+ dispatcher->use_hardware_cursor = FALSE;
+ dispatcher->active = FALSE;
+ num_active_workers--;
+ update_client_mouse_allowed();
+}
+
+static void qxl_worker_update_area(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_UPDATE;
+
+ write_message(dispatcher->channel, &message);
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+}
+
+static void qxl_worker_wakeup(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+
+ if (!test_bit(RED_WORKER_PENDING_WAKEUP, dispatcher->pending)) {
+ RedWorkeMessage message = RED_WORKER_MESSAGE_WAKEUP;
+ set_bit(RED_WORKER_PENDING_WAKEUP, &dispatcher->pending);
+ write_message(dispatcher->channel, &message);
+ }
+}
+
+static void qxl_worker_oom(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ if (!test_bit(RED_WORKER_PENDING_OOM, dispatcher->pending)) {
+ RedWorkeMessage message = RED_WORKER_MESSAGE_OOM;
+ set_bit(RED_WORKER_PENDING_OOM, &dispatcher->pending);
+ write_message(dispatcher->channel, &message);
+ }
+}
+
+static void qxl_worker_save(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_SAVE;
+
+ write_message(dispatcher->channel, &message);
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+}
+
+static void qxl_worker_load(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_LOAD;
+
+ write_message(dispatcher->channel, &message);
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+}
+
+static void qxl_worker_start(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_START;
+
+ write_message(dispatcher->channel, &message);
+}
+
+static void qxl_worker_stop(QXLWorker *qxl_worker)
+{
+ RedDispatcher *dispatcher = (RedDispatcher *)qxl_worker;
+ RedWorkeMessage message = RED_WORKER_MESSAGE_STOP;
+
+ write_message(dispatcher->channel, &message);
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+}
+
+void red_dispatcher_set_mm_time(uint32_t mm_time)
+{
+ RedDispatcher *now = dispatchers;
+ while (now) {
+ now->qxl_interface->set_mm_time(now->qxl_interface, mm_time);
+ now = now->next;
+ }
+}
+
+static inline int calc_compression_level()
+{
+ if (streaming_video || (image_compression != IMAGE_COMPRESS_QUIC)) {
+ return 0;
+ } else {
+ return 1;
+ }
+}
+
+void red_dispatcher_on_ic_change()
+{
+ int compression_level = calc_compression_level();
+ RedDispatcher *now = dispatchers;
+ while (now) {
+ RedWorkeMessage message = RED_WORKER_MESSAGE_SET_COMPRESSION;
+ now->qxl_interface->set_compression_level(now->qxl_interface, compression_level);
+ write_message(now->channel, &message);
+ send_data(now->channel, &image_compression, sizeof(image_compression_t));
+ now = now->next;
+ }
+}
+
+void red_dispatcher_on_sv_change()
+{
+ int compression_level = calc_compression_level();
+ RedDispatcher *now = dispatchers;
+ while (now) {
+ RedWorkeMessage message = RED_WORKER_MESSAGE_SET_STREAMING_VIDEO;
+ now->qxl_interface->set_compression_level(now->qxl_interface, compression_level);
+ write_message(now->channel, &message);
+ send_data(now->channel, &streaming_video, sizeof(uint32_t));
+ now = now->next;
+ }
+}
+
+void red_dispatcher_set_mouse_mode(uint32_t mode)
+{
+ RedDispatcher *now = dispatchers;
+ while (now) {
+ RedWorkeMessage message = RED_WORKER_MESSAGE_SET_MOUSE_MODE;
+ write_message(now->channel, &message);
+ send_data(now->channel, &mode, sizeof(uint32_t));
+ now = now->next;
+ }
+}
+
+int red_dispatcher_count()
+{
+ RedDispatcher *now = dispatchers;
+ int ret = 0;
+
+ while (now) {
+ ret++;
+ now = now->next;
+ }
+ return ret;
+}
+
+uint32_t red_dispatcher_qxl_ram_size()
+{
+ QXLDevInfo qxl_info;
+ dispatchers->qxl_interface->get_info(dispatchers->qxl_interface, &qxl_info);
+ return qxl_info.ram_size;
+}
+
+RedDispatcher *red_dispatcher_init(QXLInterface *qxl_interface)
+{
+ RedDispatcher *dispatcher;
+ int channels[2];
+ RedWorkeMessage message;
+ WorkerInitData init_data;
+ int r;
+ Channel *reds_channel;
+ Channel *cursor_channel;
+ sigset_t thread_sig_mask;
+ sigset_t curr_sig_mask;
+
+ if (qxl_interface->pci_vendor != REDHAT_PCI_VENDOR_ID ||
+ qxl_interface->pci_id != QXL_DEVICE_ID ||
+ qxl_interface->pci_revision != QXL_REVISION) {
+ red_printf("pci mismatch");
+ return NULL;
+ }
+
+ quic_init();
+ cairo_canvas_init();
+ gl_canvas_init();
+
+ if (socketpair(AF_LOCAL, SOCK_STREAM, 0, channels) == -1) {
+ red_error("socketpair failed %s", strerror(errno));
+ }
+
+ if (!(dispatcher = malloc(sizeof(RedDispatcher)))) {
+ red_error("malloc failed");
+ }
+ memset(dispatcher, 0, sizeof(RedDispatcher));
+ dispatcher->channel = channels[0];
+ init_data.qxl_interface = dispatcher->qxl_interface = qxl_interface;
+ init_data.id = qxl_interface->base.id;
+ init_data.channel = channels[1];
+ init_data.pending = &dispatcher->pending;
+ init_data.num_renderers = num_renderers;
+ memcpy(init_data.renderers, renderers, sizeof(init_data.renderers));
+
+ init_data.image_compression = image_compression;
+ init_data.streaming_video = streaming_video;
+
+ dispatcher->base.major_version = VD_INTERFACE_QXL_MAJOR;
+ dispatcher->base.major_version = VD_INTERFACE_QXL_MINOR;
+ dispatcher->base.attach = qxl_worker_attach;
+ dispatcher->base.detach = qxl_worker_detach;
+ dispatcher->base.wakeup = qxl_worker_wakeup;
+ dispatcher->base.oom = qxl_worker_oom;
+ dispatcher->base.save = qxl_worker_save;
+ dispatcher->base.load = qxl_worker_load;
+ dispatcher->base.start = qxl_worker_start;
+ dispatcher->base.stop = qxl_worker_stop;
+ dispatcher->base.update_area = qxl_worker_update_area;
+
+ sigfillset(&thread_sig_mask);
+ sigdelset(&thread_sig_mask, SIGILL);
+ sigdelset(&thread_sig_mask, SIGFPE);
+ sigdelset(&thread_sig_mask, SIGSEGV);
+ pthread_sigmask(SIG_SETMASK, &thread_sig_mask, &curr_sig_mask);
+ if ((r = pthread_create(&dispatcher->worker_thread, NULL, red_worker_main, &init_data))) {
+ red_error("create thread failed %d", r);
+ }
+ pthread_sigmask(SIG_SETMASK, &curr_sig_mask, NULL);
+
+ read_message(dispatcher->channel, &message);
+ ASSERT(message == RED_WORKER_MESSAGE_READY);
+ if (!(reds_channel = malloc(sizeof(Channel)))) {
+ red_error("reds channel malloc failed");
+ }
+ memset(reds_channel, 0, sizeof(Channel));
+ reds_channel->type = RED_CHANNEL_DISPLAY;
+ reds_channel->id = qxl_interface->base.id;
+ reds_channel->link = red_dispatcher_set_peer;
+ reds_channel->shutdown = red_dispatcher_shutdown_peer;
+ reds_channel->migrate = red_dispatcher_migrate;
+ reds_channel->data = dispatcher;
+ reds_register_channel(reds_channel);
+
+ if (!(cursor_channel = malloc(sizeof(Channel)))) {
+ red_error("reds channel malloc failed");
+ }
+ memset(cursor_channel, 0, sizeof(Channel));
+ cursor_channel->type = RED_CHANNEL_CURSOR;
+ cursor_channel->id = qxl_interface->base.id;
+ cursor_channel->link = red_dispatcher_set_cursor_peer;
+ cursor_channel->shutdown = red_dispatcher_shutdown_cursor_peer;
+ cursor_channel->migrate = red_dispatcher_cursor_migrate;
+ cursor_channel->data = dispatcher;
+ reds_register_channel(cursor_channel);
+ qxl_interface->attache_worker(qxl_interface, &dispatcher->base);
+ qxl_interface->set_compression_level(qxl_interface, calc_compression_level());
+
+ dispatcher->next = dispatchers;
+ dispatchers = dispatcher;
+ return dispatcher;
+}
+
diff --git a/server/red_dispatcher.h b/server/red_dispatcher.h
new file mode 100644
index 00000000..9482bc46
--- /dev/null
+++ b/server/red_dispatcher.h
@@ -0,0 +1,33 @@
+/*
+ Copyright (C) 2009 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 _H_RED_DISPATCHER
+#define _H_RED_DISPATCHER
+
+
+struct RedDispatcher *red_dispatcher_init(QXLInterface *qxl_interface);
+
+void red_dispatcher_set_mm_time(uint32_t);
+void red_dispatcher_on_ic_change();
+void red_dispatcher_on_sv_change();
+void red_dispatcher_set_mouse_mode(uint32_t mode);
+int red_dispatcher_count();
+int red_dispatcher_add_renderer(const char *name);
+uint32_t red_dispatcher_qxl_ram_size();
+int red_dispatcher_qxl_count();
+#endif
+
diff --git a/server/red_worker.c b/server/red_worker.c
new file mode 100644
index 00000000..4604e05e
--- /dev/null
+++ b/server/red_worker.c
@@ -0,0 +1,8492 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <stdio.h>
+#include <stdarg.h>
+#include <sys/epoll.h>
+#include <fcntl.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <netinet/tcp.h>
+#include <setjmp.h>
+#include <openssl/ssl.h>
+
+#include "qxl_dev.h"
+#include "vd_interface.h"
+#include "region.h"
+#include "red.h"
+#include "red_worker.h"
+#include "cairo.h"
+#include "cairo_canvas.h"
+#include "gl_canvas.h"
+#include "ogl_ctx.h"
+#include "libavcodec/avcodec.h"
+#include "quic.h"
+#include "lz.h"
+#include "glz_encoder_dictionary.h"
+#include "glz_encoder.h"
+#include "stat.h"
+#include "reds.h"
+#include "ring.h"
+
+//#define COMPRESS_STAT
+//#define DUMP_BITMAP
+#define STREAM_TRACE
+//#define PIPE_DEBUG
+#define USE_EXCLUDE_RGN
+//#define RED_WORKER_STAT
+//#define DRAW_ALL
+//#define COMPRESS_DEBUG
+
+//#define UPDATE_AREA_BY_TREE
+
+#define CMD_RING_POLL_TIMEOUT 10 //milli
+#define CMD_RING_POLL_RETRIES 200
+
+#define DETACH_TIMEOUT 4000000000 //nano
+#define DETACH_SLEEP_DURATION 10000 //micro
+
+#define DISPLAY_CLIENT_TIMEOUT 4000000000 //nano
+#define DISPLAY_CLIENT_RETRY_INTERVAL 10000 //micro
+#define DISPLAY_MAX_SUB_MESSAGES 10
+
+#define DISPLAY_FREE_LIST_DEFAULT_SIZE 128
+
+#define RED_STREAM_DETACTION_MAX_DELTA ((1000 * 1000 * 1000) / 5) // 1/5 sec
+#define RED_STREAM_CONTINUS_MAX_DELTA ((1000 * 1000 * 1000) / 2) // 1/2 sec
+#define RED_STREAM_TIMOUT (1000 * 1000 * 1000)
+#define RED_STREAM_START_CONDITION 20
+
+#define FPS_TEST_INTERVAL 1
+#define MAX_FPS 30
+
+//best bit rate per pixel base on 13000000 bps for frame size 720x576 pixels and 25 fps
+#define BEST_BIT_RATE_PER_PIXEL 38
+#define WARST_BIT_RATE_PER_PIXEL 4
+
+#define RED_COMPRESS_BUF_SIZE (1024 * 64)
+
+typedef int64_t red_time_t;
+
+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;
+
+inline stat_time_t stat_now()
+{
+ struct timespec ts;
+ clock_gettime(clock_id, &ts);
+ return ts.tv_nsec + ts.tv_sec * 1000 * 1000 * 1000;
+}
+
+double stat_cpu_time_to_sec(stat_time_t time)
+{
+ return (double)time / (1000 * 1000 * 1000);
+}
+
+typedef struct stat_info_s {
+ const char *name;
+ uint32_t count;
+ stat_time_t max;
+ stat_time_t min;
+ stat_time_t total;
+#ifdef COMPRESS_STAT
+ uint64_t orig_size;
+ uint64_t comp_size;
+#endif
+} stat_info_t;
+
+static inline void stat_reset(stat_info_t *info)
+{
+ info->count = info->max = info->total = 0;
+ info->min = ~(stat_time_t)0;
+#ifdef COMPRESS_STAT
+ info->orig_size = info->comp_size = 0;
+#endif
+}
+
+#endif
+
+#ifdef RED_WORKER_STAT
+static const char *add_stat_name = "add";
+static const char *exclude_stat_name = "exclude";
+static const char *__exclude_stat_name = "__exclude";
+
+static inline void stat_init(stat_info_t *info, const char *name)
+{
+ info->name = name;
+ stat_reset(info);
+}
+
+static inline void stat_add(stat_info_t *info, stat_time_t start)
+{
+ stat_time_t time;
+ ++info->count;
+ time = stat_now() - start;
+ info->total += time;
+ info->max = MAX(info->max, time);
+ info->min = MIN(info->min, time);
+}
+
+#else
+#define stat_add(a, b)
+#define stat_init(a, b)
+#endif
+
+#ifdef COMPRESS_STAT
+static const char *lz_stat_name = "lz";
+static const char *glz_stat_name = "glz";
+static const char *quic_stat_name = "quic";
+
+static inline void stat_compress_init(stat_info_t *info, const char *name)
+{
+ info->name = name;
+ stat_reset(info);
+}
+
+static inline void stat_compress_add(stat_info_t *info, stat_time_t start, int orig_size,
+ int comp_size)
+{
+ stat_time_t time;
+ ++info->count;
+ time = stat_now() - start;
+ info->total += time;
+ info->max = MAX(info->max, time);
+ info->min = MIN(info->min, time);
+ info->orig_size += orig_size;
+ info->comp_size += comp_size;
+}
+
+double inline stat_byte_to_mega(uint64_t size)
+{
+ return (double)size / (1000 * 1000);
+}
+
+#else
+#define stat_compress_init(a, b)
+#define stat_compress_add(a, b, c, d)
+#endif
+
+#define MAX_EPOLL_SOURCES 10
+#define INF_EPOLL_WAIT ~0
+
+
+typedef struct EventListener EventListener;
+typedef void (*event_listener_action_proc)(EventListener *ctx, uint32_t events);
+struct EventListener {
+ uint32_t refs;
+ event_listener_action_proc action;
+};
+
+enum {
+ BUF_TYPE_RAW = 1,
+ BUF_TYPE_COMPRESS_BUF,
+ BUF_TYPE_CHUNK,
+};
+
+typedef struct BufDescriptor {
+ uint32_t type;
+ uint32_t size;
+ uint8_t *data;
+} BufDescriptor;
+
+enum {
+ PIPE_ITEM_TYPE_DRAW,
+ PIPE_ITEM_TYPE_MODE,
+ PIPE_ITEM_TYPE_INVAL_ONE,
+ PIPE_ITEM_TYPE_CURSOR,
+ PIPE_ITEM_TYPE_MIGRATE,
+ PIPE_ITEM_TYPE_LOCAL_CURSOR,
+ PIPE_ITEM_TYPE_SET_ACK,
+ PIPE_ITEM_TYPE_CURSOR_INIT,
+ PIPE_ITEM_TYPE_IMAGE,
+ PIPE_ITEM_TYPE_STREAM_CREATE,
+ PIPE_ITEM_TYPE_STREAM_CLIP,
+ PIPE_ITEM_TYPE_STREAM_DESTROY,
+ PIPE_ITEM_TYPE_UPGRADE,
+ PIPE_ITEM_TYPE_VERB,
+ PIPE_ITEM_TYPE_MIGRATE_DATA,
+ PIPE_ITEM_TYPE_PIXMAP_SYNC,
+ PIPE_ITEM_TYPE_PIXMAP_RESET,
+ PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE,
+ PIPE_ITEM_TYPE_INVAL_PALLET_CACHE,
+};
+
+typedef struct PipeItem {
+ RingItem link;
+ int type;
+} PipeItem;
+
+typedef struct VerbItem {
+ PipeItem base;
+ uint16_t verb;
+} VerbItem;
+
+#define MAX_CACHE_CLIENTS 4
+#define MAX_LZ_ENCODERS MAX_CACHE_CLIENTS
+
+typedef struct NewCacheItem NewCacheItem;
+
+struct NewCacheItem {
+ RingItem lru_link;
+ NewCacheItem *next;
+ uint64_t id;
+ uint64_t sync[MAX_CACHE_CLIENTS];
+ size_t size;
+};
+
+typedef struct CacheItem CacheItem;
+
+struct CacheItem {
+ union {
+ PipeItem pipe_data;
+ struct {
+ RingItem lru_link;
+ CacheItem *next;
+ } cache_data;
+ } u;
+ uint64_t id;
+ size_t size;
+ uint32_t inval_type;
+};
+
+enum {
+ CURSOR_TYPE_INVALID,
+ CURSOR_TYPE_DEV,
+ CURSOR_TYPE_LOCAL,
+};
+
+typedef struct CursorItem {
+ PipeItem pipe_data;
+ int refs;
+ int type;
+} CursorItem;
+
+typedef struct LocalCursor {
+ CursorItem base;
+ Point16 position;
+ uint32_t data_size;
+ RedCursor red_cursor;
+} LocalCursor;
+
+#define MAX_SEND_BUFS 20
+#define MAX_BITMAPS 4
+#define MAX_PIPE_SIZE 50
+#define RECIVE_BUF_SIZE 1024
+
+#define WIDE_CLIENT_ACK_WINDOW 40
+#define NARROW_CLIENT_ACK_WINDOW 20
+
+#define BITS_CACHE_HASH_SHIFT 10
+#define BITS_CACHE_HASH_SIZE (1 << BITS_CACHE_HASH_SHIFT)
+#define BITS_CACHE_HASH_MASK (BITS_CACHE_HASH_SIZE - 1)
+#define BITS_CACHE_HASH_KEY(id) ((id) & BITS_CACHE_HASH_MASK)
+
+#define CLIENT_CURSOR_CACHE_SIZE 256
+
+#define CURSOR_CACHE_HASH_SHIFT 8
+#define CURSOR_CACHE_HASH_SIZE (1 << CURSOR_CACHE_HASH_SHIFT)
+#define CURSOR_CACHE_HASH_MASK (CURSOR_CACHE_HASH_SIZE - 1)
+#define CURSOR_CACHE_HASH_KEY(id) ((id) & CURSOR_CACHE_HASH_MASK)
+
+#define CLIENT_PALETTE_CACHE_SIZE 128
+
+#define PALETTE_CACHE_HASH_SHIFT 8
+#define PALETTE_CACHE_HASH_SIZE (1 << PALETTE_CACHE_HASH_SHIFT)
+#define PALETTE_CACHE_HASH_MASK (PALETTE_CACHE_HASH_SIZE - 1)
+#define PALETTE_CACHE_HASH_KEY(id) ((id) & PALETTE_CACHE_HASH_MASK)
+
+typedef struct RedChannel RedChannel;
+typedef void (*disconnect_channel_proc)(RedChannel *channel);
+typedef void (*hold_item_proc)(void *item);
+typedef void (*release_item_proc)(RedChannel *channel, void *item);
+typedef int (*handle_message_proc)(RedChannel *channel, RedDataHeader *message);
+
+struct RedChannel {
+ EventListener listener;
+ uint32_t id;
+ struct RedWorker *worker;
+ RedsStreamContext *peer;
+ int migrate;
+
+ Ring pipe;
+ uint32_t pipe_size;
+
+ uint32_t client_ack_window;
+ uint32_t ack_generation;
+ uint32_t client_ack_generation;
+ uint32_t messages_window;
+
+ struct {
+ int blocked;
+ RedDataHeader header;
+ union {
+ RedSetAck ack;
+ } u;
+ uint32_t n_bufs;
+ BufDescriptor bufs[MAX_SEND_BUFS];
+ uint32_t size;
+ uint32_t pos;
+ void *item;
+ } send_data;
+
+ struct {
+ uint8_t buf[RECIVE_BUF_SIZE];
+ RedDataHeader *message;
+ uint8_t *now;
+ uint8_t *end;
+ } recive_data;
+
+ disconnect_channel_proc disconnect;
+ hold_item_proc hold_item;
+ release_item_proc release_item;
+ handle_message_proc handle_message;
+#ifdef RED_STATISTICS
+ uint64_t *out_bytes_counter;
+#endif
+};
+
+typedef struct ImageItem {
+ PipeItem link;
+ int refs;
+ Point pos;
+ int width;
+ int height;
+ int stride;
+ int top_down;
+ uint8_t data[0];
+} ImageItem;
+
+typedef struct Drawable Drawable;
+
+typedef struct Stream Stream;
+struct Stream {
+ uint8_t refs;
+ Drawable *current;
+#ifdef STREAM_TRACE
+ red_time_t last_time;
+ int width;
+ int height;
+ Rect dest_area;
+#endif
+ int top_down;
+ Stream *next;
+ RingItem link;
+ AVCodecContext *av_ctx;
+ AVFrame *av_frame;
+ uint8_t* frame_buf;
+ uint8_t* frame_buf_end;
+ int bit_rate;
+};
+
+typedef struct StreamAgent {
+ QRegion vis_region;
+ PipeItem create_item;
+ PipeItem destroy_item;
+ Stream *stream;
+ uint64_t lats_send_time;
+
+ int frames;
+ int drops;
+ int fps;
+} StreamAgent;
+
+typedef struct StreamClipItem {
+ PipeItem base;
+ int refs;
+ StreamAgent *stream_agent;
+ int clip_type;
+ QRegion region;
+} StreamClipItem;
+
+typedef struct RedCompressBuf RedCompressBuf;
+struct RedCompressBuf {
+ uint32_t buf[RED_COMPRESS_BUF_SIZE / 4];
+ RedCompressBuf *next;
+ RedCompressBuf *send_next;
+};
+
+static const int BITMAP_FMT_IS_PLT[] = {0, 1, 1, 1, 1, 1, 0, 0, 0, 0};
+static const int BITMAP_FMT_IS_RGB[] = {0, 0, 0, 0, 0, 0, 1, 1, 1, 1};
+static const int BITMAP_FMP_BYTES_PER_PIXEL[] = {0, 0, 0, 0, 0, 1, 2, 3, 4, 4};
+
+typedef struct __attribute__ ((__packed__)) RedImage {
+ ImageDescriptor descriptor;
+ union { // variable length
+ Bitmap bitmap;
+ QUICData quic;
+ LZ_RGBData lz_rgb;
+ LZ_PLTData lz_plt;
+ };
+} RedImage;
+
+pthread_mutex_t cache_lock = PTHREAD_MUTEX_INITIALIZER;
+Ring pixmap_cache_list = {&pixmap_cache_list, &pixmap_cache_list};
+
+typedef struct PixmapCache PixmapCache;
+struct PixmapCache {
+ RingItem base;
+ pthread_mutex_t lock;
+ uint8_t id;
+ uint32_t refs;
+ NewCacheItem *hash_table[BITS_CACHE_HASH_SIZE];
+ Ring lru;
+ int64_t available;
+ int64_t size;
+ int32_t items;
+
+ int freezed;
+ RingItem *freezed_head;
+ RingItem *freezed_tail;
+
+ uint32_t generation;
+ struct {
+ uint8_t client;
+ uint64_t message;
+ } generation_initiator;
+ uint64_t sync[MAX_CACHE_CLIENTS];
+};
+
+#define NUM_STREAMS 50
+
+#define DISPLAY_MIGRATE_DATA_MAGIC (*(uint32_t*)"DMDA")
+#define DISPLAY_MIGRATE_DATA_VERSION 2
+
+typedef struct __attribute__ ((__packed__)) DisplayChannelMigrateData {
+ //todo: add ack_generation + move common to generic migration data
+ uint32_t magic;
+ uint32_t version;
+ uint64_t message_serial;
+
+ uint8_t pixmap_cache_freezer;
+ uint8_t pixmap_cache_id;
+ int64_t pixmap_cache_size;
+ uint64_t pixmap_cache_clients[MAX_CACHE_CLIENTS];
+
+ uint8_t glz_dict_id;
+ GlzEncDictRestoreData glz_dict_restore_data;
+} DisplayChannelMigrateData;
+
+typedef struct WaitForChannels {
+ RedWaitForChannels header;
+ RedWaitForChannel buf[MAX_CACHE_CLIENTS];
+} WaitForChannels;
+
+typedef struct FreeList {
+ int res_size;
+ RedResorceList *res;
+ uint64_t sync[MAX_CACHE_CLIENTS];
+ WaitForChannels wait;
+} FreeList;
+
+typedef struct DisplayChannel DisplayChannel;
+
+typedef struct {
+ DisplayChannel *display_channel;
+ RedCompressBuf *bufs_head;
+ RedCompressBuf *bufs_tail;
+ jmp_buf jmp_env;
+ union {
+ struct {
+ ADDRESS next;
+ long address_delta;
+ uint32_t stride;
+ } lines_data;
+ struct {
+ uint8_t* next;
+ int src_stride;
+ uint32_t dest_stride;
+ int lines;
+ int max_lines_bunch;
+ int input_bufs_pos;
+ RedCompressBuf *input_bufs[2];
+ } unstable_lines_data;
+ } u;
+ char message_buf[512];
+} EncoderData;
+
+typedef struct {
+ QuicUsrContext usr;
+ EncoderData data;
+} QuicData;
+
+typedef struct {
+ LzUsrContext usr;
+ EncoderData data;
+} LzData;
+
+typedef struct {
+ GlzEncoderUsrContext usr;
+ EncoderData data;
+} GlzData;
+
+/**********************************/
+/* LZ dictionary related entities */
+/**********************************/
+#define MAX_GLZ_DRAWABLE_INSTANCES 2
+
+typedef struct RedGlzDrawable RedGlzDrawable;
+
+/* for each qxl drawable, there may be serveral instances of lz drawables */
+typedef struct GlzDrawableInstanceItem {
+ RingItem glz_link;
+ RingItem free_link;
+ GlzEncDictImageContext *glz_instance;
+ RedGlzDrawable *red_glz_drawable;
+} GlzDrawableInstanceItem;
+
+struct RedGlzDrawable {
+ RingItem link; // ordered by the time it was encoded
+ QXLDrawable *qxl_drawable;
+ Drawable *drawable;
+ GlzDrawableInstanceItem instances_pool[MAX_GLZ_DRAWABLE_INSTANCES];
+ Ring instances;
+ uint8_t instances_count;
+ DisplayChannel *display_channel;
+};
+
+pthread_mutex_t glz_dictionary_list_lock = PTHREAD_MUTEX_INITIALIZER;
+Ring glz_dictionary_list = {&glz_dictionary_list, &glz_dictionary_list};
+
+typedef struct GlzSharedDictionary {
+ RingItem base;
+ GlzEncDictContext *dict;
+ uint32_t refs;
+ uint8_t id;
+ pthread_rwlock_t encode_lock;
+ int migrate_freeze;
+} GlzSharedDictionary;
+
+struct DisplayChannel {
+ RedChannel base;
+
+ int expect_init;
+ int expect_migrate_mark;
+ int expect_migrate_data;
+
+ PixmapCache *pixmap_cache;
+ uint32_t pixmap_cache_generation;
+ int pending_pixmaps_sync;
+
+ CacheItem *palette_cache[PALETTE_CACHE_HASH_SIZE];
+ Ring palette_cache_lru;
+ long palette_cache_available;
+ uint32_t palette_cache_items;
+
+ StreamAgent stream_agents[NUM_STREAMS];
+
+ /* global lz encoding entities */
+ GlzSharedDictionary *glz_dict;
+ GlzEncoderContext *glz;
+ GlzData glz_data;
+
+ Ring glz_drawables; // all the living lz drawable, ordered by encoding time
+ Ring glz_drawables_inst_to_free; // list of instances to be freed
+ pthread_mutex_t glz_drawables_inst_to_free_lock;
+
+ struct {
+ union {
+ RedFill fill;
+ RedOpaque opaque;
+ RedCopy copy;
+ RedTransparent transparent;
+ RedAlphaBlend alpha_blend;
+ RedCopyBits copy_bits;
+ RedBlend blend;
+ RedRop3 rop3;
+ RedBlackness blackness;
+ RedWhiteness whiteness;
+ RedInvers invers;
+ RedStroke stroke;
+ RedText text;
+ RedMode mode;
+ RedInvalOne inval_one;
+ WaitForChannels wait;
+ struct {
+ RedStreamCreate message;
+ uint32_t num_rects;
+ } stream_create;
+ RedStreamClip stream_clip;
+ RedStreamData stream_data;
+ RedStreamDestroy stream_destroy;
+ RedMigrate migrate;
+ DisplayChannelMigrateData migrate_data;
+ } u;
+
+ uint32_t bitmap_pos;
+ RedImage images[MAX_BITMAPS];
+
+ uint32_t stream_outbuf_size;
+ uint8_t *stream_outbuf; // caution stream buffer is also used as compress bufs!!!
+
+ RedCompressBuf *free_compress_bufs;
+ RedCompressBuf *used_compress_bufs;
+
+ struct {
+ RedSubMessageList sub_list;
+ uint32_t sub_messages[DISPLAY_MAX_SUB_MESSAGES];
+ } sub_list;
+ RedSubMessage sub_header[DISPLAY_MAX_SUB_MESSAGES];
+
+ FreeList free_list;
+ } send_data;
+
+#ifdef RED_STATISTICS
+ StatNodeRef stat;
+ uint64_t *cache_hits_counter;
+ uint64_t *add_to_cache_counter;
+ uint64_t *non_cache_counter;
+#endif
+#ifdef COMPRESS_STAT
+ stat_info_t lz_stat;
+ stat_info_t glz_stat;
+ stat_info_t quic_stat;
+#endif
+};
+
+typedef struct CursorChannel {
+ RedChannel base;
+
+ CacheItem *cursor_cache[CURSOR_CACHE_HASH_SIZE];
+ Ring cursor_cache_lru;
+ long cursor_cache_available;
+ uint32_t cursor_cache_items;
+
+ struct {
+ union {
+ RedCursorInit cursor_init;
+ RedCursorSet cursor_set;
+ RedCursorMove cursor_move;
+ RedCursorTrail cursor_trail;
+ RedInvalOne inval_one;
+ RedMigrate migrate;
+ } u;
+ } send_data;
+#ifdef RED_STATISTICS
+ StatNodeRef stat;
+#endif
+} CursorChannel;
+
+typedef struct __attribute__ ((__packed__)) LocalImage {
+ QXLImage qxl_image;
+ uint8_t buf[sizeof(QXLDataChunk *)]; // quic data area
+} LocalImage;
+
+typedef struct ImageCacheItem {
+ RingItem lru_link;
+ uint64_t id;
+#ifdef IMAGE_CACHE_AGE
+ uint32_t age;
+#endif
+ struct ImageCacheItem *next;
+ cairo_surface_t *surf;
+} ImageCacheItem;
+
+#define IMAGE_CACHE_HASH_SIZE 1024
+
+typedef struct ImageCache {
+ ImageCacheItem *hash_table[IMAGE_CACHE_HASH_SIZE];
+ Ring lru;
+#ifdef IMAGE_CACHE_AGE
+ uint32_t age;
+#else
+ uint32_t num_items;
+#endif
+} ImageCache;
+
+enum {
+ TREE_ITEM_TYPE_DRAWABLE,
+ TREE_ITEM_TYPE_CONTAINER,
+ TREE_ITEM_TYPE_SHADOW,
+};
+
+typedef struct TreeItem {
+ RingItem siblings_link;
+ uint32_t type;
+ struct Container *container;
+ QRegion rgn;
+#ifdef PIPE_DEBUG
+ uint32_t id;
+#endif
+} TreeItem;
+
+#define IS_DRAW_ITEM(item) ((item)->type == TREE_ITEM_TYPE_DRAWABLE)
+
+typedef struct Shadow {
+ TreeItem base;
+ QRegion on_hold;
+ struct DrawItem* owner;
+} Shadow;
+
+typedef struct Container {
+ TreeItem base;
+ Ring items;
+} Container;
+
+typedef struct DrawItem {
+ TreeItem base;
+ uint8_t effect;
+ uint8_t container_root;
+ Shadow *shadow;
+} DrawItem;
+
+struct Drawable {
+ uint8_t refs;
+ RingItem list_link;
+ DrawItem tree_item;
+ PipeItem pipe_item;
+#ifdef UPDATE_AREA_BY_TREE
+ RingItem collect_link;
+#endif
+ QXLDrawable *qxl_drawable;
+
+ RedGlzDrawable *red_glz_drawable;
+
+ red_time_t creation_time;
+ int frames_count;
+ Stream *stream;
+#ifdef STREAM_TRACE
+ int streamable;
+#endif
+};
+
+typedef struct _Drawable _Drawable;
+struct _Drawable {
+ union {
+ Drawable drawable;
+ _Drawable *next;
+ } u;
+};
+
+typedef struct UpgradeItem {
+ PipeItem base;
+ int refs;
+ Drawable *drawable;
+ QRegion region;
+} UpgradeItem;
+
+typedef void (*set_access_params_t)(void *canvas, ADDRESS delta);
+typedef void (*draw_fill_t)(void *canvas, Rect *bbox, Clip *clip, Fill *fill);
+typedef void (*draw_copy_t)(void *canvas, Rect *bbox, Clip *clip, Copy *copy);
+typedef void (*draw_opaque_t)(void *canvas, Rect *bbox, Clip *clip, Opaque *opaque);
+typedef void (*copy_bits_t)(void *canvas, Rect *bbox, Clip *clip, Point *src_pos);
+typedef void (*draw_text_t)(void *canvas, Rect *bbox, Clip *clip, Text *text);
+typedef void (*draw_stroke_t)(void *canvas, Rect *bbox, Clip *clip, Stroke *stroke);
+typedef void (*draw_rop3_t)(void *canvas, Rect *bbox, Clip *clip, Rop3 *rop3);
+typedef void (*draw_blend_t)(void *canvas, Rect *bbox, Clip *clip, Blend *blend);
+typedef void (*draw_blackness_t)(void *canvas, Rect *bbox, Clip *clip, Blackness *blackness);
+typedef void (*draw_whiteness_t)(void *canvas, Rect *bbox, Clip *clip, Whiteness *whiteness);
+typedef void (*draw_invers_t)(void *canvas, Rect *bbox, Clip *clip, Invers *invers);
+typedef void (*draw_transparent_t)(void *canvas, Rect *bbox, Clip *clip, Transparent* transparent);
+typedef void (*draw_alpha_blend_t)(void *canvas, Rect *bbox, Clip *clip, AlphaBlnd* alpha_blend);
+typedef void (*read_pixels_t)(void *canvas, uint8_t *dest, int dest_stride, const Rect *area);
+typedef void (*set_top_mask_t)(void *canvas, int num_rect, const Rect *rects);
+typedef void (*clear_top_mask_t)(void *canvas);
+typedef void (*validate_area_t)(void *canvas, const DrawArea *draw_area, const Rect *area);
+typedef void (*destroy_t)(void *canvas);
+
+
+typedef struct DrawContext {
+ void *canvas;
+ int top_down;
+ set_access_params_t set_access_params;
+ draw_fill_t draw_fill;
+ draw_copy_t draw_copy;
+ draw_opaque_t draw_opaque;
+ copy_bits_t copy_bits;
+ draw_text_t draw_text;
+ draw_stroke_t draw_stroke;
+ draw_rop3_t draw_rop3;
+ draw_blend_t draw_blend;
+ draw_blackness_t draw_blackness;
+ draw_whiteness_t draw_whiteness;
+ draw_invers_t draw_invers;
+ draw_transparent_t draw_transparent;
+ draw_alpha_blend_t draw_alpha_blend;
+ read_pixels_t read_pixels;
+ set_top_mask_t set_top_mask;
+ clear_top_mask_t clear_top_mask;
+ validate_area_t validate_area;
+ destroy_t destroy;
+} DrawContext;
+
+
+#ifdef STREAM_TRACE
+typedef struct ItemTrace {
+ red_time_t time;
+ int frames_count;
+ int width;
+ int height;
+ Rect dest_area;
+} ItemTrace;
+
+#define TRACE_ITEMS_SHIFT 3
+#define NUM_TRACE_ITEMS (1 << TRACE_ITEMS_SHIFT)
+#define ITEMS_TRACE_MASK (NUM_TRACE_ITEMS - 1)
+
+#endif
+
+#define NUM_DRAWABLES 1000
+
+typedef struct RedWorker {
+ EventListener dev_listener;
+ DisplayChannel *display_channel;
+ CursorChannel *cursor_channel;
+ QXLInterface *qxl;
+ int id;
+ int channel;
+ int attached;
+ int running;
+ uint32_t *pending;
+ QXLDevInfo dev_info;
+ int epoll;
+ unsigned int epoll_timeout;
+ uint32_t repoll_cmd_ring;
+ uint32_t repoll_cursor_ring;
+ DrawContext draw_context;
+ uint32_t num_renderers;
+ uint32_t renderers[RED_MAX_RENDERERS];
+
+ Ring current_list;
+ Ring current;
+ uint32_t current_size;
+ uint32_t drawable_count;
+ uint32_t transparent_count;
+
+ uint32_t shadows_count;
+ uint32_t containers_count;
+
+ uint32_t bits_unique;
+
+ CursorItem *cursor;
+ int cursor_visible;
+ Point16 cursor_position;
+ uint16_t cursor_trail_length;
+ uint16_t cursor_trail_frequency;
+
+ _Drawable drawables[NUM_DRAWABLES];
+ _Drawable *free_drawables;
+
+ uint32_t local_images_pos;
+ LocalImage local_images[MAX_BITMAPS];
+
+ ImageCache image_cache;
+
+ image_compression_t image_compression;
+
+ uint32_t mouse_mode;
+
+ uint32_t streaming_video;
+ Stream streams_buf[NUM_STREAMS];
+ Stream *free_streams;
+ Ring streams;
+#ifdef STREAM_TRACE
+ ItemTrace items_trace[NUM_TRACE_ITEMS];
+ uint32_t next_item_trace;
+#endif
+
+ QuicData quic_data;
+ QuicContext *quic;
+
+ LzData lz_data;
+ LzContext *lz;
+
+#ifdef PIPE_DEBUG
+ uint32_t last_id;
+#endif
+#ifdef RED_WORKER_STAT
+ stat_info_t add_stat;
+ stat_info_t exclude_stat;
+ stat_info_t __exclude_stat;
+ uint32_t add_count;
+ uint32_t add_with_shadow_count;
+#endif
+#ifdef RED_STATISTICS
+ StatNodeRef stat;
+ uint64_t *wakeup_counter;
+ uint64_t *command_counter;
+#endif
+} RedWorker;
+
+pthread_mutex_t avcodec_lock = PTHREAD_MUTEX_INITIALIZER;
+
+static void red_draw_qxl_drawable(RedWorker *worker, QXLDrawable *drawable);
+static void red_current_flush(RedWorker *worker);
+static void display_channel_push(RedWorker *worker);
+#ifdef DRAW_ALL
+#define red_update_area(worker, rect)
+#define red_draw_drawable(worker, item)
+#else
+static void red_draw_drawable(RedWorker *worker, Drawable *item);
+static void red_update_area(RedWorker *worker, const Rect *area);
+#endif
+static void red_release_cursor(RedWorker *worker, CursorItem *cursor);
+static inline void release_drawable(RedWorker *worker, Drawable *item);
+static void red_display_release_stream(DisplayChannel *display, StreamAgent *agent);
+#ifdef STREAM_TRACE
+static inline void red_detach_stream(RedWorker *worker, Stream *stream);
+#endif
+static void red_stop_stream(RedWorker *worker, Stream *stream);
+static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *sect);
+static inline void red_begin_send_massage(RedChannel *channel, void *item);
+static inline void display_begin_send_massage(DisplayChannel *channel, void *item);
+static void red_receive(RedChannel *channel);
+static void red_release_pixmap_cache(DisplayChannel *channel);
+static void red_release_glz(DisplayChannel *channel);
+static void red_freeze_glz(DisplayChannel *channel);
+static void display_channel_push_release(DisplayChannel *channel, uint8_t type, uint64_t id,
+ uint64_t* sync_data);
+static void red_display_release_stream_clip(DisplayChannel* channel, StreamClipItem *item);
+static int red_display_free_some_independent_glz_drawables(DisplayChannel *channel);
+static void red_display_free_glz_drawable(DisplayChannel *channel, RedGlzDrawable *drawable);
+static void reset_rate(StreamAgent *stream_agent);
+
+#ifdef DUMP_BITMAP
+static void dump_bitmap(RedWorker *worker, Bitmap *bitmap);
+#endif
+
+#ifdef COMPRESS_STAT
+static void print_compress_stats(DisplayChannel *display_channel)
+{
+ if (!display_channel) {
+ return;
+ }
+ red_printf("==> Compression stats for display %u", display_channel->base.id);
+ red_printf("Method \t count \torig_size(MB)\tenc_size(MB)\tenc_time(s)");
+ red_printf("QUIC \t%8d\t%13.2f\t%12.2f\t%12.2f",
+ display_channel->quic_stat.count,
+ stat_byte_to_mega(display_channel->quic_stat.orig_size),
+ stat_byte_to_mega(display_channel->quic_stat.comp_size),
+ stat_cpu_time_to_sec(display_channel->quic_stat.total)
+ );
+ red_printf("GLZ \t%8d\t%13.2f\t%12.2f\t%12.2f",
+ display_channel->glz_stat.count,
+ stat_byte_to_mega(display_channel->glz_stat.orig_size),
+ stat_byte_to_mega(display_channel->glz_stat.comp_size),
+ stat_cpu_time_to_sec(display_channel->glz_stat.total)
+ );
+ red_printf("LZ \t%8d\t%13.2f\t%12.2f\t%12.2f",
+ display_channel->lz_stat.count,
+ stat_byte_to_mega(display_channel->lz_stat.orig_size),
+ stat_byte_to_mega(display_channel->lz_stat.comp_size),
+ stat_cpu_time_to_sec(display_channel->lz_stat.total)
+ );
+ red_printf("-------------------------------------------------------------------");
+ red_printf("Total \t%8d\t%13.2f\t%12.2f\t%12.2f",
+ display_channel->lz_stat.count + display_channel->glz_stat.count +
+ display_channel->quic_stat.count,
+ stat_byte_to_mega(display_channel->lz_stat.orig_size +
+ display_channel->glz_stat.orig_size +
+ display_channel->quic_stat.orig_size),
+ stat_byte_to_mega(display_channel->lz_stat.comp_size +
+ display_channel->glz_stat.comp_size +
+ display_channel->quic_stat.comp_size),
+ stat_cpu_time_to_sec(display_channel->lz_stat.total +
+ display_channel->glz_stat.total +
+ display_channel->quic_stat.total)
+ );
+}
+
+#endif
+
+char *draw_type_to_str(UINT8 type)
+{
+ switch (type) {
+ case QXL_DRAW_FILL:
+ return "QXL_DRAW_FILL";
+ case QXL_DRAW_OPAQUE:
+ return "QXL_DRAW_OPAQUE";
+ case QXL_DRAW_COPY:
+ return "QXL_DRAW_COPY";
+ case QXL_DRAW_TRANSPARENT:
+ return "QXL_DRAW_TRANSPARENT";
+ case QXL_DRAW_ALPHA_BLEND:
+ return "QXL_DRAW_ALPHA_BLEND";
+ case QXL_COPY_BITS:
+ return "QXL_COPY_BITS";
+ case QXL_DRAW_BLEND:
+ return "QXL_DRAW_BLEND";
+ case QXL_DRAW_BLACKNESS:
+ return "QXL_DRAW_BLACKNESS";
+ case QXL_DRAW_WHITENESS:
+ return "QXL_DRAW_WHITENESS";
+ case QXL_DRAW_INVERS:
+ return "QXL_DRAW_INVERS";
+ case QXL_DRAW_ROP3:
+ return "QXL_DRAW_ROP3";
+ case QXL_DRAW_STROKE:
+ return "QXL_DRAW_STROKE";
+ case QXL_DRAW_TEXT:
+ return "QXL_DRAW_TEXT";
+ default:
+ return "?";
+ }
+}
+
+static void show_qxl_drawable(RedWorker *worker, QXLDrawable *drawable, const char *prefix)
+{
+ if (prefix) {
+ printf("%s: ", prefix);
+ }
+
+ printf("%s effect %d bbox(%d %d %d %d)",
+ draw_type_to_str(drawable->type),
+ drawable->effect,
+ drawable->bbox.top,
+ drawable->bbox.left,
+ drawable->bbox.bottom,
+ drawable->bbox.right);
+
+ switch (drawable->type) {
+ case QXL_DRAW_FILL:
+ case QXL_DRAW_OPAQUE:
+ case QXL_DRAW_COPY:
+ case QXL_DRAW_TRANSPARENT:
+ case QXL_DRAW_ALPHA_BLEND:
+ case QXL_COPY_BITS:
+ case QXL_DRAW_BLEND:
+ case QXL_DRAW_BLACKNESS:
+ case QXL_DRAW_WHITENESS:
+ case QXL_DRAW_INVERS:
+ case QXL_DRAW_ROP3:
+ case QXL_DRAW_STROKE:
+ case QXL_DRAW_TEXT:
+ break;
+ default:
+ red_error("bad rawable type");
+ }
+ printf("\n");
+}
+
+static void show_draw_item(RedWorker *worker, DrawItem *draw_item, const char *prefix)
+{
+ if (prefix) {
+ printf("%s: ", prefix);
+ }
+ printf("effect %d bbox(%d %d %d %d)\n",
+ draw_item->effect,
+ draw_item->base.rgn.bbox.top,
+ draw_item->base.rgn.bbox.left,
+ draw_item->base.rgn.bbox.bottom,
+ draw_item->base.rgn.bbox.right);
+}
+
+static inline void red_pipe_item_init(PipeItem *item, int type)
+{
+ ring_item_init(&item->link);
+ item->type = type;
+}
+
+static inline void red_pipe_add(RedChannel *channel, PipeItem *item)
+{
+ ASSERT(channel);
+ channel->pipe_size++;
+ ring_add(&channel->pipe, &item->link);
+}
+
+static inline void red_pipe_add_after(RedChannel *channel, PipeItem *item, PipeItem *pos)
+{
+ ASSERT(channel && pos);
+ channel->pipe_size++;
+ ring_add_after(&item->link, &pos->link);
+}
+
+static inline int pipe_item_is_linked(PipeItem *item)
+{
+ return ring_item_is_linked(&item->link);
+}
+
+static inline void pipe_item_remove(PipeItem *item)
+{
+ ring_remove(&item->link);
+}
+
+static inline void red_pipe_add_tail(RedChannel *channel, PipeItem *item)
+{
+ ASSERT(channel);
+ channel->pipe_size++;
+ ring_add_before(&item->link, &channel->pipe);
+}
+
+static void red_pipe_add_verb(RedChannel* channel, uint16_t verb)
+{
+ VerbItem *item = malloc(sizeof(*item));
+ if (!item) {
+ PANIC("malloc failed");
+ }
+ red_pipe_item_init(&item->base, PIPE_ITEM_TYPE_VERB);
+ item->verb = verb;
+ red_pipe_add(channel, &item->base);
+}
+
+static void red_pipe_add_type(RedChannel* channel, int pipe_item_type)
+{
+ PipeItem *item = malloc(sizeof(*item));
+ if (!item) {
+ PANIC("malloc failed");
+ }
+ red_pipe_item_init(item, pipe_item_type);
+ red_pipe_add(channel, item);
+}
+
+static inline void red_pipe_add_drawable(RedWorker *worker, Drawable *drawable)
+{
+ if (!worker->display_channel) {
+ return;
+ }
+ drawable->refs++;
+ red_pipe_add(&worker->display_channel->base, &drawable->pipe_item);
+}
+
+static inline void red_pipe_add_drawable_after(RedWorker *worker, Drawable *drawable,
+ Drawable *pos_after)
+{
+ if (!worker->display_channel) {
+ return;
+ }
+
+ if (!pos_after || !pipe_item_is_linked(&pos_after->pipe_item)) {
+ red_pipe_add_drawable(worker, drawable);
+ return;
+ }
+ drawable->refs++;
+ red_pipe_add_after(&worker->display_channel->base, &drawable->pipe_item, &pos_after->pipe_item);
+}
+
+static inline void red_pipe_remove_drawable(RedWorker *worker, Drawable *drawable)
+{
+ if (ring_item_is_linked(&drawable->pipe_item.link)) {
+ worker->display_channel->base.pipe_size--;
+ ring_remove(&drawable->pipe_item.link);
+ release_drawable(worker, drawable);
+ }
+}
+
+static inline void red_pipe_add_image_item(RedWorker *worker, ImageItem *item)
+{
+ if (!worker->display_channel) {
+ return;
+ }
+ item->refs++;
+ red_pipe_add(&worker->display_channel->base, &item->link);
+}
+
+static inline uint64_t channel_message_serial(RedChannel *channel)
+{
+ return channel->send_data.header.serial;
+}
+
+static void release_image_item(ImageItem *item)
+{
+ if (!--item->refs) {
+ free(item);
+ }
+}
+
+static void release_upgrade_item(RedWorker* worker, UpgradeItem *item)
+{
+ if (!--item->refs) {
+ release_drawable(worker, item->drawable);
+ region_destroy(&item->region);
+ free(item);
+ }
+}
+
+static void red_pipe_clear(RedChannel *channel)
+{
+ PipeItem *item;
+
+ ASSERT(channel);
+ while ((item = (PipeItem *)ring_get_head(&channel->pipe))) {
+ ring_remove(&item->link);
+ switch (item->type) {
+ case PIPE_ITEM_TYPE_DRAW:
+ release_drawable(channel->worker, CONTAINEROF(item, Drawable, pipe_item));
+ break;
+ case PIPE_ITEM_TYPE_CURSOR:
+ red_release_cursor(channel->worker, (CursorItem *)item);
+ break;
+ case PIPE_ITEM_TYPE_UPGRADE:
+ release_upgrade_item(channel->worker, (UpgradeItem *)item);
+ break;
+ case PIPE_ITEM_TYPE_PIXMAP_RESET:
+ case PIPE_ITEM_TYPE_PIXMAP_SYNC:
+ case PIPE_ITEM_TYPE_INVAL_ONE:
+ case PIPE_ITEM_TYPE_MODE:
+ case PIPE_ITEM_TYPE_MIGRATE:
+ case PIPE_ITEM_TYPE_SET_ACK:
+ case PIPE_ITEM_TYPE_CURSOR_INIT:
+ case PIPE_ITEM_TYPE_VERB:
+ case PIPE_ITEM_TYPE_MIGRATE_DATA:
+ case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE:
+ case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
+ free(item);
+ break;
+ case PIPE_ITEM_TYPE_IMAGE:
+ release_image_item((ImageItem *)item);
+ break;
+ case PIPE_ITEM_TYPE_STREAM_CREATE:
+ red_display_release_stream((DisplayChannel *)channel,
+ CONTAINEROF(item, StreamAgent, create_item));
+ break;
+ case PIPE_ITEM_TYPE_STREAM_CLIP:
+ red_display_release_stream_clip((DisplayChannel *)channel, (StreamClipItem*)item);
+ break;
+ case PIPE_ITEM_TYPE_STREAM_DESTROY:
+ red_display_release_stream((DisplayChannel *)channel,
+ CONTAINEROF(item, StreamAgent, destroy_item));
+ break;
+ }
+ }
+ channel->pipe_size = 0;
+}
+
+#define CLIENT_PIXMAPS_CACHE
+#include "red_client_shared_cache.h"
+#undef CLIENT_PIXMAPS_CACHE
+
+#define CLIENT_CURSOR_CACHE
+#include "red_client_cache.h"
+#undef CLIENT_CURSOR_CACHE
+
+#define CLIENT_PALETTE_CACHE
+#include "red_client_cache.h"
+#undef CLIENT_PALETTE_CACHE
+
+static void red_reset_palette_cache(DisplayChannel *display_channel)
+{
+ red_palette_cache_reset(display_channel, CLIENT_PALETTE_CACHE_SIZE);
+}
+
+static void red_reset_cursor_cache(CursorChannel *channel)
+{
+ red_cursor_cache_reset(channel, CLIENT_CURSOR_CACHE_SIZE);
+}
+
+static inline Drawable *alloc_drawable(RedWorker *worker)
+{
+ Drawable *drawable;
+ if (!worker->free_drawables) {
+ return NULL;
+ }
+ drawable = &worker->free_drawables->u.drawable;
+ worker->free_drawables = worker->free_drawables->u.next;
+ return drawable;
+}
+
+static inline void free_drawable(RedWorker *worker, Drawable *item)
+{
+ ((_Drawable *)item)->u.next = worker->free_drawables;
+ worker->free_drawables = (_Drawable *)item;
+}
+
+static void drawables_init(RedWorker *worker)
+{
+ int i;
+
+ worker->free_drawables = NULL;
+ for (i = 0; i < NUM_DRAWABLES; i++) {
+ free_drawable(worker, &worker->drawables[i].u.drawable);
+ }
+}
+
+static inline void free_qxl_drawable(RedWorker *worker, QXLDrawable *drawable)
+{
+ if (drawable->bitmap_offset) {
+ PHYSICAL *addr = (PHYSICAL *)((uint8_t *)drawable + drawable->bitmap_offset);
+ if (*addr) {
+ free((uint8_t *)*addr + worker->dev_info.phys_delta);
+ }
+ }
+ worker->qxl->release_resource(worker->qxl, &drawable->release_info);
+}
+
+static inline void release_drawable(RedWorker *worker, Drawable *item)
+{
+ if (!--item->refs) {
+#ifdef STREAM_TRACE
+ ASSERT(!item->stream);
+#else
+ if (item->stream) {
+ red_stop_stream(worker, item->stream);
+ }
+#endif
+ ASSERT(!item->tree_item.shadow);
+ region_destroy(&item->tree_item.base.rgn);
+
+ if (item->red_glz_drawable) {
+ item->red_glz_drawable->drawable = NULL;
+ } else { // no refernce to the qxl drawable left
+ free_qxl_drawable(worker, item->qxl_drawable);
+ }
+ free_drawable(worker, item);
+ }
+}
+
+static inline void remove_shadow(RedWorker *worker, DrawItem *item)
+{
+ Shadow *shadow;
+
+ if (!item->shadow) {
+ return;
+ }
+ shadow = item->shadow;
+ item->shadow = NULL;
+ ring_remove(&shadow->base.siblings_link);
+ region_destroy(&shadow->base.rgn);
+ region_destroy(&shadow->on_hold);
+ free(shadow);
+ worker->shadows_count--;
+}
+
+static inline void current_remove_container(RedWorker *worker, Container *container)
+{
+ ASSERT(ring_is_empty(&container->items));
+ worker->containers_count--;
+ ring_remove(&container->base.siblings_link);
+ region_destroy(&container->base.rgn);
+ free(container);
+}
+
+static inline void container_cleanup(RedWorker *worker, Container *container)
+{
+ while (container && container->items.next == container->items.prev) {
+ Container *next = container->base.container;
+ if (container->items.next != &container->items) {
+ TreeItem *item = (TreeItem *)ring_get_head(&container->items);
+ ASSERT(item);
+ ring_remove(&item->siblings_link);
+ ring_add_after(&item->siblings_link, &container->base.siblings_link);
+ item->container = container->base.container;
+ }
+ current_remove_container(worker, container);
+ container = next;
+ }
+}
+
+#ifdef STREAM_TRACE
+static inline void red_add_item_trace(RedWorker *worker, Drawable *item)
+{
+ ItemTrace *trace;
+ if (!item->streamable) {
+ return;
+ }
+
+ trace = &worker->items_trace[worker->next_item_trace++ & ITEMS_TRACE_MASK];
+ trace->time = item->creation_time;
+ trace->frames_count = item->frames_count;
+ Rect* src_area = &item->qxl_drawable->u.copy.src_area;
+ trace->width = src_area->right - src_area->left;
+ trace->height = src_area->bottom - src_area->top;
+ trace->dest_area = item->qxl_drawable->bbox;
+}
+
+#endif
+
+static inline void current_remove_drawable(RedWorker *worker, Drawable *item)
+{
+ worker->drawable_count--;
+ if (item->tree_item.effect != QXL_EFFECT_OPAQUE) {
+ worker->transparent_count--;
+ }
+#ifdef STREAM_TRACE
+ if (item->stream) {
+ red_detach_stream(worker, item->stream);
+ } else {
+ red_add_item_trace(worker, item);
+ }
+#endif
+ remove_shadow(worker, &item->tree_item);
+ ring_remove(&item->tree_item.base.siblings_link);
+ ring_remove(&item->list_link);
+ release_drawable(worker, item);
+ worker->current_size--;
+}
+
+static void remove_drawable(RedWorker *worker, Drawable *item)
+{
+ red_pipe_remove_drawable(worker, item);
+ current_remove_drawable(worker, item);
+}
+
+static inline void current_remove(RedWorker *worker, TreeItem *item)
+{
+ TreeItem *now = item;
+
+ for (;;) {
+ Container *container = now->container;
+ RingItem *ring_item;
+
+ if (now->type == TREE_ITEM_TYPE_DRAWABLE) {
+ ring_item = now->siblings_link.prev;
+ remove_drawable(worker, CONTAINEROF(now, Drawable, tree_item));
+ } else {
+ Container *container = (Container *)now;
+
+ ASSERT(now->type == TREE_ITEM_TYPE_CONTAINER);
+
+ if ((ring_item = ring_get_head(&container->items))) {
+ now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ continue;
+ }
+ ring_item = now->siblings_link.prev;
+ current_remove_container(worker, container);
+ }
+ if (now == item) {
+ return;
+ }
+
+ if ((ring_item = ring_next(&container->items, ring_item))) {
+ now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ } else {
+ now = (TreeItem *)container;
+ }
+ }
+}
+
+static void current_tree_for_each(RedWorker *worker, void (*f)(TreeItem *, void *), void * data)
+{
+ Ring *ring = &worker->current;
+ RingItem *ring_item;
+ Ring *top_ring;
+
+ if (!(ring_item = ring_get_head(ring))) {
+ return;
+ }
+ top_ring = ring;
+
+ for (;;) {
+ TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+
+ f(now, data);
+
+ if (now->type == TREE_ITEM_TYPE_CONTAINER) {
+ Container *container = (Container *)now;
+
+ if ((ring_item = ring_get_head(&container->items))) {
+ ring = &container->items;
+ continue;
+ }
+ }
+ for (;;) {
+ ring_item = ring_next(ring, &now->siblings_link);
+ if (ring_item) {
+ break;
+ }
+ if (ring == top_ring) {
+ return;
+ }
+ now = (TreeItem *)now->container;
+ ring = (now->container) ? &now->container->items : top_ring;
+ }
+ }
+}
+
+static void red_current_clear(RedWorker *worker)
+{
+ RingItem *ring_item;
+
+ while ((ring_item = ring_get_head(&worker->current))) {
+ TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ current_remove(worker, now);
+ }
+}
+
+#ifdef PIPE_DEBUG
+
+static void print_rgn(const char* prefix, const QRegion* rgn)
+{
+ int i;
+ printf("TEST: %s: RGN: bbox %u %u %u %u\n",
+ prefix,
+ rgn->bbox.top,
+ rgn->bbox.left,
+ rgn->bbox.bottom,
+ rgn->bbox.right);
+
+ for (i = 0; i < rgn->num_rects; i++) {
+ printf("TEST: %s: RECT %u %u %u %u\n",
+ prefix,
+ rgn->rects[i].top,
+ rgn->rects[i].left,
+ rgn->rects[i].bottom,
+ rgn->rects[i].right);
+ }
+}
+
+static void print_draw_item(const char* prefix, const DrawItem *draw_item)
+{
+ const TreeItem *base = &draw_item->base;
+ const Drawable *drawable = CONTAINEROF(draw_item, Drawable, tree_item);
+ printf("TEST: %s: draw id %u container %u effect %u",
+ prefix,
+ base->id, base->container ? base->container->base.id : 0,
+ draw_item->effect);
+ if (draw_item->shadow) {
+ printf(" shadow %u\n", draw_item->shadow->base.id);
+ } else {
+ printf("\n");
+ }
+ print_rgn(prefix, &base->rgn);
+}
+
+static void print_shadow_item(const char* prefix, const Shadow *item)
+{
+ printf("TEST: %s: shadow %p id %d\n", prefix, item, item->base.id);
+ print_rgn(prefix, &item->base.rgn);
+}
+
+static void print_container_item(const char* prefix, const Container *item)
+{
+ printf("TEST: %s: container %p id %d\n", prefix, item, item->base.id);
+ print_rgn(prefix, &item->base.rgn);
+}
+
+static void print_base_item(const char* prefix, const TreeItem *base)
+{
+ switch (base->type) {
+ case TREE_ITEM_TYPE_DRAWABLE:
+ print_draw_item(prefix, (const DrawItem *)base);
+ break;
+ case TREE_ITEM_TYPE_SHADOW:
+ print_shadow_item(prefix, (const Shadow *)base);
+ break;
+ case TREE_ITEM_TYPE_CONTAINER:
+ print_container_item(prefix, (const Container *)base);
+ break;
+ default:
+ red_error("invalid type %u", base->type);
+ }
+}
+
+void __show_current(TreeItem *item, void *data)
+{
+ print_base_item("TREE", item);
+}
+
+static void show_current(RedWorker *worker)
+{
+ if (ring_is_empty(&worker->current)) {
+ red_printf("TEST: TREE: EMPTY");
+ return;
+ }
+ current_tree_for_each(worker, __show_current, NULL);
+}
+
+#else
+#define print_rgn(a, b)
+#define print_draw_private(a, b)
+#define show_current(a)
+#define print_shadow_item(a, b)
+#define print_base_item(a, b)
+#endif
+
+static inline Shadow *__find_shadow(TreeItem *item)
+{
+ while (item->type == TREE_ITEM_TYPE_CONTAINER) {
+ if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) {
+ return NULL;
+ }
+ }
+
+ if (item->type != TREE_ITEM_TYPE_DRAWABLE) {
+ return NULL;
+ }
+
+ return ((DrawItem *)item)->shadow;
+}
+
+static inline Ring *ring_of(RedWorker *worker, TreeItem *item)
+{
+ return (item->container) ? &item->container->items : &worker->current;
+}
+
+static inline int __contained_by(RedWorker *worker, TreeItem *item, Ring *ring)
+{
+ ASSERT(item && ring);
+ do {
+ Ring *now = ring_of(worker, item);
+ if (now == ring) {
+ return TRUE;
+ }
+ } while ((item = (TreeItem *)item->container));
+
+ return FALSE;
+}
+
+static inline void __exclude_region(RedWorker *worker, TreeItem *item, QRegion *rgn,
+ Ring **top_ring, Drawable *frame_candidate)
+{
+ QRegion and_rgn;
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+
+ region_clone(&and_rgn, rgn);
+ region_and(&and_rgn, &item->rgn);
+ if (!region_is_empty(&and_rgn)) {
+ if (IS_DRAW_ITEM(item)) {
+ DrawItem *draw = (DrawItem *)item;
+
+ if (draw->effect == QXL_EFFECT_OPAQUE) {
+ region_exclude(rgn, &and_rgn);
+ }
+
+ if (draw->shadow) {
+ Shadow *shadow;
+ int32_t x = item->rgn.bbox.left;
+ int32_t y = item->rgn.bbox.top;
+
+ region_exclude(&draw->base.rgn, &and_rgn);
+ shadow = draw->shadow;
+ region_offset(&and_rgn, shadow->base.rgn.bbox.left - x,
+ shadow->base.rgn.bbox.top - y);
+ region_exclude(&shadow->base.rgn, &and_rgn);
+ region_and(&and_rgn, &shadow->on_hold);
+ if (!region_is_empty(&and_rgn)) {
+ region_exclude(&shadow->on_hold, &and_rgn);
+ region_or(rgn, &and_rgn);
+ // in flat representation of current, shadow is always his owner next
+ if (!__contained_by(worker, (TreeItem*)shadow, *top_ring)) {
+ *top_ring = ring_of(worker, (TreeItem*)shadow);
+ }
+ }
+ } else {
+ if (frame_candidate) {
+ Drawable *drawable = CONTAINEROF(draw, Drawable, tree_item);
+ red_stream_maintenance(worker, frame_candidate, drawable);
+ }
+ region_exclude(&draw->base.rgn, &and_rgn);
+ }
+ } else if (item->type == TREE_ITEM_TYPE_CONTAINER) {
+ region_exclude(&item->rgn, &and_rgn);
+
+ if (region_is_empty(&item->rgn)) { //assume container removal will follow
+ Shadow *shadow;
+
+ region_exclude(rgn, &and_rgn);
+ if ((shadow = __find_shadow(item))) {
+ region_or(rgn, &shadow->on_hold);
+ if (!__contained_by(worker, (TreeItem*)shadow, *top_ring)) {
+ *top_ring = ring_of(worker, (TreeItem*)shadow);
+ }
+ }
+ }
+ } else {
+ Shadow *shadow;
+
+ ASSERT(item->type == TREE_ITEM_TYPE_SHADOW);
+ shadow = (Shadow *)item;
+ region_exclude(rgn, &and_rgn);
+ region_or(&shadow->on_hold, &and_rgn);
+ }
+ }
+ region_destroy(&and_rgn);
+ stat_add(&worker->__exclude_stat, start_time);
+}
+
+#ifdef USE_EXCLUDE_RGN
+
+static void exclude_region(RedWorker *worker, Ring *ring, RingItem *ring_item, QRegion *rgn,
+ TreeItem **last, Drawable *frame_candidate)
+{
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ Ring *top_ring;
+
+ if (!ring_item) {
+ return;
+ }
+
+ top_ring = ring;
+
+ for (;;) {
+ TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ Container *container = now->container;
+
+ ASSERT(!region_is_empty(&now->rgn));
+
+ if (region_intersects(rgn, &now->rgn)) {
+ print_base_item("EXCLUDE2", now);
+ __exclude_region(worker, now, rgn, &top_ring, frame_candidate);
+ print_base_item("EXCLUDE3", now);
+
+ if (region_is_empty(&now->rgn)) {
+ ASSERT(now->type != TREE_ITEM_TYPE_SHADOW);
+ ring_item = now->siblings_link.prev;
+ print_base_item("EXCLUDE_REMOVE", now);
+ current_remove(worker, now);
+ if (last && *last == now) {
+ *last = (TreeItem *)ring_next(ring, ring_item);
+ }
+ } else if (now->type == TREE_ITEM_TYPE_CONTAINER) {
+ Container *container = (Container *)now;
+ if ((ring_item = ring_get_head(&container->items))) {
+ ring = &container->items;
+ ASSERT(((TreeItem *)ring_item)->container);
+ continue;
+ }
+ ring_item = &now->siblings_link;
+ }
+
+ if (region_is_empty(rgn)) {
+ stat_add(&worker->exclude_stat, start_time);
+ return;
+ }
+ }
+
+ while ((last && *last == (TreeItem *)ring_item) ||
+ !(ring_item = ring_next(ring, ring_item))) {
+ if (ring == top_ring) {
+ stat_add(&worker->exclude_stat, start_time);
+ return;
+ }
+ ring_item = &container->base.siblings_link;
+ container = container->base.container;
+ ring = (container) ? &container->items : top_ring;
+ }
+ }
+}
+
+#else
+
+static void exclude_region(RedWorker *worker, Ring *ring, RingItem *ring_item, QRegion *rgn)
+{
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ Ring *top_ring;
+
+ if (!ring_item) {
+ return;
+ }
+
+ top_ring = ring;
+
+ for (;;) {
+ TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ Container *container = now->container;
+
+ ASSERT(!region_is_empty(&now->rgn));
+
+ if (region_test(rgn, &now->rgn, REGION_TEST_SHARED)) {
+ print_base_item("EXCLUDE2", now);
+ __exclude_region(worker, now, rgn, &top_ring);
+ print_base_item("EXCLUDE3", now);
+
+ if (region_is_empty(&now->rgn)) {
+ ASSERT(now->type != TREE_ITEM_TYPE_SHADOW);
+ ring_item = now->siblings_link.prev;
+ print_base_item("EXCLUDE_REMOVE", now);
+ current_remove(worker, now);
+ } else if (now->type == TREE_ITEM_TYPE_CONTAINER) {
+ Container *container = (Container *)now;
+ if ((ring_item = ring_get_head(&container->items))) {
+ ring = &container->items;
+ ASSERT(((TreeItem *)ring_item)->container);
+ continue;
+ }
+ ring_item = &now->siblings_link;
+ }
+
+ if (region_is_empty(rgn)) {
+ stat_add(&worker->exclude_stat, start_time);
+ return;
+ }
+ }
+
+ while (!(ring_item = ring_next(ring, ring_item))) {
+ if (ring == top_ring) {
+ stat_add(&worker->exclude_stat, start_time);
+ return;
+ }
+ ring_item = &container->base.siblings_link;
+ container = container->base.container;
+ ring = (container) ? &container->items : top_ring;
+ }
+ }
+}
+
+#endif
+
+static inline Container *__new_container(RedWorker *worker, DrawItem *item)
+{
+ Container *container = malloc(sizeof(Container));
+ if (!container) {
+ return NULL;
+ }
+ worker->containers_count++;
+#ifdef PIPE_DEBUG
+ container->base.id = ++worker->last_id;
+#endif
+ container->base.type = TREE_ITEM_TYPE_CONTAINER;
+ container->base.container = item->base.container;
+ item->base.container = container;
+ item->container_root = TRUE;
+ region_clone(&container->base.rgn, &item->base.rgn);
+ ring_item_init(&container->base.siblings_link);
+ ring_add_after(&container->base.siblings_link, &item->base.siblings_link);
+ ring_remove(&item->base.siblings_link);
+ ring_init(&container->items);
+ ring_add(&container->items, &item->base.siblings_link);
+
+ return container;
+}
+
+static inline int is_opaque_item(TreeItem *item)
+{
+ return item->type == TREE_ITEM_TYPE_CONTAINER ||
+ (IS_DRAW_ITEM(item) && ((DrawItem *)item)->effect == QXL_EFFECT_OPAQUE);
+}
+
+static inline void __current_add_drawable(RedWorker *worker, Drawable *drawable, RingItem *pos)
+{
+ ring_add_after(&drawable->tree_item.base.siblings_link, pos);
+ ring_add(&worker->current_list, &drawable->list_link);
+ drawable->refs++;
+}
+
+#ifdef USE_EXCLUDE_RGN
+
+static int is_equal_path(ADDRESS p1, ADDRESS p2, long phys_delta)
+{
+ QXLPath *path1;
+ QXLPath *path2;
+ QXLDataChunk *chunk1;
+ QXLDataChunk *chunk2;
+ uint8_t *data1;
+ uint8_t *data2;
+ int size;
+ int size1;
+ int size2;
+
+ ASSERT(p1 && p2);
+
+ path1 = (QXLPath *)(p1 + phys_delta);
+ path2 = (QXLPath *)(p2 + phys_delta);
+
+ if ((size = path1->data_size) != path2->data_size) {
+ return FALSE;
+ }
+ ASSERT(size);
+
+ chunk1 = &path1->chunk;
+ size1 = chunk1->data_size;
+ data1 = chunk1->data;
+
+ chunk2 = &path2->chunk;
+ size2 = chunk2->data_size;
+ data2 = chunk2->data;
+
+ for (;;) {
+ int now = MIN(size1, size2);
+ ASSERT(now);
+ if (memcmp(data1, data2, now)) {
+ return FALSE;
+ }
+ if (!(size -= now)) {
+ return TRUE;
+ }
+ if ((size1 -= now) == 0) {
+ ASSERT(chunk1->next_chunk)
+ chunk1 = (QXLDataChunk *)(chunk1->next_chunk + phys_delta);
+ size1 = chunk1->data_size;
+ data1 = chunk1->data;
+ } else {
+ data1 += now;
+ }
+
+ if ((size2 -= now) == 0) {
+ ASSERT(chunk2->next_chunk)
+ chunk2 = (QXLDataChunk *)(chunk2->next_chunk + phys_delta);
+ size2 = chunk2->data_size;
+ data2 = chunk2->data;
+ } else {
+ data2 += now;
+ }
+ }
+}
+
+// partial imp
+static int is_equal_brush(Brush *b1, Brush *b2)
+{
+ return b1->type == b2->type && b1->type == BRUSH_TYPE_SOLID && b1->u.color == b1->u.color;
+}
+
+// partial imp
+static int is_equal_line_attr(LineAttr *a1, LineAttr *a2)
+{
+ return a1->flags == a2->flags && a1->join_style == a2->join_style &&
+ a1->end_style == a2->end_style && a1->style_nseg == a2->style_nseg &&
+ a1->width == a2->width && a1->miter_limit == a2->miter_limit &&
+ a1->style_nseg == 0;
+}
+
+static inline int rect_is_equal(const Rect *r1, const Rect *r2)
+{
+ return r1->top == r2->top && r1->left == r2->left &&
+ r1->bottom == r2->bottom && r1->right == r2->right;
+}
+
+// partial imp
+static int is_same_geometry(QXLDrawable *d1, QXLDrawable *d2, long phys_delta)
+{
+ if (d1->type != d2->type) {
+ return FALSE;
+ }
+
+ switch (d1->type) {
+ case QXL_DRAW_STROKE:
+ return is_equal_line_attr(&d1->u.stroke.attr, &d2->u.stroke.attr) &&
+ is_equal_path(d1->u.stroke.path, d2->u.stroke.path, phys_delta);
+ case QXL_DRAW_FILL:
+ return rect_is_equal(&d1->bbox, &d2->bbox);
+ default:
+ return FALSE;
+ }
+}
+
+static int is_same_drawable(QXLDrawable *d1, QXLDrawable *d2, long phys_delta)
+{
+ if (!is_same_geometry(d1, d2, phys_delta)) {
+ return FALSE;
+ }
+
+ switch (d1->type) {
+ case QXL_DRAW_STROKE:
+ return is_equal_brush(&d1->u.stroke.brush, &d2->u.stroke.brush);
+ case QXL_DRAW_FILL:
+ return is_equal_brush(&d1->u.fill.brush, &d2->u.fill.brush);
+ default:
+ return FALSE;
+ }
+}
+
+static inline void red_free_stream(RedWorker *worker, Stream *stream)
+{
+ stream->next = worker->free_streams;
+ worker->free_streams = stream;
+}
+
+static void red_release_stream(RedWorker *worker, Stream *stream)
+{
+ if (!--stream->refs) {
+#ifdef STREAM_TRACE
+ ASSERT(!ring_item_is_linked(&stream->link));
+#else
+ ring_remove(&stream->link);
+#endif
+ pthread_mutex_lock(&avcodec_lock);
+ avcodec_close(stream->av_ctx);
+ pthread_mutex_unlock(&avcodec_lock);
+ av_free(stream->av_ctx);
+ av_free(stream->av_frame);
+ free(stream->frame_buf);
+ red_free_stream(worker, stream);
+ }
+}
+
+#ifdef STREAM_TRACE
+static inline void red_detach_stream(RedWorker *worker, Stream *stream)
+{
+ ASSERT(stream->current && stream->current->stream);
+ ASSERT(stream->current->stream == stream);
+ stream->current->stream = NULL;
+ stream->current = NULL;
+}
+
+static StreamClipItem *__new_stream_clip(DisplayChannel* channel, StreamAgent *agent)
+{
+ StreamClipItem *item = (StreamClipItem *)malloc(sizeof(*item));
+ if (!item) {
+ PANIC("alloc failed");
+ }
+ red_pipe_item_init((PipeItem *)item, PIPE_ITEM_TYPE_STREAM_CLIP);
+
+ item->stream_agent = agent;
+ agent->stream->refs++;
+ item->refs = 1;
+ return item;
+}
+
+static void push_stream_clip_by_drawable(DisplayChannel* channel, StreamAgent *agent,
+ Drawable *drawable)
+{
+ StreamClipItem *item = __new_stream_clip(channel, agent);
+ if (!item) {
+ PANIC("alloc failed");
+ }
+
+ if (drawable->qxl_drawable->clip.type == CLIP_TYPE_NONE) {
+ region_init(&item->region);
+ item->clip_type = CLIP_TYPE_NONE;
+ } else {
+ item->clip_type = CLIP_TYPE_RECTS;
+ region_clone(&item->region, &drawable->tree_item.base.rgn);
+ }
+ red_pipe_add((RedChannel*)channel, (PipeItem *)item);
+}
+
+static void push_stream_clip(DisplayChannel* channel, StreamAgent *agent)
+{
+ StreamClipItem *item = __new_stream_clip(channel, agent);
+ if (!item) {
+ PANIC("alloc failed");
+ }
+ item->clip_type = CLIP_TYPE_RECTS;
+ region_clone(&item->region, &agent->vis_region);
+ red_pipe_add((RedChannel*)channel, (PipeItem *)item);
+}
+
+static void red_display_release_stream_clip(DisplayChannel* channel, StreamClipItem *item)
+{
+ if (!--item->refs) {
+ red_display_release_stream(channel, item->stream_agent);
+ region_destroy(&item->region);
+ free(item);
+ }
+}
+
+static void red_attach_stream(RedWorker *worker, Drawable *drawable, Stream *stream)
+{
+ DisplayChannel *channel;
+ ASSERT(!drawable->stream && !stream->current);
+ ASSERT(drawable && stream);
+ stream->current = drawable;
+ drawable->stream = stream;
+ stream->last_time = drawable->creation_time;
+
+ if ((channel = worker->display_channel)) {
+ StreamAgent *agent = &channel->stream_agents[stream - worker->streams_buf];
+ if (!region_is_equal(&agent->vis_region, &drawable->tree_item.base.rgn)) {
+ region_destroy(&agent->vis_region);
+ region_clone(&agent->vis_region, &drawable->tree_item.base.rgn);
+ push_stream_clip_by_drawable(channel, agent, drawable);
+ }
+ }
+}
+
+#endif
+
+static void red_stop_stream(RedWorker *worker, Stream *stream)
+{
+ DisplayChannel *channel;
+#ifdef STREAM_TRACE
+ ASSERT(ring_item_is_linked(&stream->link));
+ ASSERT(!stream->current);
+#else
+ ASSERT(stream->current && stream->current->stream);
+ stream->current->stream = NULL;
+ stream->current = NULL;
+#endif
+
+ if ((channel = worker->display_channel)) {
+ StreamAgent *stream_agent;
+ stream_agent = &channel->stream_agents[stream - worker->streams_buf];
+ region_clear(&stream_agent->vis_region);
+ ASSERT(!pipe_item_is_linked(&stream_agent->destroy_item));
+ stream->refs++;
+ red_pipe_add(&channel->base, &stream_agent->destroy_item);
+ }
+#ifdef STREAM_TRACE
+ ring_remove(&stream->link);
+#endif
+ red_release_stream(worker, stream);
+}
+
+#ifdef STREAM_TRACE
+static inline void red_detach_stream_gracefully(RedWorker *worker, Stream *stream)
+{
+ DisplayChannel *channel;
+ ASSERT(stream->current);
+
+ if ((channel = worker->display_channel) && !pipe_item_is_linked(&stream->current->pipe_item)) {
+ UpgradeItem *upgrade_item;
+
+ if (!(upgrade_item = (UpgradeItem *)malloc(sizeof(*upgrade_item)))) {
+ PANIC("malloc failed");
+ }
+ upgrade_item->refs = 1;
+ red_pipe_item_init(&upgrade_item->base, PIPE_ITEM_TYPE_UPGRADE);
+ upgrade_item->drawable = stream->current;
+ upgrade_item->drawable->refs++;
+ region_clone(&upgrade_item->region, &upgrade_item->drawable->tree_item.base.rgn);
+ red_pipe_add((RedChannel *)channel, &upgrade_item->base);
+ }
+ red_detach_stream(worker, stream);
+}
+
+#else
+static inline void red_stop_stream_gracefully(RedWorker *worker, Stream *stream)
+{
+ ASSERT(stream->current);
+ if (worker->display_channel && !pipe_item_is_linked(&stream->current->pipe_item)) {
+ UpgradeItem *item;
+ if ((item = (UpgradeItem *)malloc(sizeof(*item)))) {
+ item->refs = 1;
+ red_pipe_item_init(&item->base, PIPE_ITEM_TYPE_UPGRADE);
+ item->drawable = stream->current;
+ item->drawable->refs++;
+ region_clone(&item->region, &item->drawable->tree_item.base.rgn);
+ red_pipe_add((RedChannel *)worker->display_channel, &item->base);
+ }
+ }
+ red_stop_stream(worker, stream);
+}
+
+#endif
+
+#ifdef STREAM_TRACE
+static void red_detach_streams_behind(RedWorker *worker, QRegion *region)
+{
+ Ring *ring = &worker->streams;
+ RingItem *item = ring_get_head(ring);
+ DisplayChannel *channel = worker->display_channel;
+
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ item = ring_next(ring, item);
+
+ if (channel) {
+ StreamAgent *agent = &channel->stream_agents[stream - worker->streams_buf];
+ if (region_intersects(&agent->vis_region, region)) {
+ region_clear(&agent->vis_region);
+ push_stream_clip(channel, agent);
+ if (stream->current) {
+ red_detach_stream_gracefully(worker, stream);
+ }
+ }
+ } else if (stream->current && region_intersects(&stream->current->tree_item.base.rgn,
+ region)) {
+ red_detach_stream(worker, stream);
+ }
+ }
+}
+
+#else
+static void red_stop_streams_behind(RedWorker *worker, QRegion *region)
+{
+ Ring *ring = &worker->streams;
+ RingItem *item = ring_get_head(ring);
+
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ stream->refs++;
+ if (stream->current && region_intersects(region, &stream->current->tree_item.base.rgn)) {
+ red_stop_stream_gracefully(worker, stream);
+ }
+ item = ring_next(ring, item);
+ red_release_stream(worker, stream);
+ }
+}
+
+#endif
+
+static void red_streams_update_clip(RedWorker *worker, Drawable *drawable)
+{
+ DisplayChannel *channel;
+ Ring *ring;
+ RingItem *item;
+
+ if (!(channel = worker->display_channel)) {
+ return;
+ }
+
+ ring = &worker->streams;
+ item = ring_get_head(ring);
+
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ StreamAgent *agent;
+
+ item = ring_next(ring, item);
+
+ agent = &channel->stream_agents[stream - worker->streams_buf];
+
+ if (stream->current == drawable) {
+ continue;
+ }
+
+ if (region_intersects(&agent->vis_region, &drawable->tree_item.base.rgn)) {
+ region_exclude(&agent->vis_region, &drawable->tree_item.base.rgn);
+ push_stream_clip(channel, agent);
+ }
+ }
+}
+
+static inline unsigned int red_get_streams_timout(RedWorker *worker)
+{
+ unsigned int timout = -1;
+ Ring *ring = &worker->streams;
+ RingItem *item = ring;
+ struct timespec time;
+
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ red_time_t now = timespec_to_red_time(&time);
+ while ((item = ring_next(ring, item))) {
+ Stream *stream;
+
+ stream = CONTAINEROF(item, Stream, link);
+#ifdef STREAM_TRACE
+ red_time_t delta = (stream->last_time + RED_STREAM_TIMOUT) - now;
+
+ if (delta < 1000 * 1000) {
+ return 0;
+ }
+ timout = MIN(timout, (unsigned int)(delta / (1000 * 1000)));
+#else
+ if (stream->current) {
+ red_time_t delta = (stream->current->creation_time + RED_STREAM_TIMOUT) - now;
+ if (delta < 1000 * 1000) {
+ return 0;
+ }
+ timout = MIN(timout, (unsigned int)(delta / (1000 * 1000)));
+ }
+#endif
+ }
+ return timout;
+}
+
+static inline void red_handle_streams_timout(RedWorker *worker)
+{
+ Ring *ring = &worker->streams;
+ struct timespec time;
+ RingItem *item;
+
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ red_time_t now = timespec_to_red_time(&time);
+ item = ring_get_head(ring);
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+#ifdef STREAM_TRACE
+ item = ring_next(ring, item);
+ if (now >= (stream->last_time + RED_STREAM_TIMOUT)) {
+ if (stream->current) {
+ red_detach_stream_gracefully(worker, stream);
+ }
+ red_stop_stream(worker, stream);
+ }
+#else
+ stream->refs++;
+ if (stream->current && now >= (stream->current->creation_time + RED_STREAM_TIMOUT)) {
+ red_stop_stream_gracefully(worker, stream);
+ }
+ item = ring_next(ring, item);
+ red_release_stream(worker, stream);
+#endif
+ }
+}
+
+static void red_display_release_stream(DisplayChannel *display, StreamAgent *agent)
+{
+ ASSERT(agent->stream);
+ red_release_stream(display->base.worker, agent->stream);
+}
+
+static inline Stream *red_alloc_stream(RedWorker *worker)
+{
+ Stream *stream;
+ if (!worker->free_streams) {
+ return NULL;
+ }
+ stream = worker->free_streams;
+ worker->free_streams = worker->free_streams->next;
+ return stream;
+}
+
+static int get_bit_rate(int width, int height)
+{
+ uint64_t bit_rate = width * height * BEST_BIT_RATE_PER_PIXEL;
+ if (IS_LOW_BANDWIDTH()) {
+ bit_rate = MIN(bitrate_per_sec * 70 / 100, bit_rate);
+ bit_rate = MAX(bit_rate, width * height * WARST_BIT_RATE_PER_PIXEL);
+ }
+ return bit_rate;
+}
+
+static void red_dispaly_create_stream(DisplayChannel *display, Stream *stream)
+{
+ StreamAgent *agent = &display->stream_agents[stream - display->base.worker->streams_buf];
+ stream->refs++;
+ ASSERT(region_is_empty(&agent->vis_region));
+ if (stream->current) {
+ agent->frames = 1;
+ region_clone(&agent->vis_region, &stream->current->tree_item.base.rgn);
+ } else {
+ agent->frames = 0;
+ }
+ agent->drops = 0;
+ agent->fps = MAX_FPS;
+ reset_rate(agent);
+ red_pipe_add(&display->base, &agent->create_item);
+}
+
+static AVCodecContext *red_init_video_encoder(int width, int height)
+{
+ AVCodec *codec;
+ AVCodecContext *ctx;
+ int r;
+
+ codec = avcodec_find_encoder(CODEC_ID_MJPEG);
+
+ if (!codec) {
+ red_printf("codec not found");
+ return NULL;
+ }
+
+ if (!(ctx = avcodec_alloc_context())) {
+ red_printf("alloc ctx failed");
+ return NULL;
+ }
+ ctx->bit_rate = get_bit_rate(width, height);
+ ASSERT(width % 2 == 0);
+ ASSERT(height % 2 == 0);
+ ctx->width = width;
+ ctx->height = height;
+ ctx->time_base = (AVRational){1, MAX_FPS};
+ ctx->gop_size = 10;
+ ctx->max_b_frames = 0;
+ ctx->pix_fmt = PIX_FMT_YUVJ420P;
+
+ pthread_mutex_lock(&avcodec_lock);
+ r = avcodec_open(ctx, codec);
+ pthread_mutex_unlock(&avcodec_lock);
+ if (r < 0) {
+ red_printf("avcodec open failed");
+ av_free(ctx);
+ return NULL;
+ }
+ return ctx;
+}
+
+static void red_create_stream(RedWorker *worker, Drawable *drawable)
+{
+ AVCodecContext *av_ctx;
+ Stream *stream;
+ AVFrame *frame;
+ uint8_t* frame_buf;
+ Rect* src_rect;
+ int stream_width;
+ int stream_height;
+ int pict_size;
+
+ ASSERT(!drawable->stream);
+
+ if (!(stream = red_alloc_stream(worker))) {
+ return;
+ }
+
+ ASSERT(drawable->qxl_drawable->type == QXL_DRAW_COPY);
+ src_rect = &drawable->qxl_drawable->u.copy.src_area;
+ stream_width = ALIGN(src_rect->right - src_rect->left, 2);
+ stream_height = ALIGN(src_rect->bottom - src_rect->top, 2);
+
+ if (!(av_ctx = red_init_video_encoder(stream_width, stream_height))) {
+ goto error_1;
+ }
+
+ if (!(frame = avcodec_alloc_frame())) {
+ goto error_2;
+ }
+
+ if ((pict_size = avpicture_get_size(av_ctx->pix_fmt, stream_width, stream_height)) < 0) {
+ goto error_3;
+ }
+
+ if (!(frame_buf = malloc(pict_size))) {
+ goto error_3;
+ }
+
+ if (avpicture_fill((AVPicture *)frame, frame_buf, av_ctx->pix_fmt, stream_width,
+ stream_height) < 0) {
+ goto error_4;
+ }
+
+ ring_add(&worker->streams, &stream->link);
+ stream->current = drawable;
+#ifdef STREAM_TRACE
+ stream->last_time = drawable->creation_time;
+ stream->width = src_rect->right - src_rect->left;
+ stream->height = src_rect->bottom - src_rect->top;
+ stream->dest_area = drawable->qxl_drawable->bbox;
+#endif
+ stream->refs = 1;
+ stream->av_ctx = av_ctx;
+ stream->bit_rate = av_ctx->bit_rate;
+ stream->av_frame = frame;
+ stream->frame_buf = frame_buf;
+ stream->frame_buf_end = frame_buf + pict_size;
+ QXLImage *qxl_image = (QXLImage *)(drawable->qxl_drawable->u.copy.src_bitmap +
+ worker->dev_info.phys_delta);
+ stream->top_down = !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN);
+ drawable->stream = stream;
+
+ if (worker->display_channel) {
+ red_dispaly_create_stream(worker->display_channel, stream);
+ }
+
+ return;
+
+error_4:
+ free(frame_buf);
+
+error_3:
+ av_free(frame);
+
+error_2:
+ pthread_mutex_lock(&avcodec_lock);
+ avcodec_close(av_ctx);
+ pthread_mutex_unlock(&avcodec_lock);
+ av_free(av_ctx);
+
+error_1:
+ red_free_stream(worker, stream);
+}
+
+static void red_disply_start_streams(DisplayChannel *display_channel)
+{
+ Ring *ring = &display_channel->base.worker->streams;
+ RingItem *item = ring;
+
+ while ((item = ring_next(ring, item))) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ red_dispaly_create_stream(display_channel, stream);
+ }
+}
+
+static void red_display_init_streams(DisplayChannel *display)
+{
+ int i;
+ for (i = 0; i < NUM_STREAMS; i++) {
+ StreamAgent *agent = &display->stream_agents[i];
+ agent->stream = &display->base.worker->streams_buf[i];
+ region_init(&agent->vis_region);
+ red_pipe_item_init(&agent->create_item, PIPE_ITEM_TYPE_STREAM_CREATE);
+ red_pipe_item_init(&agent->destroy_item, PIPE_ITEM_TYPE_STREAM_DESTROY);
+ }
+}
+
+static void red_display_destroy_streams(DisplayChannel *display)
+{
+ int i;
+ for (i = 0; i < NUM_STREAMS; i++) {
+ StreamAgent *agent = &display->stream_agents[i];
+ region_destroy(&agent->vis_region);
+ }
+}
+
+static void red_init_streams(RedWorker *worker)
+{
+ int i;
+
+ ring_init(&worker->streams);
+ worker->free_streams = NULL;
+ for (i = 0; i < NUM_STREAMS; i++) {
+ Stream *stream = &worker->streams_buf[i];
+ ring_item_init(&stream->link);
+ red_free_stream(worker, stream);
+ }
+}
+
+#ifdef STREAM_TRACE
+
+static inline int __red_is_next_stream_frame(const Drawable *candidate,
+ const int other_src_width,
+ const int other_src_height,
+ const Rect *other_dest,
+ const red_time_t other_time,
+ const Stream *stream,
+ unsigned long phys_delta)
+{
+ QXLDrawable *qxl_drawable;
+
+ if (candidate->creation_time - other_time >
+ (stream ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) {
+ return FALSE;
+ }
+
+ qxl_drawable = candidate->qxl_drawable;
+
+ if (!rect_is_equal(&qxl_drawable->bbox, other_dest)) {
+ return FALSE;
+ }
+
+ Rect* candidate_src = &qxl_drawable->u.copy.src_area;
+ if (candidate_src->right - candidate_src->left != other_src_width ||
+ candidate_src->bottom - candidate_src->top != other_src_height) {
+ return FALSE;
+ }
+
+ if (stream) {
+ QXLImage *qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + phys_delta);
+
+ if (stream->top_down != !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN)) {
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static inline int red_is_next_stream_frame(const Drawable *candidate, const Drawable *prev,
+ const unsigned long phys_delta)
+{
+ if (!candidate->streamable) {
+ return FALSE;
+ }
+
+ Rect* prev_src = &prev->qxl_drawable->u.copy.src_area;
+ return __red_is_next_stream_frame(candidate, prev_src->right - prev_src->left,
+ prev_src->bottom - prev_src->top,
+ &prev->qxl_drawable->bbox, prev->creation_time,
+ prev->stream, phys_delta);
+}
+
+#else
+
+static inline int red_is_next_stream_frame(Drawable *candidate, Drawable *prev,
+ unsigned long phys_delta)
+{
+ QXLImage *qxl_image;
+ QXLDrawable *qxl_drawable;
+ QXLDrawable *prev_qxl_drawable;
+
+ if (candidate->creation_time - prev->creation_time >
+ ((prev->stream) ? RED_STREAM_CONTINUS_MAX_DELTA : RED_STREAM_DETACTION_MAX_DELTA)) {
+ return FALSE;
+ }
+
+ qxl_drawable = candidate->qxl_drawable;
+ prev_qxl_drawable = prev->qxl_drawable;
+ if (qxl_drawable->type != QXL_DRAW_COPY || prev_qxl_drawable->type != QXL_DRAW_COPY) {
+ return FALSE;
+ }
+
+ if (!rect_is_equal(&qxl_drawable->bbox, &prev_qxl_drawable->bbox)) {
+ return FALSE;
+ }
+
+ if (!rect_is_same_size(&qxl_drawable->u.copy.src_area, &prev_qxl_drawable->u.copy.src_area)) {
+ return FALSE;
+ }
+
+ if (qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT ||
+ prev_qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT) {
+ return FALSE;
+ }
+
+ qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + phys_delta);
+
+ if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP) {
+ return FALSE;
+ }
+
+ if (prev->stream && prev->stream->top_down != !!(qxl_image->bitmap.flags & BITMAP_TOP_DOWN)) {
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+#endif
+
+static void reset_rate(StreamAgent *stream_agent)
+{
+ Stream *stream = stream_agent->stream;
+ AVCodecContext *new_ctx;
+ int rate;
+
+ rate = get_bit_rate(stream->width, stream->height);
+ if (rate == stream->bit_rate) {
+ return;
+ }
+
+ int stream_width = ALIGN(stream->width, 2);
+ int stream_height = ALIGN(stream->height, 2);
+
+ new_ctx = red_init_video_encoder(stream_width, stream_height);
+ if (!new_ctx) {
+ red_printf("craete ctx failed");
+ return;
+ }
+
+ avcodec_close(stream->av_ctx);
+ av_free(stream->av_ctx);
+ stream->av_ctx = new_ctx;
+ stream->bit_rate = rate;
+}
+
+static inline void pre_stream_item_swap(RedWorker *worker, Stream *stream)
+{
+ ASSERT(stream->current);
+
+ if (!worker->display_channel || !IS_LOW_BANDWIDTH()) {
+ return;
+ }
+
+ int index = stream - worker->streams_buf;
+ StreamAgent *agent = &worker->display_channel->stream_agents[index];
+
+ if (pipe_item_is_linked(&stream->current->pipe_item)) {
+ ++agent->drops;
+ }
+
+ if (agent->frames / agent->fps < FPS_TEST_INTERVAL) {
+ agent->frames++;
+ return;
+ }
+
+ double drop_factor = ((double)agent->frames - (double)agent->drops) / (double)agent->frames;
+
+ if (drop_factor == 1) {
+ if (agent->fps < MAX_FPS) {
+ agent->fps++;
+ }
+ } else if (drop_factor < 0.9) {
+ if (agent->fps > 1) {
+ agent->fps--;
+ }
+ }
+ agent->frames = 1;
+ agent->drops = 0;
+}
+
+static inline void red_stream_maintenance(RedWorker *worker, Drawable *candidate, Drawable *prev)
+{
+ Stream *stream;
+
+ if (candidate->stream) {
+ return;
+ }
+
+#ifdef STREAM_TRACE
+ if (!red_is_next_stream_frame(candidate, prev, worker->dev_info.phys_delta)) {
+ return;
+ }
+#else
+ if (!worker->streaming_video ||
+ !red_is_next_stream_frame(candidate, prev, worker->dev_info.phys_delta)) {
+ return;
+ }
+#endif
+
+ if ((stream = prev->stream)) {
+#ifdef STREAM_TRACE
+ pre_stream_item_swap(worker, stream);
+ red_detach_stream(worker, stream);
+ prev->streamable = FALSE; //prevent item trace
+ red_attach_stream(worker, candidate, stream);
+#else
+ prev->stream = NULL;
+ candidate->stream = stream;
+ stream->current = candidate;
+
+ if (!region_is_equal(&stream->region, &candidate->tree_item.base.rgn)) {
+ region_destroy(&stream->region);
+ region_clone(&stream->region, &candidate->tree_item.base.rgn);
+ if (worker->display_channel) {
+ int index = stream - worker->streams_buf;
+ StreamAgent *stream_agent = &worker->display_channel->stream_agents[index];
+ if (!pipe_item_is_linked(&stream_agent->clip_item)) {
+ stream->refs++;
+ red_pipe_add((RedChannel*)worker->display_channel, &stream_agent->clip_item);
+ }
+ }
+ }
+#endif
+ } else if ((candidate->frames_count = prev->frames_count + 1) ==
+ RED_STREAM_START_CONDITION) {
+ red_create_stream(worker, candidate);
+ }
+}
+
+static inline int red_current_add_equal(RedWorker *worker, DrawItem *item, TreeItem *other)
+{
+ DrawItem *other_draw_item;
+ Drawable *drawable;
+ Drawable *other_drawable;
+ QXLDrawable *qxl_drawable;
+ QXLDrawable *other_qxl_drawable;
+
+ if (other->type != TREE_ITEM_TYPE_DRAWABLE) {
+ return FALSE;
+ }
+ other_draw_item = (DrawItem *)other;
+
+ if (item->shadow || other_draw_item->shadow || item->effect != other_draw_item->effect) {
+ return FALSE;
+ }
+
+ drawable = CONTAINEROF(item, Drawable, tree_item);
+ other_drawable = CONTAINEROF(other_draw_item, Drawable, tree_item);
+
+ qxl_drawable = drawable->qxl_drawable;
+ other_qxl_drawable = other_drawable->qxl_drawable;
+
+ if (item->effect == QXL_EFFECT_OPAQUE) {
+ int add_after = !!other_drawable->stream;
+ red_stream_maintenance(worker, drawable, other_drawable);
+ __current_add_drawable(worker, drawable, &other->siblings_link);
+ if (add_after) {
+ red_pipe_add_drawable_after(worker, drawable, other_drawable);
+ } else {
+ red_pipe_add_drawable(worker, drawable);
+ }
+ remove_drawable(worker, other_drawable);
+ return TRUE;
+ }
+
+ switch (item->effect) {
+ case QXL_EFFECT_REVERT_ON_DUP:
+ if (is_same_drawable(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) {
+ if (!ring_item_is_linked(&other_drawable->pipe_item.link)) {
+ red_pipe_add_drawable(worker, drawable);
+ }
+ remove_drawable(worker, other_drawable);
+ return TRUE;
+ }
+ break;
+ case QXL_EFFECT_OPAQUE_BRUSH:
+ if (is_same_geometry(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) {
+ __current_add_drawable(worker, drawable, &other->siblings_link);
+ remove_drawable(worker, other_drawable);
+ red_pipe_add_drawable(worker, drawable);
+ return TRUE;
+ }
+ break;
+ case QXL_EFFECT_NOP_ON_DUP:
+ if (is_same_drawable(qxl_drawable, other_qxl_drawable, worker->dev_info.phys_delta)) {
+ return TRUE;
+ }
+ break;
+ }
+ return FALSE;
+}
+
+#ifdef STREAM_TRACE
+
+static inline void red_use_stream_trace(RedWorker *worker, Drawable *drawable)
+{
+ ItemTrace *trace;
+ ItemTrace *trace_end;
+ Ring *ring;
+ RingItem *item;
+
+ if (drawable->stream || !drawable->streamable || drawable->frames_count) {
+ return;
+ }
+
+
+ ring = &worker->streams;
+ item = ring_get_head(ring);
+
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ if (!stream->current && __red_is_next_stream_frame(drawable,
+ stream->width,
+ stream->height,
+ &stream->dest_area,
+ stream->last_time,
+ stream,
+ worker->dev_info.phys_delta)) {
+ red_attach_stream(worker, drawable, stream);
+ return;
+ }
+ item = ring_next(ring, item);
+ }
+
+ trace = worker->items_trace;
+ trace_end = trace + NUM_TRACE_ITEMS;
+ for (; trace < trace_end; trace++) {
+ if (__red_is_next_stream_frame(drawable, trace->width, trace->height, &trace->dest_area,
+ trace->time, NULL, worker->dev_info.phys_delta)) {
+ if ((drawable->frames_count = trace->frames_count + 1) == RED_STREAM_START_CONDITION) {
+ red_create_stream(worker, drawable);
+ }
+ return;
+ }
+ }
+}
+
+static void red_reset_stream_trace(RedWorker *worker)
+{
+ Ring *ring = &worker->streams;
+ RingItem *item = ring_get_head(ring);
+
+ while (item) {
+ Stream *stream = CONTAINEROF(item, Stream, link);
+ item = ring_next(ring, item);
+ if (!stream->current) {
+ red_stop_stream(worker, stream);
+ } else {
+ red_printf("attached stream");
+ }
+ }
+
+ worker->next_item_trace = 0;
+ memset(worker->items_trace, 0, sizeof(worker->items_trace));
+}
+
+#endif
+
+static inline int red_current_add(RedWorker *worker, Drawable *drawable)
+{
+ DrawItem *item = &drawable->tree_item;
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ RingItem *now;
+ Ring *ring = &worker->current;
+ QRegion exclude_rgn;
+ RingItem *exclude_base = NULL;
+
+ print_base_item("ADD", &item->base);
+ ASSERT(!region_is_empty(&item->base.rgn));
+ worker->current_size++;
+ region_init(&exclude_rgn);
+ now = ring_next(ring, ring);
+
+ while (now) {
+ TreeItem *sibling = CONTAINEROF(now, TreeItem, siblings_link);
+ int test_res;
+
+ if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) {
+ print_base_item("EMPTY", sibling);
+ now = ring_next(ring, now);
+ continue;
+ }
+ test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL);
+ if (!(test_res & REGION_TEST_SHARED)) {
+ print_base_item("EMPTY", sibling);
+ now = ring_next(ring, now);
+ continue;
+ } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) {
+ if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) &&
+ !(test_res & REGION_TEST_LEFT_EXCLUSIVE) &&
+ red_current_add_equal(worker, item, sibling)) {
+ stat_add(&worker->add_stat, start_time);
+ return FALSE;
+ }
+
+ if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) {
+ Shadow *shadow;
+ int skip = now == exclude_base;
+ print_base_item("CONTAIN", sibling);
+
+ if ((shadow = __find_shadow(sibling))) {
+ if (exclude_base) {
+ TreeItem *next = sibling;
+ exclude_region(worker, ring, exclude_base, &exclude_rgn, &next, NULL);
+ if (next != sibling) {
+ now = next ? &next->siblings_link : NULL;
+ exclude_base = NULL;
+ continue;
+ }
+ }
+ region_or(&exclude_rgn, &shadow->on_hold);
+ }
+ now = now->prev;
+ current_remove(worker, sibling);
+ now = ring_next(ring, now);
+ if (shadow || skip) {
+ exclude_base = now;
+ }
+ continue;
+ }
+
+ if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) {
+ Container *container;
+
+ if (exclude_base) {
+ exclude_region(worker, ring, exclude_base, &exclude_rgn, NULL, NULL);
+ region_clear(&exclude_rgn);
+ exclude_base = NULL;
+ }
+ print_base_item("IN", sibling);
+ if (sibling->type == TREE_ITEM_TYPE_CONTAINER) {
+ container = (Container *)sibling;
+ ring = &container->items;
+ item->base.container = container;
+ now = ring_next(ring, ring);
+ continue;
+ }
+ ASSERT(IS_DRAW_ITEM(sibling));
+ if (!((DrawItem *)sibling)->container_root) {
+ container = __new_container(worker, (DrawItem *)sibling);
+ if (!container) {
+ red_printf("create new container failed");
+ region_destroy(&exclude_rgn);
+ return FALSE;
+ }
+ item->base.container = container;
+ ring = &container->items;
+ }
+ }
+ }
+ if (!exclude_base) {
+ exclude_base = now;
+ }
+ break;
+ }
+ if (item->effect == QXL_EFFECT_OPAQUE) {
+ region_or(&exclude_rgn, &item->base.rgn);
+ exclude_region(worker, ring, exclude_base, &exclude_rgn, NULL, drawable);
+#ifdef STREAM_TRACE
+ red_use_stream_trace(worker, drawable);
+#endif
+ red_streams_update_clip(worker, drawable);
+ } else {
+#ifdef STREAM_TRACE
+ red_detach_streams_behind(worker, &drawable->tree_item.base.rgn);
+#else
+ red_stop_streams_behind(worker, &drawable->tree_item.base.rgn);
+#endif
+ }
+ region_destroy(&exclude_rgn);
+ __current_add_drawable(worker, drawable, ring);
+ stat_add(&worker->add_stat, start_time);
+ return TRUE;
+}
+
+#else
+
+static inline void __handle_remove_shadow(RedWorker *worker, TreeItem *item)
+{
+ Shadow *shadow;
+ Ring *ring;
+
+ while (item->type == TREE_ITEM_TYPE_CONTAINER) {
+ if (!(item = (TreeItem *)ring_get_tail(&((Container *)item)->items))) {
+ return;
+ }
+ }
+
+ if (item->type != TREE_ITEM_TYPE_DRAWABLE || !(shadow = ((DrawItem *)item)->shadow)) {
+ return;
+ }
+ print_base_item("SHADW", &shadow->base);
+ ring = (shadow->base.container) ? &shadow->base.container->items : &worker->current;
+ exclude_region(worker, ring, ring_next(ring, &shadow->base.siblings_link), &shadow->on_hold);
+ region_clear(&shadow->on_hold);
+}
+
+static inline int red_current_add(RedWorker *worker, Drawable *drawable)
+{
+ DrawItem *item = &drawable->tree_item;
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ RingItem *now;
+ Ring *ring = &worker->current;
+
+ print_base_item("ADD", &item->base);
+ ASSERT(!region_is_empty(&item->base.rgn));
+ worker->current_size++;
+ now = ring_next(ring, ring);
+
+ while (now) {
+ TreeItem *sibling = CONTAINEROF(now, TreeItem, siblings_link);
+ int test_res;
+
+ if (!region_bounds_intersects(&item->base.rgn, &sibling->rgn)) {
+ print_base_item("EMPTY", sibling);
+ now = ring_next(ring, now);
+ continue;
+ }
+ test_res = region_test(&item->base.rgn, &sibling->rgn, REGION_TEST_ALL);
+ if (!(test_res & REGION_TEST_SHARED)) {
+ print_base_item("EMPTY", sibling);
+ now = ring_next(ring, now);
+ continue;
+ } else if (sibling->type != TREE_ITEM_TYPE_SHADOW) {
+ if (!(test_res & REGION_TEST_RIGHT_EXCLUSIVE) && item->effect == QXL_EFFECT_OPAQUE) {
+ print_base_item("CONTAIN", sibling);
+ __handle_remove_shadow(worker, sibling);
+ now = now->prev;
+ current_remove(worker, sibling);
+ now = ring_next(ring, now);
+ continue;
+ }
+
+ if (!(test_res & REGION_TEST_LEFT_EXCLUSIVE) && is_opaque_item(sibling)) {
+ Container *container;
+
+ print_base_item("IN", sibling);
+ if (sibling->type == TREE_ITEM_TYPE_CONTAINER) {
+ container = (Container *)sibling;
+ ring = &container->items;
+ item->base.container = container;
+ now = ring_next(ring, ring);
+ continue;
+ }
+ ASSERT(IS_DRAW_ITEM(sibling));
+ if (!((DrawItem *)sibling)->container_root) {
+ container = __new_container(worker, (DrawItem *)sibling);
+ if (!container) {
+ red_printf("create new container failed");
+ return FALSE;
+ }
+ item->base.container = container;
+ ring = &container->items;
+ }
+ }
+ }
+ if (item->effect == QXL_EFFECT_OPAQUE) {
+ QRegion exclude_rgn;
+ region_clone(&exclude_rgn, &item->base.rgn);
+ exclude_region(worker, ring, now, &exclude_rgn);
+ region_destroy(&exclude_rgn);
+ }
+ break;
+ }
+ __current_add_drawable(worker, drawable, ring);
+ stat_add(&worker->add_stat, start_time);
+ return TRUE;
+}
+
+#endif
+
+static void add_clip_rects(QRegion *rgn, PHYSICAL data, unsigned long phys_delta)
+{
+ while (data) {
+ QXLDataChunk *chunk;
+ Rect *now;
+ Rect *end;
+ chunk = (QXLDataChunk *)(data + phys_delta);
+ now = (Rect *)chunk->data;
+ end = now + chunk->data_size / sizeof(Rect);
+
+ for (; now < end; now++) {
+ Rect* r = (Rect *)now;
+
+ ASSERT(now->top == r->top && now->left == r->left &&
+ now->bottom == r->bottom && now->right == r->right);
+#ifdef PIPE_DEBUG
+ printf("TEST: DRAWABLE: RECT: %u %u %u %u\n", r->top, r->left, r->bottom, r->right);
+#endif
+ region_add(rgn, r);
+ }
+ data = chunk->next_chunk;
+ }
+}
+
+static inline Shadow *__new_shadow(RedWorker *worker, Drawable *item, Point *delta)
+{
+ ASSERT(delta->x || delta->y);
+
+ Shadow *shadow = malloc(sizeof(Shadow));
+ if (!shadow) {
+ return NULL;
+ }
+ worker->shadows_count++;
+#ifdef PIPE_DEBUG
+ shadow->base.id = ++worker->last_id;
+#endif
+ shadow->base.type = TREE_ITEM_TYPE_SHADOW;
+ shadow->base.container = NULL;
+ shadow->owner = &item->tree_item;
+ region_clone(&shadow->base.rgn, &item->tree_item.base.rgn);
+ region_offset(&shadow->base.rgn, delta->x, delta->y);
+ ring_item_init(&shadow->base.siblings_link);
+ region_init(&shadow->on_hold);
+ item->tree_item.shadow = shadow;
+ return shadow;
+}
+
+static inline int red_current_add_with_shadow(RedWorker *worker, Drawable *item, Point *delta)
+{
+#ifdef RED_WORKER_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ Ring *ring;
+
+ Shadow *shadow = __new_shadow(worker, item, delta);
+ if (!shadow) {
+ stat_add(&worker->add_stat, start_time);
+ return FALSE;
+ }
+ print_base_item("ADDSHADOW", &item->tree_item.base);
+ worker->current_size++;
+ ring = &worker->current;
+ // item and his shadow must initially be placed in the same container.
+ // for now putting them on root.
+#ifdef STREAM_TRACE
+ red_detach_streams_behind(worker, &shadow->base.rgn);
+#else
+ red_stop_streams_behind(worker, &shadow->base.rgn);
+#endif
+ ring_add(ring, &shadow->base.siblings_link);
+ __current_add_drawable(worker, item, ring);
+ if (item->tree_item.effect == QXL_EFFECT_OPAQUE) {
+ QRegion exclude_rgn;
+ region_clone(&exclude_rgn, &item->tree_item.base.rgn);
+#ifdef USE_EXCLUDE_RGN
+ exclude_region(worker, ring, &shadow->base.siblings_link, &exclude_rgn, NULL, NULL);
+#else
+ exclude_region(worker, ring, &shadow->base.siblings_link, &exclude_rgn);
+#endif
+ region_destroy(&exclude_rgn);
+ red_streams_update_clip(worker, item);
+ } else {
+#ifdef STREAM_TRACE
+ red_detach_streams_behind(worker, &item->tree_item.base.rgn);
+#else
+ red_stop_streams_behind(worker, &item->tree_item.base.rgn);
+#endif
+ }
+ stat_add(&worker->add_stat, start_time);
+ return TRUE;
+}
+
+static inline int has_shadow(QXLDrawable *drawable)
+{
+ return drawable->type == QXL_COPY_BITS;
+}
+
+#ifdef STREAM_TRACE
+static inline void red_update_streamable(RedWorker *worker, Drawable *drawable,
+ QXLDrawable *qxl_drawable)
+{
+ QXLImage *qxl_image;
+
+ if (!worker->streaming_video) {
+ return;
+ }
+
+ if (drawable->tree_item.effect != QXL_EFFECT_OPAQUE ||
+ qxl_drawable->type != QXL_DRAW_COPY ||
+ qxl_drawable->u.copy.rop_decriptor != ROPD_OP_PUT) {
+ return;
+ }
+
+ qxl_image = (QXLImage *)(qxl_drawable->u.copy.src_bitmap + worker->dev_info.phys_delta);
+ if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP) {
+ return;
+ }
+
+ drawable->streamable = TRUE;
+}
+
+#endif
+
+static inline int red_current_add_qxl(RedWorker *worker, Drawable *drawable,
+ QXLDrawable *qxl_drawable)
+{
+ int ret;
+
+ if (has_shadow(qxl_drawable)) {
+ Point delta;
+
+#ifdef RED_WORKER_STAT
+ ++worker->add_with_shadow_count;
+#endif
+ delta.x = qxl_drawable->u.copy_bits.src_pos.x - qxl_drawable->bbox.left;
+ delta.y = qxl_drawable->u.copy_bits.src_pos.y - qxl_drawable->bbox.top;
+ ret = red_current_add_with_shadow(worker, drawable, &delta);
+ } else {
+#ifdef STREAM_TRACE
+ red_update_streamable(worker, drawable, qxl_drawable);
+#endif
+ ret = red_current_add(worker, drawable);
+ }
+#ifdef RED_WORKER_STAT
+ if ((++worker->add_count % 100) == 0) {
+ stat_time_t total = worker->add_stat.total;
+ red_printf("add with shadow count %u",
+ worker->add_with_shadow_count);
+ worker->add_with_shadow_count = 0;
+ red_printf("add[%u] %f exclude[%u] %f __exclude[%u] %f",
+ worker->add_stat.count,
+ stat_cpu_time_to_sec(total),
+ worker->exclude_stat.count,
+ stat_cpu_time_to_sec(worker->exclude_stat.total),
+ worker->__exclude_stat.count,
+ stat_cpu_time_to_sec(worker->__exclude_stat.total));
+ red_printf("add %f%% exclude %f%% exclude2 %f%% __exclude %f%%",
+ (double)(total - worker->exclude_stat.total) / total * 100,
+ (double)(worker->exclude_stat.total) / total * 100,
+ (double)(worker->exclude_stat.total -
+ worker->__exclude_stat.total) / worker->exclude_stat.total * 100,
+ (double)(worker->__exclude_stat.total) / worker->exclude_stat.total * 100);
+ stat_reset(&worker->add_stat);
+ stat_reset(&worker->exclude_stat);
+ stat_reset(&worker->__exclude_stat);
+ }
+#endif
+ return ret;
+}
+
+static void red_get_area(RedWorker *worker, const Rect *area, uint8_t *dest, int dest_stride,
+ int update)
+{
+ if (update) {
+ red_update_area(worker, area);
+ }
+
+ worker->draw_context.read_pixels(worker->draw_context.canvas, dest, dest_stride, area);
+}
+
+static inline int red_handle_self_bitmap(RedWorker *worker, QXLDrawable *drawable)
+{
+ QXLImage *image;
+ int32_t width;
+ int32_t height;
+ uint8_t *dest;
+ int dest_stride;
+ PHYSICAL *addr;
+
+ if (!drawable->bitmap_offset) {
+ return TRUE;
+ }
+
+ width = drawable->bbox.right - drawable->bbox.left;
+ height = drawable->bbox.bottom - drawable->bbox.top;
+ dest_stride = width * sizeof(uint32_t);
+
+ if (!(image = malloc(sizeof(QXLImage) + height * dest_stride))) {
+ red_printf("alloc failed");
+ return FALSE;
+ }
+ dest = (uint8_t *)(image + 1);
+
+ image->descriptor.type = IMAGE_TYPE_BITMAP;
+ image->descriptor.flags = 0;
+
+ QXL_SET_IMAGE_ID(image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique);
+ image->bitmap.flags = QXL_BITMAP_DIRECT | (worker->draw_context.top_down ?
+ QXL_BITMAP_TOP_DOWN : 0);
+ image->bitmap.format = BITMAP_FMT_32BIT;
+ image->bitmap.stride = dest_stride;
+ image->descriptor.width = image->bitmap.x = width;
+ image->descriptor.height = image->bitmap.y = height;
+ image->bitmap.data = (PHYSICAL)(dest - worker->dev_info.phys_delta);
+ image->bitmap.palette = 0;
+
+ red_get_area(worker, &drawable->bitmap_area, dest, dest_stride, TRUE);
+
+ addr = (PHYSICAL *)((uint8_t *)drawable + drawable->bitmap_offset);
+ ASSERT(*addr == 0);
+ *addr = (PHYSICAL)((uint8_t *)image - worker->dev_info.phys_delta);
+ return TRUE;
+}
+
+static void free_one_drawable(RedWorker *worker, int force_glz_free)
+{
+ RingItem *ring_item = ring_get_tail(&worker->current_list);
+ Drawable *drawable;
+ Container *container;
+
+ ASSERT(ring_item);
+ drawable = CONTAINEROF(ring_item, Drawable, list_link);
+ if (drawable->red_glz_drawable && force_glz_free) {
+ ASSERT(worker->display_channel);
+ red_display_free_glz_drawable(worker->display_channel, drawable->red_glz_drawable);
+ }
+ red_draw_drawable(worker, drawable);
+ container = drawable->tree_item.base.container;
+ current_remove_drawable(worker, drawable);
+ container_cleanup(worker, container);
+}
+
+static Drawable *get_drawable(RedWorker *worker, uint8_t effect, QXLDrawable *qxl_drawable)
+{
+ Drawable *drawable;
+ struct timespec time;
+
+ while (!(drawable = alloc_drawable(worker))) {
+ free_one_drawable(worker, FALSE);
+ }
+ memset(drawable, 0, sizeof(Drawable));
+ drawable->refs = 1;
+ clock_gettime(CLOCK_MONOTONIC, &time);
+ drawable->creation_time = timespec_to_red_time(&time);
+#ifdef PIPE_DEBUG
+ drawable->tree_item.base.id = ++worker->last_id;
+#endif
+ ring_item_init(&drawable->list_link);
+#ifdef UPDATE_AREA_BY_TREE
+ ring_item_init(&drawable->collect_link);
+#endif
+ ring_item_init(&drawable->tree_item.base.siblings_link);
+ drawable->tree_item.base.type = TREE_ITEM_TYPE_DRAWABLE;
+ region_init(&drawable->tree_item.base.rgn);
+ drawable->tree_item.effect = effect;
+ red_pipe_item_init(&drawable->pipe_item, PIPE_ITEM_TYPE_DRAW);
+ drawable->qxl_drawable = qxl_drawable;
+
+ return drawable;
+}
+
+static inline void red_process_drawable(RedWorker *worker, QXLDrawable *drawable)
+{
+ Drawable *item = get_drawable(worker, drawable->effect, drawable);
+
+ ASSERT(item);
+ region_add(&item->tree_item.base.rgn, &drawable->bbox);
+#ifdef PIPE_DEBUG
+ printf("TEST: DRAWABLE: id %u type %s effect %u bbox %u %u %u %u\n",
+ item->tree_item.base.id,
+ draw_type_to_str(drawable->type),
+ item->tree_item.effect,
+ drawable->bbox.top, drawable->bbox.left, drawable->bbox.bottom, drawable->bbox.right);
+#endif
+
+ if (drawable->clip.type == CLIP_TYPE_RECTS) {
+ QRegion rgn;
+
+ region_init(&rgn);
+ add_clip_rects(&rgn, drawable->clip.data + OFFSETOF(QXLClipRects, chunk),
+ worker->dev_info.phys_delta);
+ region_and(&item->tree_item.base.rgn, &rgn);
+ region_destroy(&rgn);
+ } else if (drawable->clip.type == CLIP_TYPE_PATH) {
+ item->tree_item.effect = QXL_EFFECT_BLEND;
+#ifdef PIPE_DEBUG
+ printf("TEST: DRAWABLE: QXL_CLIP_TYPE_PATH\n");
+#endif
+ }
+
+ if (region_is_empty(&item->tree_item.base.rgn)) {
+ release_drawable(worker, item);
+ return;
+ }
+
+ if (!red_handle_self_bitmap(worker, drawable)) {
+ release_drawable(worker, item);
+ return;
+ }
+
+ if (red_current_add_qxl(worker, item, drawable)) {
+ worker->drawable_count++;
+ if (item->tree_item.effect != QXL_EFFECT_OPAQUE) {
+ worker->transparent_count++;
+ }
+ red_pipe_add_drawable(worker, item);
+#ifdef DRAW_ALL
+ red_draw_qxl_drawable(worker, drawable);
+#endif
+ }
+ release_drawable(worker, item);
+}
+
+static void localize_path(PHYSICAL *in_path, long phys_delta)
+{
+ QXLPath *path;
+ uint8_t *data;
+ QXLDataChunk *chunk;
+
+ ASSERT(in_path && *in_path);
+ path = (QXLPath *)(*in_path + phys_delta);
+ data = malloc(sizeof(UINT32) + path->data_size);
+ ASSERT(data);
+ *in_path = (PHYSICAL)data;
+ *(UINT32 *)data = path->data_size;
+ data += sizeof(UINT32);
+ chunk = &path->chunk;
+ do {
+ memcpy(data, chunk->data, chunk->data_size);
+ data += chunk->data_size;
+ chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL;
+ } while (chunk);
+}
+
+static void unlocalize_path(PHYSICAL *path)
+{
+ ASSERT(path && *path);
+ free((void *)*path);
+ *path = 0;
+}
+
+static void localize_str(PHYSICAL *in_str, long phys_delta)
+{
+ QXLString *qxl_str = (QXLString *)(*in_str + phys_delta);
+ QXLDataChunk *chunk;
+ String *str;
+ uint8_t *dest;
+
+ ASSERT(in_str);
+ str = malloc(sizeof(UINT32) + qxl_str->data_size);
+ ASSERT(str);
+ *in_str = (PHYSICAL)str;
+ str->length = qxl_str->length;
+ str->flags = qxl_str->flags;
+ dest = str->data;
+ chunk = &qxl_str->chunk;
+ for (;;) {
+ memcpy(dest, chunk->data, chunk->data_size);
+ if (!chunk->next_chunk) {
+ return;
+ }
+ dest += chunk->data_size;
+ chunk = (QXLDataChunk *)(chunk->next_chunk + phys_delta);
+ }
+}
+
+static void unlocalize_str(PHYSICAL *str)
+{
+ ASSERT(str && *str);
+ free((void *)*str);
+ *str = 0;
+}
+
+static void localize_clip(Clip *clip, long phys_delta)
+{
+ switch (clip->type) {
+ case CLIP_TYPE_NONE:
+ return;
+ case CLIP_TYPE_RECTS: {
+ QXLClipRects *clip_rects;
+ QXLDataChunk *chunk;
+ uint8_t *data;
+ clip_rects = (QXLClipRects *)(clip->data + phys_delta);
+ chunk = &clip_rects->chunk;
+ ASSERT(clip->data);
+ data = malloc(sizeof(UINT32) + clip_rects->num_rects * sizeof(Rect));
+ ASSERT(data);
+ clip->data = (PHYSICAL)data;
+ *(UINT32 *)(data) = clip_rects->num_rects;
+ data += sizeof(UINT32);
+ do {
+ memcpy(data, chunk->data, chunk->data_size);
+ data += chunk->data_size;
+ chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL;
+ } while (chunk);
+ break;
+ }
+ case CLIP_TYPE_PATH:
+ localize_path(&clip->data, phys_delta);
+ break;
+ default:
+ red_printf("invalid clip type");
+ }
+}
+
+static void unlocalize_clip(Clip *clip)
+{
+ switch (clip->type) {
+ case CLIP_TYPE_NONE:
+ return;
+ case CLIP_TYPE_RECTS:
+ free((void *)clip->data);
+ clip->data = 0;
+ break;
+ case CLIP_TYPE_PATH:
+ unlocalize_path(&clip->data);
+ break;
+ default:
+ red_printf("invalid clip type");
+ }
+}
+
+static LocalImage *alloc_local_image(RedWorker *worker)
+{
+ ASSERT(worker->local_images_pos < MAX_BITMAPS);
+ return &worker->local_images[worker->local_images_pos++];
+}
+
+static ImageCacheItem *image_cache_find(ImageCache *cache, UINT64 id)
+{
+ ImageCacheItem *item = cache->hash_table[id % IMAGE_CACHE_HASH_SIZE];
+
+ while (item) {
+ if (item->id == id) {
+ return item;
+ }
+ item = item->next;
+ }
+ return NULL;
+}
+
+static int image_cache_hit(ImageCache *cache, UINT64 id)
+{
+ ImageCacheItem *item;
+ if (!(item = image_cache_find(cache, id))) {
+ return FALSE;
+ }
+#ifdef IMAGE_CACHE_AGE
+ item->age = cache->age;
+#endif
+ ring_remove(&item->lru_link);
+ ring_add(&cache->lru, &item->lru_link);
+ return TRUE;
+}
+
+static void image_cache_remove(ImageCache *cache, ImageCacheItem *item)
+{
+ ImageCacheItem **now;
+
+ now = &cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
+ for (;;) {
+ ASSERT(*now);
+ if (*now == item) {
+ *now = item->next;
+ break;
+ }
+ now = &(*now)->next;
+ }
+ ring_remove(&item->lru_link);
+ cairo_surface_destroy(item->surf);
+ free(item);
+#ifndef IMAGE_CACHE_AGE
+ cache->num_items--;
+#endif
+}
+
+#define IMAGE_CACHE_MAX_ITEMS 2
+
+static void image_cache_put(void *opaque, uint64_t id, cairo_surface_t *surface)
+{
+ ImageCache *cache = (ImageCache *)opaque;
+ ImageCacheItem *item;
+
+#ifndef IMAGE_CACHE_AGE
+ if (cache->num_items == IMAGE_CACHE_MAX_ITEMS) {
+ ImageCacheItem *tail = (ImageCacheItem *)ring_get_tail(&cache->lru);
+ ASSERT(tail);
+ image_cache_remove(cache, tail);
+ }
+#endif
+
+ if (!(item = (ImageCacheItem *)malloc(sizeof(ImageCacheItem)))) {
+ red_error("alloc failed");
+ }
+ item->id = id;
+#ifdef IMAGE_CACHE_AGE
+ item->age = cache->age;
+#else
+ cache->num_items++;
+#endif
+ item->surf = cairo_surface_reference(surface);
+ ring_item_init(&item->lru_link);
+
+ item->next = cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE];
+ cache->hash_table[item->id % IMAGE_CACHE_HASH_SIZE] = item;
+
+ ring_add(&cache->lru, &item->lru_link);
+}
+
+static cairo_surface_t *image_cache_get(void *opaque, uint64_t id)
+{
+ ImageCache *cache = (ImageCache *)opaque;
+
+ ImageCacheItem *item = image_cache_find(cache, id);
+ if (!item) {
+ red_error("not found");
+ }
+ return cairo_surface_reference(item->surf);
+}
+
+static void image_cache_init(ImageCache *cache)
+{
+ memset(cache->hash_table, 0, sizeof(cache->hash_table));
+ ring_init(&cache->lru);
+#ifdef IMAGE_CACHE_AGE
+ cache->age = 0;
+#else
+ cache->num_items = 0;
+#endif
+}
+
+static void image_cache_reset(ImageCache *cache)
+{
+ ImageCacheItem *item;
+
+ while ((item = (ImageCacheItem *)ring_get_head(&cache->lru))) {
+ image_cache_remove(cache, item);
+ }
+#ifdef IMAGE_CACHE_AGE
+ cache->age = 0;
+#endif
+}
+
+#define IMAGE_CACHE_DEPTH 4
+
+static void image_cache_eaging(ImageCache *cache)
+{
+#ifdef IMAGE_CACHE_AGE
+ ImageCacheItem *item;
+
+ cache->age++;
+ while ((item = (ImageCacheItem *)ring_get_tail(&cache->lru)) &&
+ cache->age - item->age > IMAGE_CACHE_DEPTH) {
+ image_cache_remove(cache, item);
+ }
+#endif
+}
+
+static void localize_bitmap(RedWorker *worker, PHYSICAL *in_bitmap, long phys_delta)
+{
+ QXLImage *image;
+ QXLImage *local_image;
+
+ ASSERT(in_bitmap && *in_bitmap);
+ image = (QXLImage *)(*in_bitmap + phys_delta);
+ local_image = (QXLImage *)alloc_local_image(worker);
+ *local_image = *image;
+ *in_bitmap = (PHYSICAL)local_image;
+ local_image->descriptor.flags = 0;
+
+ if (image_cache_hit(&worker->image_cache, local_image->descriptor.id)) {
+ local_image->descriptor.type = IMAGE_TYPE_FROM_CACHE;
+ return;
+ }
+
+ switch (local_image->descriptor.type) {
+ case IMAGE_TYPE_QUIC: {
+ QXLDataChunk **chanks_head;
+#ifdef IMAGE_CACHE_AGE
+ local_image->descriptor.flags |= IMAGE_CACHE_ME;
+#else
+ if (local_image->descriptor.width * local_image->descriptor.height >= 640 * 480) {
+ local_image->descriptor.flags |= IMAGE_CACHE_ME;
+ }
+#endif
+ chanks_head = (QXLDataChunk **)local_image->quic.data;
+ *chanks_head = (QXLDataChunk *)image->quic.data;
+ break;
+ }
+ case IMAGE_TYPE_BITMAP:
+ if (image->bitmap.flags & QXL_BITMAP_DIRECT) {
+ local_image->bitmap.data = (PHYSICAL)(image->bitmap.data + phys_delta);
+ } else {
+ PHYSICAL src_data;
+ int size = image->bitmap.y * image->bitmap.stride;
+ uint8_t *data = malloc(size);
+ ASSERT(data);
+ local_image->bitmap.data = (PHYSICAL)data;
+ src_data = image->bitmap.data;
+
+ while (size) {
+ QXLDataChunk *chunk;
+ int cp_size;
+
+ ASSERT(src_data);
+ chunk = (QXLDataChunk *)(src_data + phys_delta);
+ cp_size = MIN(chunk->data_size, size);
+ memcpy(data, chunk->data, cp_size);
+ data += cp_size;
+ size -= cp_size;
+ src_data = chunk->next_chunk;
+ }
+ }
+
+ if (local_image->bitmap.palette) {
+ local_image->bitmap.palette = (local_image->bitmap.palette + phys_delta);
+ }
+ break;
+ default:
+ red_error("invalid image type");
+ }
+}
+
+static void unlocalize_bitmap(PHYSICAL *bitmap)
+{
+ QXLImage *image;
+
+ ASSERT(bitmap && *bitmap);
+ image = (QXLImage *)*bitmap;
+ *bitmap = 0;
+
+ switch (image->descriptor.type) {
+ case IMAGE_TYPE_BITMAP:
+ if (!(image->bitmap.flags & QXL_BITMAP_DIRECT)) {
+ free((void *)image->bitmap.data);
+ }
+ break;
+ case IMAGE_TYPE_QUIC:
+ case IMAGE_TYPE_FROM_CACHE:
+ *bitmap = 0;
+ break;
+ default:
+ red_error("invalid image type %u", image->descriptor.type);
+ }
+}
+
+static void localize_brush(RedWorker *worker, Brush *brush, long phys_delta)
+{
+ if (brush->type == BRUSH_TYPE_PATTERN) {
+ localize_bitmap(worker, &brush->u.pattern.pat, phys_delta);
+ }
+}
+
+static void unlocalize_brush(Brush *brush)
+{
+ if (brush->type == BRUSH_TYPE_PATTERN) {
+ unlocalize_bitmap(&brush->u.pattern.pat);
+ }
+}
+
+static void localize_mask(RedWorker *worker, QMask *mask, long phys_delta)
+{
+ if (mask->bitmap) {
+ localize_bitmap(worker, &mask->bitmap, phys_delta);
+ }
+}
+
+static void unlocalize_mask(QMask *mask)
+{
+ if (mask->bitmap) {
+ unlocalize_bitmap(&mask->bitmap);
+ }
+}
+
+static void localize_attr(LineAttr *attr, long phys_delta)
+{
+ if (attr->style_nseg) {
+ uint8_t *buf;
+ uint8_t *data;
+
+ ASSERT(attr->style);
+ buf = (uint8_t *)(attr->style + phys_delta);
+ data = malloc(attr->style_nseg * sizeof(uint32_t));
+ ASSERT(data);
+ memcpy(data, buf, attr->style_nseg * sizeof(uint32_t));
+ attr->style = (PHYSICAL)data;
+ }
+}
+
+static void unlocalize_attr(LineAttr *attr)
+{
+ if (attr->style_nseg) {
+ free((void *)attr->style);
+ attr->style = 0;
+ }
+}
+
+static void red_draw_qxl_drawable(RedWorker *worker, QXLDrawable *drawable)
+{
+ Clip clip = drawable->clip;
+
+ worker->local_images_pos = 0;
+ image_cache_eaging(&worker->image_cache);
+
+ worker->draw_context.set_access_params(worker->draw_context.canvas,
+ worker->dev_info.phys_delta); // todo: use delta
+ // instead of
+ // localize_x
+ localize_clip(&clip, worker->dev_info.phys_delta);
+ switch (drawable->type) {
+ case QXL_DRAW_FILL: {
+ Fill fill = drawable->u.fill;
+ localize_brush(worker, &fill.brush, worker->dev_info.phys_delta);
+ localize_mask(worker, &fill.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_fill(worker->draw_context.canvas, &drawable->bbox, &clip, &fill);
+ unlocalize_mask(&fill.mask);
+ unlocalize_brush(&fill.brush);
+ break;
+ }
+ case QXL_DRAW_OPAQUE: {
+ Opaque opaque = drawable->u.opaque;
+ localize_brush(worker, &opaque.brush, worker->dev_info.phys_delta);
+ localize_bitmap(worker, &opaque.src_bitmap, worker->dev_info.phys_delta);
+ localize_mask(worker, &opaque.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_opaque(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &opaque);
+ unlocalize_mask(&opaque.mask);
+ unlocalize_bitmap(&opaque.src_bitmap);
+ unlocalize_brush(&opaque.brush);
+ break;
+ }
+ case QXL_DRAW_COPY: {
+ Copy copy = drawable->u.copy;
+ localize_bitmap(worker, &copy.src_bitmap, worker->dev_info.phys_delta);
+ localize_mask(worker, &copy.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_copy(worker->draw_context.canvas, &drawable->bbox, &clip, &copy);
+ unlocalize_mask(&copy.mask);
+ unlocalize_bitmap(&copy.src_bitmap);
+ break;
+ }
+ case QXL_DRAW_TRANSPARENT: {
+ Transparent transparent = drawable->u.transparent;
+ localize_bitmap(worker, &transparent.src_bitmap, worker->dev_info.phys_delta);
+ worker->draw_context.draw_transparent(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &transparent);
+ unlocalize_bitmap(&transparent.src_bitmap);
+ break;
+ }
+ case QXL_DRAW_ALPHA_BLEND: {
+ AlphaBlnd alpha_blend = drawable->u.alpha_blend;
+ localize_bitmap(worker, &alpha_blend.src_bitmap, worker->dev_info.phys_delta);
+ worker->draw_context.draw_alpha_blend(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &alpha_blend);
+ unlocalize_bitmap(&alpha_blend.src_bitmap);
+ break;
+ }
+ case QXL_COPY_BITS: {
+ worker->draw_context.copy_bits(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &drawable->u.copy_bits.src_pos);
+ break;
+ }
+ case QXL_DRAW_BLEND: {
+ Blend blend = drawable->u.blend;
+ localize_bitmap(worker, &blend.src_bitmap, worker->dev_info.phys_delta);
+ localize_mask(worker, &blend.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_blend(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &blend);
+ unlocalize_mask(&blend.mask);
+ unlocalize_bitmap(&blend.src_bitmap);
+ break;
+ }
+ case QXL_DRAW_BLACKNESS: {
+ Blackness blackness = drawable->u.blackness;
+ localize_mask(worker, &blackness.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_blackness(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &blackness);
+ unlocalize_mask(&blackness.mask);
+ break;
+ }
+ case QXL_DRAW_WHITENESS: {
+ Whiteness whiteness = drawable->u.whiteness;
+ localize_mask(worker, &whiteness.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_whiteness(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &whiteness);
+ unlocalize_mask(&whiteness.mask);
+ break;
+ }
+ case QXL_DRAW_INVERS: {
+ Invers invers = drawable->u.invers;
+ localize_mask(worker, &invers.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_invers(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &invers);
+ unlocalize_mask(&invers.mask);
+ break;
+ }
+ case QXL_DRAW_ROP3: {
+ Rop3 rop3 = drawable->u.rop3;
+ localize_brush(worker, &rop3.brush, worker->dev_info.phys_delta);
+ localize_bitmap(worker, &rop3.src_bitmap, worker->dev_info.phys_delta);
+ localize_mask(worker, &rop3.mask, worker->dev_info.phys_delta);
+ worker->draw_context.draw_rop3(worker->draw_context.canvas, &drawable->bbox, &clip, &rop3);
+ unlocalize_mask(&rop3.mask);
+ unlocalize_bitmap(&rop3.src_bitmap);
+ unlocalize_brush(&rop3.brush);
+ break;
+ }
+ case QXL_DRAW_STROKE: {
+ Stroke stroke = drawable->u.stroke;
+ localize_brush(worker, &stroke.brush, worker->dev_info.phys_delta);
+ localize_path(&stroke.path, worker->dev_info.phys_delta);
+ localize_attr(&stroke.attr, worker->dev_info.phys_delta);
+ worker->draw_context.draw_stroke(worker->draw_context.canvas, &drawable->bbox, &clip,
+ &stroke);
+ unlocalize_attr(&stroke.attr);
+ unlocalize_path(&stroke.path);
+ unlocalize_brush(&stroke.brush);
+ break;
+ }
+ case QXL_DRAW_TEXT: {
+ Text text = drawable->u.text;
+ localize_brush(worker, &text.fore_brush, worker->dev_info.phys_delta);
+ localize_brush(worker, &text.back_brush, worker->dev_info.phys_delta);
+ localize_str(&text.str, worker->dev_info.phys_delta);
+ worker->draw_context.draw_text(worker->draw_context.canvas, &drawable->bbox, &clip, &text);
+ unlocalize_str(&text.str);
+ unlocalize_brush(&text.back_brush);
+ unlocalize_brush(&text.fore_brush);
+ break;
+ }
+ default:
+ red_printf("invlaid type");
+ }
+ unlocalize_clip(&clip);
+}
+
+#ifndef DRAW_ALL
+
+static void red_draw_drawable(RedWorker *worker, Drawable *drawable)
+{
+#ifdef UPDATE_AREA_BY_TREE
+ //todo: add need top mask flag
+ worker->draw_context.set_top_mask(worker->draw_context.canvas,
+ drawable->tree_item.base.rgn.num_rects,
+ drawable->tree_item.base.rgn.rects);
+#endif
+ red_draw_qxl_drawable(worker, drawable->qxl_drawable);
+#ifdef UPDATE_AREA_BY_TREE
+ worker->draw_context.clear_top_mask(worker->draw_context.canvas);
+#endif
+}
+
+#ifdef UPDATE_AREA_BY_TREE
+
+static inline void __red_collect_for_update(RedWorker *worker, Ring *ring, RingItem *ring_item,
+ QRegion *rgn, Ring *items)
+{
+ Ring *top_ring = ring;
+
+ for (;;) {
+ TreeItem *now = CONTAINEROF(ring_item, TreeItem, siblings_link);
+ Container *container = now->container;
+ if (region_intersects(rgn, &now->rgn)) {
+ if (IS_DRAW_ITEM(now)) {
+ Drawable *drawable = CONTAINEROF(now, Drawable, tree_item);
+
+ ring_add(items, &drawable->collect_link);
+ region_or(rgn, &now->rgn);
+ if (drawable->tree_item.shadow) {
+ region_or(rgn, &drawable->tree_item.shadow->base.rgn);
+ }
+ } else if (now->type == TREE_ITEM_TYPE_SHADOW) {
+ Drawable *owner = CONTAINEROF(((Shadow *)now)->owner, Drawable, tree_item);
+ if (!ring_item_is_linked(&owner->collect_link)) {
+ region_or(rgn, &now->rgn);
+ region_or(rgn, &owner->tree_item.base.rgn);
+ ring_add(items, &owner->collect_link);
+ }
+ } else if (now->type == TREE_ITEM_TYPE_CONTAINER) {
+ Container *container = (Container *)now;
+
+ if ((ring_item = ring_get_head(&container->items))) {
+ ring = &container->items;
+ ASSERT(((TreeItem *)ring_item)->container);
+ continue;
+ }
+ ring_item = &now->siblings_link;
+ }
+ }
+
+ while (!(ring_item = ring_next(ring, ring_item))) {
+ if (ring == top_ring) {
+ return;
+ }
+ ring_item = &container->base.siblings_link;
+ container = container->base.container;
+ ring = (container) ? &container->items : top_ring;
+ }
+ }
+}
+
+static void red_update_area(RedWorker *worker, const Rect *area)
+{
+ Ring *ring = &worker->current;
+ RingItem *ring_item;
+ Ring items;
+ QRegion rgn;
+
+ if (!(ring_item = ring_get_head(ring))) {
+ return;
+ }
+
+ region_init(&rgn);
+ region_add(&rgn, area);
+ ring_init(&items);
+ __red_collect_for_update(worker, ring, ring_item, &rgn, &items);
+ region_destroy(&rgn);
+
+ while ((ring_item = ring_get_head(&items))) {
+ Drawable *drawable = CONTAINEROF(ring_item, Drawable, collect_link);
+ Container *container;
+
+ ring_remove(ring_item);
+ red_draw_drawable(worker, drawable);
+ container = drawable->tree_item.base.container;
+ current_remove_drawable(worker, drawable);
+ container_cleanup(worker, container);
+ }
+ worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area,
+ area);
+}
+
+#else
+
+static void red_update_area(RedWorker *worker, const Rect *area)
+{
+ Ring *ring = &worker->current_list;
+ RingItem *ring_item = ring;
+ QRegion rgn;
+ Drawable *last = NULL;
+ Drawable *now;
+
+ region_init(&rgn);
+ region_add(&rgn, area);
+ while ((ring_item = ring_next(ring, ring_item))) {
+ now = CONTAINEROF(ring_item, Drawable, list_link);
+ if (region_intersects(&rgn, &now->tree_item.base.rgn)) {
+ last = now;
+ break;
+ }
+ }
+ region_destroy(&rgn);
+
+ if (!last) {
+ return;
+ }
+
+ do {
+ Container *container;
+
+ ring_item = ring_get_tail(&worker->current_list);
+ now = CONTAINEROF(ring_item, Drawable, list_link);
+ red_draw_drawable(worker, now);
+ container = now->tree_item.base.container;
+ current_remove_drawable(worker, now);
+ container_cleanup(worker, container);
+ } while (now != last);
+ worker->draw_context.validate_area(worker->draw_context.canvas, &worker->dev_info.draw_area,
+ area);
+}
+
+#endif
+
+#endif
+
+static void red_release_cursor(RedWorker *worker, CursorItem *cursor)
+{
+ if (!--cursor->refs) {
+ QXLCursorCmd *cursor_cmd;
+
+ if (cursor->type == CURSOR_TYPE_LOCAL) {
+ free(cursor);
+ return;
+ }
+ cursor_cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data);
+ worker->qxl->release_resource(worker->qxl, &cursor_cmd->release_info);
+ }
+}
+
+static void red_set_cursor(RedWorker *worker, CursorItem *cursor)
+{
+ if (worker->cursor) {
+ red_release_cursor(worker, worker->cursor);
+ }
+ ++cursor->refs;
+ worker->cursor = cursor;
+}
+
+void qxl_process_cursor(RedWorker *worker, QXLCursorCmd *cursor_cmd)
+{
+ CursorItem *item = (CursorItem *)cursor_cmd->device_data;
+ int cursor_show = FALSE;
+
+ red_pipe_item_init(&item->pipe_data, PIPE_ITEM_TYPE_CURSOR);
+ item->refs = 1;
+ item->type = CURSOR_TYPE_INVALID;
+
+ switch (cursor_cmd->type) {
+ case QXL_CURSOR_SET:
+ worker->cursor_visible = cursor_cmd->u.set.visible;
+ item->type = CURSOR_TYPE_DEV;
+ red_set_cursor(worker, item);
+ break;
+ case QXL_CURSOR_MOVE:
+ cursor_show = !worker->cursor_visible;
+ worker->cursor_visible = TRUE;
+ worker->cursor_position = cursor_cmd->u.position;
+ break;
+ case QXL_CURSOR_HIDE:
+ worker->cursor_visible = FALSE;
+ break;
+ case QXL_CURSOR_TRAIL:
+ worker->cursor_trail_length = cursor_cmd->u.trail.length;
+ worker->cursor_trail_frequency = cursor_cmd->u.trail.frequency;
+ break;
+ default:
+ red_error("invalid cursor command %u", cursor_cmd->type);
+ }
+
+ if (worker->cursor_channel && (worker->mouse_mode == RED_MOUSE_MODE_SERVER ||
+ cursor_cmd->type != QXL_CURSOR_MOVE || cursor_show)) {
+ red_pipe_add(&worker->cursor_channel->base, &item->pipe_data);
+ } else {
+ red_release_cursor(worker, item);
+ }
+}
+
+static inline uint64_t red_now()
+{
+ struct timespec time;
+
+ clock_gettime(CLOCK_MONOTONIC, &time);
+
+ return time.tv_sec * 1000000000 + time.tv_nsec;
+}
+
+static int red_process_cursor(RedWorker *worker, uint32_t max_pipe_size)
+{
+ QXLCommand cmd;
+ int n = 0;
+
+ while (!worker->cursor_channel || worker->cursor_channel->base.pipe_size <= max_pipe_size) {
+ if (!worker->qxl->get_cursor_command(worker->qxl, &cmd)) {
+ if (worker->repoll_cursor_ring < CMD_RING_POLL_RETRIES) {
+ worker->repoll_cursor_ring++;
+ worker->epoll_timeout = MIN(worker->epoll_timeout, CMD_RING_POLL_TIMEOUT);
+ break;
+ }
+ if (worker->repoll_cursor_ring > CMD_RING_POLL_RETRIES ||
+ worker->qxl->req_cursor_notification(worker->qxl)) {
+ worker->repoll_cursor_ring++;
+ break;
+ }
+ continue;
+ }
+ worker->repoll_cursor_ring = 0;
+ switch (cmd.type) {
+ case QXL_CMD_CURSOR: {
+ QXLCursorCmd *cursor_cmd = (QXLCursorCmd *)(cmd.data + worker->dev_info.phys_delta);
+ ASSERT((uint64_t)cursor_cmd >= worker->dev_info.phys_start);
+ ASSERT((uint64_t)cursor_cmd + sizeof(QXLCursorCmd) <= worker->dev_info.phys_end);
+ qxl_process_cursor(worker, cursor_cmd);
+ break;
+ }
+ default:
+ red_error("bad command type");
+ }
+ n++;
+ }
+ return n;
+}
+
+static int red_process_commands(RedWorker *worker, uint32_t max_pipe_size)
+{
+ QXLCommand cmd;
+ int n = 0;
+ uint64_t start = red_now();
+
+ while (!worker->display_channel || worker->display_channel->base.pipe_size <= max_pipe_size) {
+ if (!worker->qxl->get_command(worker->qxl, &cmd)) {
+ if (worker->repoll_cmd_ring < CMD_RING_POLL_RETRIES) {
+ worker->repoll_cmd_ring++;
+ worker->epoll_timeout = MIN(worker->epoll_timeout, CMD_RING_POLL_TIMEOUT);
+ break;
+ }
+ if (worker->repoll_cmd_ring > CMD_RING_POLL_RETRIES ||
+ worker->qxl->req_cmd_notification(worker->qxl)) {
+ worker->repoll_cmd_ring++;
+ break;
+ }
+ continue;
+ }
+ stat_inc_counter(worker->command_counter, 1);
+ worker->repoll_cmd_ring = 0;
+ switch (cmd.type) {
+ case QXL_CMD_DRAW: {
+ QXLDrawable *drawable = (QXLDrawable *)(cmd.data + worker->dev_info.phys_delta);
+ ASSERT((uint64_t)drawable >= worker->dev_info.phys_start);
+ ASSERT((uint64_t)drawable + sizeof(QXLDrawable) <= worker->dev_info.phys_end);
+ red_process_drawable(worker, drawable);
+ break;
+ }
+ case QXL_CMD_UPDATE: {
+ QXLUpdateCmd *draw_cmd = (QXLUpdateCmd *)(cmd.data + worker->dev_info.phys_delta);
+ ASSERT((uint64_t)draw_cmd >= worker->dev_info.phys_start);
+ ASSERT((uint64_t)draw_cmd + sizeof(QXLUpdateCmd) <= worker->dev_info.phys_end);
+ red_update_area(worker, &draw_cmd->area);
+ worker->qxl->notify_update(worker->qxl, draw_cmd->update_id);
+ worker->qxl->release_resource(worker->qxl, &draw_cmd->release_info);
+ break;
+ }
+ case QXL_CMD_CURSOR: {
+ QXLCursorCmd *cursor_cmd = (QXLCursorCmd *)(cmd.data + worker->dev_info.phys_delta);
+ ASSERT((uint64_t)cursor_cmd >= worker->dev_info.phys_start);
+ ASSERT((uint64_t)cursor_cmd + sizeof(QXLCursorCmd) <= worker->dev_info.phys_end);
+ qxl_process_cursor(worker, cursor_cmd);
+ break;
+ }
+ case QXL_CMD_MESSAGE: {
+ QXLMessage *message = (QXLMessage *)(cmd.data + worker->dev_info.phys_delta);
+ ASSERT((uint64_t)message >= worker->dev_info.phys_start);
+ ASSERT((uint64_t)message + sizeof(QXLDrawable) <= worker->dev_info.phys_end);
+ red_printf("MESSAGE: %s", message->data);
+ worker->qxl->release_resource(worker->qxl, &message->release_info);
+ break;
+ }
+ default:
+ red_error("bad command type");
+ }
+ n++;
+ if ((worker->display_channel && worker->display_channel->base.send_data.blocked) ||
+ red_now() - start > 10 * 1000 * 1000) {
+ worker->epoll_timeout = 0;
+ return n;
+ }
+ }
+ return n;
+}
+
+#define RED_RELEASE_BUNCH_SIZE 5
+
+static void red_free_some(RedWorker *worker)
+{
+ int n = 0;
+
+ if (worker->display_channel && worker->display_channel->glz_dict) {
+ // encoding using the dictionary is prevented since the following operations might
+ // change the dictionary
+ pthread_rwlock_wrlock(&worker->display_channel->glz_dict->encode_lock);
+ n = red_display_free_some_independent_glz_drawables(worker->display_channel);
+ }
+
+ while (!ring_is_empty(&worker->current_list) && n++ < RED_RELEASE_BUNCH_SIZE) {
+ free_one_drawable(worker, TRUE);
+ }
+
+ if (worker->display_channel && worker->display_channel->glz_dict) {
+ pthread_rwlock_unlock(&worker->display_channel->glz_dict->encode_lock);
+ }
+}
+
+static void red_current_flush(RedWorker *worker)
+{
+ while (!ring_is_empty(&worker->current_list)) {
+ free_one_drawable(worker, FALSE);
+ }
+ red_current_clear(worker);
+}
+
+static void red_add_screen_image(RedWorker *worker)
+{
+ ImageItem *item;
+ int stride;
+ Rect area;
+
+ if (!worker->display_channel) {
+ return;
+ }
+ stride = worker->dev_info.x_res << 2;
+ if (!(item = (ImageItem *)malloc(sizeof(ImageItem) + worker->dev_info.y_res * stride))) {
+ //warn
+ return;
+ }
+
+ red_pipe_item_init(&item->link, PIPE_ITEM_TYPE_IMAGE);
+
+ item->refs = 1;
+ item->pos.x = item->pos.y = 0;
+ item->width = worker->dev_info.x_res;
+ item->height = worker->dev_info.y_res;
+ item->stride = stride;
+ item->top_down = worker->draw_context.top_down;
+
+ area.top = area.left = 0;
+ area.right = worker->dev_info.x_res;
+ area.bottom = worker->dev_info.y_res;
+ worker->draw_context.read_pixels(worker->draw_context.canvas, item->data, stride, &area);
+ red_pipe_add_image_item(worker, item);
+ release_image_item(item);
+ display_channel_push(worker);
+}
+
+static void inline __add_buf(RedChannel *channel, uint32_t type, void *data, uint32_t size)
+{
+ int pos = channel->send_data.n_bufs++;
+ ASSERT(pos < MAX_SEND_BUFS);
+ channel->send_data.bufs[pos].type = type;
+ channel->send_data.bufs[pos].size = size;
+ channel->send_data.bufs[pos].data = data;
+}
+
+static void add_buf(RedChannel *channel, uint32_t type, void *data, uint32_t size)
+{
+ __add_buf(channel, type, data, size);
+ channel->send_data.header.size += size;
+}
+
+static void fill_path(DisplayChannel *display_channel, PHYSICAL *in_path)
+{
+ RedChannel *channel = &display_channel->base;
+ ASSERT(in_path && *in_path);
+ QXLPath *path = (QXLPath *)(*in_path + channel->worker->dev_info.phys_delta);
+ *in_path = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, &path->data_size, sizeof(UINT32));
+ add_buf(channel, BUF_TYPE_CHUNK, &path->chunk, path->data_size);
+}
+
+static void fill_str(DisplayChannel *display_channel, PHYSICAL *in_str)
+{
+ RedChannel *channel = &display_channel->base;
+ ASSERT(in_str && *in_str);
+ QXLString *str = (QXLString *)(*in_str + channel->worker->dev_info.phys_delta);
+ *in_str = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, &str->length, sizeof(UINT32));
+ add_buf(channel, BUF_TYPE_CHUNK, &str->chunk, str->data_size);
+}
+
+static inline void fill_rects_clip(RedChannel *channel, PHYSICAL *in_clip)
+{
+ QXLClipRects *clip;
+
+ ASSERT(in_clip && *in_clip);
+ clip = (QXLClipRects *)(*in_clip + channel->worker->dev_info.phys_delta);
+ *in_clip = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, &clip->num_rects, sizeof(UINT32));
+ add_buf(channel, BUF_TYPE_CHUNK, &clip->chunk, clip->num_rects * sizeof(Rect));
+}
+
+static void fill_base(DisplayChannel *display_channel, RedDrawBase *base, QXLDrawable *drawable,
+ uint32_t size)
+{
+ RedChannel *channel = &display_channel->base;
+ add_buf(channel, BUF_TYPE_RAW, base, size);
+ base->box = drawable->bbox;
+ base->clip = drawable->clip;
+
+ if (base->clip.type == CLIP_TYPE_RECTS) {
+ fill_rects_clip(channel, &base->clip.data);
+ } else if (base->clip.type == CLIP_TYPE_PATH) {
+ fill_path(display_channel, &base->clip.data);
+ }
+}
+
+static inline RedImage *alloc_image(DisplayChannel *display_channel)
+{
+ ASSERT(display_channel->send_data.bitmap_pos < MAX_BITMAPS);
+ return &display_channel->send_data.images[display_channel->send_data.bitmap_pos++];
+}
+
+/* io_palette is relative address of the palette*/
+static inline void fill_palette(DisplayChannel *display_channel, ADDRESS *io_palette, UINT8 *flags)
+{
+ RedChannel *channel = &display_channel->base;
+ Palette *palette;
+
+ if (!(*io_palette)) {
+ return;
+ }
+ palette = (Palette *)(*io_palette + channel->worker->dev_info.phys_delta);
+ if (palette->unique) {
+ if (red_palette_cache_find(display_channel, palette->unique)) {
+ *flags |= BITMAP_PAL_FROM_CACHE;
+ *io_palette = palette->unique;
+ return;
+ }
+ if (red_palette_cache_add(display_channel, palette->unique, 1)) {
+ *flags |= BITMAP_PAL_CACHE_ME;
+ }
+ }
+ *io_palette = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, palette,
+ sizeof(Palette) + palette->num_ents * sizeof(UINT32));
+}
+
+static inline RedCompressBuf *red_display_alloc_compress_buf(DisplayChannel *display_channel)
+{
+ RedCompressBuf *ret;
+
+ if (display_channel->send_data.free_compress_bufs) {
+ ret = display_channel->send_data.free_compress_bufs;
+ display_channel->send_data.free_compress_bufs = ret->next;
+ } else {
+ if (!(ret = malloc(sizeof(*ret)))) {
+ return NULL;
+ }
+ }
+
+ ret->next = display_channel->send_data.used_compress_bufs;
+ display_channel->send_data.used_compress_bufs = ret;
+ return ret;
+}
+
+static inline void __red_display_free_compress_buf(DisplayChannel *display_channel,
+ RedCompressBuf *buf)
+{
+ buf->next = display_channel->send_data.free_compress_bufs;
+ display_channel->send_data.free_compress_bufs = buf;
+}
+
+static void red_display_free_compress_buf(DisplayChannel *display_channel,
+ RedCompressBuf *buf)
+{
+ RedCompressBuf **curr_used = &display_channel->send_data.used_compress_bufs;
+
+ for (;;) {
+ ASSERT(*curr_used);
+ if (*curr_used == buf) {
+ *curr_used = buf->next;
+ break;
+ }
+ curr_used = &(*curr_used)->next;
+ }
+ __red_display_free_compress_buf(display_channel, buf);
+}
+
+static void red_display_reset_compress_buf(DisplayChannel *display_channel)
+{
+ while (display_channel->send_data.used_compress_bufs) {
+ RedCompressBuf *buf = display_channel->send_data.used_compress_bufs;
+ display_channel->send_data.used_compress_bufs = buf->next;
+ __red_display_free_compress_buf(display_channel, buf);
+ }
+}
+
+/******************************************************
+ * Global lz red drawables routines
+*******************************************************/
+
+/* if already exists, returns it. Otherwise allocates and adds it (1) to the ring tail
+ in the channel (2) to the Drawable*/
+static RedGlzDrawable *red_display_get_glz_drawable(DisplayChannel *channel, Drawable *drawable)
+{
+ RedGlzDrawable *ret;
+
+ if (drawable->red_glz_drawable) {
+ return drawable->red_glz_drawable;
+ }
+
+ if (!(ret = malloc(sizeof(*ret)))) {
+ PANIC("malloc failed");
+ }
+
+ ret->display_channel = channel;
+ ret->qxl_drawable = drawable->qxl_drawable;
+ ret->drawable = drawable;
+ ret->instances_count = 0;
+ ring_init(&ret->instances);
+
+ ring_item_init(&ret->link);
+
+ ring_add_before(&ret->link, &channel->glz_drawables);
+ drawable->red_glz_drawable = ret;
+
+ return ret;
+}
+
+/* allocates new instance and adds it to instances in the given drawable.
+ NOTE - the caller should set the glz_instance returned by the encoder by itself.*/
+static GlzDrawableInstanceItem *red_display_add_glz_drawable_instance(RedGlzDrawable *glz_drawable)
+{
+ ASSERT(glz_drawable->instances_count < MAX_GLZ_DRAWABLE_INSTANCES);
+ // NOTE: We assume the addtions are performed consecutively, without removals in the middle
+ GlzDrawableInstanceItem *ret = glz_drawable->instances_pool + glz_drawable->instances_count;
+ glz_drawable->instances_count++;
+
+ ring_item_init(&ret->free_link);
+ ring_item_init(&ret->glz_link);
+ ring_add(&glz_drawable->instances, &ret->glz_link);
+ ret->glz_instance = NULL;
+ ret->red_glz_drawable = glz_drawable;
+
+ return ret;
+}
+
+/* Remove from the to_free list and the instances_list.
+ When no instance is left - the RedGlzDrawable is released too. (and the qxl drawblae too, if
+ it is not used by Drawable).
+ NOTE - 1) can be called only by the display channel that created the drawable
+ 2) it is assumed that the instance was already removed from the dicitonary*/
+static void red_display_free_glz_drawable_instance(DisplayChannel *channel,
+ GlzDrawableInstanceItem *glz_drawable_instance)
+{
+ RedGlzDrawable *glz_drawable;
+
+ ASSERT(glz_drawable_instance);
+ ASSERT(glz_drawable_instance->red_glz_drawable);
+
+ glz_drawable = glz_drawable_instance->red_glz_drawable;
+
+ ASSERT(glz_drawable->display_channel == channel);
+ ASSERT(glz_drawable->instances_count);
+
+ ring_remove(&glz_drawable_instance->glz_link);
+ glz_drawable->instances_count--;
+ // whan the remove callback is performed from the channel that the
+ // drawable belongs to, the instance is not added to the 'to_free' list
+ if (ring_item_is_linked(&glz_drawable_instance->free_link)) {
+ ring_remove(&glz_drawable_instance->free_link);
+ }
+
+ if (ring_is_empty(&glz_drawable->instances)) {
+ ASSERT(!glz_drawable->instances_count);
+
+ Drawable *drawable = glz_drawable->drawable;
+
+ if (drawable) {
+ drawable->red_glz_drawable = NULL;
+ } else { // no reference to the qxl drawable left
+ free_qxl_drawable(channel->base.worker, glz_drawable->qxl_drawable);
+ }
+
+ if (ring_item_is_linked(&glz_drawable->link)) {
+ ring_remove(&glz_drawable->link);
+ }
+ free(glz_drawable);
+ }
+}
+
+static void red_display_handle_glz_drawables_to_free(DisplayChannel* channel)
+{
+ RingItem *ring_link;
+ pthread_mutex_lock(&channel->glz_drawables_inst_to_free_lock);
+
+ while ((ring_link = ring_get_head(&channel->glz_drawables_inst_to_free))) {
+ GlzDrawableInstanceItem *drawable_instance = CONTAINEROF(ring_link,
+ GlzDrawableInstanceItem,
+ free_link);
+ red_display_free_glz_drawable_instance(channel, drawable_instance);
+ }
+
+ pthread_mutex_unlock(&channel->glz_drawables_inst_to_free_lock);
+}
+
+/* releases all the instances of the drawable from the dictionary and the display channel.
+ The release of the last instance will also release the drawable itself and the qxl drawable
+ if possible.
+ NOTE - the caller should prevent encoding using the dicitonary during this operation*/
+static void red_display_free_glz_drawable(DisplayChannel *channel, RedGlzDrawable *drawable)
+{
+ RingItem *head_instance = ring_get_head(&drawable->instances);
+ int cont = (head_instance != NULL);
+
+ while (cont) {
+ if (drawable->instances_count == 1) {
+ /* Last instance: red_display_free_glz_drawable_instance will free the drawable */
+ cont = FALSE;
+ }
+ GlzDrawableInstanceItem *instance = CONTAINEROF(head_instance,
+ GlzDrawableInstanceItem,
+ glz_link);
+ if (!ring_item_is_linked(&instance->free_link)) {
+ // the instance didn't get out from window yet
+ glz_enc_dictionary_remove_image(channel->glz_dict->dict,
+ instance->glz_instance,
+ &channel->glz_data.usr);
+ }
+ red_display_free_glz_drawable_instance(channel, instance);
+
+ if (cont) {
+ head_instance = ring_get_head(&drawable->instances);
+ }
+ }
+}
+
+/* Clear all lz drawables - enforce their removal from the global dictionary.
+ NOTE - prevents encoding using the dicitonary during the operation*/
+static void red_display_clear_glz_drawables(DisplayChannel *channel)
+{
+ RingItem *ring_link;
+
+ if (!channel || !channel->glz_dict) {
+ return;
+ }
+
+ // assure no display channel is during global lz encoding
+ pthread_rwlock_wrlock(&channel->glz_dict->encode_lock);
+
+ while ((ring_link = ring_get_head(&channel->glz_drawables))) {
+ RedGlzDrawable *drawable = CONTAINEROF(ring_link, RedGlzDrawable, link);
+ // no need to lock the to_free list, since we assured no other thread is encoding and
+ // thus not other thread access the to_free list of the channel
+ red_display_free_glz_drawable(channel, drawable);
+ }
+
+ pthread_rwlock_unlock(&channel->glz_dict->encode_lock);
+}
+
+/* Remove from the global lz dictionary some glz_drawables that have no reference to
+ Drawable (their qxl drawables are released too).
+ NOTE - the caller should prevent encoding using the dicitonary during the operation*/
+static int red_display_free_some_independent_glz_drawables(DisplayChannel *channel)
+{
+ int n = 0;
+
+ if (!channel) {
+ return 0;
+ }
+
+ RingItem *ring_link = ring_get_head(&channel->glz_drawables);
+
+ while ((n < RED_RELEASE_BUNCH_SIZE) && (ring_link != NULL)) {
+ RedGlzDrawable *glz_drawable = CONTAINEROF(ring_link, RedGlzDrawable, link);
+ ring_link = ring_next(&channel->glz_drawables, ring_link);
+ if (!glz_drawable->drawable) {
+ red_display_free_glz_drawable(channel, glz_drawable);
+ n++;
+ }
+ }
+ return n;
+}
+
+/******************************************************
+ * Encoders callbacks
+*******************************************************/
+static void quic_usr_error(QuicUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((QuicData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+ red_printf("%s", usr_data->message_buf);
+
+ longjmp(usr_data->jmp_env, 1);
+}
+
+static void lz_usr_error(LzUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((LzData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+ red_printf("%s", usr_data->message_buf);
+
+ longjmp(usr_data->jmp_env, 1);
+}
+
+static void glz_usr_error(GlzEncoderUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((GlzData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+
+ PANIC(usr_data->message_buf); // if global lz fails in the middle
+ // the consequences are not predictable since the window
+ // can turn to be unsynchronized between the server and
+ // and the client
+}
+
+static void quic_usr_warn(QuicUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((QuicData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+ red_printf("%s", usr_data->message_buf);
+}
+
+static void lz_usr_warn(LzUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((LzData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+ red_printf("%s", usr_data->message_buf);
+}
+
+static void glz_usr_warn(GlzEncoderUsrContext *usr, const char *fmt, ...)
+{
+ EncoderData *usr_data = &(((GlzData *)usr)->data);
+ va_list ap;
+
+ va_start(ap, fmt);
+ vsnprintf(usr_data->message_buf, sizeof(usr_data->message_buf), fmt, ap);
+ va_end(ap);
+ red_printf("%s", usr_data->message_buf);
+}
+
+static void *quic_usr_malloc(QuicUsrContext *usr, int size)
+{
+ return malloc(size);
+}
+
+static void *lz_usr_malloc(LzUsrContext *usr, int size)
+{
+ return malloc(size);
+}
+
+static void *glz_usr_malloc(GlzEncoderUsrContext *usr, int size)
+{
+ return malloc(size);
+}
+
+static void quic_usr_free(QuicUsrContext *usr, void *ptr)
+{
+ free(ptr);
+}
+
+static void lz_usr_free(LzUsrContext *usr, void *ptr)
+{
+ free(ptr);
+}
+
+static void glz_usr_free(GlzEncoderUsrContext *usr, void *ptr)
+{
+ free(ptr);
+}
+
+static inline int encoder_usr_more_space(EncoderData *enc_data, uint32_t **io_ptr)
+{
+ RedCompressBuf *buf;
+
+ if (!(buf = red_display_alloc_compress_buf(enc_data->display_channel))) {
+ return 0;
+ }
+ enc_data->bufs_tail->send_next = buf;
+ enc_data->bufs_tail = buf;
+ buf->send_next = NULL;
+ *io_ptr = buf->buf;
+ return sizeof(buf->buf) >> 2;
+}
+
+static int quic_usr_more_space(QuicUsrContext *usr, uint32_t **io_ptr, int rows_completed)
+{
+ EncoderData *usr_data = &(((QuicData *)usr)->data);
+ return encoder_usr_more_space(usr_data, io_ptr);
+}
+
+static int lz_usr_more_space(LzUsrContext *usr, uint8_t **io_ptr)
+{
+ EncoderData *usr_data = &(((LzData *)usr)->data);
+ return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
+}
+
+static int glz_usr_more_space(GlzEncoderUsrContext *usr, uint8_t **io_ptr)
+{
+ EncoderData *usr_data = &(((GlzData *)usr)->data);
+ return (encoder_usr_more_space(usr_data, (uint32_t **)io_ptr) << 2);
+}
+
+static inline int encoder_usr_more_lines(EncoderData *enc_data, uint8_t **lines)
+{
+ if (!enc_data->u.lines_data.next) {
+ return 0;
+ }
+
+ QXLDataChunk *chunk = (QXLDataChunk *)(enc_data->u.lines_data.next +
+ enc_data->u.lines_data.address_delta);
+ if (chunk->data_size % enc_data->u.lines_data.stride) {
+ return 0;
+ }
+ enc_data->u.lines_data.next = chunk->next_chunk;
+ *lines = chunk->data;
+ return chunk->data_size / enc_data->u.lines_data.stride;
+}
+
+static int quic_usr_more_lines(QuicUsrContext *usr, uint8_t **lines)
+{
+ EncoderData *usr_data = &(((QuicData *)usr)->data);
+ return encoder_usr_more_lines(usr_data, lines);
+}
+
+static int lz_usr_more_lines(LzUsrContext *usr, uint8_t **lines)
+{
+ EncoderData *usr_data = &(((LzData *)usr)->data);
+ return encoder_usr_more_lines(usr_data, lines);
+}
+
+static int glz_usr_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines)
+{
+ EncoderData *usr_data = &(((GlzData *)usr)->data);
+ return encoder_usr_more_lines(usr_data, lines);
+}
+
+static int quic_usr_more_lines_revers(QuicUsrContext *usr, uint8_t **lines)
+{
+ EncoderData *quic_data = &(((QuicData *)usr)->data);
+
+ if (!quic_data->u.lines_data.next) {
+ return 0;
+ }
+
+ QXLDataChunk *chunk = (QXLDataChunk *)(quic_data->u.lines_data.next +
+ quic_data->u.lines_data.address_delta);
+ if (chunk->data_size % quic_data->u.lines_data.stride) {
+ return 0;
+ }
+ quic_data->u.lines_data.next = chunk->prev_chunk;
+ *lines = chunk->data + chunk->data_size - quic_data->u.lines_data.stride;
+ return chunk->data_size / quic_data->u.lines_data.stride;
+}
+
+static int quic_usr_more_lines_unstable(QuicUsrContext *usr, uint8_t **out_lines)
+{
+ EncoderData *quic_data = &(((QuicData *)usr)->data);
+
+ if (!quic_data->u.unstable_lines_data.lines) {
+ return 0;
+ }
+ uint8_t *src = quic_data->u.unstable_lines_data.next;
+ int lines = MIN(quic_data->u.unstable_lines_data.lines,
+ quic_data->u.unstable_lines_data.max_lines_bunch);
+ quic_data->u.unstable_lines_data.lines -= lines;
+ uint8_t *end = src + lines * quic_data->u.unstable_lines_data.src_stride;
+ quic_data->u.unstable_lines_data.next = end;
+
+ uint8_t *out = (uint8_t *)quic_data->u.unstable_lines_data.input_bufs[
+ quic_data->u.unstable_lines_data.input_bufs_pos++ & 1]->buf;
+ uint8_t *dest = out;
+ for (; src != end; src += quic_data->u.unstable_lines_data.src_stride,
+ dest += quic_data->u.unstable_lines_data.dest_stride) {
+ memcpy(dest, src, quic_data->u.unstable_lines_data.dest_stride);
+ }
+ *out_lines = out;
+ return lines;
+}
+
+static int quic_usr_no_more_lines(QuicUsrContext *usr, uint8_t **lines)
+{
+ return 0;
+}
+
+static int lz_usr_no_more_lines(LzUsrContext *usr, uint8_t **lines)
+{
+ return 0;
+}
+
+static int glz_usr_no_more_lines(GlzEncoderUsrContext *usr, uint8_t **lines)
+{
+ return 0;
+}
+
+static void glz_usr_free_image(GlzEncoderUsrContext *usr, GlzUsrImageContext *image)
+{
+ GlzData *lz_data = (GlzData *)usr;
+ GlzDrawableInstanceItem *glz_drawable_instance = (GlzDrawableInstanceItem *)image;
+ DisplayChannel *drawable_channel = glz_drawable_instance->red_glz_drawable->display_channel;
+ DisplayChannel *this_channel = CONTAINEROF(lz_data, DisplayChannel, glz_data);
+ if (this_channel == drawable_channel) {
+ red_display_free_glz_drawable_instance(drawable_channel, glz_drawable_instance);
+ } else {
+ pthread_mutex_lock(&drawable_channel->glz_drawables_inst_to_free_lock);
+ ring_add_before(&glz_drawable_instance->free_link,
+ &drawable_channel->glz_drawables_inst_to_free);
+ pthread_mutex_unlock(&drawable_channel->glz_drawables_inst_to_free_lock);
+ }
+}
+
+static inline void red_init_quic(RedWorker *worker)
+{
+ worker->quic_data.usr.error = quic_usr_error;
+ worker->quic_data.usr.warn = quic_usr_warn;
+ worker->quic_data.usr.info = quic_usr_warn;
+ worker->quic_data.usr.malloc = quic_usr_malloc;
+ worker->quic_data.usr.free = quic_usr_free;
+ worker->quic_data.usr.more_space = quic_usr_more_space;
+ worker->quic_data.usr.more_lines = quic_usr_more_lines;
+
+ worker->quic = quic_create(&worker->quic_data.usr);
+
+ if (!worker->quic) {
+ PANIC("create quic failed");
+ }
+}
+
+static inline void red_init_lz(RedWorker *worker)
+{
+ worker->lz_data.usr.error = lz_usr_error;
+ worker->lz_data.usr.warn = lz_usr_warn;
+ worker->lz_data.usr.info = lz_usr_warn;
+ worker->lz_data.usr.malloc = lz_usr_malloc;
+ worker->lz_data.usr.free = lz_usr_free;
+ worker->lz_data.usr.more_space = lz_usr_more_space;
+ worker->lz_data.usr.more_lines = lz_usr_more_lines;
+
+ worker->lz = lz_create(&worker->lz_data.usr);
+
+ if (!worker->lz) {
+ PANIC("create lz failed");
+ }
+}
+
+static inline void red_display_init_glz_data(DisplayChannel *display)
+{
+ display->glz_data.usr.error = glz_usr_error;
+ display->glz_data.usr.warn = glz_usr_warn;
+ display->glz_data.usr.info = glz_usr_warn;
+ display->glz_data.usr.malloc = glz_usr_malloc;
+ display->glz_data.usr.free = glz_usr_free;
+ display->glz_data.usr.more_space = glz_usr_more_space;
+ display->glz_data.usr.more_lines = glz_usr_more_lines;
+ display->glz_data.usr.free_image = glz_usr_free_image;
+}
+
+#ifdef __GNUC__
+#define ATTR_PACKED __attribute__ ((__packed__))
+#else
+#define ATTR_PACKED
+#pragma pack(push)
+#pragma pack(1)
+#endif
+
+
+typedef struct ATTR_PACKED rgb32_pixel_t {
+ uint8_t b;
+ uint8_t g;
+ uint8_t r;
+ uint8_t pad;
+} rgb32_pixel_t;
+
+typedef struct ATTR_PACKED rgb24_pixel_t {
+ uint8_t b;
+ uint8_t g;
+ uint8_t r;
+} rgb24_pixel_t;
+
+typedef uint16_t rgb16_pixel_t;
+
+#ifndef __GNUC__
+#pragma pack(pop)
+#endif
+
+#undef ATTR_PACKED
+
+#define RED_BITMAP_UTILS_RGB16
+#include "red_bitmap_utils.h"
+#define RED_BITMAP_UTILS_RGB24
+#include "red_bitmap_utils.h"
+#define RED_BITMAP_UTILS_RGB32
+#include "red_bitmap_utils.h"
+
+#define GRADUAL_SCORE_RGB24_TH -0.03
+#define GRADUAL_SCORE_RGB16_TH 0
+
+// assumes that stride doesn't overflow
+static int _bitmap_is_gradual(RedWorker *worker, Bitmap *bitmap)
+{
+ long address_delta = worker->dev_info.phys_delta;
+ double score = 0.0;
+ int num_samples = 0;
+
+ if ((bitmap->flags & QXL_BITMAP_DIRECT)) {
+ uint8_t *lines = (uint8_t*)(bitmap->data + address_delta);
+ switch (bitmap->format) {
+ case BITMAP_FMT_16BIT:
+ compute_lines_gradual_score_rgb16((rgb16_pixel_t*)lines, bitmap->x, bitmap->y,
+ &score, &num_samples);
+ break;
+ case BITMAP_FMT_24BIT:
+ compute_lines_gradual_score_rgb24((rgb24_pixel_t*)lines, bitmap->x, bitmap->y,
+ &score, &num_samples);
+ break;
+ case BITMAP_FMT_32BIT:
+ case BITMAP_FMT_RGBA:
+ compute_lines_gradual_score_rgb32((rgb32_pixel_t*)lines, bitmap->x, bitmap->y,
+ &score, &num_samples);
+ break;
+ default:
+ red_error("invalid bitmap format (not RGB) %u", bitmap->format);
+ }
+ } else {
+ QXLDataChunk *chunk = NULL;
+ int num_lines;
+ double chunk_score = 0.0;
+ int chunk_num_samples = 0;
+ ADDRESS relative_address = bitmap->data;
+
+ while (relative_address) {
+ chunk = (QXLDataChunk *)(relative_address + address_delta);
+ num_lines = chunk->data_size / bitmap->stride;
+ switch (bitmap->format) {
+ case BITMAP_FMT_16BIT:
+ compute_lines_gradual_score_rgb16((rgb16_pixel_t*)chunk->data, bitmap->x, num_lines,
+ &chunk_score, &chunk_num_samples);
+ break;
+ case BITMAP_FMT_24BIT:
+ compute_lines_gradual_score_rgb24((rgb24_pixel_t*)chunk->data, bitmap->x, num_lines,
+ &chunk_score, &chunk_num_samples);
+ break;
+ case BITMAP_FMT_32BIT:
+ case BITMAP_FMT_RGBA:
+ compute_lines_gradual_score_rgb32((rgb32_pixel_t*)chunk->data, bitmap->x, num_lines,
+ &chunk_score, &chunk_num_samples);
+ break;
+ default:
+ red_error("invalid bitmap format (not RGB) %u", bitmap->format);
+ }
+
+ score += chunk_score;
+ num_samples += chunk_num_samples;
+
+ relative_address = chunk->next_chunk;
+ }
+ }
+
+ ASSERT(num_samples);
+ score /= num_samples;
+
+ if (bitmap->format == BITMAP_FMT_16BIT) {
+ return (score < GRADUAL_SCORE_RGB16_TH);
+ } else {
+ return (score < GRADUAL_SCORE_RGB24_TH);
+ }
+}
+
+static inline int _stride_is_extra(Bitmap *bitmap)
+{
+ ASSERT(bitmap);
+ if (BITMAP_FMT_IS_RGB[bitmap->format]) {
+ return ((bitmap->x * BITMAP_FMP_BYTES_PER_PIXEL[bitmap->format]) < bitmap->stride);
+ } else {
+ switch (bitmap->format) {
+ case BITMAP_FMT_8BIT:
+ return (bitmap->x < bitmap->stride);
+ case BITMAP_FMT_4BIT_BE:
+ case BITMAP_FMT_4BIT_LE: {
+ int bytes_width = ALIGN(bitmap->x, 2) >> 1;
+ return bytes_width < bitmap->stride;
+ }
+ case BITMAP_FMT_1BIT_BE:
+ case BITMAP_FMT_1BIT_LE: {
+ int bytes_width = ALIGN(bitmap->x, 8) >> 3;
+ return bytes_width < bitmap->stride;
+ }
+ default:
+ red_error("invalid image type %u", bitmap->format);
+ }
+ }
+}
+
+static const LzImageType MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[] = {
+ LZ_IMAGE_TYPE_INVALID,
+ LZ_IMAGE_TYPE_PLT1_LE,
+ LZ_IMAGE_TYPE_PLT1_BE,
+ LZ_IMAGE_TYPE_PLT4_LE,
+ LZ_IMAGE_TYPE_PLT4_BE,
+ LZ_IMAGE_TYPE_PLT8,
+ LZ_IMAGE_TYPE_RGB16,
+ LZ_IMAGE_TYPE_RGB24,
+ LZ_IMAGE_TYPE_RGB32,
+ LZ_IMAGE_TYPE_RGBA
+};
+
+typedef struct compress_send_data_t {
+ uint32_t raw_size;
+ void* comp_buf;
+ uint32_t comp_buf_size;
+ ADDRESS *plt_ptr;
+ UINT8 *flags_ptr;
+} compress_send_data_t;
+
+
+static inline int red_glz_compress_image(DisplayChannel *display_channel,
+ RedImage *dest, Bitmap *src, Drawable *drawable,
+ compress_send_data_t* o_comp_data)
+{
+#ifdef COMPRESS_STAT
+ stat_time_t start_time = stat_now();
+#endif
+ ASSERT(BITMAP_FMT_IS_RGB[src->format]);
+ GlzData *glz_data = &display_channel->glz_data;
+ LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format];
+ RedGlzDrawable *glz_drawable;
+ GlzDrawableInstanceItem *glz_drawable_instance;
+ uint8_t *lines;
+ unsigned int num_lines;
+ int size;
+ long address_delta;
+
+ glz_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel);
+ glz_data->data.bufs_head = glz_data->data.bufs_tail;
+
+ if (!glz_data->data.bufs_head) {
+ return FALSE;
+ }
+
+ glz_data->data.bufs_head->send_next = NULL;
+ glz_data->data.display_channel = display_channel;
+ address_delta = display_channel->base.worker->dev_info.phys_delta;
+
+ glz_drawable = red_display_get_glz_drawable(display_channel, drawable);
+ glz_drawable_instance = red_display_add_glz_drawable_instance(glz_drawable);
+
+ if ((src->flags & QXL_BITMAP_DIRECT)) {
+ glz_data->usr.more_lines = glz_usr_no_more_lines;
+ lines = (uint8_t*)(src->data + address_delta);
+ num_lines = src->y;
+ } else {
+ glz_data->data.u.lines_data.address_delta = address_delta;
+ glz_data->data.u.lines_data.stride = src->stride;
+ glz_data->data.u.lines_data.next = src->data;
+ glz_data->usr.more_lines = glz_usr_more_lines;
+ lines = NULL;
+ num_lines = 0;
+ }
+
+ size = glz_encode(display_channel->glz, type, src->x, src->y,
+ (src->flags & QXL_BITMAP_TOP_DOWN), lines, num_lines,
+ src->stride, (uint8_t*)glz_data->data.bufs_head->buf,
+ sizeof(glz_data->data.bufs_head->buf),
+ glz_drawable_instance,
+ &glz_drawable_instance->glz_instance);
+
+ dest->descriptor.type = IMAGE_TYPE_GLZ_RGB;
+ dest->lz_rgb.data_size = size;
+
+ o_comp_data->raw_size = sizeof(LZ_RGBImage);
+ o_comp_data->comp_buf = glz_data->data.bufs_head;
+ o_comp_data->comp_buf_size = size;
+ o_comp_data->plt_ptr = NULL;
+ o_comp_data->flags_ptr = NULL;
+
+ stat_compress_add(&display_channel->glz_stat, start_time, src->stride * src->y,
+ o_comp_data->comp_buf_size);
+ return TRUE;
+}
+
+static inline int red_lz_compress_image(DisplayChannel *display_channel,
+ RedImage *dest, Bitmap *src,
+ compress_send_data_t* o_comp_data)
+{
+ RedWorker *worker = display_channel->base.worker;
+ LzData *lz_data = &worker->lz_data;
+ LzContext *lz = worker->lz;
+ LzImageType type = MAP_BITMAP_FMT_TO_LZ_IMAGE_TYPE[src->format];
+ long address_delta;
+ int size; // size of the compressed data
+
+#ifdef COMPRESS_STAT
+ stat_time_t start_time = stat_now();
+#endif
+
+ lz_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel);
+ lz_data->data.bufs_head = lz_data->data.bufs_tail;
+
+ if (!lz_data->data.bufs_head) {
+ return FALSE;
+ }
+
+ lz_data->data.bufs_head->send_next = NULL;
+ lz_data->data.display_channel = display_channel;
+ address_delta = worker->dev_info.phys_delta;
+
+ if (setjmp(lz_data->data.jmp_env)) {
+ while (lz_data->data.bufs_head) {
+ RedCompressBuf *buf = lz_data->data.bufs_head;
+ lz_data->data.bufs_head = buf->send_next;
+ red_display_free_compress_buf(display_channel, buf);
+ }
+ return FALSE;
+ }
+
+ if ((src->flags & QXL_BITMAP_DIRECT)) {
+ lz_data->usr.more_lines = lz_usr_no_more_lines;
+ size = lz_encode(lz, type, src->x, src->y, (src->flags & QXL_BITMAP_TOP_DOWN),
+ (uint8_t*)(src->data + address_delta), src->y, src->stride,
+ (uint8_t*)lz_data->data.bufs_head->buf,
+ sizeof(lz_data->data.bufs_head->buf));
+ } else {
+ lz_data->data.u.lines_data.address_delta = address_delta;
+ lz_data->data.u.lines_data.stride = src->stride;
+ lz_data->data.u.lines_data.next = src->data;
+ lz_data->usr.more_lines = lz_usr_more_lines;
+
+ size = lz_encode(lz, type, src->x, src->y, (src->flags & QXL_BITMAP_TOP_DOWN),
+ NULL, 0, src->stride,
+ (uint8_t*)lz_data->data.bufs_head->buf,
+ sizeof(lz_data->data.bufs_head->buf));
+ }
+
+ // the compressed buffer is bigger than the original data
+ if (size > (src->y * src->stride)) {
+ longjmp(lz_data->data.jmp_env, 1);
+ }
+
+ if (BITMAP_FMT_IS_RGB[src->format]) {
+ dest->descriptor.type = IMAGE_TYPE_LZ_RGB;
+ dest->lz_rgb.data_size = size;
+
+ o_comp_data->raw_size = sizeof(LZ_RGBImage);
+ o_comp_data->comp_buf = lz_data->data.bufs_head;
+ o_comp_data->comp_buf_size = size;
+ o_comp_data->plt_ptr = NULL;
+ o_comp_data->flags_ptr = NULL;
+ } else {
+ dest->descriptor.type = IMAGE_TYPE_LZ_PLT;
+ dest->lz_plt.data_size = size;
+ dest->lz_plt.flags = src->flags & BITMAP_TOP_DOWN;
+ dest->lz_plt.palette = src->palette;
+
+ o_comp_data->raw_size = sizeof(LZ_PLTImage);
+ o_comp_data->comp_buf = lz_data->data.bufs_head;
+ o_comp_data->comp_buf_size = size;
+ o_comp_data->plt_ptr = &(dest->lz_plt.palette);
+ o_comp_data->flags_ptr = &(dest->lz_plt.flags);
+ }
+ stat_compress_add(&display_channel->lz_stat, start_time, src->stride * src->y,
+ o_comp_data->comp_buf_size);
+ return TRUE;
+}
+
+static inline int red_quic_compress_image(DisplayChannel *display_channel, RedImage *dest,
+ Bitmap *src, compress_send_data_t* o_comp_data)
+{
+ RedWorker *worker = display_channel->base.worker;
+ QuicData *quic_data = &worker->quic_data;
+ QuicContext *quic = worker->quic;
+ QuicImageType type;
+ long address_delta;
+ int size;
+
+#ifdef COMPRESS_STAT
+ stat_time_t start_time = stat_now();
+#endif
+
+ switch (src->format) {
+ case BITMAP_FMT_32BIT:
+ type = QUIC_IMAGE_TYPE_RGB32;
+ break;
+ case BITMAP_FMT_RGBA:
+ type = QUIC_IMAGE_TYPE_RGBA;
+ break;
+ case BITMAP_FMT_16BIT:
+ type = QUIC_IMAGE_TYPE_RGB16;
+ break;
+ case BITMAP_FMT_24BIT:
+ type = QUIC_IMAGE_TYPE_RGB24;
+ break;
+ default:
+ return FALSE;
+ }
+
+ quic_data->data.bufs_tail = red_display_alloc_compress_buf(display_channel);
+ quic_data->data.bufs_head = quic_data->data.bufs_tail;
+
+ if (!quic_data->data.bufs_head) {
+ return FALSE;
+ }
+
+ quic_data->data.bufs_head->send_next = NULL;
+ quic_data->data.display_channel = display_channel;
+ address_delta = worker->dev_info.phys_delta;
+
+ if (setjmp(quic_data->data.jmp_env)) {
+ while (quic_data->data.bufs_head) {
+ RedCompressBuf *buf = quic_data->data.bufs_head;
+ quic_data->data.bufs_head = buf->send_next;
+ red_display_free_compress_buf(display_channel, buf);
+ }
+ return FALSE;
+ }
+
+ if ((src->flags & QXL_BITMAP_DIRECT)) {
+ int stride;
+ uint8_t *data;
+
+ if (!(src->flags & QXL_BITMAP_TOP_DOWN)) {
+ data = (uint8_t*)(src->data + address_delta) + src->stride * (src->y - 1);
+ stride = -src->stride;
+ } else {
+ data = (uint8_t*)(src->data + address_delta);
+ stride = src->stride;
+ }
+
+ if ((src->flags & QXL_BITMAP_UNSTABLE)) {
+ quic_data->data.u.unstable_lines_data.next = data;
+ quic_data->data.u.unstable_lines_data.src_stride = stride;
+ quic_data->data.u.unstable_lines_data.dest_stride = src->stride;
+ quic_data->data.u.unstable_lines_data.lines = src->y;
+ quic_data->data.u.unstable_lines_data.input_bufs_pos = 0;
+ if (!(quic_data->data.u.unstable_lines_data.input_bufs[0] =
+ red_display_alloc_compress_buf(display_channel)) ||
+ !(quic_data->data.u.unstable_lines_data.input_bufs[1] =
+ red_display_alloc_compress_buf(display_channel))) {
+ return FALSE;
+ }
+ quic_data->data.u.unstable_lines_data.max_lines_bunch =
+ sizeof(quic_data->data.u.unstable_lines_data.input_bufs[0]->buf) /
+ quic_data->data.u.unstable_lines_data.dest_stride;
+ quic_data->usr.more_lines = quic_usr_more_lines_unstable;
+ size = quic_encode(quic, type, src->x, src->y, NULL, 0, src->stride,
+ quic_data->data.bufs_head->buf,
+ sizeof(quic_data->data.bufs_head->buf) >> 2);
+ } else {
+ quic_data->usr.more_lines = quic_usr_no_more_lines;
+ size = quic_encode(quic, type, src->x, src->y, data, src->y, stride,
+ quic_data->data.bufs_head->buf,
+ sizeof(quic_data->data.bufs_head->buf) >> 2);
+ }
+ } else {
+ int stride;
+
+ if ((src->flags & QXL_BITMAP_UNSTABLE)) {
+ red_printf_once("unexpected unstable bitmap");
+ return FALSE;
+ }
+ quic_data->data.u.lines_data.address_delta = address_delta;
+ quic_data->data.u.lines_data.stride = src->stride;
+
+ if ((src->flags & QXL_BITMAP_TOP_DOWN)) {
+ quic_data->data.u.lines_data.next = src->data;
+ quic_data->usr.more_lines = quic_usr_more_lines;
+ stride = src->stride;
+ } else {
+ QXLDataChunk *chunk = (QXLDataChunk *)(src->data + address_delta);
+ while (chunk->next_chunk) {
+ chunk = (QXLDataChunk *)(chunk->next_chunk + address_delta);
+ ASSERT(chunk->prev_chunk);
+ }
+ quic_data->data.u.lines_data.next = (ADDRESS)chunk - address_delta;
+ quic_data->usr.more_lines = quic_usr_more_lines_revers;
+ stride = -src->stride;
+ }
+ size = quic_encode(quic, type, src->x, src->y, NULL, 0, stride,
+ quic_data->data.bufs_head->buf,
+ sizeof(quic_data->data.bufs_head->buf) >> 2);
+ }
+
+ // the compressed buffer is bigger than the original data
+ if ((size << 2) > (src->y * src->stride)) {
+ longjmp(quic_data->data.jmp_env, 1);
+ }
+
+ dest->descriptor.type = IMAGE_TYPE_QUIC;
+ dest->quic.data_size = size << 2;
+
+ o_comp_data->raw_size = sizeof(QUICImage);
+ o_comp_data->comp_buf = quic_data->data.bufs_head;
+ o_comp_data->comp_buf_size = size << 2;
+ o_comp_data->plt_ptr = NULL;
+ o_comp_data->flags_ptr = NULL;
+
+ stat_compress_add(&display_channel->quic_stat, start_time, src->stride * src->y,
+ o_comp_data->comp_buf_size);
+ return TRUE;
+}
+
+#define MIN_SIZE_TO_COMPRESS 54
+#define MIN_DIMENSION_TO_QUIC 3
+static inline int red_compress_image(DisplayChannel *display_channel,
+ RedImage *dest, Bitmap *src, Drawable *drawable,
+ compress_send_data_t* o_comp_data)
+{
+ image_compression_t image_compression = display_channel->base.worker->image_compression;
+ int quic_compress = FALSE;
+
+ if ((image_compression == IMAGE_COMPRESS_OFF) ||
+ ((src->y * src->stride) < MIN_SIZE_TO_COMPRESS)) { // TODO: change the size cond
+ return FALSE;
+ } else if (image_compression == IMAGE_COMPRESS_QUIC) {
+ if (BITMAP_FMT_IS_PLT[src->format]) {
+ return FALSE;
+ } else {
+ quic_compress = TRUE;
+ }
+ } else {
+ /*
+ lz doesn't handle (1) bitmaps with strides that are larger than the width
+ of the image in bytes (2) unstable bitmaps
+ */
+ if (_stride_is_extra(src) || (src->flags & QXL_BITMAP_UNSTABLE)) {
+ if ((image_compression == IMAGE_COMPRESS_LZ) ||
+ (image_compression == IMAGE_COMPRESS_GLZ) ||
+ BITMAP_FMT_IS_PLT[src->format]) {
+ return FALSE;
+ } else {
+ quic_compress = TRUE;
+ }
+ } else {
+ if ((image_compression == IMAGE_COMPRESS_AUTO_LZ) ||
+ (image_compression == IMAGE_COMPRESS_AUTO_GLZ)) {
+ if ((src->x < MIN_DIMENSION_TO_QUIC) || (src->y < MIN_DIMENSION_TO_QUIC)) {
+ quic_compress = FALSE;
+ } else {
+ quic_compress = BITMAP_FMT_IS_RGB[src->format] &&
+ _bitmap_is_gradual(display_channel->base.worker, src);
+ }
+ } else {
+ quic_compress = FALSE;
+ }
+ }
+ }
+
+ if (quic_compress) {
+#ifdef COMPRESS_DEBUG
+ red_printf("QUIC compress");
+#endif
+ return red_quic_compress_image(display_channel, dest, src, o_comp_data);
+ } else {
+ int glz;
+ int ret;
+ if ((image_compression == IMAGE_COMPRESS_AUTO_GLZ) ||
+ (image_compression == IMAGE_COMPRESS_GLZ)) {
+ glz = BITMAP_FMT_IS_RGB[src->format] && (
+ (src->x * src->y) < glz_enc_dictionary_get_size(
+ display_channel->glz_dict->dict));
+ } else if ((image_compression == IMAGE_COMPRESS_AUTO_LZ) ||
+ (image_compression == IMAGE_COMPRESS_LZ)) {
+ glz = FALSE;
+ } else {
+ red_error("invalid image compression type %u", image_compression);
+ }
+
+ if (glz) {
+ /* using the global dictionary only if it is not freezed */
+ pthread_rwlock_rdlock(&display_channel->glz_dict->encode_lock);
+ if (!display_channel->glz_dict->migrate_freeze) {
+ ret = red_glz_compress_image(
+ display_channel, dest, src, drawable, o_comp_data);
+ } else {
+ glz = FALSE;
+ }
+ pthread_rwlock_unlock(&display_channel->glz_dict->encode_lock);
+ }
+
+ if (!glz) {
+ ret = red_lz_compress_image(display_channel, dest, src, o_comp_data);
+#ifdef COMPRESS_DEBUG
+ red_printf("LZ LOCAL compress");
+#endif
+ }
+#ifdef COMPRESS_DEBUG
+ else {
+ red_printf("LZ global compress fmt=%d", src->format);
+ }
+#endif
+ return ret;
+ }
+}
+
+static inline void red_display_add_image_to_pixmap_cache(DisplayChannel *display_channel,
+ QXLImage *qxl_image, RedImage *io_image)
+{
+ if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) {
+ ASSERT(qxl_image->descriptor.width * qxl_image->descriptor.height > 0);
+ if (pixmap_cache_add(display_channel->pixmap_cache, qxl_image->descriptor.id,
+ qxl_image->descriptor.width * qxl_image->descriptor.height,
+ display_channel)) {
+ io_image->descriptor.flags |= IMAGE_CACHE_ME;
+ stat_inc_counter(display_channel->add_to_cache_counter, 1);
+ }
+ }
+
+ if (!(io_image->descriptor.flags & IMAGE_CACHE_ME)) {
+ stat_inc_counter(display_channel->non_cache_counter, 1);
+ }
+}
+
+/* if the number of times fill_bits can be called per one qxl_drawable increases -
+ MAX_LZ_DRAWABLE_INSTANCES must be increased as well */
+static void fill_bits(DisplayChannel *display_channel, PHYSICAL *in_bitmap, Drawable *drawable)
+{
+ RedChannel *channel = &display_channel->base;
+ RedImage *image;
+ QXLImage *qxl_image;
+ uint8_t *data;
+ compress_send_data_t comp_send_data;
+
+ ASSERT(*in_bitmap);
+
+ image = alloc_image(display_channel);
+ qxl_image = (QXLImage *)(*in_bitmap + channel->worker->dev_info.phys_delta);
+
+ image->descriptor.id = qxl_image->descriptor.id;
+ image->descriptor.type = qxl_image->descriptor.type;
+ image->descriptor.flags = 0;
+ image->descriptor.width = qxl_image->descriptor.width;
+ image->descriptor.height = qxl_image->descriptor.height;
+
+ *in_bitmap = channel->send_data.header.size;
+ if ((qxl_image->descriptor.flags & QXL_IMAGE_CACHE)) {
+ if (pixmap_cache_hit(display_channel->pixmap_cache, image->descriptor.id,
+ display_channel)) {
+ image->descriptor.type = IMAGE_TYPE_FROM_CACHE;
+ add_buf(channel, BUF_TYPE_RAW, image, sizeof(ImageDescriptor));
+ stat_inc_counter(display_channel->cache_hits_counter, 1);
+ return;
+ }
+ }
+
+ switch (qxl_image->descriptor.type) {
+ case IMAGE_TYPE_BITMAP:
+#ifdef DUMP_BITMAP
+ dump_bitmap(display_channel->base.worker, &qxl_image->bitmap);
+#endif
+ /* Images must be added to the cache only after they are compressed
+ in order to prevent starvation in the client between pixmap_cache and
+ global dictionary (in cases of multiple monitors) */
+ if (!red_compress_image(display_channel, image, &qxl_image->bitmap,
+ drawable, &comp_send_data)) {
+ red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+
+ image->bitmap = qxl_image->bitmap;
+ image->bitmap.flags = image->bitmap.flags & BITMAP_TOP_DOWN;
+ add_buf(channel, BUF_TYPE_RAW, image, sizeof(BitmapImage));
+ fill_palette(display_channel, &(image->bitmap.palette), &(image->bitmap.flags));
+ data = (uint8_t *)(image->bitmap.data + channel->worker->dev_info.phys_delta);
+ image->bitmap.data = channel->send_data.header.size;
+ add_buf(channel,
+ (qxl_image->bitmap.flags & QXL_BITMAP_DIRECT) ? BUF_TYPE_RAW : BUF_TYPE_CHUNK,
+ data, image->bitmap.y * image->bitmap.stride);
+ } else {
+ red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+
+ add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, image, comp_send_data.raw_size);
+ add_buf((RedChannel *)display_channel, BUF_TYPE_COMPRESS_BUF,
+ comp_send_data.comp_buf, comp_send_data.comp_buf_size);
+
+ if (comp_send_data.plt_ptr != NULL) {
+ fill_palette(display_channel, comp_send_data.plt_ptr, comp_send_data.flags_ptr);
+ }
+ }
+ break;
+ case IMAGE_TYPE_QUIC:
+ red_display_add_image_to_pixmap_cache(display_channel, qxl_image, image);
+ image->quic = qxl_image->quic;
+ add_buf(channel, BUF_TYPE_RAW, image, sizeof(QUICImage));
+ add_buf(channel, BUF_TYPE_CHUNK, qxl_image->quic.data, qxl_image->quic.data_size);
+ break;
+ default:
+ red_error("invalid image type %u", image->descriptor.type);
+ }
+}
+
+static void fill_brush(DisplayChannel *display_channel, Brush *brush, Drawable *drawable)
+{
+ if (brush->type == BRUSH_TYPE_PATTERN) {
+ fill_bits(display_channel, &brush->u.pattern.pat, drawable);
+ }
+}
+
+static void fill_mask(DisplayChannel *display_channel, QMask *mask, Drawable *drawable)
+{
+ if (mask->bitmap) {
+ if (display_channel->base.worker->image_compression != IMAGE_COMPRESS_OFF) {
+ image_compression_t save_img_comp = display_channel->base.worker->image_compression;
+ display_channel->base.worker->image_compression = IMAGE_COMPRESS_OFF;
+ fill_bits(display_channel, &mask->bitmap, drawable);
+ display_channel->base.worker->image_compression = save_img_comp;
+ } else {
+ fill_bits(display_channel, &mask->bitmap, drawable);
+ }
+ }
+}
+
+static void fill_attr(DisplayChannel *display_channel, LineAttr *attr)
+{
+ if (attr->style_nseg) {
+ RedChannel *channel = &display_channel->base;
+ uint8_t *buf = (uint8_t *)(attr->style + channel->worker->dev_info.phys_delta);
+ ASSERT(attr->style);
+ attr->style = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, buf, attr->style_nseg * sizeof(uint32_t));
+ }
+}
+
+static void fill_cursor(CursorChannel *cursor_channel, RedCursor *red_cursor, CursorItem *cursor)
+{
+ RedChannel *channel = &cursor_channel->base;
+
+ if (!cursor) {
+ red_cursor->flags = RED_CURSOR_NONE;
+ return;
+ }
+
+ if (cursor->type == CURSOR_TYPE_DEV) {
+ QXLCursorCmd *cursor_cmd;
+ QXLCursor *qxl_cursor;
+
+ cursor_cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data);
+ qxl_cursor = (QXLCursor *)(cursor_cmd->u.set.shape + channel->worker->dev_info.phys_delta);
+ red_cursor->flags = 0;
+ red_cursor->header = qxl_cursor->header;
+
+ if (red_cursor->header.unique) {
+ if (red_cursor_cache_find(cursor_channel, red_cursor->header.unique)) {
+ red_cursor->flags |= RED_CURSOR_FROM_CACHE;
+ return;
+ }
+ if (red_cursor_cache_add(cursor_channel, red_cursor->header.unique, 1)) {
+ red_cursor->flags |= RED_CURSOR_CACHE_ME;
+ }
+ }
+
+ if (qxl_cursor->data_size) {
+ add_buf(channel, BUF_TYPE_CHUNK, &qxl_cursor->chunk, qxl_cursor->data_size);
+ }
+ } else {
+ LocalCursor *local_cursor;
+ ASSERT(cursor->type == CURSOR_TYPE_LOCAL);
+ local_cursor = (LocalCursor *)cursor;
+ *red_cursor = local_cursor->red_cursor;
+ add_buf(channel, BUF_TYPE_RAW, local_cursor->red_cursor.data, local_cursor->data_size);
+ }
+}
+
+static inline void red_channel_reset_send_data(RedChannel *channel)
+{
+ channel->send_data.pos = 0;
+ channel->send_data.n_bufs = 1;
+ channel->send_data.header.size = 0;
+ channel->send_data.header.sub_list = 0;
+ ++channel->send_data.header.serial;
+ channel->send_data.bufs[0].type = BUF_TYPE_RAW;
+ channel->send_data.bufs[0].size = sizeof(RedDataHeader);
+ channel->send_data.bufs[0].data = (void *)&channel->send_data.header;
+}
+
+static inline void red_display_reset_send_data(DisplayChannel *channel)
+{
+ red_channel_reset_send_data((RedChannel *)channel);
+ channel->send_data.bitmap_pos = 0;
+ red_display_reset_compress_buf(channel);
+ channel->send_data.free_list.res->count = 0;
+ memset(channel->send_data.free_list.sync, 0, sizeof(channel->send_data.free_list.sync));
+}
+
+static inline void red_send_qxl_drawable(RedWorker *worker, DisplayChannel *display_channel,
+ Drawable *item)
+{
+ QXLDrawable *drawable = item->qxl_drawable;
+
+ RedChannel *channel = &display_channel->base;
+ switch (drawable->type) {
+ case QXL_DRAW_FILL:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_FILL;
+ fill_base(display_channel, &display_channel->send_data.u.fill.base, drawable,
+ sizeof(RedFill));
+ display_channel->send_data.u.fill.data = drawable->u.fill;
+ fill_brush(display_channel, &display_channel->send_data.u.fill.data.brush, item);
+ fill_mask(display_channel, &display_channel->send_data.u.fill.data.mask, item);
+ break;
+ case QXL_DRAW_OPAQUE:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_OPAQUE;
+ fill_base(display_channel, &display_channel->send_data.u.opaque.base, drawable,
+ sizeof(RedOpaque));
+ display_channel->send_data.u.opaque.data = drawable->u.opaque;
+ fill_bits(display_channel, &display_channel->send_data.u.opaque.data.src_bitmap, item);
+ fill_brush(display_channel, &display_channel->send_data.u.opaque.data.brush, item);
+ fill_mask(display_channel, &display_channel->send_data.u.opaque.data.mask, item);
+ break;
+ case QXL_DRAW_COPY:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_COPY;
+ fill_base(display_channel, &display_channel->send_data.u.copy.base, drawable,
+ sizeof(RedCopy));
+ display_channel->send_data.u.copy.data = drawable->u.copy;
+ fill_bits(display_channel, &display_channel->send_data.u.copy.data.src_bitmap, item);
+ fill_mask(display_channel, &display_channel->send_data.u.copy.data.mask, item);
+ break;
+ case QXL_DRAW_TRANSPARENT:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_TRANSPARENT;
+ fill_base(display_channel, &display_channel->send_data.u.transparent.base, drawable,
+ sizeof(RedTransparent));
+ display_channel->send_data.u.transparent.data = drawable->u.transparent;
+ fill_bits(display_channel, &display_channel->send_data.u.transparent.data.src_bitmap, item);
+ break;
+ case QXL_DRAW_ALPHA_BLEND:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_ALPHA_BLEND;
+ fill_base(display_channel, &display_channel->send_data.u.alpha_blend.base, drawable,
+ sizeof(RedAlphaBlend));
+ display_channel->send_data.u.alpha_blend.data = drawable->u.alpha_blend;
+ fill_bits(display_channel, &display_channel->send_data.u.alpha_blend.data.src_bitmap, item);
+ break;
+ case QXL_COPY_BITS:
+ channel->send_data.header.type = RED_DISPLAY_COPY_BITS;
+ fill_base(display_channel, &display_channel->send_data.u.copy_bits.base, drawable,
+ sizeof(RedCopyBits));
+ display_channel->send_data.u.copy_bits.src_pos = drawable->u.copy_bits.src_pos;
+ break;
+ case QXL_DRAW_BLEND:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_BLEND;
+ fill_base(display_channel, &display_channel->send_data.u.blend.base, drawable,
+ sizeof(RedBlend));
+ display_channel->send_data.u.blend.data = drawable->u.blend;
+ fill_bits(display_channel, &display_channel->send_data.u.blend.data.src_bitmap, item);
+ fill_mask(display_channel, &display_channel->send_data.u.blend.data.mask, item);
+ break;
+ case QXL_DRAW_BLACKNESS:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_BLACKNESS;
+ fill_base(display_channel, &display_channel->send_data.u.blackness.base, drawable,
+ sizeof(RedBlackness));
+ display_channel->send_data.u.blackness.data = drawable->u.blackness;
+ fill_mask(display_channel, &display_channel->send_data.u.blackness.data.mask, item);
+ break;
+ case QXL_DRAW_WHITENESS:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_WHITENESS;
+ fill_base(display_channel, &display_channel->send_data.u.whiteness.base, drawable,
+ sizeof(RedWhiteness));
+ display_channel->send_data.u.whiteness.data = drawable->u.whiteness;
+ fill_mask(display_channel, &display_channel->send_data.u.whiteness.data.mask, item);
+ break;
+ case QXL_DRAW_INVERS:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_INVERS;
+ fill_base(display_channel, &display_channel->send_data.u.invers.base, drawable,
+ sizeof(RedInvers));
+ display_channel->send_data.u.invers.data = drawable->u.invers;
+ fill_mask(display_channel, &display_channel->send_data.u.invers.data.mask, item);
+ break;
+ case QXL_DRAW_ROP3:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_ROP3;
+ fill_base(display_channel, &display_channel->send_data.u.rop3.base, drawable,
+ sizeof(RedRop3));
+ display_channel->send_data.u.rop3.data = drawable->u.rop3;
+ fill_bits(display_channel, &display_channel->send_data.u.rop3.data.src_bitmap, item);
+ fill_brush(display_channel, &display_channel->send_data.u.rop3.data.brush, item);
+ fill_mask(display_channel, &display_channel->send_data.u.rop3.data.mask, item);
+ break;
+ case QXL_DRAW_STROKE:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_STROKE;
+ fill_base(display_channel, &display_channel->send_data.u.stroke.base, drawable,
+ sizeof(RedStroke));
+ display_channel->send_data.u.stroke.data = drawable->u.stroke;
+ fill_path(display_channel, &display_channel->send_data.u.stroke.data.path);
+ fill_attr(display_channel, &display_channel->send_data.u.stroke.data.attr);
+ fill_brush(display_channel, &display_channel->send_data.u.stroke.data.brush, item);
+ break;
+ case QXL_DRAW_TEXT:
+ channel->send_data.header.type = RED_DISPLAY_DRAW_TEXT;
+ fill_base(display_channel, &display_channel->send_data.u.text.base, drawable,
+ sizeof(RedText));
+ display_channel->send_data.u.text.data = drawable->u.text;
+ fill_brush(display_channel, &display_channel->send_data.u.text.data.fore_brush, item);
+ fill_brush(display_channel, &display_channel->send_data.u.text.data.back_brush, item);
+ fill_str(display_channel, &display_channel->send_data.u.text.data.str);
+ break;
+ default:
+ red_error("invalid type");
+ }
+ display_begin_send_massage(display_channel, &item->pipe_item);
+}
+
+#define MAX_SEND_VEC 100
+
+static inline BufDescriptor *find_buf(RedChannel *channel, int buf_pos, int *buf_offset)
+{
+ BufDescriptor *buf;
+ int pos = 0;
+
+ for (buf = channel->send_data.bufs; buf_pos >= pos + buf->size; buf++) {
+ pos += buf->size;
+ ASSERT(buf != &channel->send_data.bufs[channel->send_data.n_bufs - 1]);
+ }
+ *buf_offset = buf_pos - pos;
+ return buf;
+}
+
+static inline uint32_t __fill_iovec(BufDescriptor *buf, int skip, struct iovec *vec, int *vec_index,
+ long phys_delta)
+{
+ uint32_t size = 0;
+
+ switch (buf->type) {
+ case BUF_TYPE_RAW:
+ vec[*vec_index].iov_base = buf->data + skip;
+ vec[*vec_index].iov_len = size = buf->size - skip;
+ (*vec_index)++;
+ break;
+ case BUF_TYPE_COMPRESS_BUF: {
+ RedCompressBuf *comp_buf = (RedCompressBuf *)buf->data;
+ int max = buf->size - skip;
+ int now;
+ do {
+ ASSERT(comp_buf);
+ if (skip >= sizeof(comp_buf->buf)) {
+ skip -= sizeof(comp_buf->buf);
+ comp_buf = comp_buf->send_next;
+ continue;
+ }
+ now = MIN(sizeof(comp_buf->buf) - skip, max);
+ max -= now;
+ size += now;
+ vec[*vec_index].iov_base = (uint8_t*)comp_buf->buf + skip;
+ vec[*vec_index].iov_len = now;
+ skip = 0;
+ comp_buf = comp_buf->send_next;
+ (*vec_index)++;
+ } while (max && *vec_index < MAX_SEND_VEC);
+ break;
+ }
+ case BUF_TYPE_CHUNK: {
+ QXLDataChunk *chunk = (QXLDataChunk *)buf->data;
+ do {
+ int data_size = chunk->data_size;
+ int skip_now = MIN(data_size, skip);
+ skip -= skip_now;
+ data_size -= skip_now;
+
+ if (data_size) {
+ size += data_size;
+ vec[*vec_index].iov_base = chunk->data + skip_now;
+ vec[*vec_index].iov_len = data_size;
+ (*vec_index)++;
+ }
+ chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk + phys_delta) : NULL;
+ } while (chunk && *vec_index < MAX_SEND_VEC);
+ break;
+ }
+ default:
+ red_error("invalid type");
+ }
+ return size;
+}
+
+static inline void fill_iovec(RedChannel *channel, struct iovec *vec, int *vec_size)
+{
+ int vec_index = 0;
+ uint32_t pos = channel->send_data.pos;
+
+ ASSERT(channel->send_data.size != pos && channel->send_data.size > pos);
+
+ do {
+ BufDescriptor *buf;
+ int buf_offset;
+
+ buf = find_buf(channel, pos, &buf_offset);
+ ASSERT(buf);
+ pos += __fill_iovec(buf, buf_offset, vec, &vec_index, channel->worker->dev_info.phys_delta);
+ } while (vec_index < MAX_SEND_VEC && pos != channel->send_data.size);
+ *vec_size = vec_index;
+}
+
+static void inline channel_release_res(RedChannel *channel)
+{
+ if (!channel->send_data.item) {
+ return;
+ }
+ channel->release_item(channel, channel->send_data.item);
+ channel->send_data.item = NULL;
+}
+
+static void red_send_data(RedChannel *channel, void *item)
+{
+ for (;;) {
+ uint32_t n = channel->send_data.size - channel->send_data.pos;
+ struct iovec vec[MAX_SEND_VEC];
+ int vec_size;
+
+ if (!n) {
+ channel->send_data.blocked = FALSE;
+ if (channel->send_data.item) {
+ channel->release_item(channel, channel->send_data.item);
+ channel->send_data.item = NULL;
+ }
+ break;
+ }
+ fill_iovec(channel, vec, &vec_size);
+ ASSERT(channel->peer);
+ if ((n = channel->peer->cb_writev(channel->peer->ctx, vec, vec_size)) == -1) {
+ switch (errno) {
+ case EAGAIN:
+ channel->send_data.blocked = TRUE;
+ if (item) {
+ channel->hold_item(item);
+ channel->send_data.item = item;
+ }
+ return;
+ case EINTR:
+ break;
+ case EPIPE:
+ channel->disconnect(channel);
+ return;
+ default:
+ red_printf("%s", strerror(errno));
+ channel->disconnect(channel);
+ return;
+ }
+ } else {
+ channel->send_data.pos += n;
+ stat_inc_counter(channel->out_bytes_counter, n);
+ }
+ }
+}
+
+static void display_channel_push_release(DisplayChannel *channel, uint8_t type, uint64_t id,
+ uint64_t* sync_data)
+{
+ FreeList *free_list = &channel->send_data.free_list;
+ int i;
+
+ for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
+ free_list->sync[i] = MAX(free_list->sync[i], sync_data[i]);
+ }
+
+ if (free_list->res->count == free_list->res_size) {
+ RedResorceList *new_list;
+ new_list = malloc(sizeof(*new_list) + free_list->res_size * sizeof(RedResorceID) * 2);
+ if (!new_list) {
+ PANIC("malloc failed");
+ }
+ new_list->count = free_list->res->count;
+ memcpy(new_list->resorces, free_list->res->resorces,
+ new_list->count * sizeof(RedResorceID));
+ free(free_list->res);
+ free_list->res = new_list;
+ free_list->res_size *= 2;
+ }
+ free_list->res->resorces[free_list->res->count].type = type;
+ free_list->res->resorces[free_list->res->count++].id = id;
+}
+
+static inline void red_begin_send_massage(RedChannel *channel, void *item)
+{
+ channel->send_data.size = channel->send_data.header.size + sizeof(RedDataHeader);
+ channel->messages_window++;
+ red_send_data(channel, item);
+}
+
+static inline void display_begin_send_massage(DisplayChannel *channel, void *item)
+{
+ FreeList *free_list = &channel->send_data.free_list;
+
+ if (free_list->res->count) {
+ int sync_count = 0;
+ int sub_index;
+ int i;
+
+ channel->base.send_data.header.sub_list = channel->base.send_data.header.size;
+ for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
+ if (i != channel->base.id && free_list->sync[i] != 0) {
+ free_list->wait.header.wait_list[sync_count].channel_type = RED_CHANNEL_DISPLAY;
+ free_list->wait.header.wait_list[sync_count].channel_id = i;
+ free_list->wait.header.wait_list[sync_count++].message_serial = free_list->sync[i];
+ }
+ }
+ RedSubMessageList *sub_list = &channel->send_data.sub_list.sub_list;
+ RedSubMessage *sub_header = channel->send_data.sub_header;
+ if (sync_count) {
+ sub_list->size = 2;
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_list,
+ sizeof(*sub_list) + 2 * sizeof(sub_list->sub_messages[0]));
+ sub_list->sub_messages[0] = channel->base.send_data.header.size;
+ sub_header[0].type = RED_WAIT_FOR_CHANNELS;
+ sub_header[0].size = sizeof(free_list->wait.header) +
+ sync_count * sizeof(free_list->wait.buf[0]);
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_header, sizeof(*sub_header));
+ free_list->wait.header.wait_count = sync_count;
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, &free_list->wait.header,
+ sub_header[0].size);
+ sub_list->sub_messages[1] = channel->base.send_data.header.size;
+ sub_index = 1;
+ } else {
+ sub_list->size = 1;
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, sub_list,
+ sizeof(*sub_list) + sizeof(sub_list->sub_messages[0]));
+ sub_list->sub_messages[0] = channel->base.send_data.header.size;
+ sub_index = 0;
+ }
+ sub_header[sub_index].type = RED_DISPLAY_INVAL_LIST;
+ sub_header[sub_index].size = sizeof(*free_list->res) + free_list->res->count *
+ sizeof(free_list->res->resorces[0]);
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, &sub_header[sub_index], sizeof(*sub_header));
+ add_buf((RedChannel*)channel, BUF_TYPE_RAW, free_list->res, sub_header[sub_index].size);
+ }
+ red_begin_send_massage((RedChannel *)channel, item);
+}
+
+static inline RedChannel *red_ref_channel(RedChannel *channel)
+{
+ if (!channel) {
+ return NULL;
+ }
+ ++channel->listener.refs;
+ return channel;
+}
+
+static inline void red_unref_channel(RedChannel *channel)
+{
+ ASSERT(channel);
+ if (!--channel->listener.refs) {
+ free(channel);
+ }
+}
+
+static inline uint8_t *red_get_image_line(QXLDataChunk **chunk, int *offset, int stride,
+ long phys_delta)
+{
+ uint8_t *ret;
+ if ((*chunk)->data_size == *offset) {
+ if ((*chunk)->next_chunk == 0) {
+ return NULL;
+ }
+ *offset = 0;
+ *chunk = (QXLDataChunk *)((*chunk)->next_chunk + phys_delta);
+ }
+
+ if ((*chunk)->data_size - *offset < stride) {
+ red_printf("bad chunk aligment");
+ return NULL;
+ }
+ ret = (*chunk)->data + *offset;
+ *offset += stride;
+ return ret;
+}
+
+static void red_display_share_stream_buf(DisplayChannel *display_channel)
+{
+}
+
+static void red_display_unshare_stream_buf(DisplayChannel *display_channel)
+{
+}
+
+#define YUV32
+#include "red_yuv.h"
+#undef YUV32
+
+#define YUV24
+#include "red_yuv.h"
+#undef YUV24
+
+#define YUV16
+#include "red_yuv.h"
+#undef YUV16
+
+static inline int red_send_stream_data(DisplayChannel *display_channel, Drawable *drawable)
+{
+ Stream *stream = drawable->stream;
+ QXLImage *qxl_image;
+ RedChannel *channel;
+ RedWorker* worker;
+ int n;
+
+ ASSERT(stream);
+ ASSERT(drawable->qxl_drawable->type == QXL_DRAW_COPY);
+
+ channel = &display_channel->base;
+ worker = channel->worker;
+ qxl_image = (QXLImage *)(drawable->qxl_drawable->u.copy.src_bitmap +
+ worker->dev_info.phys_delta);
+
+ if (qxl_image->descriptor.type != IMAGE_TYPE_BITMAP ||
+ (qxl_image->bitmap.flags & QXL_BITMAP_DIRECT)) {
+ return FALSE;
+ }
+
+ StreamAgent *agent = &display_channel->stream_agents[stream - worker->streams_buf];
+ uint64_t time_now = red_now();
+ if (time_now - agent->lats_send_time < (1000 * 1000 * 1000) / agent->fps) {
+ agent->frames--;
+ return TRUE;
+ }
+
+ switch (qxl_image->bitmap.format) {
+ case BITMAP_FMT_32BIT:
+ if (!red_rgb_to_yuv420_32bpp(&drawable->qxl_drawable->u.copy.src_area,
+ &qxl_image->bitmap, stream->av_frame,
+ worker->dev_info.phys_delta,
+ stream - worker->streams_buf, stream)) {
+ return FALSE;
+ }
+ break;
+ case BITMAP_FMT_16BIT:
+ if (!red_rgb_to_yuv420_16bpp(&drawable->qxl_drawable->u.copy.src_area,
+ &qxl_image->bitmap, stream->av_frame,
+ worker->dev_info.phys_delta,
+ stream - worker->streams_buf, stream)) {
+ return FALSE;
+ }
+ break;
+ case BITMAP_FMT_24BIT:
+ if (!red_rgb_to_yuv420_24bpp(&drawable->qxl_drawable->u.copy.src_area,
+ &qxl_image->bitmap, stream->av_frame,
+ worker->dev_info.phys_delta,
+ stream - worker->streams_buf, stream)) {
+ return FALSE;
+ }
+ break;
+ default:
+ red_printf_some(1000, "unsupported format %d", qxl_image->bitmap.format);
+ return FALSE;
+ }
+#if 1
+ uint32_t min_buf_size = stream->av_ctx->width * stream->av_ctx->height * sizeof(uint32_t) / 2;
+ min_buf_size += FF_MIN_BUFFER_SIZE;
+ if (display_channel->send_data.stream_outbuf_size < min_buf_size) {
+ uint8_t *new_buf;
+
+ if (!(new_buf = malloc(min_buf_size + FF_INPUT_BUFFER_PADDING_SIZE))) {
+ return FALSE;
+ }
+
+ red_display_unshare_stream_buf(display_channel);
+ free(display_channel->send_data.stream_outbuf);
+ display_channel->send_data.stream_outbuf = new_buf;
+ display_channel->send_data.stream_outbuf_size = min_buf_size;
+ red_display_share_stream_buf(display_channel);
+ }
+ n = avcodec_encode_video(stream->av_ctx, display_channel->send_data.stream_outbuf,
+ display_channel->send_data.stream_outbuf_size,
+ stream->av_frame);
+
+ if (n <= 0) {
+ red_printf("avcodec_encode_video failed");
+ return FALSE;
+ }
+
+ if (n > display_channel->send_data.stream_outbuf_size) {
+ PANIC("buf error");
+ }
+#else
+ for (;;) {
+ n = avcodec_encode_video(stream->av_ctx, display_channel->send_data.stream_outbuf,
+ display_channel->send_data.stream_outbuf_size,
+ stream->av_frame);
+ if (n < 0) {
+ uint8_t *new_buf;
+ size_t max_size;
+ size_t new_size;
+
+ max_size = stream->av_ctx->width * stream->av_ctx->height * sizeof(uint32_t);
+ max_size += MAX(1024, max_size / 10);
+
+ if (display_channel->send_data.stream_outbuf_size == max_size) {
+ return FALSE;
+ }
+
+ new_size = display_channel->send_data.stream_outbuf_size * 2;
+ new_size = MIN(new_size, max_size);
+
+ if (!(new_buf = malloc(new_size + FF_INPUT_BUFFER_PADDING_SIZE))) {
+ return FALSE;
+ }
+ red_printf("new streaming video buf size %u", new_size);
+ red_display_unshare_stream_buf(display_channel);
+ free(display_channel->send_data.stream_outbuf);
+ display_channel->send_data.stream_outbuf = new_buf;
+ display_channel->send_data.stream_outbuf_size = new_size;
+ red_display_share_stream_buf(display_channel);
+ continue;
+ }
+ ASSERT(n <= display_channel->send_data.stream_outbuf_size);
+ break;
+ }
+ #endif
+ channel->send_data.header.type = RED_DISPLAY_STREAM_DATA;
+ RedStreamData* stream_data = &display_channel->send_data.u.stream_data;
+ add_buf(channel, BUF_TYPE_RAW, stream_data, sizeof(RedStreamData));
+ add_buf(channel, BUF_TYPE_RAW, display_channel->send_data.stream_outbuf,
+ n + FF_INPUT_BUFFER_PADDING_SIZE);
+
+ stream_data->id = stream - worker->streams_buf;
+ stream_data->multi_media_time = drawable->qxl_drawable->mm_time;
+ stream_data->data_size = n;
+ stream_data->ped_size = FF_INPUT_BUFFER_PADDING_SIZE;
+ display_begin_send_massage(display_channel, NULL);
+ agent->lats_send_time = time_now;
+ return TRUE;
+}
+
+static inline void send_qxl_drawable(DisplayChannel *display_channel, Drawable *item)
+{
+ RedChannel *channel;
+
+ ASSERT(display_channel);
+ channel = &display_channel->base;
+
+ if (item->stream && red_send_stream_data(display_channel, item)) {
+ return;
+ }
+ red_send_qxl_drawable(display_channel->base.worker, display_channel, item);
+}
+
+static void red_send_mode(DisplayChannel *display_channel)
+{
+ RedChannel *channel;
+ RedWorker *worker;
+
+ ASSERT(display_channel);
+ worker = display_channel->base.worker;
+ channel = &display_channel->base;
+ channel->send_data.header.type = RED_DISPLAY_MODE;
+ display_channel->send_data.u.mode.x_res = worker->dev_info.x_res;
+ display_channel->send_data.u.mode.y_res = worker->dev_info.y_res;
+ display_channel->send_data.u.mode.bits = worker->dev_info.bits;
+
+ add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.mode, sizeof(RedMode));
+
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void red_send_set_ack(RedChannel *channel)
+{
+ ASSERT(channel);
+ channel->send_data.header.type = RED_SET_ACK;
+ channel->send_data.u.ack.generation = ++channel->ack_generation;
+ channel->send_data.u.ack.window = channel->client_ack_window;
+ channel->messages_window = 0;
+
+ add_buf(channel, BUF_TYPE_RAW, &channel->send_data.u.ack, sizeof(RedSetAck));
+ red_begin_send_massage(channel, NULL);
+}
+
+static inline void red_send_verb(RedChannel *channel, uint16_t verb)
+{
+ ASSERT(channel);
+ channel->send_data.header.type = verb;
+ red_begin_send_massage(channel, NULL);
+}
+
+static inline void display_send_verb(DisplayChannel *channel, uint16_t verb)
+{
+ ASSERT(channel);
+ channel->base.send_data.header.type = verb;
+ display_begin_send_massage(channel, NULL);
+}
+
+static inline void __red_send_inval(RedChannel *channel, CacheItem *cach_item,
+ RedInvalOne *inval_one)
+{
+ channel->send_data.header.type = cach_item->inval_type;
+ inval_one->id = *(uint64_t *)&cach_item->id;
+ add_buf(channel, BUF_TYPE_RAW, inval_one, sizeof(*inval_one));
+}
+
+static void red_send_inval(RedChannel *channel, CacheItem *cach_item, RedInvalOne *inval_one)
+{
+ __red_send_inval(channel, cach_item, inval_one);
+ red_begin_send_massage(channel, NULL);
+}
+
+static void red_display_send_inval(DisplayChannel *display_channel, CacheItem *cach_item)
+{
+ __red_send_inval((RedChannel *)display_channel, cach_item,
+ &display_channel->send_data.u.inval_one);
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void display_channel_send_migrate(DisplayChannel *display_channel)
+{
+ display_channel->base.send_data.header.type = RED_MIGRATE;
+ display_channel->send_data.u.migrate.flags = RED_MIGRATE_NEED_FLUSH |
+ RED_MIGRATE_NEED_DATA_TRANSFER;
+ add_buf((RedChannel*)display_channel, BUF_TYPE_RAW, &display_channel->send_data.u.migrate,
+ sizeof(display_channel->send_data.u.migrate));
+ display_channel->expect_migrate_mark = TRUE;
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void display_channel_send_migrate_data(DisplayChannel *display_channel)
+{
+ DisplayChannelMigrateData* display_data;
+
+ display_channel->base.send_data.header.type = RED_MIGRATE_DATA;
+ ASSERT(display_channel->pixmap_cache);
+ display_data = &display_channel->send_data.u.migrate_data;
+ display_data->magic = DISPLAY_MIGRATE_DATA_MAGIC;
+ ASSERT(MAX_CACHE_CLIENTS == 4); //MIGRATE_DATA_VERSION dependent
+ display_data->version = DISPLAY_MIGRATE_DATA_VERSION;
+
+ display_data->message_serial = channel_message_serial((RedChannel *)display_channel);
+
+ display_data->pixmap_cache_freezer = pixmap_cache_freeze(display_channel->pixmap_cache);
+ display_data->pixmap_cache_id = display_channel->pixmap_cache->id;
+ display_data->pixmap_cache_size = display_channel->pixmap_cache->size;
+ memcpy(display_data->pixmap_cache_clients, display_channel->pixmap_cache->sync,
+ sizeof(display_data->pixmap_cache_clients));
+
+ ASSERT(display_channel->glz_dict);
+ red_freeze_glz(display_channel);
+ display_data->glz_dict_id = display_channel->glz_dict->id;
+ glz_enc_dictionary_get_restore_data(display_channel->glz_dict->dict,
+ &display_data->glz_dict_restore_data,
+ &display_channel->glz_data.usr);
+
+ add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, &display_channel->send_data.u.migrate_data,
+ sizeof(display_channel->send_data.u.migrate_data));
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void display_channel_pixmap_sync(DisplayChannel *display_channel)
+{
+ RedWaitForChannels *wait;
+ PixmapCache *pixmap_cache;
+
+
+ display_channel->base.send_data.header.type = RED_WAIT_FOR_CHANNELS;
+ wait = &display_channel->send_data.u.wait.header;
+ pixmap_cache = display_channel->pixmap_cache;
+
+
+ pthread_mutex_lock(&pixmap_cache->lock);
+
+ wait->wait_count = 1;
+ wait->wait_list[0].channel_type = RED_CHANNEL_DISPLAY;
+ wait->wait_list[0].channel_id = pixmap_cache->generation_initiator.client;
+ wait->wait_list[0].message_serial = pixmap_cache->generation_initiator.message;
+ display_channel->pixmap_cache_generation = pixmap_cache->generation;
+ display_channel->pending_pixmaps_sync = FALSE;
+
+ pthread_mutex_unlock(&pixmap_cache->lock);
+
+ add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, wait,
+ sizeof(*wait) + sizeof(wait->wait_list[0]));
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void display_channel_reset_cache(DisplayChannel *display_channel)
+{
+ RedWaitForChannels *wait = &display_channel->send_data.u.wait.header;
+
+ display_channel->base.send_data.header.type = RED_DISPLAY_INVAL_ALL_PIXMAPS;
+ pixmap_cache_reset(display_channel->pixmap_cache, display_channel, wait);
+
+ add_buf((RedChannel *)display_channel, BUF_TYPE_RAW, wait,
+ sizeof(*wait) + wait->wait_count * sizeof(wait->wait_list[0]));
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void red_send_image(DisplayChannel *display_channel, ImageItem *item)
+{
+ RedChannel *channel;
+ RedImage *red_image;
+ RedWorker *worker;
+ Bitmap bitmap;
+
+ ASSERT(display_channel && item);
+ channel = &display_channel->base;
+ worker = channel->worker;
+
+ red_image = alloc_image(display_channel);
+ ASSERT(red_image);
+
+ QXL_SET_IMAGE_ID(red_image, QXL_IMAGE_GROUP_RED, ++worker->bits_unique);
+ red_image->descriptor.type = IMAGE_TYPE_BITMAP;
+ red_image->descriptor.flags = 0;
+ red_image->descriptor.width = item->width;
+ red_image->descriptor.height = item->height;
+
+ bitmap.format = BITMAP_FMT_32BIT;
+ bitmap.flags = QXL_BITMAP_DIRECT;
+ bitmap.flags |= item->top_down ? QXL_BITMAP_TOP_DOWN : 0;
+ bitmap.x = item->width;
+ bitmap.y = item->height;
+ bitmap.stride = item->stride;
+ bitmap.palette = 0;
+ bitmap.data = (ADDRESS)(item->data) - worker->dev_info.phys_delta;
+
+ channel->send_data.header.type = RED_DISPLAY_DRAW_COPY;
+
+ add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.copy, sizeof(RedCopy));
+ display_channel->send_data.u.copy.base.box.left = item->pos.x;
+ display_channel->send_data.u.copy.base.box.top = item->pos.y;
+ display_channel->send_data.u.copy.base.box.right = item->pos.x + bitmap.x;
+ display_channel->send_data.u.copy.base.box.bottom = item->pos.y + bitmap.y;
+ display_channel->send_data.u.copy.base.clip.type = CLIP_TYPE_NONE;
+ display_channel->send_data.u.copy.base.clip.data = 0;
+ display_channel->send_data.u.copy.data.rop_decriptor = ROPD_OP_PUT;
+ display_channel->send_data.u.copy.data.src_area.left = 0;
+ display_channel->send_data.u.copy.data.src_area.top = 0;
+ display_channel->send_data.u.copy.data.src_area.right = bitmap.x;
+ display_channel->send_data.u.copy.data.src_area.bottom = bitmap.y;
+ display_channel->send_data.u.copy.data.scale_mode = 0;
+ display_channel->send_data.u.copy.data.src_bitmap = channel->send_data.header.size;
+ display_channel->send_data.u.copy.data.mask.bitmap = 0;
+
+ compress_send_data_t comp_send_data;
+
+ if (red_quic_compress_image(display_channel, red_image, &bitmap, &comp_send_data)) {
+ add_buf(channel, BUF_TYPE_RAW, red_image, comp_send_data.raw_size);
+ add_buf(channel, BUF_TYPE_COMPRESS_BUF, comp_send_data.comp_buf,
+ comp_send_data.comp_buf_size);
+ } else {
+ red_image->descriptor.type = IMAGE_TYPE_BITMAP;
+ red_image->bitmap = bitmap;
+ red_image->bitmap.flags &= ~QXL_BITMAP_DIRECT;
+ add_buf(channel, BUF_TYPE_RAW, red_image, sizeof(BitmapImage));
+ red_image->bitmap.data = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, item->data, bitmap.y * bitmap.stride);
+ }
+ display_begin_send_massage(display_channel, &item->link);
+}
+
+static void red_display_send_upgrade(DisplayChannel *display_channel, UpgradeItem *item)
+{
+ RedChannel *channel;
+ QXLDrawable *qxl_drawable;
+ RedCopy *copy = &display_channel->send_data.u.copy;
+
+ ASSERT(display_channel && item && item->drawable);
+ channel = &display_channel->base;
+
+ channel->send_data.header.type = RED_DISPLAY_DRAW_COPY;
+
+ qxl_drawable = item->drawable->qxl_drawable;
+ ASSERT(qxl_drawable->type == QXL_DRAW_COPY);
+ ASSERT(qxl_drawable->u.copy.rop_decriptor == ROPD_OP_PUT);
+ ASSERT(qxl_drawable->u.copy.mask.bitmap == 0);
+
+ add_buf(channel, BUF_TYPE_RAW, copy, sizeof(RedCopy));
+ copy->base.box = qxl_drawable->bbox;
+ copy->base.clip.type = CLIP_TYPE_RECTS;
+ copy->base.clip.data = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, &item->region.num_rects, sizeof(uint32_t));
+ add_buf(channel, BUF_TYPE_RAW, item->region.rects, sizeof(Rect) * item->region.num_rects);
+ copy->data = qxl_drawable->u.copy;
+ fill_bits(display_channel, &copy->data.src_bitmap, item->drawable);
+
+ display_begin_send_massage(display_channel, &item->base);
+}
+
+static void red_display_send_stream_start(DisplayChannel *display_channel, StreamAgent *agent)
+{
+ RedChannel *channel = &display_channel->base;
+ Stream *stream = agent->stream;
+
+ agent->lats_send_time = 0;
+ ASSERT(stream);
+ channel->send_data.header.type = RED_DISPLAY_STREAM_CREATE;
+ RedStreamCreate *stream_create = &display_channel->send_data.u.stream_create.message;
+ stream_create->id = agent - display_channel->stream_agents;
+ stream_create->flags = stream->top_down ? STREAM_TOP_DOWN : 0;
+ stream_create->codec_type = RED_VIDEO_CODEC_TYPE_MJPEG;
+
+ stream_create->src_width = stream->width;
+ stream_create->src_height = stream->height;
+ stream_create->stream_width = ALIGN(stream_create->src_width, 2);
+ stream_create->stream_height = ALIGN(stream_create->src_height, 2);
+ stream_create->dest = stream->dest_area;
+
+ add_buf(channel, BUF_TYPE_RAW, stream_create, sizeof(*stream_create));
+ if (stream->current) {
+ QXLDrawable *qxl_drawable = stream->current->qxl_drawable;
+ stream_create->clip = qxl_drawable->clip;
+ if (qxl_drawable->clip.type == CLIP_TYPE_RECTS) {
+ fill_rects_clip(channel, &stream_create->clip.data);
+ } else {
+ ASSERT(qxl_drawable->clip.type == CLIP_TYPE_NONE);
+ }
+ display_begin_send_massage(display_channel, &stream->current->pipe_item);
+ } else {
+ stream_create->clip.type = CLIP_TYPE_RECTS;
+ stream_create->clip.data = channel->send_data.header.size;
+ display_channel->send_data.u.stream_create.num_rects = 0;
+ add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.stream_create.num_rects,
+ sizeof(uint32_t));
+ display_begin_send_massage(display_channel, NULL);
+ }
+}
+
+static void red_display_send_stream_clip(DisplayChannel *display_channel,
+ StreamClipItem *item)
+{
+ RedChannel *channel = &display_channel->base;
+
+ StreamAgent *agent = item->stream_agent;
+ Stream *stream = agent->stream;
+
+ ASSERT(stream);
+
+ channel->send_data.header.type = RED_DISPLAY_STREAM_CLIP;
+ RedStreamClip *stream_clip = &display_channel->send_data.u.stream_clip;
+ add_buf(channel, BUF_TYPE_RAW, stream_clip, sizeof(*stream_clip));
+ stream_clip->id = agent - display_channel->stream_agents;
+ if ((stream_clip->clip.type = item->clip_type) == CLIP_TYPE_NONE) {
+ stream_clip->clip.data = 0;
+ } else {
+ ASSERT(stream_clip->clip.type == CLIP_TYPE_RECTS);
+ stream_clip->clip.data = channel->send_data.header.size;
+ add_buf(channel, BUF_TYPE_RAW, &item->region.num_rects, sizeof(uint32_t));
+ add_buf(channel, BUF_TYPE_RAW, item->region.rects,
+ item->region.num_rects * sizeof(Rect));
+ }
+ display_begin_send_massage(display_channel, item);
+}
+
+static void red_display_send_stream_end(DisplayChannel *display_channel, StreamAgent* agent)
+{
+ RedChannel *channel = &display_channel->base;
+ channel->send_data.header.type = RED_DISPLAY_STREAM_DESTROY;
+ display_channel->send_data.u.stream_destroy.id = agent - display_channel->stream_agents;
+ add_buf(channel, BUF_TYPE_RAW, &display_channel->send_data.u.stream_destroy,
+ sizeof(RedStreamDestroy));
+ display_begin_send_massage(display_channel, NULL);
+}
+
+static void red_cursor_send_inval(CursorChannel *channel, CacheItem *cach_item)
+{
+ ASSERT(channel);
+ red_send_inval((RedChannel *)channel, cach_item, &channel->send_data.u.inval_one);
+}
+
+static void red_send_cursor_init(CursorChannel *channel)
+{
+ RedWorker *worker;
+ ASSERT(channel);
+
+ worker = channel->base.worker;
+
+ channel->base.send_data.header.type = RED_CURSOR_INIT;
+ channel->send_data.u.cursor_init.visible = worker->cursor_visible;
+ channel->send_data.u.cursor_init.position = worker->cursor_position;
+ channel->send_data.u.cursor_init.trail_length = worker->cursor_trail_length;
+ channel->send_data.u.cursor_init.trail_frequency = worker->cursor_trail_frequency;
+ add_buf(&channel->base, BUF_TYPE_RAW, &channel->send_data.u.cursor_init, sizeof(RedCursorInit));
+
+ fill_cursor(channel, &channel->send_data.u.cursor_init.cursor, worker->cursor);
+
+ red_begin_send_massage(&channel->base, worker->cursor);
+}
+
+static void red_send_local_cursor(CursorChannel *cursor_channel, LocalCursor *cursor)
+{
+ RedChannel *channel;
+
+ ASSERT(cursor_channel);
+
+ channel = &cursor_channel->base;
+ channel->send_data.header.type = RED_CURSOR_SET;
+ cursor_channel->send_data.u.cursor_set.postition = cursor->position;
+ cursor_channel->send_data.u.cursor_set.visible = channel->worker->cursor_visible;
+ add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_set,
+ sizeof(RedCursorSet));
+ fill_cursor(cursor_channel, &cursor_channel->send_data.u.cursor_set.cursor, &cursor->base);
+
+ red_begin_send_massage(channel, cursor);
+
+ red_release_cursor(channel->worker, (CursorItem *)cursor);
+}
+
+static void cursor_channel_send_migrate(CursorChannel *cursor_channel)
+{
+ cursor_channel->base.send_data.header.type = RED_MIGRATE;
+ cursor_channel->send_data.u.migrate.flags = 0;
+ add_buf((RedChannel*)cursor_channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.migrate,
+ sizeof(cursor_channel->send_data.u.migrate));
+ red_begin_send_massage((RedChannel*)cursor_channel, NULL);
+}
+
+static void red_send_cursor(CursorChannel *cursor_channel, CursorItem *cursor)
+{
+ RedChannel *channel;
+ QXLCursorCmd *cmd;
+
+ ASSERT(cursor_channel);
+
+ channel = &cursor_channel->base;
+
+ cmd = CONTAINEROF(cursor, QXLCursorCmd, device_data);
+ switch (cmd->type) {
+ case QXL_CURSOR_MOVE:
+ channel->send_data.header.type = RED_CURSOR_MOVE;
+ cursor_channel->send_data.u.cursor_move.postition = cmd->u.position;
+ add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_move,
+ sizeof(RedCursorMove));
+ break;
+ case QXL_CURSOR_SET:
+ channel->send_data.header.type = RED_CURSOR_SET;
+ cursor_channel->send_data.u.cursor_set.postition = cmd->u.set.position;
+ cursor_channel->send_data.u.cursor_set.visible = channel->worker->cursor_visible;
+ add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_set,
+ sizeof(RedCursorSet));
+ fill_cursor(cursor_channel, &cursor_channel->send_data.u.cursor_set.cursor, cursor);
+ break;
+ case QXL_CURSOR_HIDE:
+ channel->send_data.header.type = RED_CURSOR_HIDE;
+ break;
+ case QXL_CURSOR_TRAIL:
+ channel->send_data.header.type = RED_CURSOR_TRAIL;
+ cursor_channel->send_data.u.cursor_trail.length = cmd->u.trail.length;
+ cursor_channel->send_data.u.cursor_trail.frequency = cmd->u.trail.frequency;
+ add_buf(channel, BUF_TYPE_RAW, &cursor_channel->send_data.u.cursor_trail,
+ sizeof(RedCursorTrail));
+ break;
+ default:
+ red_error("bad cursor command %d", cmd->type);
+ }
+
+ red_begin_send_massage(channel, cursor);
+
+ red_release_cursor(channel->worker, cursor);
+}
+
+static inline PipeItem *red_pipe_get(RedChannel *channel)
+{
+ PipeItem *item;
+ if (!channel || channel->send_data.blocked ||
+ !(item = (PipeItem *)ring_get_tail(&channel->pipe))) {
+ return NULL;
+ }
+
+ if (channel->messages_window > channel->client_ack_window * 2) {
+ channel->send_data.blocked = TRUE;
+ return NULL;
+ }
+
+ --channel->pipe_size;
+ ring_remove(&item->link);
+ return item;
+}
+
+static void display_channel_push(RedWorker *worker)
+{
+ PipeItem *pipe_item;
+
+ while ((pipe_item = red_pipe_get((RedChannel *)worker->display_channel))) {
+ DisplayChannel *display_channel;
+ display_channel = (DisplayChannel *)red_ref_channel((RedChannel *)worker->display_channel);
+ red_display_reset_send_data(display_channel);
+ switch (pipe_item->type) {
+ case PIPE_ITEM_TYPE_DRAW: {
+ Drawable *drawable = CONTAINEROF(pipe_item, Drawable, pipe_item);
+ send_qxl_drawable(display_channel, drawable);
+ release_drawable(worker, drawable);
+ break;
+ }
+ case PIPE_ITEM_TYPE_INVAL_ONE:
+ red_display_send_inval(display_channel, (CacheItem *)pipe_item);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_STREAM_CREATE: {
+ StreamAgent *agent = CONTAINEROF(pipe_item, StreamAgent, create_item);
+ red_display_send_stream_start(display_channel, agent);
+ red_display_release_stream(display_channel, agent);
+ break;
+ }
+ case PIPE_ITEM_TYPE_STREAM_CLIP: {
+ StreamClipItem* clip_item = (StreamClipItem *)pipe_item;
+ red_display_send_stream_clip(display_channel, clip_item);
+ red_display_release_stream_clip(display_channel, clip_item);
+ break;
+ }
+ case PIPE_ITEM_TYPE_STREAM_DESTROY: {
+ StreamAgent *agent = CONTAINEROF(pipe_item, StreamAgent, destroy_item);
+ red_display_send_stream_end(display_channel, agent);
+ red_display_release_stream(display_channel, agent);
+ break;
+ }
+ case PIPE_ITEM_TYPE_UPGRADE:
+ red_display_send_upgrade(display_channel, (UpgradeItem *)pipe_item);
+ release_upgrade_item(worker, (UpgradeItem *)pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_VERB:
+ display_send_verb(display_channel, ((VerbItem*)pipe_item)->verb);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_MODE:
+ red_send_mode(display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_MIGRATE:
+ red_printf("PIPE_ITEM_TYPE_MIGRATE");
+ display_channel_send_migrate(display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_MIGRATE_DATA:
+ display_channel_send_migrate_data(display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_SET_ACK:
+ red_send_set_ack((RedChannel *)display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_IMAGE:
+ red_send_image(display_channel, (ImageItem *)pipe_item);
+ release_image_item((ImageItem *)pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_PIXMAP_SYNC:
+ display_channel_pixmap_sync(display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_PIXMAP_RESET:
+ display_channel_reset_cache(display_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_INVAL_PALLET_CACHE:
+ red_reset_palette_cache(display_channel);
+ red_send_verb((RedChannel *)display_channel, RED_DISPLAY_INVAL_ALL_PALETTES);
+ free(pipe_item);
+ break;
+ default:
+ red_error("invalid pipe item type");
+ }
+ red_unref_channel((RedChannel *)display_channel);
+ }
+}
+
+static void cursor_channel_push(RedWorker *worker)
+{
+ PipeItem *pipe_item;
+
+ while ((pipe_item = red_pipe_get((RedChannel *)worker->cursor_channel))) {
+ CursorChannel *cursor_channel;
+
+ cursor_channel = (CursorChannel *)red_ref_channel((RedChannel *)worker->cursor_channel);
+ red_channel_reset_send_data((RedChannel*)cursor_channel);
+ switch (pipe_item->type) {
+ case PIPE_ITEM_TYPE_CURSOR:
+ red_send_cursor(cursor_channel, (CursorItem *)pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_LOCAL_CURSOR:
+ red_send_local_cursor(cursor_channel, (LocalCursor *)pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_INVAL_ONE:
+ red_cursor_send_inval(cursor_channel, (CacheItem *)pipe_item);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_VERB:
+ red_send_verb((RedChannel *)cursor_channel, ((VerbItem*)pipe_item)->verb);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_MIGRATE:
+ red_printf("PIPE_ITEM_TYPE_MIGRATE");
+ cursor_channel_send_migrate(cursor_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_SET_ACK:
+ red_send_set_ack((RedChannel *)cursor_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_CURSOR_INIT:
+ red_send_cursor_init(cursor_channel);
+ free(pipe_item);
+ break;
+ case PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE:
+ red_reset_cursor_cache(cursor_channel);
+ red_send_verb((RedChannel *)cursor_channel, RED_CURSOR_INVAL_ALL);
+ free(pipe_item);
+ break;
+ default:
+ red_error("invalid pipe item type");
+ }
+ red_unref_channel((RedChannel *)cursor_channel);
+ }
+}
+
+static inline void red_push(RedWorker *worker)
+{
+ cursor_channel_push(worker);
+ display_channel_push(worker);
+}
+
+typedef struct ShowTreeData {
+ RedWorker *worker;
+ int level;
+ Container *container;
+} ShowTreeData;
+
+static void __show_tree_call(TreeItem *item, void *data)
+{
+ ShowTreeData *tree_data = data;
+ const char *item_prefix = "|--";
+ int i;
+
+ while (tree_data->container != item->container) {
+ ASSERT(tree_data->container);
+ tree_data->level--;
+ tree_data->container = tree_data->container->base.container;
+ }
+
+ switch (item->type) {
+ case TREE_ITEM_TYPE_DRAWABLE: {
+ Drawable *drawable = CONTAINEROF(item, Drawable, tree_item);
+ const int max_indent = 200;
+ char indent_str[max_indent + 1];
+ int indent_str_len;
+
+ for (i = 0; i < tree_data->level; i++) {
+ printf(" ");
+ }
+ printf(item_prefix, 0);
+ show_qxl_drawable(tree_data->worker, drawable->qxl_drawable, NULL);
+ for (i = 0; i < tree_data->level; i++) {
+ printf(" ");
+ }
+ printf("| ");
+ show_draw_item(tree_data->worker, &drawable->tree_item, NULL);
+ indent_str_len = MIN(max_indent, strlen(item_prefix) + tree_data->level * 2);
+ memset(indent_str, ' ', indent_str_len);
+ indent_str[indent_str_len] = 0;
+ region_dump(&item->rgn, indent_str);
+ printf("\n");
+ break;
+ }
+ case TREE_ITEM_TYPE_CONTAINER:
+ tree_data->level++;
+ tree_data->container = (Container *)item;
+ break;
+ case TREE_ITEM_TYPE_SHADOW:
+ break;
+ }
+}
+
+void red_show_tree(RedWorker *worker)
+{
+ ShowTreeData show_tree_data;
+ show_tree_data.worker = worker;
+ show_tree_data.level = 0;
+ show_tree_data.container = NULL;
+ current_tree_for_each(worker, __show_tree_call, &show_tree_data);
+}
+
+static inline int channel_is_connected(RedChannel *channel)
+{
+ return !!channel->peer;
+}
+
+static void red_disconnect_channel(RedChannel *channel)
+{
+ channel_release_res(channel);
+ red_pipe_clear(channel);
+
+ channel->peer->cb_free(channel->peer);
+
+ channel->peer = NULL;
+ channel->send_data.blocked = FALSE;
+ channel->send_data.size = channel->send_data.pos = 0;
+ red_unref_channel(channel);
+}
+
+static void red_disconnect_display(RedChannel *channel)
+{
+ DisplayChannel *display_channel;
+
+ if (!channel || !channel->peer) {
+ return;
+ }
+
+ display_channel = (DisplayChannel *)channel;
+ ASSERT(display_channel == channel->worker->display_channel);
+#ifdef COMPRESS_STAT
+ print_compress_stats(display_channel);
+#endif
+ channel->worker->display_channel = NULL;
+ red_display_unshare_stream_buf(display_channel);
+ red_release_pixmap_cache(display_channel);
+ red_release_glz(display_channel);
+ red_reset_palette_cache(display_channel);
+ free(display_channel->send_data.stream_outbuf);
+ red_display_reset_compress_buf(display_channel);
+ while (display_channel->send_data.free_compress_bufs) {
+ RedCompressBuf *buf = display_channel->send_data.free_compress_bufs;
+ display_channel->send_data.free_compress_bufs = buf->next;
+ free(buf);
+ }
+ free(display_channel->send_data.free_list.res);
+ red_display_destroy_streams(display_channel);
+ red_disconnect_channel(channel);
+}
+
+static void red_migrate_display(RedWorker *worker)
+{
+ if (worker->display_channel) {
+ red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_STREAM_DESTROY_ALL);
+ red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_MIGRATE);
+ }
+}
+
+static void destroy_cairo_canvas(CairoCanvas *canvas)
+{
+ cairo_t *cairo;
+
+ if (!canvas) {
+ return;
+ }
+
+ cairo = canvas_get_cairo(canvas);
+ canvas_destroy(canvas);
+ cairo_destroy(cairo);
+}
+
+static void validate_area_nop(void *canvas, const DrawArea *draw_area, const Rect *area)
+{
+}
+
+static void init_cairo_draw_context(RedWorker *worker, CairoCanvas *canvas)
+{
+ worker->draw_context.canvas = canvas;
+ worker->draw_context.top_down = TRUE;
+ worker->draw_context.set_access_params = (set_access_params_t)canvas_set_access_params;
+ worker->draw_context.draw_fill = (draw_fill_t)canvas_draw_fill;
+ worker->draw_context.draw_copy = (draw_copy_t)canvas_draw_copy;
+ worker->draw_context.draw_opaque = (draw_opaque_t)canvas_draw_opaque;
+ worker->draw_context.copy_bits = (copy_bits_t)canvas_copy_bits;
+ worker->draw_context.draw_text = (draw_text_t)canvas_draw_text;
+ worker->draw_context.draw_stroke = (draw_stroke_t)canvas_draw_stroke;
+ worker->draw_context.draw_rop3 = (draw_rop3_t)canvas_draw_rop3;
+ worker->draw_context.draw_blend = (draw_blend_t)canvas_draw_blend;
+ worker->draw_context.draw_blackness = (draw_blackness_t)canvas_draw_blackness;
+ worker->draw_context.draw_whiteness = (draw_whiteness_t)canvas_draw_whiteness;
+ worker->draw_context.draw_invers = (draw_invers_t)canvas_draw_invers;
+ worker->draw_context.draw_transparent = (draw_transparent_t)canvas_draw_transparent;
+ worker->draw_context.draw_alpha_blend = (draw_alpha_blend_t)canvas_draw_alpha_blend;
+ worker->draw_context.read_pixels = (read_pixels_t)canvas_read_bits;
+
+ worker->draw_context.set_top_mask = (set_top_mask_t)canvas_group_start;
+ worker->draw_context.clear_top_mask = (clear_top_mask_t)canvas_group_end;
+ worker->draw_context.validate_area = validate_area_nop;
+ worker->draw_context.destroy = (destroy_t)destroy_cairo_canvas;
+}
+
+static int create_cairo_context(RedWorker *worker)
+{
+ cairo_surface_t *cairo_surface;
+ CairoCanvas *canvas;
+ cairo_t *cairo;
+
+ cairo_surface = cairo_image_surface_create_for_data(worker->dev_info.draw_area.line_0,
+ CAIRO_FORMAT_RGB24,
+ worker->dev_info.draw_area.width,
+ worker->dev_info.draw_area.heigth,
+ worker->dev_info.draw_area.stride);
+
+ if (cairo_surface_status(cairo_surface) != CAIRO_STATUS_SUCCESS) {
+ red_error("create cairo surface failed, %s",
+ cairo_status_to_string(cairo_surface_status(cairo_surface)));
+ }
+ cairo = cairo_create(cairo_surface);
+ cairo_surface_destroy(cairo_surface);
+ if (cairo_status(cairo) != CAIRO_STATUS_SUCCESS) {
+ red_error("create cairo failed, %s",
+ cairo_status_to_string(cairo_status(cairo)));
+ }
+
+ if (!(canvas = canvas_create(cairo, worker->dev_info.bits, &worker->image_cache,
+ image_cache_put, image_cache_get))) {
+ red_error("create cairo canvas failed");
+ }
+ init_cairo_draw_context(worker, canvas);
+ red_printf("using cairo canvas");
+ return TRUE;
+}
+
+static void destroy_gl_canvas(GLCanvas *canvas)
+{
+ OGLCtx *ctx;
+
+ if (!canvas) {
+ return;
+ }
+
+ ctx = gl_canvas_get_usr_data(canvas);
+ ASSERT(ctx);
+ gl_canvas_destroy(canvas, 0);
+ oglctx_destroy(ctx);
+}
+
+static void gl_validate_area(GLCanvas *canvas, DrawArea *draw_area, const Rect *area)
+{
+ int h;
+
+ if (!(h = area->bottom - area->top)) {
+ return;
+ }
+
+ ASSERT(draw_area->stride < 0);
+ uint8_t *dest = draw_area->line_0 + (area->top * draw_area->stride) +
+ area->left * sizeof(uint32_t);
+ dest += (h - 1) * draw_area->stride;
+ gl_canvas_read_pixels(canvas, dest, -draw_area->stride, area);
+}
+
+static void init_ogl_draw_context(RedWorker *worker, GLCanvas *canvas)
+{
+ worker->draw_context.canvas = canvas;
+ worker->draw_context.top_down = FALSE;
+ worker->draw_context.set_access_params = (set_access_params_t)gl_canvas_set_access_params;
+ worker->draw_context.draw_fill = (draw_fill_t)gl_canvas_draw_fill;
+ worker->draw_context.draw_copy = (draw_copy_t)gl_canvas_draw_copy;
+ worker->draw_context.draw_opaque = (draw_opaque_t)gl_canvas_draw_opaque;
+ worker->draw_context.copy_bits = (copy_bits_t)gl_canvas_copy_pixels;
+ worker->draw_context.draw_text = (draw_text_t)gl_canvas_draw_text;
+ worker->draw_context.draw_stroke = (draw_stroke_t)gl_canvas_draw_stroke;
+ worker->draw_context.draw_rop3 = (draw_rop3_t)gl_canvas_draw_rop3;
+ worker->draw_context.draw_blend = (draw_blend_t)gl_canvas_draw_blend;
+ worker->draw_context.draw_blackness = (draw_blackness_t)gl_canvas_draw_blackness;
+ worker->draw_context.draw_whiteness = (draw_whiteness_t)gl_canvas_draw_whiteness;
+ worker->draw_context.draw_invers = (draw_invers_t)gl_canvas_draw_invers;
+ worker->draw_context.draw_transparent = (draw_transparent_t)gl_canvas_draw_transparent;
+ worker->draw_context.draw_alpha_blend = (draw_alpha_blend_t)gl_canvas_draw_alpha_blend;
+ worker->draw_context.read_pixels = (read_pixels_t)gl_canvas_read_pixels;
+
+ worker->draw_context.set_top_mask = (set_top_mask_t)gl_canvas_set_top_mask;
+ worker->draw_context.clear_top_mask = (clear_top_mask_t)gl_canvas_clear_top_mask;
+ worker->draw_context.validate_area = (validate_area_t)gl_validate_area;
+ worker->draw_context.destroy = (destroy_t)destroy_gl_canvas;
+}
+
+static int create_ogl_context_common(RedWorker *worker, OGLCtx *ctx)
+{
+ GLCanvas *canvas;
+ int width;
+ int height;
+
+ width = worker->dev_info.x_res;
+ height = worker->dev_info.y_res;
+
+ oglctx_make_current(ctx);
+ if (!(canvas = gl_canvas_create(ctx, width, height, worker->dev_info.bits, &worker->image_cache,
+ image_cache_put, image_cache_get))) {
+ return FALSE;
+ }
+
+ init_ogl_draw_context(worker, canvas);
+ gl_canvas_clear(canvas);
+ red_printf("using ogl %s canvas", oglctx_type_str(ctx));
+ return TRUE;
+}
+
+static int create_ogl_pbuf_context(RedWorker *worker)
+{
+ OGLCtx *ctx;
+
+ if (!(ctx = pbuf_create(worker->dev_info.x_res, worker->dev_info.y_res))) {
+ return FALSE;
+ }
+
+ if (!create_ogl_context_common(worker, ctx)) {
+ oglctx_destroy(ctx);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static int create_ogl_pixmap_context(RedWorker *worker)
+{
+ OGLCtx *ctx;
+
+ if (!(ctx = pixmap_create(worker->dev_info.x_res, worker->dev_info.y_res))) {
+ return FALSE;
+ }
+
+ if (!create_ogl_context_common(worker, ctx)) {
+ oglctx_destroy(ctx);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void red_create_draw_context(RedWorker *worker)
+{
+ int i;
+
+ worker->draw_context.destroy(worker->draw_context.canvas);
+ worker->draw_context.canvas = NULL;
+ for (i = 0; i < worker->num_renderers; i++) {
+ switch (worker->renderers[i]) {
+ case RED_RENDERER_CAIRO:
+ if (create_cairo_context(worker)) {
+ return;
+ }
+ break;
+ case RED_RENDERER_OGL_PBUF:
+ if (create_ogl_pbuf_context(worker)) {
+ return;
+ }
+ break;
+ case RED_RENDERER_OGL_PIXMAP:
+ if (create_ogl_pixmap_context(worker)) {
+ return;
+ }
+ break;
+ default:
+ red_error("invalid renderer type");
+ }
+ }
+ red_error("create renderer failed");
+ memset(worker->dev_info.draw_area.buf, 0, worker->dev_info.draw_area.size);
+}
+
+static void push_new_mode(RedWorker *worker)
+{
+ DisplayChannel *display_channel;
+
+ if ((display_channel = worker->display_channel)) {
+ red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE);
+ if (!display_channel->base.migrate) {
+ red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_MODE);
+ }
+ display_channel_push(worker);
+ }
+}
+
+static int display_channel_wait_for_init(DisplayChannel *display_channel)
+{
+ display_channel->expect_init = TRUE;
+ uint64_t end_time = red_now() + DISPLAY_CLIENT_TIMEOUT;
+ for (;;) {
+ red_receive((RedChannel *)display_channel);
+ if (!display_channel->base.peer) {
+ break;
+ }
+ if (display_channel->pixmap_cache && display_channel->glz_dict) {
+ display_channel->pixmap_cache_generation = display_channel->pixmap_cache->generation;
+ display_channel->glz = glz_encoder_create(display_channel->base.id,
+ display_channel->glz_dict->dict,
+ &display_channel->glz_data.usr);
+ if (!display_channel->glz) {
+ PANIC("create global lz failed");
+ }
+ return TRUE;
+ }
+ if (red_now() > end_time) {
+ red_printf("timeout");
+ red_disconnect_display((RedChannel *)display_channel);
+ break;
+ }
+ usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
+ }
+ return FALSE;
+}
+
+static void on_new_display_channel(RedWorker *worker)
+{
+ DisplayChannel *display_channel = worker->display_channel;
+ ASSERT(display_channel);
+
+ red_pipe_add_type(&display_channel->base, PIPE_ITEM_TYPE_SET_ACK);
+
+ if (display_channel->base.migrate) {
+ display_channel->expect_migrate_data = TRUE;
+ return;
+ }
+
+ if (!display_channel_wait_for_init(display_channel)) {
+ return;
+ }
+ display_channel->base.messages_window = 0;
+ if (worker->attached) {
+ red_current_flush(worker);
+ push_new_mode(worker);
+ red_add_screen_image(worker);
+ if (channel_is_connected(&display_channel->base)) {
+ red_pipe_add_verb(&display_channel->base, RED_DISPLAY_MARK);
+ red_disply_start_streams(display_channel);
+ }
+ }
+}
+
+static int channel_handle_message(RedChannel *channel, RedDataHeader *message)
+{
+ switch (message->type) {
+ case REDC_ACK_SYNC:
+ if (message->size != sizeof(uint32_t)) {
+ red_printf("bad message size");
+ return FALSE;
+ }
+ channel->client_ack_generation = *(uint32_t *)(message + 1);
+ break;
+ case REDC_ACK:
+ if (channel->client_ack_generation == channel->ack_generation) {
+ channel->messages_window -= channel->client_ack_window;
+ }
+ break;
+ case REDC_DISCONNECTING:
+ break;
+ default:
+ red_printf("invalid message type %u", message->type);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static GlzSharedDictionary *_red_find_glz_dictionary(uint8_t dict_id)
+{
+ RingItem *now;
+ GlzSharedDictionary *ret = NULL;
+
+ now = &glz_dictionary_list;
+
+ while ((now = ring_next(&glz_dictionary_list, now))) {
+ if (((GlzSharedDictionary *)now)->id == dict_id) {
+ ret = (GlzSharedDictionary *)now;
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static GlzSharedDictionary *_red_create_glz_dictionary(DisplayChannel *display,
+ uint8_t id,
+ GlzEncDictContext *opaque_dict)
+{
+ GlzSharedDictionary *shared_dict = malloc(sizeof(*shared_dict));
+ memset(shared_dict, 0, sizeof(*shared_dict)); // nullify the ring base
+
+ if (!shared_dict) {
+ return NULL;
+ }
+ shared_dict->dict = opaque_dict;
+ shared_dict->id = id;
+ shared_dict->refs = 1;
+ shared_dict->migrate_freeze = FALSE;
+ ring_item_init(&shared_dict->base);
+ pthread_rwlock_init(&shared_dict->encode_lock, NULL);
+ return shared_dict;
+}
+
+static GlzSharedDictionary *red_create_glz_dictionary(DisplayChannel *display,
+ uint8_t id, int window_size)
+{
+ GlzEncDictContext *glz_dict = glz_enc_dictionary_create(window_size,
+ MAX_LZ_ENCODERS,
+ &display->glz_data.usr);
+#ifdef COMPRESS_DEBUG
+ red_printf("Lz Window %d Size=%d", id, window_size);
+#endif
+ if (!glz_dict) {
+ PANIC("failed creating lz dictionary");
+ return NULL;
+ }
+ return _red_create_glz_dictionary(display, id, glz_dict);
+}
+
+static GlzSharedDictionary *red_create_restored_glz_dictionary(DisplayChannel *display,
+ uint8_t id,
+ GlzEncDictRestoreData *restore_data)
+{
+ GlzEncDictContext *glz_dict = glz_enc_dictionary_restore(restore_data,
+ &display->glz_data.usr);
+ if (!glz_dict) {
+ PANIC("failed creating lz dictionary");
+ return NULL;
+ }
+ return _red_create_glz_dictionary(display, id, glz_dict);
+}
+
+static GlzSharedDictionary *red_get_glz_dictionary(DisplayChannel *display,
+ uint8_t id, int window_size)
+{
+ GlzSharedDictionary *shared_dict = NULL;
+
+ pthread_mutex_lock(&glz_dictionary_list_lock);
+
+ shared_dict = _red_find_glz_dictionary(id);
+
+ if (!shared_dict) {
+ shared_dict = red_create_glz_dictionary(display, id, window_size);
+ ring_add(&glz_dictionary_list, &shared_dict->base);
+ } else {
+ shared_dict->refs++;
+ }
+ pthread_mutex_unlock(&glz_dictionary_list_lock);
+ return shared_dict;
+}
+
+static GlzSharedDictionary *red_restore_glz_dictionary(DisplayChannel *display,
+ uint8_t id,
+ GlzEncDictRestoreData *restore_data)
+{
+ GlzSharedDictionary *shared_dict = NULL;
+
+ pthread_mutex_lock(&glz_dictionary_list_lock);
+
+ shared_dict = _red_find_glz_dictionary(id);
+
+ if (!shared_dict) {
+ shared_dict = red_create_restored_glz_dictionary(display, id, restore_data);
+ ring_add(&glz_dictionary_list, &shared_dict->base);
+ } else {
+ shared_dict->refs++;
+ }
+ pthread_mutex_unlock(&glz_dictionary_list_lock);
+ return shared_dict;
+}
+
+static void red_freeze_glz(DisplayChannel *channel)
+{
+ pthread_rwlock_wrlock(&channel->glz_dict->encode_lock);
+ if (!channel->glz_dict->migrate_freeze) {
+ channel->glz_dict->migrate_freeze = TRUE;
+ }
+ pthread_rwlock_unlock(&channel->glz_dict->encode_lock);
+}
+
+/* destroy encoder, and dictionary if no one uses it*/
+static void red_release_glz(DisplayChannel *channel)
+{
+ GlzSharedDictionary *shared_dict;
+
+ red_display_clear_glz_drawables(channel);
+
+ glz_encoder_destroy(channel->glz);
+ channel->glz = NULL;
+
+ if (!(shared_dict = channel->glz_dict)) {
+ return;
+ }
+
+ channel->glz_dict = NULL;
+ pthread_mutex_lock(&cache_lock);
+ if (--shared_dict->refs) {
+ pthread_mutex_unlock(&cache_lock);
+ return;
+ }
+ ring_remove(&shared_dict->base);
+ pthread_mutex_unlock(&cache_lock);
+ glz_enc_dictionary_destroy(shared_dict->dict, &channel->glz_data.usr);
+ free(shared_dict);
+}
+
+static PixmapCache *red_create_pixmap_cache(uint8_t id, int64_t size)
+{
+ PixmapCache *cache = malloc(sizeof(*cache));
+ if (!cache) {
+ return NULL;
+ }
+ memset(cache, 0, sizeof(*cache));
+ ring_item_init(&cache->base);
+ pthread_mutex_init(&cache->lock, NULL);
+ cache->id = id;
+ cache->refs = 1;
+ ring_init(&cache->lru);
+ cache->available = size;
+ cache->size = size;
+ return cache;
+}
+
+static PixmapCache *red_get_pixmap_cache(uint8_t id, int64_t size)
+{
+ PixmapCache *cache = NULL;
+ RingItem *now;
+ pthread_mutex_lock(&cache_lock);
+
+ now = &pixmap_cache_list;
+ while ((now = ring_next(&pixmap_cache_list, now))) {
+ if (((PixmapCache *)now)->id == id) {
+ cache = (PixmapCache *)now;
+ cache->refs++;
+ break;
+ }
+ }
+ if (!cache) {
+ cache = red_create_pixmap_cache(id, size);
+ ring_add(&pixmap_cache_list, &cache->base);
+ }
+ pthread_mutex_unlock(&cache_lock);
+ return cache;
+}
+
+static void red_release_pixmap_cache(DisplayChannel *channel)
+{
+ PixmapCache *cache;
+ if (!(cache = channel->pixmap_cache)) {
+ return;
+ }
+ channel->pixmap_cache = NULL;
+ pthread_mutex_lock(&cache_lock);
+ if (--cache->refs) {
+ pthread_mutex_unlock(&cache_lock);
+ return;
+ }
+ ring_remove(&cache->base);
+ pthread_mutex_unlock(&cache_lock);
+ pixmap_cache_destroy(cache);
+ free(cache);
+}
+
+static int display_channel_init_cache(DisplayChannel *channel, RedcDisplayInit *init_info)
+{
+ ASSERT(!channel->pixmap_cache);
+ return !!(channel->pixmap_cache = red_get_pixmap_cache(init_info->pixmap_cache_id,
+ init_info->pixmap_cache_size));
+}
+
+static int display_channel_init_glz_dictionary(DisplayChannel *channel, RedcDisplayInit *init_info)
+{
+ ASSERT(!channel->glz_dict);
+ ring_init(&channel->glz_drawables);
+ ring_init(&channel->glz_drawables_inst_to_free);
+ pthread_mutex_init(&channel->glz_drawables_inst_to_free_lock, NULL);
+ return !!(channel->glz_dict = red_get_glz_dictionary(channel,
+ init_info->glz_dictionary_id,
+ init_info->glz_dictionary_window_size));
+}
+
+static int display_channel_init(DisplayChannel *channel, RedcDisplayInit *init_info)
+{
+ return (display_channel_init_cache(channel, init_info) &&
+ display_channel_init_glz_dictionary(channel, init_info));
+}
+
+static int display_channel_handle_migrate_glz_dictionary(DisplayChannel *channel,
+ DisplayChannelMigrateData *migrate_info)
+{
+ ASSERT(!channel->glz_dict);
+ ring_init(&channel->glz_drawables);
+ ring_init(&channel->glz_drawables_inst_to_free);
+ pthread_mutex_init(&channel->glz_drawables_inst_to_free_lock, NULL);
+ return !!(channel->glz_dict = red_restore_glz_dictionary(channel,
+ migrate_info->glz_dict_id,
+ &migrate_info->glz_dict_restore_data));
+}
+
+static int display_channel_handle_migrate_mark(DisplayChannel *channel)
+{
+ if (!channel->expect_migrate_mark) {
+ red_printf("unexpected");
+ return FALSE;
+ }
+ channel->expect_migrate_mark = FALSE;
+ red_pipe_add_type((RedChannel *)channel, PIPE_ITEM_TYPE_MIGRATE_DATA);
+ return TRUE;
+}
+
+static int display_channel_handle_migrate_data(DisplayChannel *channel, RedDataHeader *message)
+{
+ DisplayChannelMigrateData *migrate_data;
+ int i;
+
+ if (!channel->expect_migrate_data) {
+ red_printf("unexpected");
+ return FALSE;
+ }
+ channel->expect_migrate_data = FALSE;
+ if (message->size < sizeof(*migrate_data)) {
+ red_printf("bad message size");
+ return FALSE;
+ }
+ migrate_data = (DisplayChannelMigrateData *)(message + 1);
+ if (migrate_data->magic != DISPLAY_MIGRATE_DATA_MAGIC ||
+ migrate_data->version != DISPLAY_MIGRATE_DATA_VERSION) {
+ red_printf("invalid content");
+ return FALSE;
+ }
+ ASSERT(channel->base.send_data.header.serial == 0);
+ channel->base.send_data.header.serial = migrate_data->message_serial;
+ if (!(channel->pixmap_cache = red_get_pixmap_cache(migrate_data->pixmap_cache_id, -1))) {
+ return FALSE;
+ }
+ pthread_mutex_lock(&channel->pixmap_cache->lock);
+ for (i = 0; i < MAX_CACHE_CLIENTS; i++) {
+ channel->pixmap_cache->sync[i] = MAX(channel->pixmap_cache->sync[i],
+ migrate_data->pixmap_cache_clients[i]);
+ }
+ pthread_mutex_unlock(&channel->pixmap_cache->lock);
+
+ if (migrate_data->pixmap_cache_freezer) {
+ channel->pixmap_cache->size = migrate_data->pixmap_cache_size;
+ red_pipe_add_type((RedChannel *)channel, PIPE_ITEM_TYPE_PIXMAP_RESET);
+ }
+
+ if (display_channel_handle_migrate_glz_dictionary(channel, migrate_data)) {
+ channel->glz = glz_encoder_create(channel->base.id,
+ channel->glz_dict->dict, &channel->glz_data.usr);
+ if (!channel->glz) {
+ PANIC("create global lz failed");
+ }
+ } else {
+ PANIC("restoring global lz dictionary failed");
+ }
+
+ channel->base.messages_window = 0;
+ return TRUE;
+}
+
+static int display_channel_handle_message(RedChannel *channel, RedDataHeader *message)
+{
+ switch (message->type) {
+ case REDC_DISPLAY_INIT:
+ if (message->size != sizeof(RedcDisplayInit)) {
+ red_printf("bad message size");
+ return FALSE;
+ }
+ if (!((DisplayChannel *)channel)->expect_init) {
+ red_printf("unexpected REDC_DISPLAY_INIT");
+ return FALSE;
+ }
+ ((DisplayChannel *)channel)->expect_init = FALSE;
+ return display_channel_init((DisplayChannel *)channel, (RedcDisplayInit *)(message + 1));
+ case REDC_MIGRATE_FLUSH_MARK:
+ return display_channel_handle_migrate_mark((DisplayChannel *)channel);
+ case REDC_MIGRATE_DATA:
+ return display_channel_handle_migrate_data((DisplayChannel *)channel, message);
+ default:
+ return channel_handle_message(channel, message);
+ }
+}
+
+static void red_receive(RedChannel *channel)
+{
+ for (;;) {
+ ssize_t n;
+ n = channel->recive_data.end - channel->recive_data.now;
+ ASSERT(n);
+ ASSERT(channel->peer);
+ if ((n = channel->peer->cb_read(channel->peer->ctx, channel->recive_data.now, n)) <= 0) {
+ if (n == 0) {
+ channel->disconnect(channel);
+ return;
+ }
+ ASSERT(n == -1);
+ switch (errno) {
+ case EAGAIN:
+ return;
+ case EINTR:
+ break;
+ case EPIPE:
+ channel->disconnect(channel);
+ return;
+ default:
+ red_printf("%s", strerror(errno));
+ channel->disconnect(channel);
+ return;
+ }
+ } else {
+ channel->recive_data.now += n;
+ for (;;) {
+ RedDataHeader *message = channel->recive_data.message;
+ n = channel->recive_data.now - (uint8_t *)message;
+ if (n < sizeof(RedDataHeader) ||
+ n < sizeof(RedDataHeader) + message->size) {
+ break;
+ }
+ if (!channel->handle_message(channel, message)) {
+ channel->disconnect(channel);
+ return;
+ }
+ channel->recive_data.message = (RedDataHeader *)((uint8_t *)message +
+ sizeof(RedDataHeader) +
+ message->size);
+ }
+
+ if (channel->recive_data.now == (uint8_t *)channel->recive_data.message) {
+ channel->recive_data.now = channel->recive_data.buf;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ } else if (channel->recive_data.now == channel->recive_data.end) {
+ memcpy(channel->recive_data.buf, channel->recive_data.message, n);
+ channel->recive_data.now = channel->recive_data.buf + n;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ }
+ }
+ }
+}
+
+static RedChannel *__new_channel(RedWorker *worker, int size, RedsStreamContext *peer, int migrate,
+ event_listener_action_proc handler,
+ disconnect_channel_proc disconnect,
+ hold_item_proc hold_item,
+ release_item_proc release_item,
+ handle_message_proc handle_message)
+{
+ struct epoll_event event;
+ RedChannel *channel;
+ int flags;
+ int delay_val;
+
+ if ((flags = fcntl(peer->socket, F_GETFL)) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ if (fcntl(peer->socket, F_SETFL, flags | O_NONBLOCK) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ delay_val = IS_LOW_BANDWIDTH() ? 0 : 1;
+ if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ ASSERT(size >= sizeof(*channel));
+ if (!(channel = malloc(size))) {
+ red_printf("malloc failed");
+ goto error1;
+ }
+ memset(channel, 0, size);
+ channel->id = worker->id;
+ channel->listener.refs = 1;
+ channel->listener.action = handler;
+ channel->disconnect = disconnect;
+ channel->hold_item = hold_item;
+ channel->release_item = release_item;
+ channel->handle_message = handle_message;
+ channel->peer = peer;
+ channel->worker = worker;
+ channel->messages_window = ~0; // blocks send message (maybe use send_data.blocked +
+ // block flags)
+ channel->client_ack_window = IS_LOW_BANDWIDTH() ? WIDE_CLIENT_ACK_WINDOW :
+ NARROW_CLIENT_ACK_WINDOW;
+ channel->client_ack_generation = ~0;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ channel->recive_data.now = channel->recive_data.buf;
+ channel->recive_data.end = channel->recive_data.buf + sizeof(channel->recive_data.buf);
+ ring_init(&channel->pipe);
+
+ event.events = EPOLLIN | EPOLLOUT | EPOLLET;
+ event.data.ptr = channel;
+ if (epoll_ctl(worker->epoll, EPOLL_CTL_ADD, peer->socket, &event) == -1) {
+ red_printf("epoll_ctl failed, %s", strerror(errno));
+ goto error2;
+ }
+
+ channel->migrate = migrate;
+ return channel;
+
+error2:
+ free(channel);
+error1:
+ peer->cb_free(peer);
+
+ return NULL;
+}
+
+static void handle_channel_events(EventListener *in_listener, uint32_t events)
+{
+ RedChannel *channel = (RedChannel *)in_listener;
+
+ if ((events & EPOLLIN)) {
+ red_receive(channel);
+ }
+
+ if (channel->send_data.blocked) {
+ red_send_data(channel, NULL);
+ }
+}
+
+static void display_channel_hold_item(void *item)
+{
+ ASSERT(item);
+ switch (((PipeItem *)item)->type) {
+ case PIPE_ITEM_TYPE_DRAW:
+ case PIPE_ITEM_TYPE_STREAM_CREATE:
+ CONTAINEROF(item, Drawable, pipe_item)->refs++;
+ break;
+ case PIPE_ITEM_TYPE_STREAM_CLIP:
+ ((StreamClipItem *)item)->refs++;
+ break;
+ case PIPE_ITEM_TYPE_UPGRADE:
+ ((UpgradeItem *)item)->refs++;
+ break;
+ case PIPE_ITEM_TYPE_IMAGE:
+ ((ImageItem *)item)->refs++;
+ break;
+ default:
+ PANIC("invalid item type");
+ }
+}
+
+static void display_channel_release_item(RedChannel *channel, void *item)
+{
+ ASSERT(item);
+ switch (((PipeItem *)item)->type) {
+ case PIPE_ITEM_TYPE_DRAW:
+ case PIPE_ITEM_TYPE_STREAM_CREATE:
+ release_drawable(channel->worker, CONTAINEROF(item, Drawable, pipe_item));
+ break;
+ case PIPE_ITEM_TYPE_STREAM_CLIP:
+ red_display_release_stream_clip((DisplayChannel *)channel, (StreamClipItem *)item);
+ break;
+ case PIPE_ITEM_TYPE_UPGRADE:
+ release_upgrade_item(channel->worker, (UpgradeItem *)item);
+ break;
+ case PIPE_ITEM_TYPE_IMAGE:
+ release_image_item((ImageItem *)item);
+ break;
+ default:
+ PANIC("invalid item type");
+ }
+}
+
+static void handle_new_display_channel(RedWorker *worker, RedsStreamContext *peer, int migrate)
+{
+ DisplayChannel *display_channel;
+ size_t stream_buf_size;
+
+ red_disconnect_display((RedChannel *)worker->display_channel);
+
+ if (!(display_channel = (DisplayChannel *)__new_channel(worker, sizeof(*display_channel), peer,
+ migrate, handle_channel_events,
+ red_disconnect_display,
+ display_channel_hold_item,
+ display_channel_release_item,
+ display_channel_handle_message))) {
+ return;
+ }
+#ifdef RED_STATISTICS
+ display_channel->stat = stat_add_node(worker->stat, "display_channel", TRUE);
+ display_channel->base.out_bytes_counter = stat_add_counter(display_channel->stat,
+ "out_bytes", TRUE);
+ display_channel->cache_hits_counter = stat_add_counter(display_channel->stat,
+ "cache_hits", TRUE);
+ display_channel->add_to_cache_counter = stat_add_counter(display_channel->stat,
+ "add_to_cache", TRUE);
+ display_channel->non_cache_counter = stat_add_counter(display_channel->stat,
+ "non_cache", TRUE);
+#endif
+ ring_init(&display_channel->palette_cache_lru);
+ display_channel->palette_cache_available = CLIENT_PALETTE_CACHE_SIZE;
+ red_display_init_streams(display_channel);
+
+ stream_buf_size = FF_MIN_BUFFER_SIZE + FF_INPUT_BUFFER_PADDING_SIZE;
+ if (!(display_channel->send_data.stream_outbuf = malloc(stream_buf_size))) {
+ PANIC("alloc failed");
+ }
+ display_channel->send_data.stream_outbuf_size = FF_MIN_BUFFER_SIZE;
+ red_display_share_stream_buf(display_channel);
+ red_display_init_glz_data(display_channel);
+ worker->display_channel = display_channel;
+
+
+ if (!(display_channel->send_data.free_list.res = malloc(sizeof(RedResorceList) +
+ DISPLAY_FREE_LIST_DEFAULT_SIZE *
+ sizeof(RedResorceID)))) {
+ PANIC("free list alloc failed");
+ }
+ display_channel->send_data.free_list.res_size = DISPLAY_FREE_LIST_DEFAULT_SIZE;
+ red_ref_channel((RedChannel*)display_channel);
+ on_new_display_channel(worker);
+ red_unref_channel((RedChannel*)display_channel);
+
+ stat_compress_init(&display_channel->lz_stat, lz_stat_name);
+ stat_compress_init(&display_channel->glz_stat, glz_stat_name);
+ stat_compress_init(&display_channel->quic_stat, quic_stat_name);
+}
+
+static void red_disconnect_cursor(RedChannel *channel)
+{
+ if (!channel || !channel->peer) {
+ return;
+ }
+
+ ASSERT(channel == (RedChannel *)channel->worker->cursor_channel);
+ channel->worker->cursor_channel = NULL;
+ red_reset_cursor_cache((CursorChannel *)channel);
+ red_disconnect_channel(channel);
+}
+
+static void red_migrate_cursor(RedWorker *worker)
+{
+ if (worker->cursor_channel) {
+ red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
+ red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_MIGRATE);
+ }
+}
+
+static void on_new_cursor_channel(RedWorker *worker)
+{
+ CursorChannel *channel = worker->cursor_channel;
+
+ ASSERT(channel);
+
+ channel->base.messages_window = 0;
+ red_pipe_add_type(&channel->base, PIPE_ITEM_TYPE_SET_ACK);
+ if (worker->attached && !channel->base.migrate) {
+ red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_CURSOR_INIT);
+ }
+}
+
+static void cursor_channel_hold_item(void *item)
+{
+ ASSERT(item);
+ ((CursorItem *)item)->refs++;
+}
+
+static void cursor_channel_release_item(RedChannel *channel, void *item)
+{
+ ASSERT(item);
+ red_release_cursor(channel->worker, item);
+}
+
+static void red_connect_cursor(RedWorker *worker, RedsStreamContext *peer, int migrate)
+{
+ CursorChannel *channel;
+
+ red_disconnect_cursor((RedChannel *)worker->cursor_channel);
+
+ if (!(channel = (CursorChannel *)__new_channel(worker, sizeof(*channel), peer, migrate,
+ handle_channel_events,
+ red_disconnect_cursor,
+ cursor_channel_hold_item,
+ cursor_channel_release_item,
+ channel_handle_message))) {
+ return;
+ }
+#ifdef RED_STATISTICS
+ channel->stat = stat_add_node(worker->stat, "cursor_channel", TRUE);
+ channel->base.out_bytes_counter = stat_add_counter(channel->stat, "out_bytes", TRUE);
+#endif
+ ring_init(&channel->cursor_cache_lru);
+ channel->cursor_cache_available = CLIENT_CURSOR_CACHE_SIZE;
+ worker->cursor_channel = channel;
+ on_new_cursor_channel(worker);
+}
+
+typedef struct __attribute__ ((__packed__)) CursorData {
+ uint32_t visible;
+ Point16 position;
+ uint16_t trail_length;
+ uint16_t trail_frequency;
+ uint32_t data_size;
+ RedCursor _cursor;
+} CursorData;
+
+static void red_save_cursor(RedWorker *worker)
+{
+ CursorData *cursor_data;
+ LocalCursor *local;
+ int size;
+
+ ASSERT(worker->cursor);
+ ASSERT(worker->cursor->type == CURSOR_TYPE_LOCAL);
+
+ local = (LocalCursor *)worker->cursor;
+ size = sizeof(CursorData) + sizeof(RedCursor) + local->data_size;
+ cursor_data = malloc(size);
+ ASSERT(cursor_data);
+
+ cursor_data->position = worker->cursor_position;
+ cursor_data->visible = worker->cursor_visible;
+ cursor_data->trail_frequency = worker->cursor_trail_frequency;
+ cursor_data->trail_length = worker->cursor_trail_length;
+ cursor_data->data_size = local->data_size;
+ cursor_data->_cursor.header = local->red_cursor.header;
+ memcpy(cursor_data->_cursor.data, local->red_cursor.data, local->data_size);
+ worker->qxl->set_save_data(worker->qxl, cursor_data, size);
+}
+
+static LocalCursor *_new_local_cursor(CursorHeader *header, int data_size, Point16 position)
+{
+ LocalCursor *local;
+
+ local = (LocalCursor *)malloc(sizeof(LocalCursor) + data_size);
+ if (!local) {
+ return NULL;
+ }
+
+ red_pipe_item_init(&local->base.pipe_data, PIPE_ITEM_TYPE_LOCAL_CURSOR);
+ local->base.refs = 1;
+ local->base.type = CURSOR_TYPE_LOCAL;
+
+ local->red_cursor.header = *header;
+ local->red_cursor.header.unique = 0;
+ local->red_cursor.flags = 0;
+ local->position = position;
+ local->data_size = data_size;
+ return local;
+}
+
+static void red_cursor_flush(RedWorker *worker)
+{
+ QXLCursorCmd *cursor_cmd;
+ QXLCursor *qxl_cursor;
+ LocalCursor *local;
+ uint32_t data_size;
+ QXLDataChunk *chunk;
+ uint8_t *dest;
+
+ if (!worker->cursor || worker->cursor->type == CURSOR_TYPE_LOCAL) {
+ return;
+ }
+
+ ASSERT(worker->cursor->type == CURSOR_TYPE_DEV);
+
+ cursor_cmd = CONTAINEROF(worker->cursor, QXLCursorCmd, device_data);
+ ASSERT(cursor_cmd->type == QXL_CURSOR_SET);
+ qxl_cursor = (QXLCursor *)(cursor_cmd->u.set.shape + worker->dev_info.phys_delta);
+
+ local = _new_local_cursor(&qxl_cursor->header, qxl_cursor->data_size,
+ worker->cursor_position);
+ ASSERT(local);
+ data_size = local->data_size;
+ dest = local->red_cursor.data;
+ chunk = &qxl_cursor->chunk;
+
+ while (data_size) {
+ ASSERT(chunk);
+ ASSERT(chunk->data_size <= data_size);
+ memcpy(dest, chunk->data, chunk->data_size);
+ data_size -= chunk->data_size;
+ dest += chunk->data_size;
+ chunk = chunk->next_chunk ? (QXLDataChunk *)(chunk->next_chunk +
+ worker->dev_info.phys_delta) : NULL;
+ }
+ red_set_cursor(worker, &local->base);
+ red_release_cursor(worker, &local->base);
+}
+
+static void red_save(RedWorker *worker)
+{
+ if (!worker->cursor) {
+ worker->qxl->set_save_data(worker->qxl, NULL, 0);
+ return;
+ }
+ red_save_cursor(worker);
+}
+
+static void red_cursor_load(RedWorker *worker)
+{
+ CursorData *cursor_data = worker->qxl->get_save_data(worker->qxl);
+ LocalCursor *local;
+
+ if (!cursor_data) {
+ return;
+ }
+
+ worker->cursor_position = cursor_data->position;
+ worker->cursor_visible = cursor_data->visible;
+ worker->cursor_trail_frequency = cursor_data->trail_frequency;
+ worker->cursor_trail_length = cursor_data->trail_length;
+
+ local = _new_local_cursor(&cursor_data->_cursor.header, cursor_data->data_size,
+ cursor_data->position);
+ ASSERT(local);
+ memcpy(local->red_cursor.data, cursor_data->_cursor.data, cursor_data->data_size);
+ red_set_cursor(worker, &local->base);
+ red_release_cursor(worker, &local->base);
+}
+
+static void red_wait_outgoiong_item(RedChannel *channel)
+{
+ uint64_t end_time;
+ int blocked;
+
+ if (!channel || !channel->send_data.blocked) {
+ return;
+ }
+ red_ref_channel(channel);
+
+ end_time = red_now() + DETACH_TIMEOUT;
+ red_printf("blocked");
+
+ do {
+ usleep(DETACH_SLEEP_DURATION);
+ red_receive(channel);
+ red_send_data(channel, NULL);
+ } while ((blocked = channel->send_data.blocked) && red_now() < end_time);
+
+ if (blocked) {
+ red_printf("timeout");
+ channel->disconnect(channel);
+ }
+ red_unref_channel(channel);
+}
+
+static inline void handle_dev_update(RedWorker *worker)
+{
+ RedWorkeMessage message;
+
+ ASSERT(worker->attached && worker->running);
+ for (;;) {
+ uint64_t end_time;
+
+ while (red_process_commands(worker, MAX_PIPE_SIZE)) {
+ display_channel_push(worker);
+ }
+
+ if (!worker->qxl->has_command(worker->qxl)) {
+ break;
+ }
+ end_time = red_now() + DISPLAY_CLIENT_TIMEOUT;
+ int sleep_count = 0;
+ for (;;) {
+ display_channel_push(worker);
+ if (!worker->display_channel ||
+ worker->display_channel->base.pipe_size <= MAX_PIPE_SIZE) {
+ break;
+ }
+ RedChannel *channel = (RedChannel *)worker->display_channel;
+ red_ref_channel(channel);
+ red_receive(channel);
+ red_send_data(channel, NULL);
+ if (red_now() >= end_time) {
+ red_printf("update timout");
+ red_disconnect_display((RedChannel *)worker->display_channel);
+ } else {
+ sleep_count++;
+ usleep(DISPLAY_CLIENT_RETRY_INTERVAL);
+ }
+ red_unref_channel(channel);
+ }
+ }
+ red_update_area(worker, worker->qxl->get_update_area(worker->qxl));
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+}
+
+static void handle_dev_input(EventListener *listener, uint32_t events)
+{
+ RedWorker *worker = CONTAINEROF(listener, RedWorker, dev_listener);
+ RedWorkeMessage message;
+
+ read_message(worker->channel, &message);
+
+ switch (message) {
+ case RED_WORKER_MESSAGE_UPDATE:
+ handle_dev_update(worker);
+ break;
+ case RED_WORKER_MESSAGE_WAKEUP:
+ clear_bit(RED_WORKER_PENDING_WAKEUP, worker->pending);
+ stat_inc_counter(worker->wakeup_counter, 1);
+ break;
+ case RED_WORKER_MESSAGE_OOM:
+ ASSERT(worker->attached && worker->running);
+ while (red_process_commands(worker, MAX_PIPE_SIZE)) {
+ display_channel_push(worker);
+ }
+ if (worker->qxl->flush_resources(worker->qxl) == 0) {
+ red_printf("oom current %u pipe %u", worker->current_size, worker->display_channel ?
+ worker->display_channel->base.pipe_size : 0);
+ red_free_some(worker);
+ }
+ clear_bit(RED_WORKER_PENDING_OOM, worker->pending);
+ break;
+ case RED_WORKER_MESSAGE_ATTACH:
+ ASSERT(!worker->attached);
+ red_printf("attach");
+ receive_data(worker->channel, &worker->dev_info, sizeof(QXLDevInfo));
+ worker->attached = TRUE;
+ worker->repoll_cmd_ring = worker->repoll_cursor_ring = 0;
+ red_create_draw_context(worker);
+ ASSERT(ring_is_empty(&worker->current));
+ if (worker->display_channel && !worker->display_channel->base.migrate) {
+ red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_MODE);
+ red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_MARK);
+ display_channel_push(worker);
+ }
+ if (worker->cursor_channel && !worker->cursor_channel->base.migrate) {
+ red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_CURSOR_INIT);
+ }
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+ break;
+ case RED_WORKER_MESSAGE_DETACH:
+ red_printf("detach");
+ red_display_clear_glz_drawables(worker->display_channel);
+ red_current_clear(worker);
+#ifdef STREAM_TRACE
+ red_reset_stream_trace(worker);
+#endif
+ if (worker->cursor) {
+ red_release_cursor(worker, worker->cursor);
+ worker->cursor = NULL;
+ }
+
+ red_wait_outgoiong_item((RedChannel *)worker->cursor_channel);
+ if (worker->cursor_channel) {
+ red_pipe_add_type(&worker->cursor_channel->base, PIPE_ITEM_TYPE_INVAL_CURSOR_CACHE);
+ if (!worker->cursor_channel->base.migrate) {
+ red_pipe_add_verb(&worker->cursor_channel->base, RED_CURSOR_RESET);
+ }
+ ASSERT(!worker->cursor_channel->base.send_data.item);
+ }
+
+ red_wait_outgoiong_item((RedChannel *)worker->display_channel);
+ ASSERT(ring_is_empty(&worker->streams));
+ if (worker->display_channel) {
+ red_pipe_add_type(&worker->display_channel->base, PIPE_ITEM_TYPE_INVAL_PALLET_CACHE);
+ red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_STREAM_DESTROY_ALL);
+ if (!worker->display_channel->base.migrate) {
+ red_pipe_add_verb(&worker->display_channel->base, RED_DISPLAY_RESET);
+ }
+ ASSERT(!worker->display_channel->base.send_data.item);
+ }
+
+ worker->attached = FALSE;
+ worker->cursor_visible = TRUE;
+ worker->cursor_position.x = worker->cursor_position.y = 0;
+ worker->cursor_trail_length = worker->cursor_trail_frequency = 0;
+
+ image_cache_reset(&worker->image_cache);
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+ break;
+ case RED_WORKER_MESSAGE_DISPLAY_CONNECT: {
+ RedsStreamContext *peer;
+ int migrate;
+ red_printf("connect");
+
+ receive_data(worker->channel, &peer, sizeof(RedsStreamContext *));
+ receive_data(worker->channel, &migrate, sizeof(int));
+ handle_new_display_channel(worker, peer, migrate);
+ break;
+ }
+ case RED_WORKER_MESSAGE_DISPLAY_DISCONNECT:
+ red_printf("disconnect");
+ red_disconnect_display((RedChannel *)worker->display_channel);
+ break;
+ case RED_WORKER_MESSAGE_SAVE:
+ red_printf("save");
+ ASSERT(!worker->running);
+ red_save(worker);
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+ break;
+ case RED_WORKER_MESSAGE_LOAD:
+ red_printf("load");
+ ASSERT(!worker->running);
+ red_add_screen_image(worker);
+ red_cursor_load(worker);
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+ break;
+ case RED_WORKER_MESSAGE_STOP: {
+ red_printf("stop");
+ ASSERT(worker->running);
+ worker->running = FALSE;
+ red_display_clear_glz_drawables(worker->display_channel);
+ red_current_flush(worker);
+ red_cursor_flush(worker);
+ red_wait_outgoiong_item((RedChannel *)worker->display_channel);
+ red_wait_outgoiong_item((RedChannel *)worker->cursor_channel);
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+ break;
+ }
+ case RED_WORKER_MESSAGE_START:
+ red_printf("start");
+ ASSERT(!worker->running);
+ if (worker->cursor_channel) {
+ worker->cursor_channel->base.migrate = FALSE;
+ }
+ if (worker->display_channel) {
+ worker->display_channel->base.migrate = FALSE;
+ }
+ worker->running = TRUE;
+ break;
+ case RED_WORKER_MESSAGE_DISPLAY_MIGRATE:
+ red_printf("migrate");
+ red_migrate_display(worker);
+ break;
+ case RED_WORKER_MESSAGE_CURSOR_CONNECT: {
+ RedsStreamContext *peer;
+ int migrate;
+
+ red_printf("cursor connect");
+ receive_data(worker->channel, &peer, sizeof(RedsStreamContext *));
+ receive_data(worker->channel, &migrate, sizeof(int));
+ red_connect_cursor(worker, peer, migrate);
+ break;
+ }
+ case RED_WORKER_MESSAGE_CURSOR_DISCONNECT:
+ red_printf("cursor disconnect");
+ red_disconnect_cursor((RedChannel *)worker->cursor_channel);
+ break;
+ case RED_WORKER_MESSAGE_CURSOR_MIGRATE:
+ red_printf("cursor migrate");
+ red_migrate_cursor(worker);
+ break;
+ case RED_WORKER_MESSAGE_SET_COMPRESSION:
+ receive_data(worker->channel, &worker->image_compression, sizeof(image_compression_t));
+ switch (worker->image_compression) {
+ case IMAGE_COMPRESS_AUTO_LZ:
+ red_printf("ic auto_lz");
+ break;
+ case IMAGE_COMPRESS_AUTO_GLZ:
+ red_printf("ic auto_glz");
+ break;
+ case IMAGE_COMPRESS_QUIC:
+ red_printf("ic quic");
+ break;
+ case IMAGE_COMPRESS_LZ:
+ red_printf("ic lz");
+ break;
+ case IMAGE_COMPRESS_GLZ:
+ red_printf("ic glz");
+ break;
+ case IMAGE_COMPRESS_OFF:
+ red_printf("ic off");
+ break;
+ default:
+ red_printf("ic invalid");
+ }
+#ifdef COMPRESS_STAT
+ print_compress_stats(worker->display_channel);
+ if (worker->display_channel) {
+ stat_reset(&worker->display_channel->quic_stat);
+ stat_reset(&worker->display_channel->lz_stat);
+ stat_reset(&worker->display_channel->glz_stat);
+ }
+#endif
+ break;
+ case RED_WORKER_MESSAGE_SET_STREAMING_VIDEO:
+ receive_data(worker->channel, &worker->streaming_video, sizeof(uint32_t));
+ red_printf("sv %u", worker->streaming_video);
+ break;
+ case RED_WORKER_MESSAGE_SET_MOUSE_MODE:
+ receive_data(worker->channel, &worker->mouse_mode, sizeof(uint32_t));
+ red_printf("mouse mode %u", worker->mouse_mode);
+ break;
+ default:
+ red_error("message error");
+ }
+}
+
+static void default_draw_context_destroy(void *canvas)
+{
+}
+
+static void red_init(RedWorker *worker, WorkerInitData *init_data)
+{
+ struct epoll_event event;
+ RedWorkeMessage message;
+ int epoll;
+
+ ASSERT(sizeof(CursorItem) <= QXL_CURSUR_DEVICE_DATA_SIZE);
+
+ memset(worker, 0, sizeof(RedWorker));
+ worker->qxl = init_data->qxl_interface;
+ worker->id = init_data->id;
+ worker->channel = init_data->channel;
+ worker->pending = init_data->pending;
+ worker->dev_listener.refs = 1;
+ worker->dev_listener.action = handle_dev_input;
+ worker->cursor_visible = TRUE;
+ worker->draw_context.destroy = default_draw_context_destroy;
+ ASSERT(init_data->num_renderers > 0);
+ worker->num_renderers = init_data->num_renderers;
+ memcpy(worker->renderers, init_data->renderers, sizeof(worker->renderers));
+ worker->mouse_mode = RED_MOUSE_MODE_SERVER;
+ worker->image_compression = init_data->image_compression;
+ worker->streaming_video = init_data->streaming_video;
+ ring_init(&worker->current_list);
+ ring_init(&worker->current);
+ image_cache_init(&worker->image_cache);
+ drawables_init(worker);
+ red_init_streams(worker);
+ stat_init(&worker->add_stat, add_stat_name);
+ stat_init(&worker->exclude_stat, exclude_stat_name);
+ stat_init(&worker->__exclude_stat, __exclude_stat_name);
+#ifdef RED_STATISTICS
+ char worker_str[20];
+ sprintf(worker_str, "display[%d]", worker->id);
+ worker->stat = stat_add_node(INVALID_STAT_REF, worker_str, TRUE);
+ worker->wakeup_counter = stat_add_counter(worker->stat, "wakeups", TRUE);
+ worker->command_counter = stat_add_counter(worker->stat, "commands", TRUE);
+#endif
+ if ((epoll = epoll_create(MAX_EPOLL_SOURCES)) == -1) {
+ red_error("epoll_create failed, %s", strerror(errno));
+ }
+ worker->epoll = epoll;
+
+ event.events = EPOLLIN;
+ event.data.ptr = &worker->dev_listener;
+
+ if (epoll_ctl(epoll, EPOLL_CTL_ADD, worker->channel, &event) == -1) {
+ red_error("add channel failed, %s", strerror(errno));
+ }
+
+ message = RED_WORKER_MESSAGE_READY;
+ write_message(worker->channel, &message);
+}
+
+void *red_worker_main(void *arg)
+{
+ RedWorker worker;
+
+ red_printf("begin");
+ ASSERT(MAX_PIPE_SIZE > WIDE_CLIENT_ACK_WINDOW &&
+ MAX_PIPE_SIZE > NARROW_CLIENT_ACK_WINDOW); //ensure wakeup by ack message
+ ASSERT(QXL_BITMAP_TOP_DOWN == BITMAP_TOP_DOWN);
+
+#if defined(RED_WORKER_STAT) || defined(COMPRESS_STAT)
+ if (pthread_getcpuclockid(pthread_self(), &clock_id)) {
+ red_error("pthread_getcpuclockid failed");
+ }
+#endif
+
+ avcodec_init();
+ avcodec_register_all();
+ red_init(&worker, (WorkerInitData *)arg);
+ red_init_quic(&worker);
+ red_init_lz(&worker);
+ worker.epoll_timeout = INF_EPOLL_WAIT;
+ for (;;) {
+ struct epoll_event events[MAX_EPOLL_SOURCES];
+ int num_events;
+ struct epoll_event *event;
+ struct epoll_event *end;
+
+ worker.epoll_timeout = MIN(red_get_streams_timout(&worker), worker.epoll_timeout);
+ num_events = epoll_wait(worker.epoll, events, MAX_EPOLL_SOURCES, worker.epoll_timeout);
+ red_handle_streams_timout(&worker);
+ if (worker.display_channel && worker.display_channel->glz_dict) {
+ /* during migration, in the dest, the display channel can be initialized
+ while the global lz data not since migrate data msg hasn't been
+ recieved yet */
+ red_display_handle_glz_drawables_to_free(worker.display_channel);
+ }
+ worker.epoll_timeout = INF_EPOLL_WAIT;
+ if (num_events == -1) {
+ if (errno != EINTR) {
+ red_error("poll_wait failed, %s", strerror(errno));
+ }
+ num_events = 0;
+ }
+
+ for (event = events, end = event + num_events; event < end; event++) {
+ EventListener *evt_listener = (EventListener *)event->data.ptr;
+ evt_listener->refs++;
+ }
+
+ for (event = events, end = event + num_events; event < end; event++) {
+ EventListener *evt_listener = (EventListener *)event->data.ptr;
+
+ if (evt_listener->refs > 1) {
+ evt_listener->action(evt_listener, event->events);
+ if (--evt_listener->refs) {
+ continue;
+ }
+ }
+ free(evt_listener);
+ }
+
+ if (worker.attached && worker.running) {
+ red_process_cursor(&worker, MAX_PIPE_SIZE);
+ red_process_commands(&worker, MAX_PIPE_SIZE);
+ }
+ red_push(&worker);
+ }
+ red_printf("exit");
+ return 0;
+}
+
+#ifdef DUMP_BITMAP
+#include <stdio.h>
+static void dump_palette(FILE *f, Palette* plt)
+{
+ int i;
+ for (i = 0; i < plt->num_ents; i++) {
+ fwrite(plt->ents + i, sizeof(uint32_t), 1, f);
+ }
+}
+
+static void dump_line(FILE *f, uint8_t* line, uint16_t n_pixel_bits, int width, int row_size)
+{
+ int i;
+ int copy_bytes_size = ALIGN(n_pixel_bits * width, 8) / 8;
+
+ fwrite(line, 1, copy_bytes_size, f);
+ if (row_size > copy_bytes_size) {
+ // each line should be 4 bytes aligned
+ for (i = copy_bytes_size; i < row_size; i++) {
+ fprintf(f, "%c", 0);
+ }
+ }
+}
+
+#define RAM_PATH "/tmp/tmpfs"
+
+static void dump_bitmap(RedWorker *worker, Bitmap *bitmap)
+{
+ static uint32_t file_id = 0;
+
+ char file_str[200];
+ int rgb = TRUE;
+ uint16_t n_pixel_bits;
+ Palette *plt = NULL;
+ uint32_t id;
+ int row_size;
+ uint32_t file_size;
+ long address_delta = worker->dev_info.phys_delta;
+ int alpha = 0;
+ uint32_t header_size = 14 + 40;
+ uint32_t bitmap_data_offset;
+ uint32_t tmp_u32;
+ int32_t tmp_32;
+ uint16_t tmp_u16;
+ FILE *f;
+
+ switch (bitmap->format) {
+ case BITMAP_FMT_1BIT_BE:
+ case BITMAP_FMT_1BIT_LE:
+ rgb = FALSE;
+ n_pixel_bits = 1;
+ break;
+ case BITMAP_FMT_4BIT_BE:
+ case BITMAP_FMT_4BIT_LE:
+ rgb = FALSE;
+ n_pixel_bits = 4;
+ break;
+ case BITMAP_FMT_8BIT:
+ rgb = FALSE;
+ n_pixel_bits = 8;
+ break;
+ case BITMAP_FMT_16BIT:
+ n_pixel_bits = 16;
+ break;
+ case BITMAP_FMT_24BIT:
+ n_pixel_bits = 24;
+ break;
+ case BITMAP_FMT_32BIT:
+ n_pixel_bits = 32;
+ break;
+ case BITMAP_FMT_RGBA:
+ n_pixel_bits = 32;
+ alpha = 1;
+ break;
+ default:
+ red_error("invalid bitmap format %u", bitmap->format);
+ }
+
+ if (!rgb) {
+ if (!bitmap->palette) {
+ return; // dont dump masks.
+ }
+ plt = (Palette *)(bitmap->palette + address_delta);
+ }
+ row_size = (((bitmap->x * n_pixel_bits) + 31) / 32) * 4;
+ bitmap_data_offset = header_size;
+
+ if (plt) {
+ bitmap_data_offset += plt->num_ents * 4;
+ }
+ file_size = bitmap_data_offset + (bitmap->y * row_size);
+
+ id = ++file_id;
+ sprintf(file_str, "%s/%u.bmp", RAM_PATH, id);
+
+ f = fopen(file_str, "wb");
+ if (!f) {
+ red_error("Error creating bmp\n");
+ return;
+ }
+
+ /* writing the bmp v3 header */
+ fprintf(f, "BM");
+ fwrite(&file_size, sizeof(file_size), 1, f);
+ tmp_u16 = alpha ? 1 : 0;
+ fwrite(&tmp_u16, sizeof(tmp_u16), 1, f); // reserved for application
+ tmp_u16 = 0;
+ fwrite(&tmp_u16, sizeof(tmp_u16), 1, f);
+ fwrite(&bitmap_data_offset, sizeof(bitmap_data_offset), 1, f);
+ tmp_u32 = header_size - 14;
+ fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // sub header size
+ tmp_32 = bitmap->x;
+ fwrite(&tmp_32, sizeof(tmp_32), 1, f);
+ tmp_32 = bitmap->y;
+ fwrite(&tmp_32, sizeof(tmp_32), 1, f);
+
+ tmp_u16 = 1;
+ fwrite(&tmp_u16, sizeof(tmp_u16), 1, f); // color plane
+ fwrite(&n_pixel_bits, sizeof(n_pixel_bits), 1, f); // pixel depth
+
+ tmp_u32 = 0;
+ fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // compression method
+
+ tmp_u32 = 0; //file_size - bitmap_data_offset;
+ fwrite(&tmp_u32, sizeof(tmp_u32), 1, f); // image size
+ tmp_32 = 0;
+ fwrite(&tmp_32, sizeof(tmp_32), 1, f);
+ fwrite(&tmp_32, sizeof(tmp_32), 1, f);
+ tmp_u32 = (!plt) ? 0 : plt->num_ents; // plt entries
+ fwrite(&tmp_u32, sizeof(tmp_u32), 1, f);
+ tmp_u32 = 0;
+ fwrite(&tmp_u32, sizeof(tmp_u32), 1, f);
+
+ if (plt) {
+ dump_palette(f, plt);
+ }
+ /* writing the data */
+ if ((bitmap->flags & QXL_BITMAP_DIRECT)) {
+ uint8_t *lines = (uint8_t*)(bitmap->data + address_delta);
+ int i;
+ for (i = 0; i < bitmap->y; i++) {
+ dump_line(f, lines + (i * bitmap->stride), n_pixel_bits, bitmap->x, row_size);
+ }
+ } else {
+ QXLDataChunk *chunk = NULL;
+ int num_lines;
+ ADDRESS relative_address = bitmap->data;
+
+ while (relative_address) {
+ int i;
+ chunk = (QXLDataChunk *)(relative_address + address_delta);
+ num_lines = chunk->data_size / bitmap->stride;
+ for (i = 0; i < num_lines; i++) {
+ dump_line(f, chunk->data + (i * bitmap->stride), n_pixel_bits, bitmap->x, row_size);
+ }
+ relative_address = chunk->next_chunk;
+ }
+ }
+ fclose(f);
+}
+
+#endif
+
diff --git a/server/red_worker.h b/server/red_worker.h
new file mode 100644
index 00000000..097d4540
--- /dev/null
+++ b/server/red_worker.h
@@ -0,0 +1,134 @@
+/*
+ Copyright (C) 2009 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 _H_REDWORKER
+#define _H_REDWORKER
+
+#include "red_common.h"
+
+
+static inline void set_bit(int index, uint32_t *addr)
+{
+ __asm__ __volatile__ ("lock btsl %1, %0": : "m" (*addr), "r" (index));
+}
+
+static inline void clear_bit(int index, uint32_t *addr)
+{
+ __asm__ __volatile__ ("lock btrl %1, %0": : "m" (*addr), "r" (index));
+}
+
+static inline int test_bit(int index, uint32_t val)
+{
+ return val & (1u << index);
+}
+
+enum {
+ RED_WORKER_PENDING_WAKEUP,
+ RED_WORKER_PENDING_OOM,
+};
+
+enum {
+ RED_WORKER_MESSAGE_NOP,
+ RED_WORKER_MESSAGE_UPDATE,
+ RED_WORKER_MESSAGE_WAKEUP,
+ RED_WORKER_MESSAGE_OOM,
+ RED_WORKER_MESSAGE_ATTACH,
+ RED_WORKER_MESSAGE_DETACH,
+ RED_WORKER_MESSAGE_READY,
+ RED_WORKER_MESSAGE_DISPLAY_CONNECT,
+ RED_WORKER_MESSAGE_DISPLAY_DISCONNECT,
+ RED_WORKER_MESSAGE_DISPLAY_MIGRATE,
+ RED_WORKER_MESSAGE_SAVE,
+ RED_WORKER_MESSAGE_LOAD,
+ RED_WORKER_MESSAGE_START,
+ RED_WORKER_MESSAGE_STOP,
+ RED_WORKER_MESSAGE_CURSOR_CONNECT,
+ RED_WORKER_MESSAGE_CURSOR_DISCONNECT,
+ RED_WORKER_MESSAGE_CURSOR_MIGRATE,
+ RED_WORKER_MESSAGE_SET_COMPRESSION,
+ RED_WORKER_MESSAGE_SET_STREAMING_VIDEO,
+ RED_WORKER_MESSAGE_SET_MOUSE_MODE,
+};
+
+typedef uint32_t RedWorkeMessage;
+
+#define RED_MAX_RENDERERS 4
+
+enum {
+ RED_RENDERER_INVALID,
+ RED_RENDERER_CAIRO,
+ RED_RENDERER_OGL_PBUF,
+ RED_RENDERER_OGL_PIXMAP,
+};
+
+typedef struct WorkerInitData {
+ struct QXLInterface *qxl_interface;
+ int id;
+ int channel;
+ uint32_t *pending;
+ uint32_t num_renderers;
+ uint32_t renderers[RED_MAX_RENDERERS];
+ image_compression_t image_compression;
+ int streaming_video;
+} WorkerInitData;
+
+void *red_worker_main(void *arg);
+
+static inline void send_data(int fd, void *in_buf, int n)
+{
+ uint8_t *buf = in_buf;
+ do {
+ int now;
+ if ((now = write(fd, buf, n)) == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ red_error("%s", strerror(errno));
+ }
+ buf += now;
+ n -= now;
+ } while (n);
+}
+
+static inline void write_message(int fd, RedWorkeMessage *message)
+{
+ send_data(fd, message, sizeof(RedWorkeMessage));
+}
+
+static inline void receive_data(int fd, void *in_buf, int n)
+{
+ uint8_t *buf = in_buf;
+ do {
+ int now;
+ if ((now = read(fd, buf, n)) == -1) {
+ if (errno == EINTR) {
+ continue;
+ }
+ red_error("%s", strerror(errno));
+ }
+ buf += now;
+ n -= now;
+ } while (n);
+}
+
+static inline void read_message(int fd, RedWorkeMessage *message)
+{
+ receive_data(fd, message, sizeof(RedWorkeMessage));
+}
+
+#endif
+
diff --git a/server/red_yuv.h b/server/red_yuv.h
new file mode 100644
index 00000000..9a2242cc
--- /dev/null
+++ b/server/red_yuv.h
@@ -0,0 +1,154 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#if defined(YUV32)
+#define PIXEL_SIZE 4
+#define R(pixel) (((uint8_t*)(pixel))[0])
+#define G(pixel) (((uint8_t*)(pixel))[1])
+#define B(pixel) (((uint8_t*)(pixel))[2])
+
+#define FUNC_NAME(name) name##_32bpp
+
+#elif defined(YUV24)
+#define PIXEL_SIZE 3
+#define R(pixel) (((uint8_t*)(pixel))[0])
+#define G(pixel) (((uint8_t*)(pixel))[1])
+#define B(pixel) (((uint8_t*)(pixel))[2])
+
+#define FUNC_NAME(name) name##_24bpp
+
+#elif defined(YUV16)
+#define PIXEL_SIZE 2
+#define PIX16(pixel) (*(uint16_t*)(pixel))
+
+#define R(pixel) ((PIX16(pixel) << 3) & 0xff)
+#define G(pixel) ((PIX16(pixel) >> 2) & 0xff)
+#define B(pixel) ((PIX16(pixel) >> 7) & 0xff)
+
+#define FUNC_NAME(name) name##_16bpp
+
+#else
+#error "invalid format."
+#endif
+
+#define Y(pixel) (((66 * R(pixel) + 129 * G(pixel) + 25 * B(pixel) + 128) >> 8) + 16)
+#define U(pixel) (((-38 * R(pixel) - 74 * G(pixel) + 112 * B(pixel) + 128) >> 8) + 128)
+#define V(pixel) (((112 * R(pixel) - 94 * G(pixel) - 18 * B(pixel) + 128) >> 8) + 128)
+
+static inline void FUNC_NAME(red_rgb_to_yuv420_line)(const uint8_t* line0, const uint8_t* line1,
+ const uint32_t width, uint8_t* y, uint8_t *u,
+ uint8_t *v, int y_stride)
+{
+ int i;
+
+ // Y = (0.257 * R) + (0.504 * G) + (0.098 * B) + 16
+ // Cb = U = -(0.148 * R) - (0.291 * G) + (0.439 * B) + 128
+ // Cr = V = (0.439 * R) - (0.368 * G) - (0.071 * B) + 128
+
+ for (i = 0; i < width / 2; i++) {
+ *y = Y(line0);
+ *(y + 1) = Y(line0 + PIXEL_SIZE);
+ *(y + y_stride) = Y(line1);
+ *(y + y_stride + 1) = Y(line1 + PIXEL_SIZE);
+
+ u[i] = (U(line0) + U(line0 + PIXEL_SIZE) + U(line1) + U(line1 + PIXEL_SIZE)) / 4;
+ v[i] = (V(line0) + V(line0 + PIXEL_SIZE) + V(line1) + V(line1 + PIXEL_SIZE)) / 4;
+
+ line0 += 2 * PIXEL_SIZE;
+ line1 += 2 * PIXEL_SIZE;
+ y += 2;
+ }
+
+ if ((width & 1)) {
+ *y = Y(line0);
+ *(y + 1) = *y;
+ *(y + y_stride) = Y(line1);
+ *(y + y_stride + 1) = *(y + y_stride);
+ u[i] = (U(line0) + U(line1)) / 2;
+ v[i] = (V(line0) + V(line1)) / 2;
+ }
+}
+
+static inline int FUNC_NAME(red_rgb_to_yuv420)(const Rect *src, const Bitmap *image,
+ AVFrame *frame, long phys_delta, int id,
+ Stream *stream)
+{
+ QXLDataChunk *chunk;
+ uint32_t image_stride;
+ int y_stride;
+ uint8_t* y;
+ uint8_t* u;
+ uint8_t* v;
+ int offset;
+ int i;
+
+ y = frame->data[0];
+ u = frame->data[1];
+ v = frame->data[2];
+ y_stride = frame->linesize[0];
+
+ offset = 0;
+ chunk = (QXLDataChunk *)(image->data + phys_delta);
+ image_stride = image->stride;
+
+ const int skip_lines = stream->top_down ? src->top : image->y - (src->bottom - 0);
+ for (i = 0; i < skip_lines; i++) {
+ red_get_image_line(&chunk, &offset, image_stride, phys_delta);
+ }
+
+ const int image_hight = src->bottom - src->top;
+ const int image_width = src->right - src->left;
+ for (i = 0; i < image_hight / 2; i++) {
+ uint8_t* line0 = red_get_image_line(&chunk, &offset, image_stride, phys_delta);
+ uint8_t* line1 = red_get_image_line(&chunk, &offset, image_stride, phys_delta);
+
+ if (!line0 || !line1) {
+ return FALSE;
+ }
+
+ line0 += src->left * PIXEL_SIZE;
+ line1 += src->left * PIXEL_SIZE;
+
+ FUNC_NAME(red_rgb_to_yuv420_line)(line0, line1, image_width, y, u, v, y_stride);
+
+ y += 2 * y_stride;
+ u += frame->linesize[1];
+ v += frame->linesize[2];
+ }
+
+ if ((image_hight & 1)) {
+ uint8_t* line = red_get_image_line(&chunk, &offset, image_stride, phys_delta);
+ if (!line) {
+ return FALSE;
+ }
+ line += src->left * PIXEL_SIZE;
+ FUNC_NAME(red_rgb_to_yuv420_line)(line, line, image_width, y, u, v, y_stride);
+ }
+ return TRUE;
+}
+
+
+#undef R
+#undef G
+#undef B
+#undef Y
+#undef U
+#undef V
+#undef FUNC_NAME
+#undef PIXEL_SIZE
+#undef PIX16
+
diff --git a/server/reds.c b/server/reds.c
new file mode 100644
index 00000000..067304d7
--- /dev/null
+++ b/server/reds.c
@@ -0,0 +1,4996 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <stdint.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/tcp.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <limits.h>
+#include <time.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/user.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <ctype.h>
+
+#include <openssl/bio.h>
+#include <openssl/pem.h>
+#include <openssl/bn.h>
+#include <openssl/rsa.h>
+#include <openssl/ssl.h>
+#include <openssl/err.h>
+
+#include "spice.h"
+#include "reds.h"
+#include "red.h"
+#include "vd_agent.h"
+
+#include "red_common.h"
+#include "red_dispatcher.h"
+#include "snd_worker.h"
+#include "reds_stat.h"
+#include "stat.h"
+#include "ring.h"
+#include "config.h"
+
+CoreInterface *core = NULL;
+static MigrationInterface *mig = NULL;
+static KeyboardInterface *keyboard = NULL;
+static MouseInterface *mouse = NULL;
+static TabletInterface *tablet = NULL;
+static VDIPortInterface *vdagent = NULL;
+
+#define MIGRATION_NOTIFY_SPICE_KEY "spice_mig_ext"
+
+#define REDS_MIG_VERSION 1
+#define REDS_MIG_CONTINUE 1
+#define REDS_MIG_ABORT 2
+
+#define REDS_AGENT_WINDOW_SIZE 10
+#define REDS_TOKENS_TO_SEND 5
+#define REDS_NUM_INTERNAL_AGENT_MESSAGES 1
+#define REDS_VDI_PORT_NUM_RECIVE_BUFFS 5
+#define REDS_MAX_SEND_IOVEC 100
+
+#define NET_TEST_WARMUP_BYTES 0
+#define NET_TEST_BYTES (1024 * 250)
+
+static int spice_port = -1;
+static int spice_secure_port = -1;
+
+static struct in_addr spice_addr = {INADDR_ANY};
+static int ticketing_enabled = 1; //Ticketing is enabled by default
+static pthread_mutex_t *lock_cs;
+static long *lock_count;
+uint32_t streaming_video = TRUE;
+image_compression_t image_compression = IMAGE_COMPRESS_AUTO_GLZ;
+int agent_mouse = TRUE;
+
+static void openssl_init();
+
+#define MIGRATE_TIMEOUT (1000 * 10) /* 10sec */
+#define PING_INTERVAL (1000 * 10)
+#define KEY_MODIFIERS_TTL (1000 * 2) /*2sec*/
+#define MM_TIMER_GRANULARITY_MS (1000 / 30)
+#define MM_TIME_DELTA 400 /*ms*/
+
+// approximate max recive message size
+#define RECIVE_BUF_SIZE \
+ (4096 + (REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES) * RED_AGENT_MAX_DATA_SIZE)
+
+#define SEND_BUF_SIZE 4096
+
+#define SCROLL_LOCK_SCAN_CODE 0x46
+#define NUM_LOCK_SCAN_CODE 0x45
+#define CAPS_LOCK_SCAN_CODE 0x3a
+
+typedef struct IncomingHandler {
+ void *opaque;
+ int shut;
+ uint8_t buf[RECIVE_BUF_SIZE];
+ uint32_t end_pos;
+ void (*handle_message)(void *opaque, RedDataHeader *message);
+} IncomingHandler;
+
+typedef struct OutgoingHandler {
+ void *opaque;
+ uint8_t buf[SEND_BUF_SIZE];
+ uint8_t *now;
+ uint32_t length;
+ void (*select)(void *opaque, int select);
+ void (*may_write)(void *opaque);
+} OutgoingHandler;
+
+typedef struct TicketAuthentication {
+ char password[RED_MAX_PASSWORD_LENGTH];
+ time_t expiration_time;
+} TicketAuthentication;
+
+static TicketAuthentication taTicket;
+
+typedef struct TicketInfo {
+ RSA *rsa;
+ int rsa_size;
+ BIGNUM *bn;
+ RedLinkEncryptedTicket encrypted_ticket;
+} TicketInfo;
+
+typedef struct MonitorMode {
+ uint32_t x_res;
+ uint32_t y_res;
+} MonitorMode;
+
+typedef struct RedsOutItem RedsOutItem;
+struct RedsOutItem {
+ RingItem link;
+ void (*prepare)(RedsOutItem *item, struct iovec* vec, int *len);
+ void (*release)(RedsOutItem *item);
+};
+
+typedef struct VDIReadBuf {
+ RedsOutItem out_item;
+ int len;
+ RedDataHeader header;
+ uint8_t data[RED_AGENT_MAX_DATA_SIZE];
+} VDIReadBuf;
+
+enum {
+ VDI_PORT_READ_STATE_READ_HADER,
+ VDI_PORT_READ_STATE_GET_BUFF,
+ VDI_PORT_READ_STATE_READ_DATA,
+};
+
+enum {
+ VDP_CLIENT_PORT = 1,
+ VDP_SERVER_PORT,
+};
+
+typedef struct __attribute__ ((__packed__)) VDIChunkHeader {
+ uint32_t port;
+ uint32_t size;
+} VDIChunkHeader;
+
+typedef struct VDIPortState {
+ VDIPortPlug plug;
+ VDObjectRef plug_ref;
+ uint32_t plug_generation;
+
+ uint32_t num_tokens;
+ uint32_t num_client_tokens;
+ Ring external_bufs;
+ Ring internal_bufs;
+ Ring write_queue;
+
+ Ring read_bufs;
+ uint32_t read_state;
+ uint32_t message_recive_len;
+ uint8_t *recive_pos;
+ uint32_t recive_len;
+ VDIReadBuf *current_read_buf;
+
+ VDIChunkHeader vdi_chunk_header;
+
+ int client_agent_started;
+ uint32_t send_tokens;
+} VDIPortState;
+
+typedef struct InputsState {
+ Channel *channel;
+ RedsStreamContext *peer;
+ uint8_t buf[RECIVE_BUF_SIZE];
+ uint32_t end_pos;
+ IncomingHandler in_handler;
+ OutgoingHandler out_handler;
+ VDAgentMouseState mouse_state;
+ int pending_mouse_event;
+ uint32_t motion_count;
+ uint64_t serial; //migrate me
+} InputsState;
+
+typedef struct RedsOutgoingData {
+ Ring pipe;
+ RedsOutItem *item;
+ int vec_size;
+ struct iovec vec_buf[REDS_MAX_SEND_IOVEC];
+ struct iovec *vec;
+} RedsOutgoingData;
+
+enum NetTestStage {
+ NET_TEST_STAGE_INVALID,
+ NET_TEST_STAGE_WARMUP,
+ NET_TEST_STAGE_LATENCY,
+ NET_TEST_STAGE_RATE,
+};
+
+#ifdef RED_STATISTICS
+
+#define REDS_MAX_STAT_NODES 100
+#define REDS_STAT_SHM_SIZE (sizeof(RedsStat) + REDS_MAX_STAT_NODES * sizeof(StatNode))
+
+typedef struct RedsStatValue {
+ uint32_t value;
+ uint32_t min;
+ uint32_t max;
+ uint32_t average;
+ uint32_t count;
+} RedsStatValue;
+
+#endif
+
+typedef struct RedsState {
+ int listen_socket;
+ int secure_listen_socket;
+ RedsStreamContext *peer;
+ int disconnecting;
+ uint32_t link_id;
+ uint64_t serial; //migrate me
+ VDIPortState agent_state;
+ InputsState *inputs_state;
+
+ VDObjectRef mig_notifier;
+ int mig_wait_connect;
+ int mig_wait_disconnect;
+ int mig_inprogress;
+ int mig_target;
+ int num_of_channels;
+ IncomingHandler in_handler;
+ RedsOutgoingData outgoing;
+ Channel *channels;
+ int mouse_mode;
+ int is_client_mouse_allowed;
+ int dispatcher_allows_client_mouse;
+ MonitorMode monitor_mode;
+ VDObjectRef mig_timer;
+ VDObjectRef key_modifiers_timer;
+ VDObjectRef mm_timer;
+
+ TicketAuthentication taTicket;
+ SSL_CTX *ctx;
+
+#ifdef RED_STATISTICS
+ char *stat_shm_name;
+ RedsStat *stat;
+ pthread_mutex_t stat_lock;
+ RedsStatValue roundtrip_stat;
+ VDObjectRef ping_timer;
+ int ping_interval;
+#endif
+ uint32_t ping_id;
+ uint32_t net_test_id;
+ int net_test_stage;
+} RedsState;
+
+uint64_t bitrate_per_sec = ~0;
+static uint64_t letancy = 0;
+
+static RedsState *reds = NULL;
+
+typedef struct AsyncRead {
+ RedsStreamContext *peer;
+ void *opaque;
+ uint8_t *now;
+ uint8_t *end;
+ int active_file_handlers;
+ void (*done)(void *opaque);
+ void (*error)(void *opaque, int err);
+} AsyncRead;
+
+typedef struct RedLinkInfo {
+ RedsStreamContext *peer;
+ AsyncRead asyc_read;
+ RedLinkHeader link_header;
+ RedLinkMess *link_mess;
+ int mess_pos;
+ TicketInfo tiTicketing;
+} RedLinkInfo;
+
+typedef struct VDIPortBuf VDIPortBuf;
+struct __attribute__ ((__packed__)) VDIPortBuf {
+ RingItem link;
+ uint8_t *now;
+ int write_len;
+ void (*free)(VDIPortBuf *buf);
+ VDIChunkHeader chunk_header; //start send from &chunk_header
+};
+
+typedef struct __attribute__ ((__packed__)) VDAgentExtBuf {
+ VDIPortBuf base;
+ uint8_t buf[RED_AGENT_MAX_DATA_SIZE];
+ VDIChunkHeader migrate_overflow;
+} VDAgentExtBuf;
+
+typedef struct __attribute__ ((__packed__)) VDInternalBuf {
+ VDIPortBuf base;
+ VDAgentMessage header;
+ union {
+ VDAgentMouseState mouse_state;
+ }
+ u;
+ VDIChunkHeader migrate_overflow;
+} VDInternalBuf;
+
+typedef struct RedSSLParameters {
+ char keyfile_password[256];
+ char certs_file[256];
+ char private_key_file[256];
+ char ca_certificate_file[256];
+ char dh_key_file[256];
+ char ciphersuite[256];
+} RedSSLParameters;
+
+#define CHANNEL_SECURITY_NON (1 << 0)
+#define CHANNEL_SECURITY_SSL (1 << 1)
+
+typedef struct ChannelSecurityOptions ChannelSecurityOptions;
+struct ChannelSecurityOptions {
+ uint32_t channel_id;
+ uint32_t options;
+ ChannelSecurityOptions *next;
+};
+
+typedef struct PingItem {
+ RedsOutItem base;
+ RedDataHeader header;
+ RedPing ping;
+ int size;
+} PingItem;
+
+static uint8_t zero_page[PAGE_SIZE] = {0};
+
+static void reds_main_write(void *data);
+static void reds_push();
+
+static ChannelSecurityOptions *channels_security = NULL;
+static int default_channel_security = CHANNEL_SECURITY_NON | CHANNEL_SECURITY_SSL;
+
+static RedSSLParameters ssl_parameters;
+
+
+void (*log_proc)(CoreInterface *core, LogLevel level, const char* component,
+ const char* format, ...) = NULL;
+
+#define LOG_MESSAGE(level, format, ...) { \
+ if (log_proc) { \
+ log_proc(core, level, "spice", format, ## __VA_ARGS__ ); \
+ } \
+}
+
+static ChannelSecurityOptions *find_channel_security(int id)
+{
+ ChannelSecurityOptions *now = channels_security;
+ while (now && now->channel_id != id) {
+ now = now->next;
+ }
+ return now;
+}
+
+static int reds_write(void *ctx, void *buf, size_t size)
+{
+ int return_code;
+ int sock = (long)ctx;
+ size_t count = size;
+
+ return_code = write(sock, buf, count);
+
+ return (return_code);
+}
+
+static int reds_read(void *ctx, void *buf, size_t size)
+{
+ int return_code;
+ int sock = (long)ctx;
+ size_t count = size;
+
+ return_code = read(sock, buf, count);
+
+ return (return_code);
+}
+
+static int reds_free(RedsStreamContext *peer)
+{
+ close(peer->socket);
+ free(peer);
+ return 0;
+}
+
+static int reds_ssl_write(void *ctx, void *buf, size_t size)
+{
+ int return_code;
+ int ssl_error;
+ SSL *ssl = ctx;
+
+ return_code = SSL_write(ssl, buf, size);
+
+ if (return_code < 0) {
+ ssl_error = SSL_get_error(ssl, return_code);
+ }
+
+ return (return_code);
+}
+
+static int reds_ssl_read(void *ctx, void *buf, size_t size)
+{
+ int return_code;
+ int ssl_error;
+ SSL *ssl = ctx;
+
+ return_code = SSL_read(ssl, buf, size);
+
+ if (return_code < 0) {
+ ssl_error = SSL_get_error(ssl, return_code);
+ }
+
+ return (return_code);
+}
+
+static int reds_ssl_writev(void *ctx, const struct iovec *vector, int count)
+{
+ int i;
+ int n;
+ int return_code = 0;
+ int ssl_error;
+ SSL *ssl = ctx;
+
+ for (i = 0; i < count; ++i) {
+ n = SSL_write(ssl, vector[i].iov_base, vector[i].iov_len);
+ if (n <= 0) {
+ ssl_error = SSL_get_error(ssl, n);
+ if (return_code <= 0) {
+ return n;
+ } else {
+ break;
+ }
+ } else {
+ return_code += n;
+ }
+ }
+
+ return return_code;
+}
+
+static int reds_ssl_free(RedsStreamContext *peer)
+{
+ SSL_free(peer->ssl);
+ close(peer->socket);
+ free(peer);
+ return 0;
+}
+
+static void __reds_release_link(RedLinkInfo *link)
+{
+ ASSERT(link->peer);
+ core->set_file_handlers(core, link->peer->socket, NULL, NULL, NULL);
+ free(link->link_mess);
+ BN_free(link->tiTicketing.bn);
+ if (link->tiTicketing.rsa) {
+ RSA_free(link->tiTicketing.rsa);
+ }
+ free(link);
+}
+
+static inline void reds_release_link(RedLinkInfo *link)
+{
+ RedsStreamContext *peer = link->peer;
+ __reds_release_link(link);
+ peer->cb_free(peer);
+}
+
+static void reds_do_disable_ticketing(void)
+{
+ ticketing_enabled = 0;
+ memset(taTicket.password, 0, sizeof(taTicket.password));
+ core->term_printf(core, "Ticketing is now disabled.\n");
+}
+
+static char *base64decode(const char *input, int length)
+{
+ BIO *b64;
+ BIO *bmem;
+ int n;
+ char *buffer = (char *)malloc(length);
+ memset(buffer, 0, length);
+
+ char *inbuffer = (char *)malloc(length + 1);
+ memset(inbuffer, 0, length + 1);
+ memcpy(inbuffer, input, length);
+ inbuffer[length] = '\n';
+
+ b64 = BIO_new(BIO_f_base64());
+ bmem = BIO_new_mem_buf(inbuffer, length + 1);
+
+ if (b64 != NULL && bmem != NULL) {
+ bmem = BIO_push(b64, bmem);
+
+ n = BIO_read(bmem, buffer, length);
+
+ if (n != 0) {
+ buffer[n - 1] = '\0';
+ } else {
+ free(buffer);
+ buffer = NULL;
+ }
+ } else {
+ free(buffer);
+ buffer = NULL;
+ }
+
+ BIO_free_all(bmem);
+
+ return buffer;
+}
+
+static void reds_do_info_ticket(void)
+{
+ core->term_printf(core, "Ticket Information:");
+ if (ticketing_enabled) {
+ if (strlen(taTicket.password) == 0) {
+ core->term_printf(core, " blocked\n");
+ } else {
+ if (taTicket.expiration_time == INT_MAX) {
+ core->term_printf(core, " expiration NEVER\n");
+ } else {
+ time_t now;
+
+ time(&now);
+ int expired = taTicket.expiration_time < now;
+ if (expired) {
+ core->term_printf(core, " expiration EXPIRED\n");
+ } else {
+ core->term_printf(core, " expiration %s\n",
+ ctime((time_t *)&(taTicket.expiration_time)));
+ }
+ }
+ }
+ } else {
+ core->term_printf(core, " disabled\n");
+ }
+}
+
+static struct iovec *reds_iovec_skip(struct iovec vec[], int skip, int *vec_size)
+{
+ struct iovec *now = vec;
+
+ while (skip && skip >= now->iov_len) {
+ skip -= now->iov_len;
+ --*vec_size;
+ now++;
+ }
+ now->iov_base = (uint8_t *)now->iov_base + skip;
+ now->iov_len -= skip;
+ return now;
+}
+
+#ifdef RED_STATISTICS
+
+#define STAT_TAB_LEN 4
+#define STAT_VALUE_TABS 7
+
+static void print_stat_tree(uint32_t node_index, int depth)
+{
+ StatNode *node = &reds->stat->nodes[node_index];
+
+ if ((node->flags & STAT_NODE_MASK_SHOW) == STAT_NODE_MASK_SHOW) {
+ core->term_printf(core, "%*s%s", depth * STAT_TAB_LEN, "", node->name);
+ if (node->flags & STAT_NODE_FLAG_VALUE) {
+ core->term_printf(core, ":%*s%llu\n",
+ (STAT_VALUE_TABS - depth) * STAT_TAB_LEN - strlen(node->name) - 1, "",
+ node->value);
+ } else {
+ core->term_printf(core, "\n");
+ if (node->first_child_index != INVALID_STAT_REF) {
+ print_stat_tree(node->first_child_index, depth + 1);
+ }
+ }
+ }
+ if (node->next_sibling_index != INVALID_STAT_REF) {
+ print_stat_tree(node->next_sibling_index, depth);
+ }
+}
+
+static void do_info_statistics()
+{
+ core->term_printf(core, "Spice Statistics:\n");
+ print_stat_tree(reds->stat->root_index, 0);
+}
+
+static void do_reset_statistics()
+{
+ StatNode *node;
+ int i;
+
+ for (i = 0; i <= REDS_MAX_STAT_NODES; i++) {
+ node = &reds->stat->nodes[i];
+ if (node->flags & STAT_NODE_FLAG_VALUE) {
+ node->value = 0;
+ }
+ }
+}
+
+void insert_stat_node(StatNodeRef parent, StatNodeRef ref)
+{
+ StatNode *node = &reds->stat->nodes[ref];
+ uint32_t pos = INVALID_STAT_REF;
+ uint32_t node_index;
+ uint32_t *head;
+ StatNode *n;
+
+ node->first_child_index = INVALID_STAT_REF;
+ head = (parent == INVALID_STAT_REF ? &reds->stat->root_index :
+ &reds->stat->nodes[parent].first_child_index);
+ node_index = *head;
+ while (node_index != INVALID_STAT_REF && (n = &reds->stat->nodes[node_index]) &&
+ strcmp(node->name, n->name) > 0) {
+ pos = node_index;
+ node_index = n->next_sibling_index;
+ }
+ if (pos == INVALID_STAT_REF) {
+ node->next_sibling_index = *head;
+ *head = ref;
+ } else {
+ n = &reds->stat->nodes[pos];
+ node->next_sibling_index = n->next_sibling_index;
+ n->next_sibling_index = ref;
+ }
+}
+
+StatNodeRef stat_add_node(StatNodeRef parent, const char *name, int visible)
+{
+ StatNodeRef ref;
+ StatNode *node;
+
+ ASSERT(name && strlen(name) > 0);
+ if (strlen(name) >= sizeof(node->name)) {
+ return INVALID_STAT_REF;
+ }
+ pthread_mutex_lock(&reds->stat_lock);
+ ref = (parent == INVALID_STAT_REF ? reds->stat->root_index :
+ reds->stat->nodes[parent].first_child_index);
+ while (ref != INVALID_STAT_REF) {
+ node = &reds->stat->nodes[ref];
+ if (strcmp(name, node->name)) {
+ ref = node->next_sibling_index;
+ } else {
+ pthread_mutex_unlock(&reds->stat_lock);
+ return ref;
+ }
+ }
+ if (reds->stat->num_of_nodes >= REDS_MAX_STAT_NODES || reds->stat == NULL) {
+ pthread_mutex_unlock(&reds->stat_lock);
+ return INVALID_STAT_REF;
+ }
+ reds->stat->generation++;
+ reds->stat->num_of_nodes++;
+ for (ref = 0; ref <= REDS_MAX_STAT_NODES; ref++) {
+ node = &reds->stat->nodes[ref];
+ if (!(node->flags & STAT_NODE_FLAG_ENABLED)) {
+ break;
+ }
+ }
+ ASSERT(!(node->flags & STAT_NODE_FLAG_ENABLED));
+ node->value = 0;
+ node->flags = STAT_NODE_FLAG_ENABLED | (visible ? STAT_NODE_FLAG_VISIBLE : 0);
+ strncpy(node->name, name, sizeof(node->name));
+ insert_stat_node(parent, ref);
+ pthread_mutex_unlock(&reds->stat_lock);
+ return ref;
+}
+
+void stat_remove(StatNode *node)
+{
+ pthread_mutex_lock(&reds->stat_lock);
+ node->flags &= ~STAT_NODE_FLAG_ENABLED;
+ reds->stat->generation++;
+ reds->stat->num_of_nodes--;
+ pthread_mutex_unlock(&reds->stat_lock);
+}
+
+void stat_remove_node(StatNodeRef ref)
+{
+ stat_remove(&reds->stat->nodes[ref]);
+}
+
+uint64_t *stat_add_counter(StatNodeRef parent, const char *name, int visible)
+{
+ StatNodeRef ref = stat_add_node(parent, name, visible);
+ StatNode *node;
+
+ if (ref == INVALID_STAT_REF) {
+ return NULL;
+ }
+ node = &reds->stat->nodes[ref];
+ node->flags |= STAT_NODE_FLAG_VALUE;
+ return &node->value;
+}
+
+void stat_remove_counter(uint64_t *counter)
+{
+ stat_remove((StatNode *)(counter - offsetof(StatNode, value)));
+}
+
+static void reds_update_stat_value(RedsStatValue* stat_value, uint32_t value)
+{
+ stat_value->value = value;
+ stat_value->min = (stat_value->count ? MIN(stat_value->min, value) : value);
+ stat_value->max = MAX(stat_value->max, value);
+ stat_value->average = (stat_value->average * stat_value->count + value) /
+ (stat_value->count + 1);
+ stat_value->count++;
+}
+
+#endif
+
+void reds_register_channel(Channel *channel)
+{
+ ASSERT(reds);
+ channel->next = reds->channels;
+ reds->channels = channel;
+ reds->num_of_channels++;
+}
+
+void reds_unregister_channel(Channel *channel)
+{
+ Channel **now = &reds->channels;
+
+ while (*now) {
+ if (*now == channel) {
+ *now = channel->next;
+ reds->num_of_channels--;
+ return;
+ }
+ now = &(*now)->next;
+ }
+ red_printf("not found");
+}
+
+static Channel *reds_find_channel(uint32_t type, uint32_t id)
+{
+ Channel *channel = reds->channels;
+ while (channel && !(channel->type == type && channel->id == id)) {
+ channel = channel->next;
+ }
+ return channel;
+}
+
+static void reds_shatdown_channels()
+{
+ Channel *channel = reds->channels;
+ while (channel) {
+ channel->shutdown(channel);
+ channel = channel->next;
+ }
+}
+
+static void reds_mig_cleanup()
+{
+ if (reds->mig_inprogress) {
+ reds->mig_inprogress = FALSE;
+ reds->mig_wait_connect = FALSE;
+ reds->mig_wait_disconnect = FALSE;
+ core->disarm_timer(core, reds->mig_timer);
+ mig->notifier_done(mig, reds->mig_notifier);
+ }
+}
+
+static void reds_reset_vdp()
+{
+ VDIPortState *state = &reds->agent_state;
+
+ while (!ring_is_empty(&state->write_queue)) {
+ VDIPortBuf *buf;
+ RingItem *item;
+
+ item = ring_get_tail(&state->write_queue);
+ ring_remove(item);
+ buf = (VDIPortBuf *)item;
+ buf->free(buf);
+ }
+ state->read_state = VDI_PORT_READ_STATE_READ_HADER;
+ state->recive_pos = (uint8_t *)&state->vdi_chunk_header;
+ state->recive_len = sizeof(state->vdi_chunk_header);
+ state->message_recive_len = 0;
+ if (state->current_read_buf) {
+ ring_add(&state->read_bufs, &state->current_read_buf->out_item.link);
+ state->current_read_buf = NULL;
+ }
+ state->client_agent_started = FALSE;
+ state->send_tokens = 0;
+}
+
+static void reds_reset_outgoing()
+{
+ RedsOutgoingData *outgoing = &reds->outgoing;
+ RingItem *ring_item;
+
+ if (outgoing->item) {
+ outgoing->item->release(outgoing->item);
+ outgoing->item = NULL;
+ }
+ while ((ring_item = ring_get_tail(&outgoing->pipe))) {
+ RedsOutItem *out_item = (RedsOutItem *)ring_item;
+ ring_remove(ring_item);
+ out_item->release(out_item);
+ }
+ outgoing->vec_size = 0;
+ outgoing->vec = outgoing->vec_buf;
+}
+
+static void reds_disconnect()
+{
+ if (!reds->peer || reds->disconnecting) {
+ return;
+ }
+
+ red_printf("");
+ LOG_MESSAGE(VD_LOG_INFO, "user disconnected");
+ reds->disconnecting = TRUE;
+ reds_reset_outgoing();
+
+ if (reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF) {
+ ASSERT(vdagent);
+ vdagent->unplug(vdagent, reds->agent_state.plug_ref);
+ reds->agent_state.plug_ref = INVALID_VD_OBJECT_REF;
+ reds_reset_vdp();
+ }
+
+ reds_shatdown_channels();
+ core->set_file_handlers(core, reds->peer->socket, NULL, NULL, NULL);
+ reds->peer->cb_free(reds->peer);
+ reds->peer = NULL;
+ reds->in_handler.shut = TRUE;
+ reds->link_id = 0;
+ reds->serial = 0;
+ reds->ping_id = 0;
+ reds->net_test_id = 0;
+ reds->net_test_stage = NET_TEST_STAGE_INVALID;
+ reds->in_handler.end_pos = 0;
+
+ bitrate_per_sec = ~0;
+ letancy = 0;
+
+ reds_mig_cleanup();
+ reds->disconnecting = FALSE;
+}
+
+static void reds_mig_disconnect()
+{
+ if (reds->peer) {
+ reds_disconnect();
+ } else {
+ reds_mig_cleanup();
+ }
+}
+
+static int handle_incoming(RedsStreamContext *peer, IncomingHandler *handler)
+{
+ for (;;) {
+ uint8_t *buf = handler->buf;
+ uint32_t pos = handler->end_pos;
+ uint8_t *end = buf + pos;
+ RedDataHeader *header;
+ int n;
+ n = peer->cb_read(peer->ctx, buf + pos, RECIVE_BUF_SIZE - pos);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ pos += n;
+ end = buf + pos;
+ while (buf + sizeof(RedDataHeader) <= end &&
+ buf + sizeof(RedDataHeader) + (header = (RedDataHeader *)buf)->size <= end) {
+ buf += sizeof(RedDataHeader) + header->size;
+ handler->handle_message(handler->opaque, header);
+
+ if (handler->shut) {
+ return -1;
+ }
+ }
+ memmove(handler->buf, buf, (handler->end_pos = end - buf));
+ }
+ }
+}
+
+static int handle_outgoing(RedsStreamContext *peer, OutgoingHandler *handler)
+{
+ if (!handler->length) {
+ return 0;
+ }
+
+ while (handler->length) {
+ int n;
+
+ n = peer->cb_write(peer->ctx, handler->now, handler->length);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ handler->now += n;
+ handler->length -= n;
+ }
+ }
+ handler->select(handler->opaque, FALSE);
+ handler->may_write(handler->opaque);
+ return 0;
+}
+
+#define OUTGOING_OK 0
+#define OUTGOING_FAILED -1
+#define OUTGOING_BLOCKED 1
+
+static int outgoing_write(RedsStreamContext *peer, OutgoingHandler *handler, void *in_data,
+ int length)
+{
+ uint8_t *data = in_data;
+ ASSERT(length <= SEND_BUF_SIZE);
+ if (handler->length) {
+ return OUTGOING_BLOCKED;
+ }
+
+ while (length) {
+ int n = peer->cb_write(peer->ctx, data, length);
+ if (n < 0) {
+ switch (errno) {
+ case EAGAIN:
+ handler->length = length;
+ memcpy(handler->buf, data, length);
+ handler->select(handler->opaque, TRUE);
+ return OUTGOING_OK;
+ case EINTR:
+ break;
+ case EPIPE:
+ return OUTGOING_FAILED;
+ default:
+ red_printf("%s", strerror(errno));
+ return OUTGOING_FAILED;
+ }
+ } else {
+ data += n;
+ length -= n;
+ }
+ }
+ return OUTGOING_OK;
+}
+
+typedef struct SimpleOutItem {
+ RedsOutItem base;
+ RedDataHeader header;
+ uint8_t data[0];
+} SimpleOutItem;
+
+static void reds_prepare_basic_out_item(RedsOutItem *in_item, struct iovec* vec, int *len)
+{
+ SimpleOutItem *item = (SimpleOutItem *)in_item;
+
+ vec[0].iov_base = &item->header;
+ vec[0].iov_len = sizeof(item->header);
+ if (item->header.size) {
+ vec[1].iov_base = item->data;
+ vec[1].iov_len = item->header.size;
+ *len = 2;
+ } else {
+ *len = 1;
+ }
+}
+
+static void reds_free_basic_out_item(RedsOutItem *item)
+{
+ free(item);
+}
+
+static SimpleOutItem *new_simple_out_item(uint32_t type, int message_size)
+{
+ SimpleOutItem *item;
+
+ if (!(item = (SimpleOutItem *)malloc(sizeof(*item) + message_size))) {
+ return NULL;
+ }
+ ring_item_init(&item->base.link);
+ item->base.prepare = reds_prepare_basic_out_item;
+ item->base.release = reds_free_basic_out_item;
+
+ item->header.serial = ++reds->serial;
+ item->header.type = type;
+ item->header.size = message_size;
+ item->header.sub_list = 0;
+
+ return item;
+}
+
+static void reds_push_pipe_item(RedsOutItem *item)
+{
+ ring_add(&reds->outgoing.pipe, &item->link);
+ reds_push();
+}
+
+static void reds_send_channels()
+{
+ RedChannels* channels_info;
+ SimpleOutItem *item;
+ int message_size;
+ Channel *channel;
+ int i;
+
+ message_size = sizeof(RedChannels) + reds->num_of_channels * sizeof(RedChannelInit);
+ if (!(item = new_simple_out_item(RED_CHANNELS_LIST, message_size))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ channels_info = (RedChannels *)item->data;
+ channels_info->num_of_channels = reds->num_of_channels;
+ channel = reds->channels;
+
+ for (i = 0; i < reds->num_of_channels; i++) {
+ ASSERT(channel);
+ channels_info->channels[i].type = channel->type;
+ channels_info->channels[i].id = channel->id;
+ channel = channel->next;
+ }
+ reds_push_pipe_item(&item->base);
+}
+
+static void reds_prepare_ping_item(RedsOutItem *in_item, struct iovec* vec, int *len)
+{
+ PingItem *item = (PingItem *)in_item;
+
+ vec[0].iov_base = &item->header;
+ vec[0].iov_len = sizeof(item->header);
+ vec[1].iov_base = &item->ping;
+ vec[1].iov_len = sizeof(item->ping);
+ int size = item->size;
+ int pos = 2;
+ while (size) {
+ ASSERT(pos < REDS_MAX_SEND_IOVEC);
+ int now = MIN(PAGE_SIZE, size);
+ size -= now;
+ vec[pos].iov_base = zero_page;
+ vec[pos].iov_len = now;
+ pos++;
+ }
+ *len = pos;
+}
+
+static void reds_free_ping_item(RedsOutItem *item)
+{
+ free(item);
+}
+
+static int send_ping(int size)
+{
+ struct timespec time_space;
+ PingItem *item;
+
+ if (!reds->peer || !(item = (PingItem *)malloc(sizeof(*item)))) {
+ return FALSE;
+ }
+ ring_item_init(&item->base.link);
+ item->base.prepare = reds_prepare_ping_item;
+ item->base.release = reds_free_ping_item;
+
+ item->header.serial = ++reds->serial;
+ item->header.type = RED_PING;
+ item->header.size = sizeof(item->ping) + size;
+ item->header.sub_list = 0;
+
+ item->ping.id = ++reds->ping_id;
+ clock_gettime(CLOCK_MONOTONIC, &time_space);
+ item->ping.timestamp = time_space.tv_sec * 1000000LL + time_space.tv_nsec / 1000LL;
+
+ item->size = size;
+ reds_push_pipe_item(&item->base);
+ return TRUE;
+}
+
+#ifdef RED_STATISTICS
+
+static void do_ping_client(const char *opt, int has_interval, int interval)
+{
+ if (!reds->peer) {
+ red_printf("not connected to peer");
+ return;
+ }
+
+ if (!opt) {
+ send_ping(0);
+ } else if (!strcmp(opt, "on")) {
+ if (has_interval && interval > 0) {
+ reds->ping_interval = interval * 1000;
+ }
+ core->arm_timer(core, reds->ping_timer, reds->ping_interval);
+ core->term_printf(core, "ping on, interval %u s\n", reds->ping_interval / 1000);
+ } else if (!strcmp(opt, "off")) {
+ core->disarm_timer(core, reds->ping_timer);
+ core->term_printf(core, "ping off\n");
+ } else {
+ core->term_printf(core, "ping invalid option: %s\n", opt);
+ return;
+ }
+}
+
+static void ping_timer_cb()
+{
+ if (!reds->peer) {
+ red_printf("not connected to peer, ping off");
+ core->disarm_timer(core, reds->ping_timer);
+ return;
+ }
+ do_ping_client(NULL, 0, 0);
+ core->arm_timer(core, reds->ping_timer, reds->ping_interval);
+}
+
+static void do_info_rtt_client()
+{
+ core->term_printf(core, "rtt=%uus, min/max/avg=%u/%u/%uus\n", reds->roundtrip_stat.value,
+ reds->roundtrip_stat.min, reds->roundtrip_stat.max,
+ reds->roundtrip_stat.average);
+}
+
+#endif
+
+static void reds_send_mouse_mode()
+{
+ RedMouseMode *mouse_mode;
+ SimpleOutItem *item;
+
+ if (!reds->peer) {
+ return;
+ }
+
+ if (!(item = new_simple_out_item(RED_MOUSE_MODE, sizeof(RedMouseMode)))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ mouse_mode = (RedMouseMode *)item->data;
+ mouse_mode->supported_modes = RED_MOUSE_MODE_SERVER;
+ if (reds->is_client_mouse_allowed) {
+ mouse_mode->supported_modes |= RED_MOUSE_MODE_CLIENT;
+ }
+ mouse_mode->current_mode = reds->mouse_mode;
+ reds_push_pipe_item(&item->base);
+}
+
+static void reds_set_mouse_mode(uint32_t mode)
+{
+ if (reds->mouse_mode == mode) {
+ return;
+ }
+ reds->mouse_mode = mode;
+ red_dispatcher_set_mouse_mode(reds->mouse_mode);
+ reds_send_mouse_mode();
+}
+
+static void reds_update_mouse_mode()
+{
+ int allowed = 0;
+ int qxl_count = red_dispatcher_qxl_count();
+
+ if ((agent_mouse && vdagent) || (tablet && qxl_count == 1)) {
+ allowed = reds->dispatcher_allows_client_mouse;
+ }
+ if (allowed == reds->is_client_mouse_allowed) {
+ return;
+ }
+ reds->is_client_mouse_allowed = allowed;
+ if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT && !allowed) {
+ reds_set_mouse_mode(RED_MOUSE_MODE_SERVER);
+ return;
+ }
+ reds_send_mouse_mode();
+}
+
+static void reds_send_agent_connected()
+{
+ SimpleOutItem *item;
+ if (!(item = new_simple_out_item(RED_AGENT_CONNECTED, 0))) {
+ PANIC("alloc item failed");
+ }
+ reds_push_pipe_item(&item->base);
+}
+
+static void reds_send_agent_disconnected()
+{
+ RedAgentDisconnect *disconnect;
+ SimpleOutItem *item;
+
+ if (!(item = new_simple_out_item(RED_AGENT_DISCONNECTED, sizeof(RedAgentDisconnect)))) {
+ PANIC("alloc item failed");
+ }
+ disconnect = (RedAgentDisconnect *)item->data;
+ disconnect->error_code = RED_ERR_OK;
+ reds_push_pipe_item(&item->base);
+}
+
+static void reds_agent_remove()
+{
+ VDIPortInterface *interface = vdagent;
+
+ vdagent = NULL;
+ reds_update_mouse_mode();
+
+ if (!reds->peer || !interface) {
+ return;
+ }
+
+ ASSERT(reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF);
+ interface->unplug(interface, reds->agent_state.plug_ref);
+ reds->agent_state.plug_ref = INVALID_VD_OBJECT_REF;
+
+ if (reds->mig_target) {
+ return;
+ }
+
+ reds_reset_vdp();
+ reds_send_agent_disconnected();
+}
+
+static void reds_send_tokens()
+{
+ RedAgentTokens *tokens;
+ SimpleOutItem *item;
+
+ if (!reds->peer) {
+ return;
+ }
+
+ if (!(item = new_simple_out_item(RED_AGENT_TOKEN, sizeof(RedAgentTokens)))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ tokens = (RedAgentTokens *)item->data;
+ tokens->num_tokens = reds->agent_state.num_tokens;
+ reds->agent_state.num_client_tokens += tokens->num_tokens;
+ ASSERT(reds->agent_state.num_client_tokens <= REDS_AGENT_WINDOW_SIZE);
+ reds->agent_state.num_tokens = 0;
+ reds_push_pipe_item(&item->base);
+}
+
+static int write_to_vdi_port()
+{
+ VDIPortState *state = &reds->agent_state;
+ RingItem *ring_item;
+ VDIPortBuf *buf;
+ int total = 0;
+ int n;
+
+ if (reds->agent_state.plug_ref == INVALID_VD_OBJECT_REF || reds->mig_target) {
+ return 0;
+ }
+
+ for (;;) {
+ if (!(ring_item = ring_get_tail(&state->write_queue))) {
+ break;
+ }
+ buf = (VDIPortBuf *)ring_item;
+ n = vdagent->write(vdagent, state->plug_ref, buf->now, buf->write_len);
+ if (n == 0) {
+ break;
+ }
+ total += n;
+ buf->write_len -= n;
+ if (!buf->write_len) {
+ ring_remove(ring_item);
+ buf->free(buf);
+ continue;
+ }
+ buf->now += n;
+ }
+ return total;
+}
+
+static void dispatch_vdi_port_data(int port, VDIReadBuf *buf)
+{
+ VDIPortState *state = &reds->agent_state;
+ switch (port) {
+ case VDP_CLIENT_PORT: {
+ buf->header.serial = ++reds->serial;
+ buf->header.size = buf->len;
+ reds_push_pipe_item(&buf->out_item);
+ break;
+ }
+ case VDP_SERVER_PORT:
+ ring_add(&state->read_bufs, &buf->out_item.link);
+ break;
+ default:
+ ring_add(&state->read_bufs, &buf->out_item.link);
+ red_printf("invalid port");
+ reds_agent_remove();
+ }
+}
+
+static int read_from_vdi_port()
+{
+ VDIPortState *state = &reds->agent_state;
+ VDIReadBuf *dispatch_buf;
+ int total = 0;
+ int n;
+
+ if (reds->mig_target) {
+ return 0;
+ }
+
+ while (reds->agent_state.plug_ref != INVALID_VD_OBJECT_REF) {
+ switch (state->read_state) {
+ case VDI_PORT_READ_STATE_READ_HADER:
+ n = vdagent->read(vdagent, state->plug_ref, state->recive_pos, state->recive_len);
+ if (!n) {
+ return total;
+ }
+ total += n;
+ if ((state->recive_len -= n)) {
+ state->recive_pos += n;
+ break;
+ }
+ state->message_recive_len = state->vdi_chunk_header.size;
+ state->read_state = VDI_PORT_READ_STATE_GET_BUFF;
+ case VDI_PORT_READ_STATE_GET_BUFF: {
+ RingItem *item;
+
+ if (!(item = ring_get_head(&state->read_bufs))) {
+ return total;
+ }
+
+ if (state->vdi_chunk_header.port == VDP_CLIENT_PORT) {
+ if (!state->send_tokens) {
+ return total;
+ }
+ --state->send_tokens;
+ }
+ ring_remove(item);
+ state->current_read_buf = (VDIReadBuf *)item;
+ state->recive_pos = state->current_read_buf->data;
+ state->recive_len = MIN(state->message_recive_len,
+ sizeof(state->current_read_buf->data));
+ state->current_read_buf->len = state->recive_len;
+ state->message_recive_len -= state->recive_len;
+ state->read_state = VDI_PORT_READ_STATE_READ_DATA;
+ }
+ case VDI_PORT_READ_STATE_READ_DATA:
+ n = vdagent->read(vdagent, state->plug_ref, state->recive_pos, state->recive_len);
+ if (!n) {
+ return total;
+ }
+ total += n;
+ if ((state->recive_len -= n)) {
+ state->recive_pos += n;
+ break;
+ }
+ dispatch_buf = state->current_read_buf;
+ state->current_read_buf = NULL;
+ state->recive_pos = NULL;
+ if (state->message_recive_len == 0) {
+ state->read_state = VDI_PORT_READ_STATE_READ_HADER;
+ state->recive_pos = (uint8_t *)&state->vdi_chunk_header;
+ state->recive_len = sizeof(state->vdi_chunk_header);
+ } else {
+ state->read_state = VDI_PORT_READ_STATE_GET_BUFF;
+ }
+ dispatch_vdi_port_data(state->vdi_chunk_header.port, dispatch_buf);
+ }
+ }
+ return total;
+}
+
+static void reds_agent_wakeup(VDIPortPlug *plug)
+{
+ while (write_to_vdi_port() || read_from_vdi_port());
+}
+
+static void reds_handle_agent_mouse_event()
+{
+ RingItem *ring_item;
+ VDInternalBuf *buf;
+
+ if (!reds->inputs_state) {
+ return;
+ }
+ if (reds->mig_target || !(ring_item = ring_get_head(&reds->agent_state.internal_bufs))) {
+ reds->inputs_state->pending_mouse_event = TRUE;
+ return;
+ }
+ reds->inputs_state->pending_mouse_event = FALSE;
+ ring_remove(ring_item);
+ buf = (VDInternalBuf *)ring_item;
+ buf->base.now = (uint8_t *)&buf->base.chunk_header;
+ buf->base.write_len = sizeof(VDIChunkHeader) + sizeof(VDAgentMessage) +
+ sizeof(VDAgentMouseState);
+ buf->u.mouse_state = reds->inputs_state->mouse_state;
+ ring_add(&reds->agent_state.write_queue, &buf->base.link);
+ write_to_vdi_port();
+}
+
+static void add_token()
+{
+ VDIPortState *state = &reds->agent_state;
+
+ if (++state->num_tokens == REDS_TOKENS_TO_SEND) {
+ reds_send_tokens();
+ }
+}
+
+typedef struct MainMigrateData {
+ uint32_t version;
+ uint32_t serial;
+ uint32_t ping_id;
+
+ uint32_t agent_connected;
+ uint32_t client_agent_started;
+ uint32_t num_client_tokens;
+ uint32_t send_tokens;
+
+ uint32_t read_state;
+ VDIChunkHeader vdi_chunk_header;
+ uint32_t recive_len;
+ uint32_t message_recive_len;
+ uint32_t read_buf_len;
+
+ uint32_t write_queue_size;
+} MainMigrateData;
+
+#define MAIN_CHANNEL_MIG_DATA_VERSION 1
+
+typedef struct WriteQueueInfo {
+ uint32_t port;
+ uint32_t len;
+} WriteQueueInfo;
+
+typedef struct SendMainMigrateItem {
+ RedsOutItem base;
+ RedDataHeader header;
+ MainMigrateData data;
+ WriteQueueInfo queue_info[REDS_AGENT_WINDOW_SIZE + REDS_NUM_INTERNAL_AGENT_MESSAGES];
+} SendMainMigrateItem;
+
+static void main_channel_send_migrate_data_item(RedsOutItem *in_item, struct iovec* vec_start,
+ int *len)
+{
+ SendMainMigrateItem *item = (SendMainMigrateItem *)in_item;
+ VDIPortState *state = &reds->agent_state;
+ struct iovec* vec;
+ int buf_index;
+ RingItem *now;
+
+ vec = vec_start;
+
+ item->header.serial = ++reds->serial;
+ item->header.type = RED_MIGRATE_DATA;
+ item->header.size = sizeof(item->data);
+ item->header.sub_list = 0;
+
+ vec[0].iov_base = &item->header;
+ vec[0].iov_len = sizeof(item->header);
+ vec[1].iov_base = &item->data;
+ vec[1].iov_len = sizeof(item->data);
+
+ vec += 2;
+ *len = 2;
+
+ item->data.version = MAIN_CHANNEL_MIG_DATA_VERSION;
+ item->data.serial = reds->serial;
+ item->data.ping_id = reds->ping_id;
+
+ item->data.agent_connected = !!state->plug_ref;
+ item->data.client_agent_started = state->client_agent_started;
+ item->data.num_client_tokens = state->num_client_tokens;
+ item->data.send_tokens = state->send_tokens;
+
+ item->data.read_state = state->read_state;
+ item->data.vdi_chunk_header = state->vdi_chunk_header;
+ item->data.recive_len = state->recive_len;
+ item->data.message_recive_len = state->message_recive_len;
+
+
+ if (state->current_read_buf) {
+ item->data.read_buf_len = state->current_read_buf->len;
+ if ((vec->iov_len = item->data.read_buf_len - item->data.recive_len)) {
+ vec->iov_base = state->current_read_buf->data;
+ item->header.size += vec->iov_len;
+ vec++;
+ (*len)++;
+ }
+ } else {
+ item->data.read_buf_len = 0;
+ }
+
+ now = &state->write_queue;
+ item->data.write_queue_size = 0;
+ while ((now = ring_prev(&state->write_queue, now))) {
+ item->data.write_queue_size++;
+ }
+ if (!item->data.write_queue_size) {
+ return;
+ }
+ ASSERT(item->data.write_queue_size <= sizeof(item->queue_info) / sizeof(item->queue_info[0]));
+ vec->iov_base = item->queue_info;
+ vec->iov_len = item->data.write_queue_size * sizeof(item->queue_info[0]);
+ item->header.size += vec->iov_len;
+ vec++;
+ (*len)++;
+
+ buf_index = 0;
+ now = &state->write_queue;
+ while ((now = ring_prev(&state->write_queue, now))) {
+ VDIPortBuf *buf = (VDIPortBuf *)now;
+ item->queue_info[buf_index].port = buf->chunk_header.port;
+ item->queue_info[buf_index++].len = buf->write_len;
+ ASSERT(vec - vec_start < REDS_MAX_SEND_IOVEC);
+ vec->iov_base = buf->now;
+ vec->iov_len = buf->write_len;
+ item->header.size += vec->iov_len;
+ vec++;
+ (*len)++;
+ }
+}
+
+static void main_channelrelease_migrate_data_item(RedsOutItem *in_item)
+{
+ SendMainMigrateItem *item = (SendMainMigrateItem *)in_item;
+ free(item);
+}
+
+static void main_channel_push_migrate_data_item()
+{
+ SendMainMigrateItem *item;
+
+ if (!(item = (SendMainMigrateItem *)malloc(sizeof(*item)))) {
+ PANIC("malloc failed");
+ }
+ memset(item, 0, sizeof(*item));
+ ring_item_init(&item->base.link);
+ item->base.prepare = main_channel_send_migrate_data_item;
+ item->base.release = main_channelrelease_migrate_data_item;
+
+ reds_push_pipe_item((RedsOutItem *)item);
+}
+
+static int main_channel_restore_vdi_read_state(MainMigrateData *data, uint8_t **in_pos,
+ uint8_t *end)
+{
+ VDIPortState *state = &reds->agent_state;
+ uint8_t *pos = *in_pos;
+ RingItem *ring_item;
+
+ state->read_state = data->read_state;
+ state->vdi_chunk_header = data->vdi_chunk_header;
+ state->recive_len = data->recive_len;
+ state->message_recive_len = data->message_recive_len;
+
+ switch (state->read_state) {
+ case VDI_PORT_READ_STATE_READ_HADER:
+ if (data->read_buf_len) {
+ red_printf("unexpected recive buf");
+ reds_disconnect();
+ return FALSE;
+ }
+ state->recive_pos = (uint8_t *)(&state->vdi_chunk_header + 1) - state->recive_len;
+ break;
+ case VDI_PORT_READ_STATE_GET_BUFF:
+ if (state->message_recive_len > state->vdi_chunk_header.size) {
+ red_printf("invalid message recive len");
+ reds_disconnect();
+ return FALSE;
+ }
+
+ if (data->read_buf_len) {
+ red_printf("unexpected recive buf");
+ reds_disconnect();
+ return FALSE;
+ }
+ break;
+ case VDI_PORT_READ_STATE_READ_DATA: {
+ VDIReadBuf *buff;
+ uint32_t n;
+
+ if (!data->read_buf_len) {
+ red_printf("read state and read_buf_len == 0");
+ reds_disconnect();
+ return FALSE;
+ }
+
+ if (state->message_recive_len > state->vdi_chunk_header.size) {
+ red_printf("invalid message recive len");
+ reds_disconnect();
+ return FALSE;
+ }
+
+
+ if (!(ring_item = ring_get_head(&state->read_bufs))) {
+ red_printf("get read buf failed");
+ reds_disconnect();
+ return FALSE;
+ }
+
+ ring_remove(ring_item);
+ buff = state->current_read_buf = (VDIReadBuf *)ring_item;
+ buff->len = data->read_buf_len;
+ n = buff->len - state->recive_len;
+ if (buff->len > RED_AGENT_MAX_DATA_SIZE || n > RED_AGENT_MAX_DATA_SIZE) {
+ red_printf("bad read position");
+ reds_disconnect();
+ return FALSE;
+ }
+ memcpy(buff->data, pos, n);
+ pos += n;
+ state->recive_pos = buff->data + n;
+ break;
+ }
+ default:
+ red_printf("invalid read state");
+ reds_disconnect();
+ return FALSE;
+ }
+ *in_pos = pos;
+ return TRUE;
+}
+
+static void free_tmp_internal_buf(VDIPortBuf *buf)
+{
+ free(buf);
+}
+
+static int main_channel_restore_vdi_wqueue(MainMigrateData *data, uint8_t *pos, uint8_t *end)
+{
+ VDIPortState *state = &reds->agent_state;
+ WriteQueueInfo *inf;
+ WriteQueueInfo *inf_end;
+ RingItem *ring_item;
+
+ if (!data->write_queue_size) {
+ return TRUE;
+ }
+
+ inf = (WriteQueueInfo *)pos;
+ inf_end = inf + data->write_queue_size;
+ pos = (uint8_t *)inf_end;
+ if (pos > end) {
+ red_printf("access violation");
+ reds_disconnect();
+ return FALSE;
+ }
+
+ for (; inf < inf_end; inf++) {
+ if (pos + inf->len > end) {
+ red_printf("access violation");
+ reds_disconnect();
+ return FALSE;
+ }
+ if (inf->port == VDP_SERVER_PORT) {
+ VDInternalBuf *buf;
+
+ if (inf->len > sizeof(*buf) - OFFSETOF(VDInternalBuf, header)) {
+ red_printf("bad buffer len");
+ reds_disconnect();
+ return FALSE;
+ }
+ if (!(buf = malloc(sizeof(VDInternalBuf)))) {
+ red_printf("no internal buff");
+ reds_disconnect();
+ return FALSE;
+ }
+ ring_item_init(&buf->base.link);
+ buf->base.free = free_tmp_internal_buf;
+ buf->base.now = (uint8_t *)&buf->base.chunk_header;
+ buf->base.write_len = inf->len;
+ memcpy(buf->base.now, pos, buf->base.write_len);
+ ring_add(&reds->agent_state.write_queue, &buf->base.link);
+ } else if (inf->port == VDP_CLIENT_PORT) {
+ VDAgentExtBuf *buf;
+
+ state->num_tokens--;
+ if (inf->len > sizeof(*buf) - OFFSETOF(VDAgentExtBuf, buf)) {
+ red_printf("bad buffer len");
+ reds_disconnect();
+ return FALSE;
+ }
+ if (!(ring_item = ring_get_head(&reds->agent_state.external_bufs))) {
+ red_printf("no external buff");
+ reds_disconnect();
+ return FALSE;
+ }
+ ring_remove(ring_item);
+ buf = (VDAgentExtBuf *)ring_item;
+ memcpy(&buf->buf, pos, inf->len);
+ buf->base.now = (uint8_t *)buf->buf;
+ buf->base.write_len = inf->len;
+ ring_add(&reds->agent_state.write_queue, &buf->base.link);
+ } else {
+ red_printf("invalid data");
+ reds_disconnect();
+ return FALSE;
+ }
+ pos += inf->len;
+ }
+ return TRUE;
+}
+
+static void main_channel_recive_migrate_data(MainMigrateData *data, uint8_t *end)
+{
+ VDIPortState *state = &reds->agent_state;
+ uint8_t *pos;
+
+ if (data->version != MAIN_CHANNEL_MIG_DATA_VERSION) {
+ red_printf("version mismatch");
+ reds_disconnect();
+ return;
+ }
+
+ reds->serial = data->serial;
+ reds->ping_id = data->ping_id;
+
+ state->num_client_tokens = data->num_client_tokens;
+ ASSERT(state->num_client_tokens + data->write_queue_size <= REDS_AGENT_WINDOW_SIZE +
+ REDS_NUM_INTERNAL_AGENT_MESSAGES);
+ state->num_tokens = REDS_AGENT_WINDOW_SIZE - state->num_client_tokens;
+ state->send_tokens = data->send_tokens;
+
+
+ if (!data->agent_connected) {
+ if (state->plug_ref) {
+ reds_send_agent_connected();
+ }
+ return;
+ }
+
+ if (state->plug_ref == INVALID_VD_OBJECT_REF) {
+ reds_send_agent_disconnected();
+ return;
+ }
+
+ if (state->plug_generation > 1) {
+ reds_send_agent_disconnected();
+ reds_send_agent_connected();
+ return;
+ }
+
+ state->client_agent_started = data->client_agent_started;
+
+ pos = (uint8_t *)(data + 1);
+
+ if (!main_channel_restore_vdi_read_state(data, &pos, end)) {
+ return;
+ }
+
+ main_channel_restore_vdi_wqueue(data, pos, end);
+ ASSERT(state->num_client_tokens + state->num_tokens == REDS_AGENT_WINDOW_SIZE);
+}
+
+static void reds_main_handle_message(void *opaque, RedDataHeader *message)
+{
+ switch (message->type) {
+ case REDC_AGENT_START: {
+ RedcAgentTokens *agent_start;
+
+ red_printf("agent start");
+ if (!reds->peer) {
+ return;
+ }
+ agent_start = (RedcAgentTokens *)(message + 1);
+ reds->agent_state.client_agent_started = TRUE;
+ reds->agent_state.send_tokens = agent_start->num_tokens;
+ read_from_vdi_port();
+ break;
+ }
+ case REDC_AGENT_DATA: {
+ RingItem *ring_item;
+ VDAgentExtBuf *buf;
+
+ if (!reds->agent_state.num_client_tokens) {
+ red_printf("token vailoation");
+ reds_disconnect();
+ break;
+ }
+ --reds->agent_state.num_client_tokens;
+
+ if (!vdagent) {
+ add_token();
+ break;
+ }
+
+ if (!reds->agent_state.client_agent_started) {
+ red_printf("REDC_AGENT_DATA race");
+ add_token();
+ break;
+ }
+
+ if (message->size > RED_AGENT_MAX_DATA_SIZE) {
+ red_printf("invalid agent message");
+ reds_disconnect();
+ break;
+ }
+
+ if (!(ring_item = ring_get_head(&reds->agent_state.external_bufs))) {
+ red_printf("no agent free bufs");
+ reds_disconnect();
+ break;
+ }
+ ring_remove(ring_item);
+ buf = (VDAgentExtBuf *)ring_item;
+ buf->base.now = (uint8_t *)&buf->base.chunk_header.port;
+ buf->base.write_len = message->size + sizeof(VDIChunkHeader);
+ buf->base.chunk_header.size = message->size;
+ memcpy(buf->buf, message + 1, message->size);
+ ring_add(&reds->agent_state.write_queue, ring_item);
+ write_to_vdi_port();
+ break;
+ }
+ case REDC_AGENT_TOKEN: {
+ RedcAgentTokens *token;
+
+ if (!reds->agent_state.client_agent_started) {
+ red_printf("REDC_AGENT_TOKEN race");
+ break;
+ }
+
+ token = (RedcAgentTokens *)(message + 1);
+ reds->agent_state.send_tokens += token->num_tokens;
+ read_from_vdi_port();
+ break;
+ }
+ case REDC_ATTACH_CHANNELS:
+ reds_send_channels();
+ break;
+ case REDC_MIGRATE_CONNECTED:
+ red_printf("connected");
+ if (reds->mig_wait_connect) {
+ reds_mig_cleanup();
+ }
+ break;
+ case REDC_MIGRATE_CONNECT_ERROR:
+ red_printf("mig connect error");
+ if (reds->mig_wait_connect) {
+ reds_mig_cleanup();
+ }
+ break;
+ case REDC_MOUSE_MODE_REQUEST: {
+ switch (((RedcMouseModeRequest *)(message + 1))->mode) {
+ case RED_MOUSE_MODE_CLIENT:
+ if (reds->is_client_mouse_allowed) {
+ reds_set_mouse_mode(RED_MOUSE_MODE_CLIENT);
+ } else {
+ red_printf("client mouse is disabled");
+ }
+ break;
+ case RED_MOUSE_MODE_SERVER:
+ reds_set_mouse_mode(RED_MOUSE_MODE_SERVER);
+ break;
+ default:
+ red_printf("unsupported mouse mode");
+ }
+ break;
+ }
+ case REDC_PONG: {
+ RedPing *ping = (RedPing *)(message + 1);
+ uint64_t roundtrip;
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ roundtrip = ts.tv_sec * 1000000LL + ts.tv_nsec / 1000LL - ping->timestamp;
+
+ if (ping->id == reds->net_test_id) {
+ switch (reds->net_test_stage) {
+ case NET_TEST_STAGE_WARMUP:
+ reds->net_test_id++;
+ reds->net_test_stage = NET_TEST_STAGE_LATENCY;
+ break;
+ case NET_TEST_STAGE_LATENCY:
+ reds->net_test_id++;
+ reds->net_test_stage = NET_TEST_STAGE_RATE;
+ letancy = roundtrip;
+ break;
+ case NET_TEST_STAGE_RATE:
+ reds->net_test_id = 0;
+ if (roundtrip <= letancy) {
+ // probably high load on client or server result with incorrect values
+ letancy = 0;
+ red_printf("net test: invalid values, letancy %lu roundtrip %lu. assuming high"
+ "bendwidth", letancy, roundtrip);
+ break;
+ }
+ bitrate_per_sec = (uint64_t)(NET_TEST_BYTES * 8) * 1000000 / (roundtrip - letancy);
+ red_printf("net test: letancy %f ms, bitrate %lu bps (%f Mbps)%s",
+ (double)letancy / 1000,
+ bitrate_per_sec,
+ (double)bitrate_per_sec / 1024 / 1024,
+ IS_LOW_BANDWIDTH() ? " LOW BANDWIDTH" : "");
+ reds->net_test_stage = NET_TEST_STAGE_INVALID;
+ break;
+ default:
+ red_printf("invalid net test stage, ping id %d test id %d stage %d",
+ ping->id,
+ reds->net_test_id,
+ reds->net_test_stage);
+ }
+ break;
+ }
+#ifdef RED_STATISTICS
+ reds_update_stat_value(&reds->roundtrip_stat, roundtrip);
+ do_info_rtt_client();
+#endif
+ break;
+ }
+ case REDC_MIGRATE_FLUSH_MARK:
+ main_channel_push_migrate_data_item();
+ break;
+ case REDC_MIGRATE_DATA:
+ main_channel_recive_migrate_data((MainMigrateData *)(message + 1),
+ (uint8_t *)(message + 1) + message->size);
+ reds->mig_target = FALSE;
+ while (write_to_vdi_port() || read_from_vdi_port());
+ break;
+ case REDC_DISCONNECTING:
+ break;
+ default:
+ red_printf("unexpected type %d", message->type);
+ }
+}
+
+static void reds_main_read(void *data)
+{
+ if (handle_incoming(reds->peer, &reds->in_handler)) {
+ reds_disconnect();
+ }
+}
+
+static int reds_send_data()
+{
+ RedsOutgoingData *outgoing = &reds->outgoing;
+ int n;
+
+ if (!outgoing->item) {
+ return TRUE;
+ }
+
+ ASSERT(outgoing->vec_size);
+ for (;;) {
+ if ((n = reds->peer->cb_writev(reds->peer->ctx, outgoing->vec, outgoing->vec_size)) == -1) {
+ switch (errno) {
+ case EAGAIN:
+ core->set_file_handlers(core, reds->peer->socket, reds_main_read, reds_main_write,
+ NULL);
+ return FALSE;
+ case EINTR:
+ break;
+ case EPIPE:
+ reds_disconnect();
+ return FALSE;
+ default:
+ red_printf("%s", strerror(errno));
+ reds_disconnect();
+ return FALSE;
+ }
+ } else {
+ outgoing->vec = reds_iovec_skip(outgoing->vec, n, &outgoing->vec_size);
+ if (!outgoing->vec_size) {
+ outgoing->item->release(outgoing->item);
+ outgoing->item = NULL;
+ outgoing->vec = outgoing->vec_buf;
+ return TRUE;
+ }
+ }
+ }
+}
+
+static void reds_push()
+{
+ RedsOutgoingData *outgoing = &reds->outgoing;
+ RingItem *item;
+
+ for (;;) {
+ if (!reds->peer || outgoing->item || !(item = ring_get_tail(&outgoing->pipe))) {
+ return;
+ }
+ ring_remove(item);
+ outgoing->item = (RedsOutItem *)item;
+ outgoing->item->prepare(outgoing->item, outgoing->vec_buf, &outgoing->vec_size);
+ reds_send_data();
+ }
+}
+
+static void reds_main_write(void *data)
+{
+ RedsOutgoingData *outgoing = &reds->outgoing;
+
+ if (reds_send_data()) {
+ reds_push();
+ if (!outgoing->item) {
+ core->set_file_handlers(core, reds->peer->socket, reds_main_read, NULL, NULL);
+ }
+ }
+}
+
+static int sync_write(RedsStreamContext *peer, void *in_buf, size_t n)
+{
+ uint8_t *buf = (uint8_t *)in_buf;
+ while (n) {
+ int now = peer->cb_write(peer->ctx, buf, n);
+ if (now <= 0) {
+ if (now == -1 && (errno == EINTR || errno == EAGAIN)) {
+ continue;
+ }
+ return FALSE;
+ }
+ n -= now;
+ buf += now;
+ }
+ return TRUE;
+}
+
+static int reds_send_link_ack(RedLinkInfo *link)
+{
+ RedLinkHeader header;
+ RedLinkReply ack;
+ Channel *channel;
+ BUF_MEM *bmBuf;
+ BIO *bio;
+ int ret;
+
+ header.magic = RED_MAGIC;
+ header.size = sizeof(ack);
+ header.major_version = RED_VERSION_MAJOR;
+ header.minor_version = RED_VERSION_MINOR;
+
+ ack.error = RED_ERR_OK;
+
+ if ((channel = reds_find_channel(link->link_mess->channel_type, 0))) {
+ ack.num_common_caps = channel->num_common_caps;
+ ack.num_channel_caps = channel->num_caps;
+ header.size += (ack.num_common_caps + ack.num_channel_caps) * sizeof(uint32_t);
+ } else {
+ ack.num_common_caps = 0;
+ ack.num_channel_caps = 0;
+ }
+ ack.caps_offset = sizeof(RedLinkReply);
+
+ if (!(link->tiTicketing.rsa = RSA_new())) {
+ red_printf("RSA nes failed");
+ return FALSE;
+ }
+
+ if (!(bio = BIO_new(BIO_s_mem()))) {
+ red_printf("BIO new failed");
+ return FALSE;
+ }
+
+ RSA_generate_key_ex(link->tiTicketing.rsa, RED_TICKET_KEY_PAIR_LENGTH, link->tiTicketing.bn,
+ NULL);
+ link->tiTicketing.rsa_size = RSA_size(link->tiTicketing.rsa);
+
+ i2d_RSA_PUBKEY_bio(bio, link->tiTicketing.rsa);
+ BIO_get_mem_ptr(bio, &bmBuf);
+ memcpy(ack.pub_key, bmBuf->data, sizeof(ack.pub_key));
+
+ ret = sync_write(link->peer, &header, sizeof(header)) && sync_write(link->peer, &ack,
+ sizeof(ack));
+ if (channel) {
+ ret = ret && sync_write(link->peer, channel->common_caps,
+ channel->num_common_caps * sizeof(uint32_t)) &&
+ sync_write(link->peer, channel->caps, channel->num_caps * sizeof(uint32_t));
+ }
+ BIO_free(bio);
+ return ret;
+}
+
+static int reds_send_link_error(RedLinkInfo *link, uint32_t error)
+{
+ RedLinkHeader header;
+ RedLinkReply reply;
+
+ header.magic = RED_MAGIC;
+ header.size = sizeof(reply);
+ header.major_version = RED_VERSION_MAJOR;
+ header.minor_version = RED_VERSION_MINOR;
+ memset(&reply, 0, sizeof(reply));
+ reply.error = error;
+ return sync_write(link->peer, &header, sizeof(header)) && sync_write(link->peer, &reply,
+ sizeof(reply));
+}
+
+static void reds_show_new_channel(RedLinkInfo *link)
+{
+ red_printf("channel %d:%d, connected sucessfully, over %s link",
+ link->link_mess->channel_type,
+ link->link_mess->channel_id,
+ link->peer->ssl == NULL ? "Non Secure" : "Secure");
+}
+
+static void reds_send_link_result(RedLinkInfo *link, uint32_t error)
+{
+ sync_write(link->peer, &error, sizeof(error));
+}
+
+static void reds_start_net_test()
+{
+ if (!reds->peer || reds->net_test_id) {
+ return;
+ }
+
+ if (send_ping(NET_TEST_WARMUP_BYTES) && send_ping(0) && send_ping(NET_TEST_BYTES)) {
+ reds->net_test_id = reds->ping_id - 2;
+ reds->net_test_stage = NET_TEST_STAGE_WARMUP;
+ }
+}
+
+static void reds_handle_main_link(RedLinkInfo *link)
+{
+ uint32_t connection_id;
+
+ red_printf("");
+
+ reds_disconnect();
+
+ if (!link->link_mess->connection_id) {
+ reds_send_link_result(link, RED_ERR_OK);
+ while((connection_id = rand()) == 0);
+ reds->agent_state.num_tokens = 0;
+ reds->agent_state.send_tokens = 0;
+ memcpy(&(reds->taTicket), &taTicket, sizeof(reds->taTicket));
+ reds->mig_target = FALSE;
+ } else {
+ if (link->link_mess->connection_id != reds->link_id) {
+ reds_send_link_result(link, RED_ERR_BAD_CONNECTION_ID);
+ reds_release_link(link);
+ return;
+ }
+ reds_send_link_result(link, RED_ERR_OK);
+ connection_id = link->link_mess->connection_id;
+ reds->mig_target = TRUE;
+ }
+
+ reds->link_id = connection_id;
+ reds->mig_inprogress = FALSE;
+ reds->mig_wait_connect = FALSE;
+ reds->mig_wait_disconnect = FALSE;
+ reds->peer = link->peer;
+ reds->in_handler.shut = FALSE;
+ if (reds->mig_target) {
+ LOG_MESSAGE(VD_LOG_INFO, "migrate connection");
+ } else {
+ LOG_MESSAGE(VD_LOG_INFO, "new user connection");
+ }
+
+ reds_show_new_channel(link);
+ __reds_release_link(link);
+ if (vdagent) {
+ reds->agent_state.plug_ref = vdagent->plug(vdagent, &reds->agent_state.plug);
+ if (reds->agent_state.plug_ref == INVALID_VD_OBJECT_REF) {
+ PANIC("vdagent plug failed");
+ }
+ reds->agent_state.plug_generation++;
+ }
+ core->set_file_handlers(core, reds->peer->socket, reds_main_read, NULL, NULL);
+
+ if (!reds->mig_target) {
+ SimpleOutItem *item;
+ RedInit *init;
+
+ if (!(item = new_simple_out_item(RED_INIT, sizeof(RedInit)))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ init = (RedInit *)item->data;
+ init->session_id = connection_id;
+ init->display_channels_hint = red_dispatcher_count();
+ init->current_mouse_mode = reds->mouse_mode;
+ init->supported_mouse_modes = RED_MOUSE_MODE_SERVER;
+ if (reds->is_client_mouse_allowed) {
+ init->supported_mouse_modes |= RED_MOUSE_MODE_CLIENT;
+ }
+ init->agent_connected = !!vdagent;
+ init->agent_tokens = REDS_AGENT_WINDOW_SIZE;
+ reds->agent_state.num_client_tokens = REDS_AGENT_WINDOW_SIZE;
+ init->multi_media_time = reds_get_mm_time() - MM_TIME_DELTA;
+ init->ram_hint = red_dispatcher_qxl_ram_size();
+ reds_push_pipe_item(&item->base);
+ reds_start_net_test();
+ }
+}
+
+#define RED_MOUSE_STATE_TO_LOCAL(state) \
+ ((state & REDC_LBUTTON_MASK) | \
+ ((state & REDC_MBUTTON_MASK) << 1) | \
+ ((state & REDC_RBUTTON_MASK) >> 1))
+
+#define RED_MOUSE_BUTTON_STATE_TO_AGENT(state) \
+ (((state & REDC_LBUTTON_MASK) ? VD_AGENT_LBUTTON_MASK : 0) | \
+ ((state & REDC_MBUTTON_MASK) ? VD_AGENT_MBUTTON_MASK : 0) | \
+ ((state & REDC_RBUTTON_MASK) ? VD_AGENT_RBUTTON_MASK : 0))
+
+static void activate_modifiers_watch()
+{
+ core->arm_timer(core, reds->key_modifiers_timer, KEY_MODIFIERS_TTL);
+}
+
+static void push_key_scan(uint8_t scan)
+{
+ if (!keyboard) {
+ return;
+ }
+ keyboard->push_scan_freg(keyboard, scan);
+}
+
+static void inputs_handle_input(void *opaque, RedDataHeader *header)
+{
+ InputsState *state = (InputsState *)opaque;
+ uint8_t *buf = (uint8_t *)(header + 1);
+
+ switch (header->type) {
+ case REDC_INPUTS_KEY_DOWN: {
+ RedcKeyDown *key_up = (RedcKeyDown *)buf;
+ if (key_up->code == CAPS_LOCK_SCAN_CODE || key_up->code == NUM_LOCK_SCAN_CODE ||
+ key_up->code == SCROLL_LOCK_SCAN_CODE) {
+ activate_modifiers_watch();
+ }
+ }
+ case REDC_INPUTS_KEY_UP: {
+ RedcKeyDown *key_down = (RedcKeyDown *)buf;
+ uint8_t *now = (uint8_t *)&key_down->code;
+ uint8_t *end = now + sizeof(key_down->code);
+ for (; now < end && *now; now++) {
+ push_key_scan(*now);
+ }
+ break;
+ }
+ case REDC_INPUTS_MOUSE_MOTION: {
+ RedcMouseMotion *mouse_motion = (RedcMouseMotion *)buf;
+
+ if (++state->motion_count % RED_MOTION_ACK_BUNCH == 0) {
+ RedDataHeader header;
+
+ header.serial = ++state->serial;
+ header.type = RED_INPUTS_MOUSE_MOTION_ACK;
+ header.size = 0;
+ header.sub_list = 0;
+ if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader))
+ != OUTGOING_OK) {
+ red_printf("motion ack failed");
+ reds_disconnect();
+ }
+ }
+ if (mouse && reds->mouse_mode == RED_MOUSE_MODE_SERVER) {
+ mouse->moution(mouse, mouse_motion->dx, mouse_motion->dy, 0,
+ RED_MOUSE_STATE_TO_LOCAL(mouse_motion->buttons_state));
+ }
+ break;
+ }
+ case REDC_INPUTS_MOUSE_POSITION: {
+ RedcMousePosition *pos = (RedcMousePosition *)buf;
+
+ if (++state->motion_count % RED_MOTION_ACK_BUNCH == 0) {
+ RedDataHeader header;
+
+ header.serial = ++state->serial;
+ header.type = RED_INPUTS_MOUSE_MOTION_ACK;
+ header.size = 0;
+ header.sub_list = 0;
+ if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader))
+ != OUTGOING_OK) {
+ red_printf("position ack failed");
+ reds_disconnect();
+ }
+ }
+ if (reds->mouse_mode != RED_MOUSE_MODE_CLIENT) {
+ break;
+ }
+ ASSERT((agent_mouse && vdagent) || tablet);
+ if (!agent_mouse || !vdagent) {
+ tablet->position(tablet, pos->x, pos->y, RED_MOUSE_STATE_TO_LOCAL(pos->buttons_state));
+ break;
+ }
+ VDAgentMouseState *mouse_state = &state->mouse_state;
+ mouse_state->x = pos->x;
+ mouse_state->y = pos->y;
+ mouse_state->buttons = RED_MOUSE_BUTTON_STATE_TO_AGENT(pos->buttons_state);
+ mouse_state->display_id = pos->display_id;
+ reds_handle_agent_mouse_event();
+ break;
+ }
+ case REDC_INPUTS_MOUSE_PRESS: {
+ RedcMousePress *mouse_press = (RedcMousePress *)buf;
+ int dz = 0;
+ if (mouse_press->button == REDC_MOUSE_UBUTTON) {
+ dz = -1;
+ } else if (mouse_press->button == REDC_MOUSE_DBUTTON) {
+ dz = 1;
+ }
+ if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT) {
+ if (agent_mouse && vdagent) {
+ reds->inputs_state->mouse_state.buttons =
+ RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_press->buttons_state) |
+ (dz == -1 ? VD_AGENT_UBUTTON_MASK : 0) |
+ (dz == 1 ? VD_AGENT_DBUTTON_MASK : 0);
+ reds_handle_agent_mouse_event();
+ } else if (tablet) {
+ tablet->wheel(tablet, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
+ }
+ } else if (mouse) {
+ mouse->moution(mouse, 0, 0, dz, RED_MOUSE_STATE_TO_LOCAL(mouse_press->buttons_state));
+ }
+ break;
+ }
+ case REDC_INPUTS_MOUSE_RELEASE: {
+ RedcMouseRelease *mouse_release = (RedcMouseRelease *)buf;
+ if (reds->mouse_mode == RED_MOUSE_MODE_CLIENT) {
+ if (agent_mouse && vdagent) {
+ reds->inputs_state->mouse_state.buttons =
+ RED_MOUSE_BUTTON_STATE_TO_AGENT(mouse_release->buttons_state);
+ reds_handle_agent_mouse_event();
+ } else if (tablet) {
+ tablet->buttons(tablet, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
+ }
+ } else if (mouse) {
+ mouse->buttons(mouse, RED_MOUSE_STATE_TO_LOCAL(mouse_release->buttons_state));
+ }
+ break;
+ }
+ case REDC_INPUTS_KEY_MODIFAIERS: {
+ RedcKeyModifiers *modifiers = (RedcKeyModifiers *)buf;
+ if (!keyboard) {
+ break;
+ }
+ uint8_t leds = keyboard->get_leds(keyboard);
+ if ((modifiers->modifiers & RED_SCROLL_LOCK_MODIFIER) !=
+ (leds & RED_SCROLL_LOCK_MODIFIER)) {
+ push_key_scan(SCROLL_LOCK_SCAN_CODE);
+ push_key_scan(SCROLL_LOCK_SCAN_CODE | 0x80);
+ }
+ if ((modifiers->modifiers & RED_NUM_LOCK_MODIFIER) != (leds & RED_NUM_LOCK_MODIFIER)) {
+ push_key_scan(NUM_LOCK_SCAN_CODE);
+ push_key_scan(NUM_LOCK_SCAN_CODE | 0x80);
+ }
+ if ((modifiers->modifiers & RED_CAPS_LOCK_MODIFIER) != (leds & RED_CAPS_LOCK_MODIFIER)) {
+ push_key_scan(CAPS_LOCK_SCAN_CODE);
+ push_key_scan(CAPS_LOCK_SCAN_CODE | 0x80);
+ }
+ activate_modifiers_watch();
+ break;
+ }
+ case REDC_DISCONNECTING:
+ break;
+ default:
+ red_printf("unexpected type %d", header->type);
+ }
+}
+
+void reds_set_client_mouse_allowed(int is_client_mouse_allowed, int x_res, int y_res)
+{
+ reds->monitor_mode.x_res = x_res;
+ reds->monitor_mode.y_res = y_res;
+ reds->dispatcher_allows_client_mouse = is_client_mouse_allowed;
+ reds_update_mouse_mode();
+ if (reds->is_client_mouse_allowed && tablet) {
+ tablet->set_logical_size(tablet, reds->monitor_mode.x_res, reds->monitor_mode.y_res);
+ }
+}
+
+static void inputs_relase_keys(void)
+{
+ push_key_scan(0x2a | 0x80); //LSHIFT
+ push_key_scan(0x36 | 0x80); //RSHIFT
+ push_key_scan(0xe0); push_key_scan(0x1d | 0x80); //RCTRL
+ push_key_scan(0x1d | 0x80); //LCTRL
+ push_key_scan(0xe0); push_key_scan(0x38 | 0x80); //RALT
+ push_key_scan(0x38 | 0x80); //LALT
+}
+
+static void inputs_read(void *data)
+{
+ InputsState *inputs_state = (InputsState *)data;
+ if (handle_incoming(inputs_state->peer, &inputs_state->in_handler)) {
+ inputs_relase_keys();
+ core->set_file_handlers(core, inputs_state->peer->socket, NULL, NULL, NULL);
+ if (inputs_state->channel) {
+ inputs_state->channel->data = NULL;
+ reds->inputs_state = NULL;
+ }
+ inputs_state->peer->cb_free(inputs_state->peer);
+ free(inputs_state);
+ }
+}
+
+static void inputs_write(void *data)
+{
+ InputsState *inputs_state = (InputsState *)data;
+
+ red_printf("");
+ if (handle_outgoing(inputs_state->peer, &inputs_state->out_handler)) {
+ reds_disconnect();
+ }
+}
+
+static void inputs_shutdown(Channel *channel)
+{
+ InputsState *state = (InputsState *)channel->data;
+ if (state) {
+ state->in_handler.shut = TRUE;
+ shutdown(state->peer->socket, SHUT_RDWR);
+ channel->data = NULL;
+ state->channel = NULL;
+ reds->inputs_state = NULL;
+ }
+}
+
+static void inputs_migrate(Channel *channel)
+{
+ InputsState *state = (InputsState *)channel->data;
+ RedDataHeader header;
+ RedMigrate migrate;
+
+ red_printf("");
+ header.serial = ++state->serial;
+ header.type = RED_MIGRATE;
+ header.size = sizeof(migrate);
+ header.sub_list = 0;
+ migrate.flags = 0;
+ if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(header))
+ != OUTGOING_OK ||
+ outgoing_write(state->peer, &state->out_handler, &migrate, sizeof(migrate))
+ != OUTGOING_OK) {
+ red_printf("write failed");
+ }
+}
+
+static void inputs_select(void *opaque, int select)
+{
+ InputsState *inputs_state;
+ red_printf("");
+
+ inputs_state = (InputsState *)opaque;
+ if (select) {
+ core->set_file_handlers(core, inputs_state->peer->socket, inputs_read, inputs_write,
+ inputs_state);
+ } else {
+ core->set_file_handlers(core, inputs_state->peer->socket, inputs_read, NULL, inputs_state);
+ }
+}
+
+static void inputs_may_write(void *opaque)
+{
+ red_printf("");
+}
+
+static void inputs_link(Channel *channel, RedsStreamContext *peer, int migration,
+ int num_common_caps, uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ InputsState *inputs_state;
+ int delay_val;
+ int flags;
+
+ red_printf("");
+ ASSERT(channel->data == NULL);
+
+ if (!(inputs_state = malloc(sizeof(InputsState)))) {
+ red_printf("alloc input state failed");
+ close(peer->socket);
+ return;
+ }
+
+ delay_val = 1;
+ if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ if ((flags = fcntl(peer->socket, F_GETFL)) == -1 ||
+ fcntl(peer->socket, F_SETFL, flags | O_ASYNC) == -1) {
+ red_printf("fcntl failed, %s", strerror(errno));
+ }
+
+ memset(inputs_state, 0, sizeof(*inputs_state));
+ inputs_state->peer = peer;
+ inputs_state->end_pos = 0;
+ inputs_state->channel = channel;
+ inputs_state->in_handler.opaque = inputs_state;
+ inputs_state->in_handler.handle_message = inputs_handle_input;
+ inputs_state->out_handler.length = 0;
+ inputs_state->out_handler.opaque = inputs_state;
+ inputs_state->out_handler.select = inputs_select;
+ inputs_state->out_handler.may_write = inputs_may_write;
+ inputs_state->pending_mouse_event = FALSE;
+ channel->data = inputs_state;
+ reds->inputs_state = inputs_state;
+ core->set_file_handlers(core, peer->socket, inputs_read, NULL, inputs_state);
+
+ RedDataHeader header;
+ RedInputsInit inputs_init;
+ header.serial = ++inputs_state->serial;
+ header.type = RED_INPUTS_INIT;
+ header.size = sizeof(RedInputsInit);
+ header.sub_list = 0;
+ inputs_init.keyboard_modifiers = keyboard ? keyboard->get_leds(keyboard) : 0;
+ if (outgoing_write(inputs_state->peer, &inputs_state->out_handler, &header,
+ sizeof(RedDataHeader)) != OUTGOING_OK ||
+ outgoing_write(inputs_state->peer, &inputs_state->out_handler, &inputs_init,
+ sizeof(RedInputsInit)) != OUTGOING_OK) {
+ red_printf("failed to send modifiers state");
+ reds_disconnect();
+ }
+}
+
+static void reds_send_keyborad_modifiers(uint8_t modifiers)
+{
+ Channel *channel = reds_find_channel(RED_CHANNEL_INPUTS, 0);
+ InputsState *state;
+
+ if (!channel || !(state = (InputsState *)channel->data)) {
+ return;
+ }
+ ASSERT(state->peer);
+ RedDataHeader header;
+ RedKeyModifiers key_modifiers;
+ header.serial = ++state->serial;
+ header.type = RED_INPUTS_KEY_MODIFAIERS;
+ header.size = sizeof(RedKeyModifiers);
+ header.sub_list = 0;
+ key_modifiers.modifiers = modifiers;
+
+ if (outgoing_write(state->peer, &state->out_handler, &header, sizeof(RedDataHeader))
+ != OUTGOING_OK ||
+ outgoing_write(state->peer, &state->out_handler, &key_modifiers, sizeof(RedKeyModifiers))
+ != OUTGOING_OK) {
+ red_printf("failed to send modifiers state");
+ reds_disconnect();
+ }
+}
+
+static void reds_on_keyborad_leads_change(void *opaque, uint8_t leds)
+{
+ reds_send_keyborad_modifiers(leds);
+}
+
+static void openssl_init(RedLinkInfo *link)
+{
+ unsigned long f4 = RSA_F4;
+ link->tiTicketing.bn = BN_new();
+
+ if (!link->tiTicketing.bn) {
+ red_error("OpenSSL BIGNUMS alloc failed");
+ }
+
+ BN_set_word(link->tiTicketing.bn, f4);
+}
+
+static void inputs_init()
+{
+ Channel *channel;
+ if (!(channel = malloc(sizeof(Channel)))) {
+ red_error("alloc inputs chanel failed");
+ }
+ memset(channel, 0, sizeof(Channel));
+ channel->type = RED_CHANNEL_INPUTS;
+ channel->link = inputs_link;
+ channel->shutdown = inputs_shutdown;
+ channel->migrate = inputs_migrate;
+ reds_register_channel(channel);
+}
+
+static void reds_handle_other_links(RedLinkInfo *link)
+{
+ Channel *channel;
+ RedsStreamContext *peer;
+ RedLinkMess *link_mess;
+ uint32_t *caps;
+
+ link_mess = link->link_mess;
+
+ if (!reds->link_id || reds->link_id != link_mess->connection_id) {
+ reds_send_link_result(link, RED_ERR_BAD_CONNECTION_ID);
+ reds_release_link(link);
+ return;
+ }
+
+ if (!(channel = reds_find_channel(link_mess->channel_type,
+ link_mess->channel_id))) {
+ reds_send_link_result(link, RED_ERR_CHANNEL_NOT_AVAILABLE);
+ reds_release_link(link);
+ return;
+ }
+
+ reds_send_link_result(link, RED_ERR_OK);
+ reds_show_new_channel(link);
+ if (link_mess->channel_type == RED_CHANNEL_INPUTS && !link->peer->ssl) {
+ SimpleOutItem *item;
+ RedNotify *notify;
+ char *mess = "keybord channel is unsecure";
+ const int mess_len = strlen(mess);
+
+ LOG_MESSAGE(VD_LOG_WARN, "%s", mess);
+
+ if (!(item = new_simple_out_item(RED_NOTIFY, sizeof(RedNotify) + mess_len + 1))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+
+ notify = (RedNotify *)item->data;
+ notify->time_stamp = get_time_stamp();
+ notify->severty = RED_NOTIFY_SEVERITY_WARN;
+ notify->visibilty = RED_NOTIFY_VISIBILITY_HIGH;
+ notify->what = RED_WARN_GENERAL;
+ notify->message_len = mess_len;
+ memcpy(notify->message, mess, mess_len + 1);
+ reds_push_pipe_item(&item->base);
+ }
+ peer = link->peer;
+ link->link_mess = NULL;
+ __reds_release_link(link);
+ caps = (uint32_t *)((uint8_t *)link_mess + link_mess->caps_offset);
+ channel->link(channel, peer, reds->mig_target, link_mess->num_common_caps,
+ link_mess->num_common_caps ? caps : NULL, link_mess->num_channel_caps,
+ link_mess->num_channel_caps ? caps + link_mess->num_common_caps : NULL);
+ free(link_mess);
+}
+
+static void reds_handle_ticket(void *opaque)
+{
+ RedLinkInfo *link = (RedLinkInfo *)opaque;
+ char password[RED_MAX_PASSWORD_LENGTH];
+ time_t ltime;
+
+ //todo: use monotonic time
+ time(&ltime);
+ RSA_private_decrypt(link->tiTicketing.rsa_size,
+ link->tiTicketing.encrypted_ticket.encrypted_data,
+ (unsigned char *)password, link->tiTicketing.rsa, RSA_PKCS1_OAEP_PADDING);
+
+ if (ticketing_enabled) {
+ int expired = !link->link_mess->connection_id && taTicket.expiration_time < ltime;
+ char *actual_sever_pass = link->link_mess->connection_id ? reds->taTicket.password :
+ taTicket.password;
+ if (strlen(actual_sever_pass) == 0) {
+ reds_send_link_result(link, RED_ERR_PERMISSION_DENIED);
+ red_printf("Ticketing is enabled, but no password is set. "
+ "please set a ticket first");
+ reds_release_link(link);
+ return;
+ }
+
+ if (expired || strncmp(password, actual_sever_pass, RED_MAX_PASSWORD_LENGTH) != 0) {
+ reds_send_link_result(link, RED_ERR_PERMISSION_DENIED);
+ LOG_MESSAGE(VD_LOG_WARN, "bad connection password or time expired");
+ reds_release_link(link);
+ return;
+ }
+ }
+ if (link->link_mess->channel_type == RED_CHANNEL_MAIN) {
+ reds_handle_main_link(link);
+ } else {
+ reds_handle_other_links(link);
+ }
+}
+
+static inline void async_read_clear_handlers(AsyncRead *obj)
+{
+ if (!obj->active_file_handlers) {
+ return;
+ }
+ obj->active_file_handlers = FALSE;
+ core->set_file_handlers(core, obj->peer->socket, NULL, NULL, NULL);
+}
+
+static void async_read_handler(void *data)
+{
+ AsyncRead *obj = (AsyncRead *)data;
+
+ for (;;) {
+ int n = obj->end - obj->now;
+
+ ASSERT(n > 0);
+ if ((n = obj->peer->cb_read(obj->peer->ctx, obj->now, n)) <= 0) {
+ if (n < 0) {
+ switch (errno) {
+ case EAGAIN:
+ if (!obj->active_file_handlers) {
+ obj->active_file_handlers = TRUE;
+ core->set_file_handlers(core, obj->peer->socket, async_read_handler, NULL,
+ obj);
+ }
+ return;
+ case EINTR:
+ break;
+ default:
+ async_read_clear_handlers(obj);
+ obj->error(obj->opaque, errno);
+ return;
+ }
+ } else {
+ async_read_clear_handlers(obj);
+ obj->error(obj->opaque, 0);
+ return;
+ }
+ } else {
+ obj->now += n;
+ if (obj->now == obj->end) {
+ async_read_clear_handlers(obj);
+ obj->done(obj->opaque);
+ return;
+ }
+ }
+ }
+}
+
+static int reds_security_check(RedLinkInfo *link)
+{
+ ChannelSecurityOptions *security_option = find_channel_security(link->link_mess->channel_type);
+ uint32_t security = security_option ? security_option->options : default_channel_security;
+ return (link->peer->ssl && (security & CHANNEL_SECURITY_SSL)) || (!link->peer->ssl &&
+ (security & CHANNEL_SECURITY_NON));
+}
+
+static void reds_handle_read_link_done(void *opaque)
+{
+ RedLinkInfo *link = (RedLinkInfo *)opaque;
+ RedLinkMess *link_mess = link->link_mess;
+ AsyncRead *obj = &link->asyc_read;
+ uint32_t num_caps = link_mess->num_common_caps + link_mess->num_channel_caps;
+
+ if (num_caps && (num_caps * sizeof(uint32_t) + link_mess->caps_offset >
+ link->link_header.size ||
+ link_mess->caps_offset < sizeof(*link_mess))) {
+ reds_send_link_error(link, RED_ERR_INVALID_DATA);
+ reds_release_link(link);
+ return;
+ }
+
+ if (!reds_security_check(link)) {
+ if (link->peer->ssl) {
+ LOG_MESSAGE(VD_LOG_INFO, "channels of type %d should connect only over "
+ "a non secure link", link_mess->channel_type);
+ red_printf("spice channels %d should not be encrypted", link_mess->channel_type);
+ reds_send_link_error(link, RED_ERR_NEED_UNSECURED);
+ } else {
+ LOG_MESSAGE(VD_LOG_INFO, "channels of type %d should connect only over "
+ "a secure link", link_mess->channel_type);
+ red_printf("spice channels %d should be encrypted", link_mess->channel_type);
+ reds_send_link_error(link, RED_ERR_NEED_SECURED);
+ }
+ reds_release_link(link);
+ return;
+ }
+
+ if (!reds_send_link_ack(link)) {
+ reds_release_link(link);
+ return;
+ }
+
+ obj->now = (uint8_t *)&link->tiTicketing.encrypted_ticket.encrypted_data;
+ obj->end = obj->now + link->tiTicketing.rsa_size;
+ obj->done = reds_handle_ticket;
+ async_read_handler(&link->asyc_read);
+}
+
+static void reds_handle_link_error(void *opaque, int err)
+{
+ RedLinkInfo *link = (RedLinkInfo *)opaque;
+ switch (err) {
+ case 0:
+ case EPIPE:
+ break;
+ default:
+ red_printf("%s", strerror(errno));
+ break;
+ }
+ reds_release_link(link);
+}
+
+static void reds_handle_read_header_done(void *opaque)
+{
+ RedLinkInfo *link = (RedLinkInfo *)opaque;
+ RedLinkHeader *header = &link->link_header;
+ AsyncRead *obj = &link->asyc_read;
+
+ if (header->magic != RED_MAGIC) {
+ reds_send_link_error(link, RED_ERR_INVALID_MAGIC);
+ LOG_MESSAGE(VD_LOG_ERROR, "bad magic %u", header->magic);
+ reds_release_link(link);
+ return;
+ }
+
+ if (header->major_version != RED_VERSION_MAJOR) {
+ if (header->major_version > 0) {
+ reds_send_link_error(link, RED_ERR_VERSION_MISMATCH);
+ }
+ LOG_MESSAGE(VD_LOG_INFO, "version mismatch client %u.%u server %u.%u",
+ header->major_version,
+ header->minor_version,
+ RED_VERSION_MAJOR,
+ RED_VERSION_MINOR);
+
+ red_printf("version mismatch");
+ reds_release_link(link);
+ return;
+ }
+
+ if (header->size < sizeof(RedLinkMess)) {
+ reds_send_link_error(link, RED_ERR_INVALID_DATA);
+ red_printf("bad size %u", header->size);
+ reds_release_link(link);
+ return;
+ }
+
+ if (!(link->link_mess = malloc(header->size))) {
+ red_printf("malloc failed %u", header->size);
+ reds_release_link(link);
+ return;
+ }
+
+ obj->now = (uint8_t *)link->link_mess;
+ obj->end = obj->now + header->size;
+ obj->done = reds_handle_read_link_done;
+ async_read_handler(&link->asyc_read);
+}
+
+static void reds_handle_new_link(RedLinkInfo *link)
+{
+ AsyncRead *obj = &link->asyc_read;
+ obj->opaque = link;
+ obj->peer = link->peer;
+ obj->now = (uint8_t *)&link->link_header;
+ obj->end = (uint8_t *)((RedLinkHeader *)&link->link_header + 1);
+ obj->active_file_handlers = FALSE;
+ obj->done = reds_handle_read_header_done;
+ obj->error = reds_handle_link_error;
+ async_read_handler(&link->asyc_read);
+}
+
+static void reds_handle_ssl_accept(void *data)
+{
+ RedLinkInfo *link = (RedLinkInfo *)data;
+ int return_code;
+
+ if ((return_code = SSL_accept(link->peer->ssl)) != 1) {
+ int ssl_error = SSL_get_error(link->peer->ssl, return_code);
+
+ if (ssl_error != SSL_ERROR_WANT_READ && ssl_error != SSL_ERROR_WANT_WRITE) {
+ red_printf("SSL_accept failed, error=%d", ssl_error);
+ reds_release_link(link);
+ }
+ return;
+ }
+ reds_handle_new_link(link);
+}
+
+static RedLinkInfo *__reds_accept_connection(int listen_socket)
+{
+ RedLinkInfo *link;
+ RedsStreamContext *peer;
+ int delay_val = 1;
+ int flags;
+ int socket;
+
+ if ((socket = accept(listen_socket, NULL, 0)) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ return NULL;
+ }
+
+ if ((flags = fcntl(socket, F_GETFL)) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ if (setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ if (!(link = malloc(sizeof(RedLinkInfo)))) {
+ red_printf("malloc failed");
+ goto error1;
+ }
+
+ if (!(peer = malloc(sizeof(RedsStreamContext)))) {
+ red_printf("malloc failed");
+ goto error2;
+ }
+
+ memset(link, 0, sizeof(RedLinkInfo));
+ memset(peer, 0, sizeof(RedsStreamContext));
+ link->peer = peer;
+ peer->socket = socket;
+ openssl_init(link);
+
+ return link;
+
+error2:
+ free(link);
+
+error1:
+ close(socket);
+
+ return NULL;
+}
+
+static RedLinkInfo *reds_accept_connection(int listen_socket)
+{
+ RedLinkInfo *link;
+ RedsStreamContext *peer;
+
+ if (!(link = __reds_accept_connection(listen_socket))) {
+ return NULL;
+ }
+ peer = link->peer;
+ peer->ctx = (void *)((unsigned long)link->peer->socket);
+ peer->cb_read = (int (*)(void *, void *, int))reds_read;
+ peer->cb_write = (int (*)(void *, void *, int))reds_write;
+ peer->cb_readv = (int (*)(void *, const struct iovec *vector, int count))readv;
+ peer->cb_writev = (int (*)(void *, const struct iovec *vector, int count))writev;
+ peer->cb_free = (int (*)(RedsStreamContext *))reds_free;
+
+ return link;
+}
+
+static void reds_accept_ssl_connection(void *data)
+{
+ RedLinkInfo *link;
+ int return_code;
+ int ssl_error;
+ BIO *sbio;
+
+ link = __reds_accept_connection(reds->secure_listen_socket);
+ if (link == NULL) {
+ return;
+ }
+
+ // Handle SSL handshaking
+ if (!(sbio = BIO_new_socket(link->peer->socket, BIO_NOCLOSE))) {
+ red_printf("could not allocate ssl bio socket");
+ goto error;
+ }
+
+ link->peer->ssl = SSL_new(reds->ctx);
+ if (!link->peer->ssl) {
+ red_printf("could not allocate ssl context");
+ BIO_free(sbio);
+ goto error;
+ }
+
+ SSL_set_bio(link->peer->ssl, sbio, sbio);
+
+ link->peer->ctx = (void *)(link->peer->ssl);
+ link->peer->cb_write = (int (*)(void *, void *, int))reds_ssl_write;
+ link->peer->cb_read = (int (*)(void *, void *, int))reds_ssl_read;
+ link->peer->cb_readv = NULL;
+ link->peer->cb_writev = reds_ssl_writev;
+ link->peer->cb_free = (int (*)(RedsStreamContext *))reds_ssl_free;
+
+ return_code = SSL_accept(link->peer->ssl);
+ if (return_code == 1) {
+ reds_handle_new_link(link);
+ return;
+ }
+
+ ssl_error = SSL_get_error(link->peer->ssl, return_code);
+ if (return_code == -1 && (ssl_error == SSL_ERROR_WANT_READ ||
+ ssl_error == SSL_ERROR_WANT_WRITE)) {
+ core->set_file_handlers(core, link->peer->socket, reds_handle_ssl_accept,
+ reds_handle_ssl_accept, link);
+ return;
+ }
+
+ ERR_print_errors_fp(stderr);
+ red_printf("SSL_accept failed, error=%d", ssl_error);
+ SSL_free(link->peer->ssl);
+
+error:
+ close(link->peer->socket);
+ free(link->peer);
+ BN_free(link->tiTicketing.bn);
+ free(link);
+}
+
+static void reds_accept(void *data)
+{
+ RedLinkInfo *link;
+
+ link = reds_accept_connection(reds->listen_socket);
+ if (link == NULL) {
+ red_printf("accept failed");
+ return;
+ }
+ reds_handle_new_link(link);
+}
+
+static int reds_init_socket(uint16_t port)
+{
+ struct sockaddr_in addr;
+ int sock;
+ int flags;
+
+ if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1) {
+ red_error("socket failed, %s", strerror(errno));
+ }
+
+ flags = 1;
+ if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &flags, sizeof(flags)) < 0) {
+ red_error("socket set sockopt failed, %s", strerror(errno));
+ }
+
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = spice_addr.s_addr;
+ if (bind(sock, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
+ red_error("bind failed, %s", strerror(errno));
+ }
+
+ if ((flags = fcntl(sock, F_GETFL)) == -1) {
+ red_error("fcntl get failed, %s", strerror(errno));
+ }
+
+ if (fcntl(sock, F_SETFL, flags | O_NONBLOCK) == -1) {
+ red_error("fcntl set failed, %s", strerror(errno));
+ }
+
+ if (listen(sock, 2) == -1) {
+ red_error("listen failed, %s", strerror(errno));
+ }
+
+ return sock;
+}
+
+static void reds_init_net()
+{
+ if (spice_port != -1) {
+ reds->listen_socket = reds_init_socket(spice_port);
+ if (core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL)) {
+ red_error("set fd handle failed");
+ }
+ }
+
+ if (spice_secure_port != -1) {
+ reds->secure_listen_socket = reds_init_socket(spice_secure_port);
+ if (core->set_file_handlers(core, reds->secure_listen_socket,
+ reds_accept_ssl_connection, NULL, NULL)) {
+ red_error("set fd handle failed");
+ }
+ }
+}
+
+static void load_dh_params(SSL_CTX *ctx, char *file)
+{
+ DH *ret = 0;
+ BIO *bio;
+
+ if ((bio = BIO_new_file(file, "r")) == NULL) {
+ red_error("Could not open DH file");
+ }
+
+ ret = PEM_read_bio_DHparams(bio, NULL, NULL, NULL);
+ if (ret == 0) {
+ red_error("Could not read DH params");
+ }
+
+ BIO_free(bio);
+
+ if (SSL_CTX_set_tmp_dh(ctx, ret) < 0) {
+ red_error("Could not set DH params");
+ }
+}
+
+/*The password code is not thread safe*/
+static int ssl_password_cb(char *buf, int size, int flags, void *userdata)
+{
+ char *pass = ssl_parameters.keyfile_password;
+ if (size < strlen(pass) + 1) {
+ return (0);
+ }
+
+ strcpy(buf, pass);
+ return (strlen(pass));
+}
+
+static unsigned long pthreads_thread_id(void)
+{
+ unsigned long ret;
+
+ ret = (unsigned long)pthread_self();
+ return (ret);
+}
+
+static void pthreads_locking_callback(int mode, int type, char *file, int line)
+{
+ if (mode & CRYPTO_LOCK) {
+ pthread_mutex_lock(&(lock_cs[type]));
+ lock_count[type]++;
+ } else {
+ pthread_mutex_unlock(&(lock_cs[type]));
+ }
+}
+
+static void openssl_thread_setup()
+{
+ int i;
+
+ lock_cs = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
+ lock_count = OPENSSL_malloc(CRYPTO_num_locks() * sizeof(long));
+
+ for (i = 0; i < CRYPTO_num_locks(); i++) {
+ lock_count[i] = 0;
+ pthread_mutex_init(&(lock_cs[i]), NULL);
+ }
+
+ CRYPTO_set_id_callback((unsigned long (*)())pthreads_thread_id);
+ CRYPTO_set_locking_callback((void (*)())pthreads_locking_callback);
+}
+
+static void reds_init_ssl()
+{
+ SSL_METHOD *ssl_method;
+ int return_code;
+ long ssl_options = SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3;
+
+ /* Global system initialization*/
+ SSL_library_init();
+ SSL_load_error_strings();
+
+ /* Create our context*/
+ ssl_method = TLSv1_method();
+ reds->ctx = SSL_CTX_new(ssl_method);
+ if (!reds->ctx) {
+ red_error("Could not allocate new SSL context");
+ }
+
+ /* Limit connection to TLSv1 only */
+#ifdef SSL_OP_NO_COMPRESSION
+ ssl_options |= SSL_OP_NO_COMPRESSION;
+#endif
+ SSL_CTX_set_options(reds->ctx, ssl_options);
+
+ /* Load our keys and certificates*/
+ return_code = SSL_CTX_use_certificate_chain_file(reds->ctx, ssl_parameters.certs_file);
+ if (return_code != 1) {
+ red_error("Could not load certificates from %s", ssl_parameters.certs_file);
+ }
+
+ SSL_CTX_set_default_passwd_cb(reds->ctx, ssl_password_cb);
+
+ return_code = SSL_CTX_use_PrivateKey_file(reds->ctx, ssl_parameters.private_key_file,
+ SSL_FILETYPE_PEM);
+ if (return_code != 1) {
+ red_error("Could not user private key file");
+ }
+
+ /* Load the CAs we trust*/
+ return_code = SSL_CTX_load_verify_locations(reds->ctx, ssl_parameters.ca_certificate_file, 0);
+ if (return_code != 1) {
+ red_error("Could not use ca file");
+ }
+
+#if (OPENSSL_VERSION_NUMBER < 0x00905100L)
+ SSL_CTX_set_verify_depth(reds->ctx, 1);
+#endif
+
+ load_dh_params(reds->ctx, ssl_parameters.dh_key_file);
+
+ SSL_CTX_set_session_id_context(reds->ctx, (const unsigned char *)"SPICE", 5);
+ SSL_CTX_set_cipher_list(reds->ctx, ssl_parameters.ciphersuite);
+
+ openssl_thread_setup();
+
+#ifndef SSL_OP_NO_COMPRESSION
+ STACK *cmp_stack = SSL_COMP_get_compression_methods();
+ sk_zero(cmp_stack);
+#endif
+}
+
+static void reds_exit()
+{
+ if (reds->peer) {
+ close(reds->peer->socket);
+ }
+#ifdef RED_STATISTICS
+ shm_unlink(reds->stat_shm_name);
+ free(reds->stat_shm_name);
+#endif
+ unsetenv("QEMU_AUDIO_DRV");
+}
+
+enum {
+ SPICE_OPTION_INVALID,
+ SPICE_OPTION_PORT,
+ SPICE_OPTION_SPORT,
+ SPICE_OPTION_HOST,
+ SPICE_OPTION_IMAGE_COMPRESSION,
+ SPICE_OPTION_PASSWORD,
+ SPICE_OPTION_DISABLE_TICKET,
+ SPICE_OPTION_RENDERER,
+ SPICE_OPTION_SSLKEY,
+ SPICE_OPTION_SSLCERTS,
+ SPICE_OPTION_SSLCAFILE,
+ SPICE_OPTION_SSLDHFILE,
+ SPICE_OPTION_SSLPASSWORD,
+ SPICE_OPTION_SSLCIPHERSUITE,
+ SPICE_SECURED_CHANNELS,
+ SPICE_UNSECURED_CHANNELS,
+ SPICE_OPTION_STREAMING_VIDEO,
+ SPICE_OPTION_AGENT_MOUSE,
+ SPICE_OPTION_PLAYBACK_COMPRESSION,
+};
+
+typedef struct OptionsMap {
+ const char *name;
+ int val;
+} OptionsMap;
+
+static int find_option(const char *str, OptionsMap *options_map)
+{
+ int i = 0;
+
+ for (i = 0; options_map[i].name != NULL; i++) {
+ if (strcmp(str, options_map[i].name) == 0) {
+ return options_map[i].val;
+ }
+ }
+ return SPICE_OPTION_INVALID;
+}
+
+static void clear_blanks(char **ptr)
+{
+ char *str = *ptr;
+ while (isspace(*str)) {
+ str++;
+ }
+ while (isspace(str[strlen(str) - 1])) {
+ str[strlen(str) - 1] = 0;
+ }
+ *ptr = str;
+}
+
+static int get_option(char **args, char **out_val, OptionsMap *map, char seperator)
+{
+ char *p;
+ char *next;
+ char *val;
+
+ ASSERT(args && out_val);
+
+ p = *args;
+ if ((next = strchr(p, seperator))) {
+ *next = 0;
+ *args = next + 1;
+ } else {
+ *args = NULL;
+ }
+
+ if ((val = strchr(p, '='))) {
+ *(val++) = 0;
+ clear_blanks(&val);
+ *out_val = (strlen(val) == 0) ? NULL : val;
+ } else {
+ *out_val = NULL;
+ }
+
+ clear_blanks(&p);
+ return find_option(p, map);
+}
+
+enum {
+ SPICE_TICKET_OPTION_INVALID,
+ SPICE_TICKET_OPTION_EXPIRATION,
+ SPICE_TICKET_OPTION_CONNECTED,
+};
+
+static OptionsMap _spice_ticket_options[] = {
+ {"expiration", SPICE_TICKET_OPTION_EXPIRATION},
+ {"connected", SPICE_TICKET_OPTION_CONNECTED},
+ {NULL, 0},
+};
+
+static inline void on_activating_ticketing()
+{
+ if (!ticketing_enabled && reds->peer) {
+ red_printf("disconnecting");
+ reds_disconnect();
+ }
+}
+
+static void reds_reset_ticketing()
+{
+ on_activating_ticketing();
+ ticketing_enabled = 1;
+ taTicket.expiration_time = 0;
+ memset(taTicket.password, 0, sizeof(taTicket.password));
+}
+
+static void reds_set_ticketing(const char *pass, long expiration)
+{
+ ASSERT(expiration >= 0);
+ on_activating_ticketing();
+ ticketing_enabled = 1;
+ if (expiration == 0) {
+ taTicket.expiration_time = INT_MAX;
+ } else {
+ time_t ltime;
+
+ time(&ltime);
+ taTicket.expiration_time = ltime + expiration;
+ }
+ strncpy(taTicket.password, pass, sizeof(taTicket.password));
+}
+
+static void reds_do_set_ticket(const char *password, const char *args)
+{
+ long expiration = 0;
+ char *local_args = NULL;
+ const char *term_str = "invalid args";
+ int disconnect = FALSE;
+ int fail = FALSE;
+
+ if (!password) {
+ term_str = "unexpected NULL password";
+ goto error;
+ }
+
+ if (args) {
+ char *in_args;
+ int option;
+ char *val;
+
+ in_args = local_args = malloc(strlen(args) + 1);
+ strcpy(local_args, args);
+ do {
+ switch (option = get_option(&in_args, &val, _spice_ticket_options, ',')) {
+ case SPICE_TICKET_OPTION_EXPIRATION: {
+ char *endptr;
+
+ if (!val) {
+ goto error;
+ }
+ expiration = strtol(val, &endptr, 0);
+ if (endptr != val + strlen(val) || expiration < 0) {
+ term_str = "invalid expiration";
+ goto error;
+ }
+ break;
+ }
+ case SPICE_TICKET_OPTION_CONNECTED:
+ if (!val) {
+ goto error;
+ }
+
+ if (strcmp(val, "disconnect") == 0) {
+ disconnect = TRUE;
+ fail = FALSE;
+ } else if (strcmp(val, "fail") == 0) {
+ fail = TRUE;
+ disconnect = FALSE;
+ } else if (strcmp(val, "keep") == 0) {
+ fail = FALSE;
+ disconnect = FALSE;
+ } else {
+ goto error;
+ }
+ break;
+ default:
+ goto error;
+ }
+ } while (in_args);
+ }
+
+ if (fail && reds->peer) {
+ term_str = "Ticket set failed";
+ } else {
+ if (disconnect) {
+ reds_disconnect();
+ }
+ reds_set_ticketing(password, expiration);
+ term_str = "Ticket set successfully";
+ }
+ core->term_printf(core, "%s\n", term_str);
+ free(local_args);
+ return;
+
+error:
+ reds_reset_ticketing();
+ core->term_printf(core, "%s\n", term_str);
+ free(local_args);
+}
+
+static void reds_do_set_ticket64(const char *password64, const char *args)
+{
+ char *password;
+
+ if (!password64) {
+ reds_reset_ticketing();
+ core->term_printf(core, "unexpected NULL password\n");
+ return;
+ }
+
+ if (!(password = base64decode(password64, strlen(password64)))) {
+ reds_reset_ticketing();
+ core->term_printf(core, "set_ticket64 failed!\n");
+ return;
+ }
+ reds_do_set_ticket(password, args);
+ free(password);
+}
+
+static void reds_do_info_spice()
+{
+ core->term_printf(core, "spice info:");
+ if (reds->peer) {
+ char *ip = NULL;
+ struct sockaddr_in sock_addr;
+ socklen_t len = sizeof(sock_addr);
+ if (getpeername(reds->peer->socket, (struct sockaddr *)&sock_addr, &len) != -1) {
+ ip = inet_ntoa(sock_addr.sin_addr);
+ }
+ core->term_printf(core, " client=%s", ip);
+ } else {
+ core->term_printf(core, " disconnected");
+ }
+ core->term_printf(core, " ticketing=%s", ticketing_enabled ? "on" : "off");
+ switch (image_compression) {
+ case IMAGE_COMPRESS_AUTO_GLZ:
+ core->term_printf(core, " ic=auto_glz");
+ break;
+ case IMAGE_COMPRESS_AUTO_LZ:
+ core->term_printf(core, " ic=auto_lz");
+ break;
+ case IMAGE_COMPRESS_QUIC:
+ core->term_printf(core, " ic=quic");
+ break;
+ case IMAGE_COMPRESS_LZ:
+ core->term_printf(core, " ic=lz");
+ break;
+ case IMAGE_COMPRESS_GLZ:
+ core->term_printf(core, " ic=glz");
+ break;
+ case IMAGE_COMPRESS_OFF:
+ core->term_printf(core, " ic=off");
+ break;
+ case IMAGE_COMPRESS_INVALID:
+ default:
+ core->term_printf(core, " ic=invalid");
+ }
+
+ core->term_printf(core, " sv=%s", streaming_video ? "on" : "off");
+ core->term_printf(core, " playback-compression=%s\n",
+ snd_get_playback_compression() ? "on" : "off");
+}
+
+static void set_image_compression(image_compression_t val)
+{
+ if (val == image_compression) {
+ return;
+ }
+ image_compression = val;
+ red_dispatcher_on_ic_change();
+}
+
+static image_compression_t reds_get_image_compression(const char *val)
+{
+ if ((strcmp(val, "on") == 0) || (strcmp(val, "auto_glz") == 0)) {
+ return IMAGE_COMPRESS_AUTO_GLZ;
+ } else if (strcmp(val, "auto_lz") == 0) {
+ return IMAGE_COMPRESS_AUTO_LZ;
+ } else if (strcmp(val, "quic") == 0) {
+ return IMAGE_COMPRESS_QUIC;
+ } else if (strcmp(val, "glz") == 0) {
+ return IMAGE_COMPRESS_GLZ;
+ } else if (strcmp(val, "lz") == 0) {
+ return IMAGE_COMPRESS_LZ;
+ } else if (strcmp(val, "off") == 0) {
+ return IMAGE_COMPRESS_OFF;
+ }
+ return IMAGE_COMPRESS_INVALID;
+}
+
+static void reds_do_set_image_compression(const char *val)
+{
+ image_compression_t real_val = reds_get_image_compression(val);
+ if (real_val == IMAGE_COMPRESS_INVALID) {
+ core->term_printf(core, "bad image compression arg\n");
+ return;
+ }
+ set_image_compression(real_val);
+}
+
+static void reds_do_set_streaming_video(const char *val)
+{
+ uint32_t new_val;
+ if (strcmp(val, "on") == 0) {
+ new_val = TRUE;
+ } else if (strcmp(val, "off") == 0) {
+ new_val = FALSE;
+ } else {
+ core->term_printf(core, "bad streaming video arg\n");
+ return;
+ }
+ if (new_val == streaming_video) {
+ return;
+ }
+ streaming_video = new_val;
+ red_dispatcher_on_sv_change();
+}
+
+static void reds_do_set_agent_mouse(const char *val)
+{
+ int new_val;
+ if (strcmp(val, "on") == 0) {
+ new_val = TRUE;
+ } else if (strcmp(val, "off") == 0) {
+ new_val = FALSE;
+ } else {
+ core->term_printf(core, "bad agent mouse arg\n");
+ return;
+ }
+ if (new_val == agent_mouse) {
+ return;
+ }
+ agent_mouse = new_val;
+ reds_update_mouse_mode();
+}
+
+static void reds_do_set_playback_compression(const char *val)
+{
+ int on;
+ if (strcmp(val, "on") == 0) {
+ on = TRUE;
+ } else if (strcmp(val, "off") == 0) {
+ on = FALSE;
+ } else {
+ core->term_printf(core, "bad playback compression arg\n");
+ return;
+ }
+ snd_set_playback_compression(on);
+}
+
+static OptionsMap _spice_options[] = {
+ {"port", SPICE_OPTION_PORT},
+ {"sport", SPICE_OPTION_SPORT},
+ {"host", SPICE_OPTION_HOST},
+ {"ic", SPICE_OPTION_IMAGE_COMPRESSION},
+ {"password", SPICE_OPTION_PASSWORD},
+ {"disable-ticketing", SPICE_OPTION_DISABLE_TICKET},
+ {"renderer", SPICE_OPTION_RENDERER},
+ {"sslkey", SPICE_OPTION_SSLKEY},
+ {"sslcert", SPICE_OPTION_SSLCERTS},
+ {"sslcafile", SPICE_OPTION_SSLCAFILE},
+ {"ssldhfile", SPICE_OPTION_SSLDHFILE},
+ {"sslpassword", SPICE_OPTION_SSLPASSWORD},
+ {"sslciphersuite", SPICE_OPTION_SSLCIPHERSUITE},
+ {"secure-channels", SPICE_SECURED_CHANNELS},
+ {"unsecure-channels", SPICE_UNSECURED_CHANNELS},
+ {"sv", SPICE_OPTION_STREAMING_VIDEO},
+ {"agent-mouse", SPICE_OPTION_AGENT_MOUSE},
+ {"playback-compression", SPICE_OPTION_PLAYBACK_COMPRESSION},
+ {NULL, 0},
+};
+
+enum {
+ CHANNEL_NAME_INVALID,
+ CHANNEL_NAME_ALL,
+ CHANNEL_NAME_MAIN,
+ CHANNEL_NAME_DISPLAY,
+ CHANNEL_NAME_INPUTS,
+ CHANNEL_NAME_CURSOR,
+ CHANNEL_NAME_PLAYBACK,
+ CHANNEL_NAME_RECORD,
+};
+
+static OptionsMap _channel_map[] = {
+ {"all", CHANNEL_NAME_ALL},
+ {"main", CHANNEL_NAME_MAIN},
+ {"display", CHANNEL_NAME_DISPLAY},
+ {"inputs", CHANNEL_NAME_INPUTS},
+ {"cursor", CHANNEL_NAME_CURSOR},
+ {"playback", CHANNEL_NAME_PLAYBACK},
+ {"record", CHANNEL_NAME_RECORD},
+ {NULL, 0},
+};
+
+static void set_all_channels_security(uint32_t security)
+{
+ while (channels_security) {
+ ChannelSecurityOptions *temp = channels_security;
+ channels_security = channels_security->next;
+ free(temp);
+ }
+ default_channel_security = security;
+}
+
+static void set_one_channel_security(int id, uint32_t security)
+{
+ ChannelSecurityOptions *security_options;
+
+ if ((security_options = find_channel_security(id))) {
+ security_options->options = security;
+ return;
+ }
+ security_options = (ChannelSecurityOptions *)malloc(sizeof(*security_options));
+ if (!security_options) {
+ red_error("malloc failed");
+ }
+ security_options->channel_id = id;
+ security_options->options = security;
+ security_options->next = channels_security;
+ channels_security = security_options;
+}
+
+static int set_channels_security(const char *channels, uint32_t security)
+{
+ char *local_str = malloc(strlen(channels) + 1);
+ int channel_name;
+ char *str;
+ char *val;
+ int all = 0;
+ int specific = 0;
+
+ if (!local_str) {
+ red_error("malloc failed");
+ }
+ strcpy(local_str, channels);
+ str = local_str;
+ do {
+ switch (channel_name = get_option(&str, &val, _channel_map, '+')) {
+ case CHANNEL_NAME_ALL:
+ all++;
+ break;
+ case CHANNEL_NAME_MAIN:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_MAIN, security);
+ break;
+ case CHANNEL_NAME_DISPLAY:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_DISPLAY, security);
+ break;
+ case CHANNEL_NAME_INPUTS:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_INPUTS, security);
+ break;
+ case CHANNEL_NAME_CURSOR:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_CURSOR, security);
+ break;
+ case CHANNEL_NAME_PLAYBACK:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_PLAYBACK, security);
+ break;
+ case CHANNEL_NAME_RECORD:
+ specific++;
+ set_one_channel_security(RED_CHANNEL_RECORD, security);
+ break;
+ default:
+ goto error;
+ }
+ if (val) {
+ goto error;
+ }
+ } while (str);
+
+ if (all) {
+ if (specific || all > 1) {
+ goto error;
+ }
+ set_all_channels_security(security);
+ return TRUE;
+ }
+ return TRUE;
+
+error:
+ free(local_str);
+ return FALSE;
+}
+
+int __attribute__ ((visibility ("default"))) spice_parse_args(const char *in_args)
+{
+ char *local_args;
+ char *args;
+ int option;
+ char *val;
+ int renderers_opt = FALSE;
+
+ int ssl_port = FALSE;
+ int ssl_key = FALSE;
+ int ssl_certs = FALSE;
+ int ssl_ciphersuite = FALSE;
+ int ssl_cafile = FALSE;
+ int ssl_dhfile = FALSE;
+
+ memset(&ssl_parameters, 0, sizeof(ssl_parameters));
+
+ local_args = malloc(strlen(in_args) + 1);
+ strcpy(local_args, in_args);
+
+ args = local_args;
+ do {
+ switch (option = get_option(&args, &val, _spice_options, ',')) {
+ case SPICE_OPTION_PORT: {
+ char *endptr;
+ long int port;
+
+ if (!val) {
+ goto error;
+ }
+ port = strtol(val, &endptr, 0);
+ if (endptr != val + strlen(val) || port < 0 || port > 0xffff) {
+ goto error;
+ }
+ spice_port = port;
+ break;
+ }
+ case SPICE_OPTION_SPORT: {
+ char *endptr;
+ long int port;
+
+ if (!val) {
+ goto error;
+ }
+ port = strtol(val, &endptr, 0);
+ if (endptr != val + strlen(val) || port < 0 || port > 0xffff) {
+ goto error;
+ }
+
+ ssl_port = TRUE;
+ spice_secure_port = port;
+ break;
+ }
+ case SPICE_OPTION_HOST: {
+ struct hostent* host_addr;
+ if (!val) {
+ goto error;
+ }
+ if ((host_addr = gethostbyname(val)) == NULL || host_addr->h_addrtype != AF_INET) {
+ goto error;
+ }
+ ASSERT(host_addr->h_length == sizeof(spice_addr));
+ memcpy(&spice_addr, host_addr->h_addr, sizeof(spice_addr));
+ break;
+ }
+ case SPICE_OPTION_IMAGE_COMPRESSION:
+ if (!val) {
+ goto error;
+ }
+ image_compression = reds_get_image_compression(val);
+ if (image_compression == IMAGE_COMPRESS_INVALID) {
+ goto error;
+ }
+ break;
+ case SPICE_OPTION_PASSWORD:
+ ticketing_enabled = 1;
+
+ if (val) {
+ strncpy(taTicket.password, val, sizeof taTicket.password);
+ //todo: add expiration option
+ taTicket.expiration_time = INT_MAX;
+ }
+
+ break;
+ case SPICE_OPTION_DISABLE_TICKET:
+ ticketing_enabled = 0;
+ break;
+ case SPICE_OPTION_RENDERER:
+ renderers_opt = TRUE;
+ if (!val) {
+ goto error;
+ }
+ while (val) {
+ char *now = val;
+ if ((val = strchr(now, '+'))) {
+ *val++ = 0;
+ }
+ if (!red_dispatcher_add_renderer(now)) {
+ goto error;
+ }
+ }
+
+ break;
+ case SPICE_OPTION_SSLCIPHERSUITE:
+ ssl_ciphersuite = TRUE;
+
+ if (val) {
+ strncpy(ssl_parameters.ciphersuite, val, sizeof(ssl_parameters.ciphersuite));
+ }
+
+ break;
+ case SPICE_OPTION_SSLPASSWORD:
+ if (val) {
+ strncpy(ssl_parameters.keyfile_password, val,
+ sizeof(ssl_parameters.keyfile_password));
+ }
+ break;
+ case SPICE_OPTION_SSLKEY:
+ ssl_key = TRUE;
+
+ if (val) {
+ strncpy(ssl_parameters.private_key_file, val,
+ sizeof(ssl_parameters.private_key_file));
+ }
+ break;
+ case SPICE_OPTION_SSLCERTS:
+ ssl_certs = TRUE;
+
+ if (val) {
+ strncpy(ssl_parameters.certs_file, val, sizeof(ssl_parameters.certs_file));
+ }
+ break;
+ case SPICE_OPTION_SSLCAFILE:
+ ssl_cafile = TRUE;
+
+ if (val) {
+ strncpy(ssl_parameters.ca_certificate_file, val,
+ sizeof(ssl_parameters.ca_certificate_file));
+ }
+ break;
+ case SPICE_OPTION_SSLDHFILE:
+ ssl_dhfile = TRUE;
+
+ if (val) {
+ strncpy(ssl_parameters.dh_key_file, val, sizeof(ssl_parameters.dh_key_file));
+ }
+ break;
+ case SPICE_SECURED_CHANNELS:
+ if (!val || !set_channels_security(val, CHANNEL_SECURITY_SSL)) {
+ goto error;
+ }
+ break;
+ case SPICE_UNSECURED_CHANNELS:
+ if (!val || !set_channels_security(val, CHANNEL_SECURITY_NON)) {
+ goto error;
+ }
+ break;
+ case SPICE_OPTION_STREAMING_VIDEO:
+ if (!val) {
+ goto error;
+ }
+ if (strcmp(val, "off") == 0) {
+ streaming_video = FALSE;
+ } else if (strcmp(val, "on") != 0) {
+ goto error;
+ }
+ break;
+ case SPICE_OPTION_PLAYBACK_COMPRESSION:
+ if (!val) {
+ goto error;
+ }
+ if (strcmp(val, "on") == 0) {
+ snd_set_playback_compression(TRUE);
+ } else if (strcmp(val, "off") == 0) {
+ snd_set_playback_compression(FALSE);
+ } else {
+ goto error;
+ }
+ break;
+ case SPICE_OPTION_AGENT_MOUSE:
+ if (!val) {
+ goto error;
+ }
+ if (strcmp(val, "on") == 0) {
+ agent_mouse = TRUE;
+ } else if (strcmp(val, "off") == 0) {
+ agent_mouse = FALSE;
+ } else {
+ goto error;
+ }
+ break;
+ default:
+ goto error;
+ }
+ } while (args);
+
+ if (!renderers_opt && !red_dispatcher_add_renderer("cairo")) {
+ goto error;
+ }
+
+ // All SSL parameters should be either on or off.
+ if (ssl_port != ssl_key || ssl_key != ssl_certs || ssl_certs != ssl_cafile ||
+ ssl_cafile != ssl_dhfile || ssl_dhfile != ssl_ciphersuite) {
+
+ goto error;
+ }
+ free(local_args);
+ return TRUE;
+
+error:
+ free(local_args);
+ return FALSE;
+}
+
+const char *spice_usage_str[] __attribute__ ((visibility ("default"))) = {
+ "[port=<port>][,sport=<port>][,host=<host>]",
+ "[,ic=on|auto_glz|auto_lz|quic|glz|lz|off]",
+ "[,playback-compression=on|off]",
+ "[,password=password][,disable-ticketing]",
+ "[,renderer=oglpbuf+oglpixmap+cairo]",
+ "[,sslkeys=key directory,sslcerts=certs directory,sslpassword=pem password,",
+ " sslciphersuite=cipher suite]",
+ "[,secure-channels=all|channel+channel+...]",
+ "[,unsecure-channels=all|channel+channel+...]",
+ "[,vs=on|off] [,ac=on|off]",
+ " listen on interface address <host> port <port> and/or sport <port>",
+ " setting ticket password using \"ticket\" option",
+ " setting image compression using \"ic\" option [default=auto_local]",
+ " setting playback compression using \"playback-compression\" option [default=on]",
+ " select renderers using \"renderer\" option",
+ " sslkeys - set directory where ssl key file resides.",
+ " sslcerts - set directory where ssl cert file resides.",
+ " sslpassword - set the password to open the private key file.",
+ " sslciphersuite - set the cipher suite to use.",
+ " setting streaming video using \"sv\" option [default=on]",
+ " setting audio compression codec using \"ac\" option [default=off]",
+ " secure-channels - force secure connection on all/specific chnnels.",
+ " channels names: main, inputs, display, cursor,",
+ " playback and record.",
+ " unsecure-channels - force unsecure connection on all/specific chnnels.",
+ " channels names as in secure-channels.",
+ NULL,
+};
+
+#define REDS_SAVE_VERSION 1
+
+static OptionsMap spice_mig_options[] = {
+ {"spicesport", SPICE_OPTION_SPORT},
+ {"spiceport", SPICE_OPTION_PORT},
+ {"spicehost", SPICE_OPTION_HOST},
+ {NULL, 0},
+};
+
+struct RedsMigSpice;
+
+typedef struct RedsMigRead {
+ uint8_t buf[RECIVE_BUF_SIZE];
+ uint32_t end_pos;
+ uint32_t size;
+
+ void (*handle_data)(struct RedsMigSpice *message);
+} RedsMigRead;
+
+typedef struct RedsMigWrite {
+ uint8_t buf[SEND_BUF_SIZE];
+ uint8_t *now;
+ uint32_t length;
+
+ void (*handle_done)(struct RedsMigSpice *s);
+} RedsMigWrite;
+
+typedef struct RedsMigSpice {
+ int fd;
+ RedsMigWrite write;
+ RedsMigRead read;
+
+ char pub_key[RED_TICKET_PUBKEY_BYTES];
+ uint32_t mig_key;
+
+ char *local_args;
+ char *host;
+ int port;
+ int sport;
+} RedsMigSpice;
+
+typedef struct RedsMigSpiceMessage {
+ uint32_t link_id;
+} RedsMigSpiceMessage;
+
+static int reds_mig_actual_read(RedsMigSpice *s)
+{
+ for (;;) {
+ uint8_t *buf = s->read.buf;
+ uint32_t pos = s->read.end_pos;
+ int n;
+ n = read(s->fd, buf + pos, s->read.size - pos);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ s->read.end_pos += n;
+ if (s->read.end_pos == s->read.size) {
+ s->read.handle_data(s);
+ return 0;
+ }
+ }
+ }
+}
+
+static int reds_mig_actual_write(RedsMigSpice *s)
+{
+ if (!s->write.length) {
+ return 0;
+ }
+
+ while (s->write.length) {
+ int n;
+
+ n = write(s->fd, s->write.now, s->write.length);
+ if (n <= 0) {
+ if (n == 0) {
+ return -1;
+ }
+ switch (errno) {
+ case EAGAIN:
+ return 0;
+ case EINTR:
+ break;
+ case EPIPE:
+ return -1;
+ default:
+ red_printf("%s", strerror(errno));
+ return -1;
+ }
+ } else {
+ s->write.now += n;
+ s->write.length -= n;
+ }
+ }
+
+ s->write.handle_done(s);
+ return 0;
+}
+
+static void reds_mig_failed(RedsMigSpice *s)
+{
+ red_printf("");
+ core->set_file_handlers(core, s->fd, NULL, NULL, NULL);
+ if (s->local_args) {
+ free(s->local_args);
+ }
+ free(s);
+
+ reds_mig_disconnect();
+}
+
+static void reds_mig_write(void *data)
+{
+ RedsMigSpice *s = data;
+
+ if (reds_mig_actual_write((RedsMigSpice *)data)) {
+ red_printf("write error cannot continue spice migration");
+ reds_mig_failed(s);
+ }
+}
+
+static void reds_mig_read(void *data)
+{
+ RedsMigSpice *s = data;
+
+ if (reds_mig_actual_read((RedsMigSpice *)data)) {
+ red_printf("read error cannot continue spice migration");
+ reds_mig_failed(s);
+ }
+}
+
+static void reds_mig_continue(RedsMigSpice *s)
+{
+ RedMigrationBegin *migrate;
+ SimpleOutItem *item;
+ int host_len;
+
+ red_printf("");
+ core->set_file_handlers(core, s->fd, NULL, NULL, NULL);
+ host_len = strlen(s->host) + 1;
+ if (!(item = new_simple_out_item(RED_MIGRATE_BEGIN, sizeof(RedMigrationBegin) + host_len))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ migrate = (RedMigrationBegin *)item->data;
+ migrate->port = s->port;
+ migrate->sport = s->sport;
+ memcpy(migrate->host, s->host, host_len);
+ reds_push_pipe_item(&item->base);
+
+ free(s->local_args);
+ free(s);
+ reds->mig_wait_connect = TRUE;
+ core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT);
+}
+
+static void reds_mig_receive_ack(RedsMigSpice *s)
+{
+ s->read.size = sizeof(uint32_t);
+ s->read.end_pos = 0;
+ s->read.handle_data = reds_mig_continue;
+
+ core->set_file_handlers(core, s->fd, reds_mig_read, NULL, s);
+}
+
+static void reds_mig_send_link_id(RedsMigSpice *s)
+{
+ RedsMigSpiceMessage *data = (RedsMigSpiceMessage *)s->write.buf;
+
+ memcpy(&data->link_id, &reds->link_id, sizeof(reds->link_id));
+
+ s->write.now = s->write.buf;
+ s->write.length = sizeof(RedsMigSpiceMessage);
+ s->write.handle_done = reds_mig_receive_ack;
+
+ core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
+}
+
+static void reds_mig_send_ticket(RedsMigSpice *s)
+{
+ EVP_PKEY *pubkey = NULL;
+ BIO *bio_key;
+ RSA *rsa;
+ int rsa_size = 0;
+
+ red_printf("");
+
+ bio_key = BIO_new(BIO_s_mem());
+ if (bio_key != NULL) {
+ BIO_write(bio_key, s->read.buf, RED_TICKET_PUBKEY_BYTES);
+ pubkey = d2i_PUBKEY_bio(bio_key, NULL);
+ rsa = pubkey->pkey.rsa;
+ rsa_size = RSA_size(rsa);
+ if (RSA_public_encrypt(strlen(reds->taTicket.password) + 1,
+ (unsigned char *)reds->taTicket.password,
+ (uint8_t *)(s->write.buf),
+ rsa, RSA_PKCS1_OAEP_PADDING) > 0) {
+ s->write.length = RSA_size(rsa);
+ s->write.now = s->write.buf;
+ s->write.handle_done = reds_mig_send_link_id;
+ core->set_file_handlers(core, s->fd, reds_mig_write, reds_mig_write, s);
+ } else {
+ reds_mig_failed(s);
+ }
+ } else {
+ reds_mig_failed(s);
+ }
+
+ EVP_PKEY_free(pubkey);
+ BIO_free(bio_key);
+}
+
+static void reds_mig_control(RedsMigSpice *spice_migration)
+{
+ uint32_t *control;
+
+ core->set_file_handlers(core, spice_migration->fd, NULL, NULL, NULL);
+ control = (uint32_t *)spice_migration->read.buf;
+
+ switch (*control) {
+ case REDS_MIG_CONTINUE:
+ spice_migration->read.size = RED_TICKET_PUBKEY_BYTES;
+ spice_migration->read.end_pos = 0;
+ spice_migration->read.handle_data = reds_mig_send_ticket;
+
+ core->set_file_handlers(core, spice_migration->fd, reds_mig_read,
+ NULL, spice_migration);
+ break;
+ case REDS_MIG_ABORT:
+ red_printf("abort");
+ reds_mig_failed(spice_migration);
+ break;
+ default:
+ red_printf("invalid control");
+ reds_mig_failed(spice_migration);
+ }
+}
+
+static void reds_mig_receive_control(RedsMigSpice *spice_migration)
+{
+ spice_migration->read.size = sizeof(uint32_t);
+ spice_migration->read.end_pos = 0;
+ spice_migration->read.handle_data = reds_mig_control;
+
+ core->set_file_handlers(core, spice_migration->fd, reds_mig_read, NULL, spice_migration);
+}
+
+static void reds_mig_started(void *opaque, const char *in_args)
+{
+ RedsMigSpice *spice_migration = NULL;
+ uint32_t *version;
+ char *val;
+ char *args;
+ int option;
+
+ ASSERT(in_args);
+ red_printf("");
+
+ reds->mig_inprogress = TRUE;
+
+ if (reds->listen_socket != -1) {
+ core->set_file_handlers(core, reds->listen_socket, NULL, NULL, NULL);
+ }
+
+ if (reds->secure_listen_socket != -1) {
+ core->set_file_handlers(core, reds->secure_listen_socket, NULL, NULL, NULL);
+ }
+
+ if (reds->peer == NULL) {
+ red_printf("not connected to peer");
+ goto error;
+ }
+
+ spice_migration = (RedsMigSpice *)malloc(sizeof(RedsMigSpice));
+ if (!spice_migration) {
+ red_printf("Could not allocate memory for spice migration structure");
+ goto error;
+ }
+ memset(spice_migration, 0, sizeof(RedsMigSpice));
+ spice_migration->port = -1;
+ spice_migration->sport = -1;
+
+ if (!(spice_migration->local_args = malloc(strlen(in_args) + 1))) {
+ red_printf("str malloc failed");
+ goto error;
+ }
+
+ strcpy(spice_migration->local_args, in_args);
+ args = spice_migration->local_args;
+ do {
+ switch (option = get_option(&args, &val, spice_mig_options, ',')) {
+ case SPICE_OPTION_SPORT: {
+ char *endptr;
+
+ if (!val) {
+ goto error;
+ }
+ spice_migration->sport = strtol(val, &endptr, 0);
+ if (endptr != val + strlen(val) || spice_migration->sport < 0 ||
+ spice_migration->sport > 0xffff) {
+ goto error;
+ }
+ break;
+ }
+ case SPICE_OPTION_PORT: {
+ char *endptr;
+
+ if (!val) {
+ goto error;
+ }
+ spice_migration->port = strtol(val, &endptr, 0);
+ if (
+ endptr != val + strlen(val) ||
+ spice_migration->port < 0 ||
+ spice_migration->port > 0xffff
+ ) {
+ goto error;
+ }
+ break;
+ }
+ case SPICE_OPTION_HOST:
+ if (!val) {
+ goto error;
+ }
+ spice_migration->host = val;
+ break;
+ }
+ } while (args);
+
+ if ((spice_migration->sport == -1 && spice_migration->port == -1) || !spice_migration->host) {
+ red_printf("invalid args port %d sport %d host %s",
+ spice_migration->port,
+ spice_migration->sport,
+ (spice_migration->host) ? spice_migration->host : "NULL");
+ goto error;
+ }
+
+ spice_migration->fd = mig->begin_hook(mig, reds->mig_notifier);
+
+ if (spice_migration->fd == -1) {
+ goto error;
+ }
+
+ spice_migration->write.now = spice_migration->write.buf;
+ spice_migration->write.length = sizeof(uint32_t);
+ version = (uint32_t *)spice_migration->write.buf;
+ *version = REDS_MIG_VERSION;
+ spice_migration->write.handle_done = reds_mig_receive_control;
+ core->set_file_handlers(core, spice_migration->fd, reds_mig_write,
+ reds_mig_write, spice_migration);
+ return;
+
+error:
+ if (spice_migration) {
+ if (spice_migration->local_args) {
+ free(spice_migration->local_args);
+ }
+ free(spice_migration);
+ }
+
+ reds_mig_disconnect();
+}
+
+static void reds_mig_finished(void *opaque, int completed)
+{
+ SimpleOutItem *item;
+
+ red_printf("");
+ if (reds->listen_socket != -1) {
+ core->set_file_handlers(core, reds->listen_socket, reds_accept, NULL, NULL);
+ }
+
+ if (reds->secure_listen_socket != -1) {
+ core->set_file_handlers(core, reds->secure_listen_socket, reds_accept_ssl_connection,
+ NULL, NULL);
+ }
+
+ if (reds->peer == NULL) {
+ red_printf("no peer connected");
+ mig->notifier_done(mig, reds->mig_notifier);
+ return;
+ }
+ reds->mig_inprogress = TRUE;
+
+ if (completed) {
+ Channel *channel;
+ RedMigrate *migrate;
+
+ reds->mig_wait_disconnect = TRUE;
+ core->arm_timer(core, reds->mig_timer, MIGRATE_TIMEOUT);
+
+ if (!(item = new_simple_out_item(RED_MIGRATE, sizeof(RedMigrate)))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ migrate = (RedMigrate *)item->data;
+ migrate->flags = RED_MIGRATE_NEED_FLUSH | RED_MIGRATE_NEED_DATA_TRANSFER;
+ reds_push_pipe_item(&item->base);
+ channel = reds->channels;
+ while (channel) {
+ channel->migrate(channel);
+ channel = channel->next;
+ }
+ } else {
+ if (!(item = new_simple_out_item(RED_MIGRATE_CANCEL, 0))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ reds_push_pipe_item(&item->base);
+ reds_mig_cleanup();
+ }
+}
+
+static int write_all(int fd, const void *in_buf, int len1)
+{
+ int ret, len;
+ uint8_t *buf = (uint8_t *)in_buf;
+
+ len = len1;
+ while (len > 0) {
+ ret = write(fd, buf, len);
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else if (ret == 0) {
+ break;
+ } else {
+ buf += ret;
+ len -= ret;
+ }
+ }
+ return len1 - len;
+}
+
+static int read_all(int fd, void *in_nuf, int lenl)
+{
+ int ret, len;
+ uint8_t *buf = in_nuf;
+
+ len = lenl;
+ while (len > 0) {
+ ret = read(fd, buf, len);
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ } else if (ret == 0) {
+ break;
+ } else {
+ buf += ret;
+ len -= ret;
+ }
+ }
+ return lenl - len;
+}
+
+static void reds_mig_read_all(int fd, void *buf, int len, const char *name)
+{
+ int n = read_all(fd, buf, len);
+ if (n != len) {
+ red_error("read %s failed, n=%d (%s)", name, n, strerror(errno));
+ }
+}
+
+static void reds_mig_write_all(int fd, void *buf, int len, const char *name)
+{
+ int n = write_all(fd, buf, len);
+ if (n != len) {
+ red_error("write %s faile, n=%d (%s)", name, n, strerror(errno));
+ }
+}
+
+static void reds_mig_recv(void *opaque, int fd)
+{
+ uint32_t ack_message = *(uint32_t *)"ack_";
+ char password[RED_MAX_PASSWORD_LENGTH];
+ RedsMigSpiceMessage mig_message;
+ unsigned long f4 = RSA_F4;
+ TicketInfo ticketing_info;
+ uint32_t version;
+ uint32_t resault;
+ BIO *bio;
+
+ BUF_MEM *buff;
+
+ reds_mig_read_all(fd, &version, sizeof(version), "version");
+
+ if (version != REDS_MIG_VERSION) {
+ resault = REDS_MIG_ABORT;
+ reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
+ mig->notifier_done(mig, reds->mig_notifier);
+ return;
+ }
+
+ resault = REDS_MIG_CONTINUE;
+ reds_mig_write_all(fd, &resault, sizeof(resault), "resault");
+
+ ticketing_info.bn = BN_new();
+ if (!ticketing_info.bn) {
+ red_error("OpenSSL BIGNUMS alloc failed");
+ }
+
+ BN_set_word(ticketing_info.bn, f4);
+ if (!(ticketing_info.rsa = RSA_new())) {
+ red_error("OpenSSL RSA alloc failed");
+ }
+
+ RSA_generate_key_ex(ticketing_info.rsa, RED_TICKET_KEY_PAIR_LENGTH, ticketing_info.bn, NULL);
+ ticketing_info.rsa_size = RSA_size(ticketing_info.rsa);
+
+ if (!(bio = BIO_new(BIO_s_mem()))) {
+ red_error("OpenSSL BIO alloc failed");
+ }
+
+ i2d_RSA_PUBKEY_bio(bio, ticketing_info.rsa);
+ BIO_get_mem_ptr(bio, &buff);
+
+ reds_mig_write_all(fd, buff->data, RED_TICKET_PUBKEY_BYTES, "publick key");
+ reds_mig_read_all(fd, ticketing_info.encrypted_ticket.encrypted_data, ticketing_info.rsa_size,
+ "ticket");
+
+ RSA_private_decrypt(ticketing_info.rsa_size, ticketing_info.encrypted_ticket.encrypted_data,
+ (unsigned char *)password, ticketing_info.rsa, RSA_PKCS1_OAEP_PADDING);
+
+ BN_free(ticketing_info.bn);
+ BIO_free(bio);
+ RSA_free(ticketing_info.rsa);
+
+ memcpy(reds->taTicket.password, password, sizeof(reds->taTicket.password));
+ reds_mig_read_all(fd, &mig_message, sizeof(mig_message), "mig data");
+ reds->link_id = mig_message.link_id;
+ reds_mig_write_all(fd, &ack_message, sizeof(uint32_t), "ack");
+ mig->notifier_done(mig, reds->mig_notifier);
+}
+
+static void migrate_timout(void *opaque)
+{
+ red_printf("");
+ ASSERT(reds->mig_wait_connect || reds->mig_wait_disconnect);
+ reds_mig_disconnect();
+}
+
+static void key_modifiers_sender(void *opaque)
+{
+ reds_send_keyborad_modifiers(keyboard ? keyboard->get_leds(keyboard) : 0);
+}
+
+uint32_t reds_get_mm_time()
+{
+ struct timespec time_space;
+ clock_gettime(CLOCK_MONOTONIC, &time_space);
+ return time_space.tv_sec * 1000 + time_space.tv_nsec / 1000 / 1000;
+}
+
+void reds_update_mm_timer(uint32_t mm_time)
+{
+ red_dispatcher_set_mm_time(mm_time);
+}
+
+void reds_enable_mm_timer()
+{
+ RedMultiMediaTime *time_mes;
+ SimpleOutItem *item;
+
+ core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS);
+ if (!reds->peer) {
+ return;
+ }
+
+ if (!(item = new_simple_out_item(RED_MULTI_MEDIA_TIME, sizeof(RedMultiMediaTime)))) {
+ red_printf("alloc item failed");
+ reds_disconnect();
+ return;
+ }
+ time_mes = (RedMultiMediaTime *)item->data;
+ time_mes->time = reds_get_mm_time() - MM_TIME_DELTA;
+ reds_push_pipe_item(&item->base);
+}
+
+void reds_desable_mm_timer()
+{
+ core->disarm_timer(core, reds->mm_timer);
+}
+
+static void mm_timer_proc(void *opaque)
+{
+ red_dispatcher_set_mm_time(reds_get_mm_time());
+ core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS);
+}
+
+static void add_monitor_action_commands(QTermInterface *mon)
+{
+ mon->add_action_command_handler(mon, "spice", "set_image_compression", "s",
+ reds_do_set_image_compression,
+ "",
+ "<[on|auto_glz|auto_lz|quic|glz|lz|off]>");
+ mon->add_action_command_handler(mon, "spice", "set_streaming_video", "s",
+ reds_do_set_streaming_video,
+ "",
+ "<on|off>");
+ mon->add_action_command_handler(mon, "spice", "set_playback_compression", "s",
+ reds_do_set_playback_compression,
+ "",
+ "<on|off>");
+ mon->add_action_command_handler(mon, "spice", "set_ticket", "ss?",
+ reds_do_set_ticket,
+ "<password> [expiration=<seconds>]"
+ "[,connected=keep|disconnect|fail]",
+ "set the spice connection ticket");
+ mon->add_action_command_handler(mon, "spice", "set_ticket64", "ss?",
+ reds_do_set_ticket64,
+ "<password> [expiration=<seconds>]"
+ " [,connected=keep|disconnect|fail]",
+ "set the spice connection ticket");
+ mon->add_action_command_handler(mon, "spice", "disable_ticketing", "",
+ reds_do_disable_ticketing,
+ "",
+ "entirely disables OTP");
+ mon->add_action_command_handler(mon, "spice", "set_agent_mouse", "s",
+ reds_do_set_agent_mouse,
+ "",
+ "<on|off>");
+#ifdef RED_STATISTICS
+ mon->add_action_command_handler(mon, "spice", "reset_stat", "",
+ do_reset_statistics,
+ "",
+ "reset spice statistics");
+ mon->add_action_command_handler(mon, "spice", "ping_client", "s?i?",
+ do_ping_client,
+ "[on [interval]|off]",
+ "ping spice client to measure roundtrip");
+#endif
+}
+
+static void add_monitor_info_commands(QTermInterface *mon)
+{
+ mon->add_info_command_handler(mon, "spice", "state",
+ reds_do_info_spice,
+ "show spice state");
+ mon->add_info_command_handler(mon, "spice", "ticket",
+ reds_do_info_ticket,
+ "show ticket");
+#ifdef RED_STATISTICS
+ mon->add_info_command_handler(mon, "spice", "stat",
+ do_info_statistics,
+ "show spice statistics");
+ mon->add_info_command_handler(mon, "spice", "rtt_client",
+ do_info_rtt_client,
+ "show rtt to spice client");
+#endif
+}
+
+static void attach_to_red_agent(VDIPortInterface *interface)
+{
+ VDIPortState *state = &reds->agent_state;
+
+ vdagent = interface;
+ reds_update_mouse_mode();
+ if (!reds->peer) {
+ return;
+ }
+ state->plug_ref = vdagent->plug(vdagent, &state->plug);
+ reds->agent_state.plug_generation++;
+
+ if (reds->mig_target) {
+ return;
+ }
+
+ reds_send_agent_connected();
+}
+
+static void interface_change_notifier(void *opaque, VDInterface *interface,
+ VDInterfaceChangeType change)
+{
+ if (interface->base_version != VM_INTERFACE_VERSION) {
+ red_printf("unsuported base interface version");
+ return;
+ }
+ switch (change) {
+ case VD_INTERFACE_ADDING:
+ if (strcmp(interface->type, VD_INTERFACE_KEYBOARD) == 0) {
+ red_printf("VD_INTERFACE_KEYBOARD");
+ if (keyboard) {
+ red_printf("already have keyboard");
+ return;
+ }
+ if (interface->major_version != VD_INTERFACE_KEYBOARD_MAJOR ||
+ interface->minor_version < VD_INTERFACE_KEYBOARD_MINOR) {
+ red_printf("unsuported keyboard interface");
+ return;
+ }
+ keyboard = (KeyboardInterface *)interface;
+ if (!keyboard->register_leds_notifier(keyboard, reds_on_keyborad_leads_change, NULL)) {
+ red_error("register leds notifier failed");
+ }
+ } else if (strcmp(interface->type, VD_INTERFACE_MOUSE) == 0) {
+ red_printf("VD_INTERFACE_MOUSE");
+ if (mouse) {
+ red_printf("already have mouse");
+ return;
+ }
+ if (interface->major_version != VD_INTERFACE_MOUSE_MAJOR ||
+ interface->minor_version < VD_INTERFACE_MOUSE_MINOR) {
+ red_printf("unsuported mouse interface");
+ return;
+ }
+ mouse = (MouseInterface *)interface;
+ } else if (strcmp(interface->type, VD_INTERFACE_MIGRATION) == 0) {
+ red_printf("VD_INTERFACE_MIGRATION");
+ if (mig) {
+ red_printf("already have migration");
+ return;
+ }
+ if (interface->major_version != VD_INTERFACE_MIGRATION_MAJOR ||
+ interface->minor_version < VD_INTERFACE_MIGRATION_MINOR) {
+ red_printf("unsuported migration interface");
+ return;
+ }
+ mig = (MigrationInterface *)interface;
+ reds->mig_notifier = mig->register_notifiers(mig, MIGRATION_NOTIFY_SPICE_KEY,
+ reds_mig_started, reds_mig_finished,
+ reds_mig_recv, NULL);
+ if (reds->mig_notifier == INVALID_VD_OBJECT_REF) {
+ red_error("migration register failed");
+ }
+ } else if (strcmp(interface->type, VD_INTERFACE_QXL) == 0) {
+ QXLInterface *qxl_interface;
+
+ red_printf("VD_INTERFACE_QXL");
+ if (interface->major_version != VD_INTERFACE_QXL_MAJOR ||
+ interface->minor_version < VD_INTERFACE_QXL_MINOR) {
+ red_printf("unsuported qxl interface");
+ return;
+ }
+ qxl_interface = (QXLInterface *)interface;
+ red_dispatcher_init(qxl_interface);
+ } else if (strcmp(interface->type, VD_INTERFACE_QTERM) == 0) {
+ static int was_here = FALSE;
+ red_printf("VD_INTERFACE_QTERM");
+ if (was_here) {
+ return;
+ }
+ was_here = TRUE;
+ if (interface->major_version != VD_INTERFACE_QTERM_MAJOR ||
+ interface->minor_version < VD_INTERFACE_QTERM_MINOR) {
+ red_printf("unsuported qterm interface");
+ return;
+ }
+ add_monitor_action_commands((QTermInterface *)interface);
+ add_monitor_info_commands((QTermInterface *)interface);
+ } else if (strcmp(interface->type, VD_INTERFACE_TABLET) == 0) {
+ red_printf("VD_INTERFACE_TABLET");
+ if (tablet) {
+ red_printf("already have tablet");
+ return;
+ }
+ if (interface->major_version != VD_INTERFACE_TABLET_MAJOR ||
+ interface->minor_version < VD_INTERFACE_TABLET_MINOR) {
+ red_printf("unsuported tablet interface");
+ return;
+ }
+ tablet = (TabletInterface *)interface;
+ reds_update_mouse_mode();
+ if (reds->is_client_mouse_allowed) {
+ tablet->set_logical_size(tablet, reds->monitor_mode.x_res,
+ reds->monitor_mode.y_res);
+ }
+ } else if (strcmp(interface->type, VD_INTERFACE_PLAYBACK) == 0) {
+ red_printf("VD_INTERFACE_PLAYBACK");
+ if (interface->major_version != VD_INTERFACE_PLAYBACK_MAJOR ||
+ interface->minor_version < VD_INTERFACE_PLAYBACK_MINOR) {
+ red_printf("unsuported playback interface");
+ return;
+ }
+ snd_attach_playback((PlaybackInterface *)interface);
+ } else if (strcmp(interface->type, VD_INTERFACE_RECORD) == 0) {
+ red_printf("VD_INTERFACE_RECORD");
+ if (interface->major_version != VD_INTERFACE_RECORD_MAJOR ||
+ interface->minor_version < VD_INTERFACE_RECORD_MINOR) {
+ red_printf("unsuported record interface");
+ return;
+ }
+ snd_attach_record((RecordInterface *)interface);
+ } else if (strcmp(interface->type, VD_INTERFACE_VDI_PORT) == 0) {
+ red_printf("VD_INTERFACE_VDI_PORT");
+ if (vdagent) {
+ red_printf("vdi port already attached");
+ return;
+ }
+ if (interface->major_version != VD_INTERFACE_VDI_PORT_MAJOR ||
+ interface->minor_version < VD_INTERFACE_VDI_PORT_MINOR) {
+ red_printf("unsuported vdi port interface");
+ return;
+ }
+ attach_to_red_agent((VDIPortInterface *)interface);
+ }
+ break;
+ case VD_INTERFACE_REMOVING:
+ if (strcmp(interface->type, VD_INTERFACE_TABLET) == 0) {
+ red_printf("remove VD_INTERFACE_TABLET");
+ if (interface == (VDInterface *)tablet) {
+ tablet = NULL;
+ reds_update_mouse_mode();
+ }
+ break;
+ } else if (strcmp(interface->type, VD_INTERFACE_PLAYBACK) == 0) {
+ red_printf("remove VD_INTERFACE_PLAYBACK");
+ snd_detach_playback((PlaybackInterface *)interface);
+ break;
+ } else if (strcmp(interface->type, VD_INTERFACE_RECORD) == 0) {
+ red_printf("remove VD_INTERFACE_RECORD");
+ snd_detach_record((RecordInterface *)interface);
+ break;
+ } else if (strcmp(interface->type, VD_INTERFACE_VDI_PORT) == 0) {
+ red_printf("remove VD_INTERFACE_VDI_PORT");
+ if (interface == (VDInterface *)vdagent) {
+ reds_agent_remove();
+ }
+ break;
+ }
+ red_error("VD_INTERFACE_REMOVING unsupported");
+ break;
+ }
+}
+
+static void free_external_agent_buff(VDIPortBuf *in_buf)
+{
+ VDIPortState *state = &reds->agent_state;
+
+ ring_add(&state->external_bufs, &in_buf->link);
+ add_token();
+}
+
+static void free_internal_agent_buff(VDIPortBuf *in_buf)
+{
+ VDIPortState *state = &reds->agent_state;
+
+ ring_add(&state->internal_bufs, &in_buf->link);
+ if (reds->inputs_state && reds->inputs_state->pending_mouse_event) {
+ reds_handle_agent_mouse_event();
+ }
+}
+
+void reds_prepare_read_buf(RedsOutItem *in_nuf, struct iovec* vec, int *len)
+{
+ VDIReadBuf *buf = (VDIReadBuf *)in_nuf;
+
+ vec[0].iov_base = &buf->header;
+ vec[0].iov_len = sizeof(buf->header);
+ vec[1].iov_base = buf->data;
+ vec[1].iov_len = buf->len;
+ *len = 2;
+}
+
+void reds_release_read_buf(RedsOutItem *in_nuf)
+{
+ VDIReadBuf *buf = (VDIReadBuf *)in_nuf;
+
+ ring_add(&reds->agent_state.read_bufs, &buf->out_item.link);
+ read_from_vdi_port();
+}
+
+static void init_vd_agent_resources()
+{
+ VDIPortState *state = &reds->agent_state;
+ int i;
+
+ ring_init(&state->external_bufs);
+ ring_init(&state->internal_bufs);
+ ring_init(&state->write_queue);
+ ring_init(&state->read_bufs);
+
+ state->read_state = VDI_PORT_READ_STATE_READ_HADER;
+ state->recive_pos = (uint8_t *)&state->vdi_chunk_header;
+ state->recive_len = sizeof(state->vdi_chunk_header);
+
+ for (i = 0; i < REDS_AGENT_WINDOW_SIZE; i++) {
+ VDAgentExtBuf *buf = (VDAgentExtBuf *)malloc(sizeof(VDAgentExtBuf));
+ if (!buf) {
+ PANIC("alloc failed");
+ }
+ memset(buf, 0, sizeof(*buf));
+ ring_item_init(&buf->base.link);
+ buf->base.chunk_header.port = VDP_CLIENT_PORT;
+ buf->base.free = free_external_agent_buff;
+ ring_add(&reds->agent_state.external_bufs, &buf->base.link);
+ }
+
+ for (i = 0; i < REDS_NUM_INTERNAL_AGENT_MESSAGES; i++) {
+ VDInternalBuf *buf = (VDInternalBuf *)malloc(sizeof(VDInternalBuf));
+ if (!buf) {
+ PANIC("alloc failed");
+ }
+ memset(buf, 0, sizeof(*buf));
+ ring_item_init(&buf->base.link);
+ buf->base.free = free_internal_agent_buff;
+ buf->base.chunk_header.port = VDP_SERVER_PORT;
+ buf->base.chunk_header.size = sizeof(VDAgentMessage) + sizeof(VDAgentMouseState);
+ buf->header.protocol = VD_AGENT_PROTOCOL;
+ buf->header.type = VD_AGENT_MOUSE_STATE;
+ buf->header.opaque = 0;
+ buf->header.size = sizeof(VDAgentMouseState);
+ ring_add(&reds->agent_state.internal_bufs, &buf->base.link);
+ }
+
+ for (i = 0; i < REDS_VDI_PORT_NUM_RECIVE_BUFFS; i++) {
+ VDIReadBuf *buf = (VDIReadBuf *)malloc(sizeof(VDIReadBuf));
+ if (!buf) {
+ PANIC("alloc failed");
+ }
+ memset(buf, 0, sizeof(*buf));
+ buf->out_item.prepare = reds_prepare_read_buf;
+ buf->out_item.release = reds_release_read_buf;
+ buf->header.type = RED_AGENT_DATA;
+ buf->header.sub_list = 0;
+ ring_item_init(&buf->out_item.link);
+ ring_add(&reds->agent_state.read_bufs, &buf->out_item.link);
+ }
+
+ state->plug.major_version = VD_INTERFACE_VDI_PORT_MAJOR;
+ state->plug.minor_version = VD_INTERFACE_VDI_PORT_MINOR;
+ state->plug.wakeup = reds_agent_wakeup;
+}
+
+const char *version_string = VERSION;
+
+void __attribute__ ((visibility ("default"))) spice_init(CoreInterface *core_interface)
+{
+ VDInterface *interface = NULL;
+
+ red_printf("starting %s", version_string);
+
+ if (core_interface->base.base_version != VM_INTERFACE_VERSION) {
+ red_error("bad base interface version");
+ }
+
+ if (core_interface->base.major_version != VD_INTERFACE_CORE_MAJOR) {
+ red_error("bad core interface version");
+ }
+ core = core_interface;
+ if (core_interface->base.minor_version > 1) {
+ log_proc = core->log;
+ }
+ if (!(reds = malloc(sizeof(RedsState)))) {
+ red_error("reds alloc failed");
+ }
+ memset(reds, 0, sizeof(RedsState));
+ reds->listen_socket = -1;
+ reds->secure_listen_socket = -1;
+ reds->peer = NULL;
+ reds->in_handler.handle_message = reds_main_handle_message;
+ ring_init(&reds->outgoing.pipe);
+ reds->outgoing.vec = reds->outgoing.vec_buf;
+
+ init_vd_agent_resources();
+
+ if (!(reds->mig_timer = core->create_timer(core, migrate_timout, NULL))) {
+ red_error("migration timer create failed");
+ }
+ if (!(reds->key_modifiers_timer = core->create_timer(core, key_modifiers_sender, NULL))) {
+ red_error("key modifiers timer create failed");
+ }
+
+ while ((interface = core->next(core, interface))) {
+ interface_change_notifier(&reds, interface, VD_INTERFACE_ADDING);
+ }
+ core->register_change_notifiers(core, &reds, interface_change_notifier);
+
+#ifdef RED_STATISTICS
+ int shm_name_len = strlen(REDS_STAT_SHM_NAME) + 20;
+ int fd;
+
+ if (!(reds->stat_shm_name = (char *)malloc(shm_name_len))) {
+ red_error("stat_shm_name alloc failed");
+ }
+ snprintf(reds->stat_shm_name, shm_name_len, REDS_STAT_SHM_NAME, getpid());
+ if ((fd = shm_open(reds->stat_shm_name, O_CREAT | O_RDWR, 0444)) == -1) {
+ red_error("statistics shm_open failed, %s", strerror(errno));
+ }
+ if (ftruncate(fd, REDS_STAT_SHM_SIZE) == -1) {
+ red_error("statistics ftruncate failed, %s", strerror(errno));
+ }
+ reds->stat = mmap(NULL, REDS_STAT_SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (reds->stat == (RedsStat *)MAP_FAILED) {
+ red_error("statistics mmap failed, %s", strerror(errno));
+ }
+ memset(reds->stat, 0, REDS_STAT_SHM_SIZE);
+ reds->stat->magic = REDS_STAT_MAGIC;
+ reds->stat->version = REDS_STAT_VERSION;
+ reds->stat->root_index = INVALID_STAT_REF;
+ if (pthread_mutex_init(&reds->stat_lock, NULL)) {
+ red_error("mutex init failed");
+ }
+ if (!(reds->ping_timer = core->create_timer(core, ping_timer_cb, NULL))) {
+ red_error("ping timer create failed");
+ }
+ reds->ping_interval = PING_INTERVAL;
+#endif
+
+ if (!(reds->mm_timer = core->create_timer(core, mm_timer_proc, NULL))) {
+ red_error("mm timer create failed");
+ }
+ core->arm_timer(core, reds->mm_timer, MM_TIMER_GRANULARITY_MS);
+
+ reds_init_net();
+ if (reds->secure_listen_socket != -1) {
+ reds_init_ssl();
+ }
+ inputs_init();
+
+ reds->mouse_mode = RED_MOUSE_MODE_SERVER;
+ atexit(reds_exit);
+}
+
diff --git a/server/reds.h b/server/reds.h
new file mode 100644
index 00000000..ce09e5b6
--- /dev/null
+++ b/server/reds.h
@@ -0,0 +1,70 @@
+/*
+ Copyright (C) 2009 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 _H_REDS
+#define _H_REDS
+
+#include <stdint.h>
+#include <openssl/ssl.h>
+#include <sys/uio.h>
+
+typedef struct RedsStreamContext {
+ void *ctx;
+
+ int socket;
+
+ SSL *ssl;
+
+ int (*cb_write)(void *, void *, int);
+ int (*cb_read)(void *, void *, int);
+
+ int (*cb_readv)(void *, const struct iovec *vector, int count);
+ int (*cb_writev)(void *, const struct iovec *vector, int count);
+ int (*cb_free)(struct RedsStreamContext *);
+} RedsStreamContext;
+
+typedef struct Channel {
+ struct Channel *next;
+ uint32_t type;
+ uint32_t id;
+ int num_common_caps;
+ uint32_t *common_caps;
+ int num_caps;
+ uint32_t *caps;
+ void (*link)(struct Channel *, RedsStreamContext *peer, int migration, int num_common_caps,
+ uint32_t *common_caps, int num_caps, uint32_t *caps);
+ void (*shutdown)(struct Channel *);
+ void (*migrate)(struct Channel *);
+ void *data;
+} Channel;
+
+void reds_desable_mm_timer();
+void reds_enable_mm_timer();
+void reds_update_mm_timer(uint32_t mm_time);
+uint32_t reds_get_mm_time();
+void reds_set_client_mouse_allowed(int is_client_mouse_allowed,
+ int x_res, int y_res);
+void reds_register_channel(Channel *channel);
+void reds_unregister_channel(Channel *channel);
+
+extern struct CoreInterface *core;
+extern uint64_t bitrate_per_sec;
+
+#define IS_LOW_BANDWIDTH() (bitrate_per_sec < 10 * 1024 * 1024)
+
+#endif
+
diff --git a/server/snd_worker.c b/server/snd_worker.c
new file mode 100644
index 00000000..8cfabaef
--- /dev/null
+++ b/server/snd_worker.c
@@ -0,0 +1,1300 @@
+/*
+ Copyright (C) 2009 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/>.
+*/
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <netinet/tcp.h>
+#include <celt051/celt.h>
+
+#include "vd_interface.h"
+#include "red_common.h"
+#include "reds.h"
+#include "red_dispatcher.h"
+#include "snd_worker.h"
+
+#define MAX_SEND_VEC 100
+#define MAX_SEND_BUFS 200
+
+#define RECIVE_BUF_SIZE (16 * 1024 * 2)
+
+#define FRAME_SIZE 256
+#define PLAYBACK_BUF_SIZE (FRAME_SIZE * 4)
+
+#define CELT_BIT_RATE (64 * 1024)
+#define CELT_COMPRESSED_FRAME_BYTES (FRAME_SIZE * CELT_BIT_RATE / VD_INTERFACE_PLAYBACK_FREQ / 8)
+
+#define RECORD_SAMPLES_SIZE (RECIVE_BUF_SIZE >> 2)
+
+enum PlaybackeCommand {
+ SND_PLAYBACK_MIGRATE,
+ SND_PLAYBACK_MODE,
+ SND_PLAYBACK_CTRL,
+ SND_PLAYBACK_PCM,
+};
+
+enum RecordCommand {
+ SND_RECORD_MIGRATE,
+ SND_RECORD_CTRL,
+};
+
+#define SND_PLAYBACK_MIGRATE_MASK (1 << SND_PLAYBACK_MIGRATE)
+#define SND_PLAYBACK_MODE_MASK (1 << SND_PLAYBACK_MODE)
+#define SND_PLAYBACK_CTRL_MASK (1 << SND_PLAYBACK_CTRL)
+#define SND_PLAYBACK_PCM_MASK (1 << SND_PLAYBACK_PCM)
+
+#define SND_RECORD_MIGRATE_MASK (1 << SND_RECORD_MIGRATE)
+#define SND_RECORD_CTRL_MASK (1 << SND_RECORD_CTRL)
+
+typedef struct BufDescriptor {
+ uint32_t size;
+ uint8_t *data;
+} BufDescriptor;
+
+typedef struct SndChannel SndChannel;
+typedef void (*send_messages_proc)(void *in_channel);
+typedef int (*handle_message_proc)(SndChannel *channel, RedDataHeader *message);
+typedef void (*on_message_done_proc)(SndChannel *channel);
+typedef void (*cleanup_channel_proc)(SndChannel *channel);
+
+typedef struct SndWorker SndWorker;
+
+struct SndChannel {
+ RedsStreamContext *peer;
+ SndWorker *worker;
+
+ int active;
+ int client_active;
+ int blocked;
+
+ uint32_t command;
+ int migrate;
+ uint32_t ack_generation;
+ uint32_t client_ack_generation;
+ uint32_t out_messages;
+ uint32_t ack_messages;
+
+ struct {
+ RedDataHeader header;
+ uint32_t n_bufs;
+ BufDescriptor bufs[MAX_SEND_BUFS];
+
+ uint32_t size;
+ uint32_t pos;
+ } send_data;
+
+ struct {
+ uint8_t buf[RECIVE_BUF_SIZE];
+ RedDataHeader *message;
+ uint8_t *now;
+ uint8_t *end;
+ } recive_data;
+
+ send_messages_proc send_messages;
+ handle_message_proc handle_message;
+ on_message_done_proc on_message_done;
+ cleanup_channel_proc cleanup;
+};
+
+typedef struct AudioFrame AudioFrame;
+struct AudioFrame {
+ uint32_t time;
+ uint32_t samples[FRAME_SIZE];
+ AudioFrame *next;
+};
+
+typedef struct PlaybackChannel {
+ SndChannel base;
+ AudioFrame frames[3];
+ PlaybackPlug plug;
+ VDObjectRef plug_ref;
+ AudioFrame *free_frames;
+ AudioFrame *in_progress;
+ AudioFrame *pending_frame;
+ CELTMode *celt_mode;
+ CELTEncoder *celt_encoder;
+ int celt_allowed;
+ uint32_t mode;
+ struct {
+ union {
+ RedPlaybackMode mode;
+ RedPlaybackStart start;
+ RedMigrate migrate;
+ uint8_t celt_buf[CELT_COMPRESSED_FRAME_BYTES];
+ } u;
+ } send_data;
+} PlaybackChannel;
+
+struct SndWorker {
+ Channel base;
+ VDInterface *interface;
+ SndChannel *connection;
+ SndWorker *next;
+};
+
+#define RECORD_MIG_VERSION 1
+
+typedef struct __attribute__ ((__packed__)) RecordMigrateData {
+ uint32_t version;
+ uint64_t serial;
+ uint32_t start_time;
+ uint32_t mode;
+ uint32_t mode_time;
+} RecordMigrateData;
+
+typedef struct __attribute__ ((__packed__)) RecordMigrateMessage {
+ RedMigrate migrate;
+ RedDataHeader header;
+ RecordMigrateData data;
+} RecordMigrateMessage;
+
+typedef struct RecordChannel {
+ SndChannel base;
+ RecordPlug plug;
+ VDObjectRef plug_ref;
+ uint32_t samples[RECORD_SAMPLES_SIZE];
+ uint32_t write_pos;
+ uint32_t read_pos;
+ uint32_t mode;
+ uint32_t mode_time;
+ uint32_t start_time;
+ CELTDecoder *celt_decoder;
+ CELTMode *celt_mode;
+ uint32_t celt_buf[FRAME_SIZE];
+ struct {
+ union {
+ RedRecordStart start;
+ RecordMigrateMessage migrate;
+ } u;
+ } send_data;
+} RecordChannel;
+
+static SndWorker *workers = NULL;
+static uint32_t playback_compression = RED_AUDIO_DATA_MODE_CELT_0_5_1;
+
+static void snd_receive(void* data);
+
+static inline BufDescriptor *snd_find_buf(SndChannel *channel, int buf_pos, int *buf_offset)
+{
+ BufDescriptor *buf;
+ int pos = 0;
+
+ for (buf = channel->send_data.bufs; buf_pos >= pos + buf->size; buf++) {
+ pos += buf->size;
+ ASSERT(buf != &channel->send_data.bufs[channel->send_data.n_bufs - 1]);
+ }
+ *buf_offset = buf_pos - pos;
+ return buf;
+}
+
+static inline uint32_t __snd_fill_iovec(BufDescriptor *buf, int skip, struct iovec *vec,
+ int *vec_index, long phys_delta)
+{
+ uint32_t size = 0;
+ vec[*vec_index].iov_base = buf->data + skip;
+ vec[*vec_index].iov_len = size = buf->size - skip;
+ (*vec_index)++;
+ return size;
+}
+
+static inline void snd_fill_iovec(SndChannel *channel, struct iovec *vec, int *vec_size)
+{
+ int vec_index = 0;
+ uint32_t pos = channel->send_data.pos;
+ ASSERT(channel->send_data.size != pos && channel->send_data.size > pos);
+
+ do {
+ BufDescriptor *buf;
+ int buf_offset;
+
+ buf = snd_find_buf(channel, pos, &buf_offset);
+ ASSERT(buf);
+ pos += __snd_fill_iovec(buf, buf_offset, vec, &vec_index, 0);
+ } while (vec_index < MAX_SEND_VEC && pos != channel->send_data.size);
+ *vec_size = vec_index;
+}
+
+static void snd_disconnect_channel(SndChannel *channel)
+{
+ SndWorker *worker;
+
+ if (!channel) {
+ return;
+ }
+ channel->cleanup(channel);
+ worker = channel->worker;
+ worker->connection = NULL;
+ core->set_file_handlers(core, channel->peer->socket, NULL, NULL, NULL);
+ channel->peer->cb_free(channel->peer);
+ free(channel);
+}
+
+static void snd_playback_free_frame(PlaybackChannel *playback_channel, AudioFrame *frame)
+{
+ frame->next = playback_channel->free_frames;
+ playback_channel->free_frames = frame;
+}
+
+static void snd_playback_on_message_done(SndChannel *channel)
+{
+ PlaybackChannel *playback_channel = (PlaybackChannel *)channel;
+ if (playback_channel->in_progress) {
+ snd_playback_free_frame(playback_channel, playback_channel->in_progress);
+ playback_channel->in_progress = NULL;
+ if (playback_channel->pending_frame) {
+ channel->command |= SND_PLAYBACK_PCM_MASK;
+ }
+ }
+}
+
+static void snd_record_on_message_done(SndChannel *channel)
+{
+}
+
+static int snd_send_data(SndChannel *channel)
+{
+ uint32_t n;
+
+ if (!channel) {
+ return FALSE;
+ }
+
+ if (!(n = channel->send_data.size - channel->send_data.pos)) {
+ return TRUE;
+ }
+
+ for (;;) {
+ struct iovec vec[MAX_SEND_VEC];
+ int vec_size;
+
+ if (!n) {
+ channel->on_message_done(channel);
+
+ if (channel->blocked) {
+ channel->blocked = FALSE;
+ if (core->set_file_handlers(core, channel->peer->socket, snd_receive,
+ NULL, channel) == -1) {
+ red_printf("qemu_set_fd_handler failed");
+ }
+ }
+ break;
+ }
+
+ snd_fill_iovec(channel, vec, &vec_size);
+ if ((n = channel->peer->cb_writev(channel->peer->ctx, vec, vec_size)) == -1) {
+ switch (errno) {
+ case EAGAIN:
+ channel->blocked = TRUE;
+ if (core->set_file_handlers(core, channel->peer->socket, snd_receive,
+ channel->send_messages, channel) == -1) {
+ red_printf("qemu_set_fd_handler failed");
+ }
+ return FALSE;
+ case EINTR:
+ break;
+ case EPIPE:
+ snd_disconnect_channel(channel);
+ return FALSE;
+ default:
+ red_printf("%s", strerror(errno));
+ snd_disconnect_channel(channel);
+ return FALSE;
+ }
+ } else {
+ channel->send_data.pos += n;
+ }
+ n = channel->send_data.size - channel->send_data.pos;
+ }
+ return TRUE;
+}
+
+static int snd_record_handle_write(RecordChannel *record_channel, RedDataHeader *message)
+{
+ RedcRecordPacket *packet;
+ uint32_t write_pos;
+ uint32_t* data;
+ uint32_t size;
+ uint32_t len;
+ uint32_t now;
+
+ if (!record_channel) {
+ return FALSE;
+ }
+
+ packet = (RedcRecordPacket *)(message + 1);
+ size = message->size - sizeof(*packet);
+
+ if (record_channel->mode == RED_AUDIO_DATA_MODE_CELT_0_5_1) {
+ int celt_err = celt051_decode(record_channel->celt_decoder, packet->data, size,
+ (celt_int16_t *)record_channel->celt_buf);
+ if (celt_err != CELT_OK) {
+ red_printf("celt decode failed (%d)", celt_err);
+ return FALSE;
+ }
+ data = record_channel->celt_buf;
+ size = FRAME_SIZE;
+ } else if (record_channel->mode == RED_AUDIO_DATA_MODE_RAW) {
+ data = (uint32_t *)packet->data;
+ size = size >> 2;
+ size = MIN(size, RECORD_SAMPLES_SIZE);
+ } else {
+ return FALSE;
+ }
+
+ write_pos = record_channel->write_pos % RECORD_SAMPLES_SIZE;
+ record_channel->write_pos += size;
+ len = RECORD_SAMPLES_SIZE - write_pos;
+ now = MIN(len, size);
+ size -= now;
+ memcpy(record_channel->samples + write_pos, data, now << 2);
+
+ if (size) {
+ memcpy(record_channel->samples, data + now, size << 2);
+ }
+
+ if (record_channel->write_pos - record_channel->read_pos > RECORD_SAMPLES_SIZE) {
+ record_channel->read_pos = record_channel->write_pos - RECORD_SAMPLES_SIZE;
+ }
+ return TRUE;
+}
+
+static int snd_playback_handle_message(SndChannel *channel, RedDataHeader *message)
+{
+ if (!channel) {
+ return FALSE;
+ }
+
+ switch (message->type) {
+ case REDC_DISCONNECTING:
+ break;
+ default:
+ red_printf("invalid message type %u", message->type);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static int snd_record_handle_message(SndChannel *channel, RedDataHeader *message)
+{
+ RecordChannel *record_channel = (RecordChannel *)channel;
+
+ if (!channel) {
+ return FALSE;
+ }
+ switch (message->type) {
+ case REDC_RECORD_DATA:
+ return snd_record_handle_write((RecordChannel *)channel, message);
+ case REDC_RECORD_MODE: {
+ RedcRecordMode *mode = (RedcRecordMode *)(message + 1);
+ record_channel->mode = mode->mode;
+ record_channel->mode_time = mode->time;
+ if (record_channel->mode != RED_AUDIO_DATA_MODE_CELT_0_5_1 &&
+ record_channel->mode != RED_AUDIO_DATA_MODE_RAW) {
+ red_printf("unsupported mode");
+ }
+ break;
+ }
+ case REDC_RECORD_START_MARK: {
+ RedcRecordStartMark *mark = (RedcRecordStartMark *)(message + 1);
+ record_channel->start_time = mark->time;
+ break;
+ }
+ case REDC_DISCONNECTING:
+ break;
+ case REDC_MIGRATE_DATA: {
+ RecordMigrateData* mig_data = (RecordMigrateData *)(message + 1);
+ if (mig_data->version != RECORD_MIG_VERSION) {
+ red_printf("invalid mig version");
+ break;
+ }
+ record_channel->mode = mig_data->mode;
+ record_channel->mode_time = mig_data->mode_time;
+ record_channel->start_time = mig_data->start_time;
+ break;
+ }
+ default:
+ red_printf("invalid message type %u", message->type);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void snd_receive(void* data)
+{
+ SndChannel *channel = (SndChannel*)data;
+ if (!channel) {
+ return;
+ }
+
+ for (;;) {
+ ssize_t n;
+ n = channel->recive_data.end - channel->recive_data.now;
+ ASSERT(n);
+ if ((n = channel->peer->cb_read(channel->peer->ctx, channel->recive_data.now, n)) <= 0) {
+ if (n == 0) {
+ snd_disconnect_channel(channel);
+ return;
+ }
+ ASSERT(n == -1);
+ switch (errno) {
+ case EAGAIN:
+ return;
+ case EINTR:
+ break;
+ case EPIPE:
+ snd_disconnect_channel(channel);
+ return;
+ default:
+ red_printf("%s", strerror(errno));
+ snd_disconnect_channel(channel);
+ return;
+ }
+ } else {
+ channel->recive_data.now += n;
+ for (;;) {
+ RedDataHeader *message = channel->recive_data.message;
+ n = channel->recive_data.now - (uint8_t *)message;
+ if (n < sizeof(RedDataHeader) || n < sizeof(RedDataHeader) + message->size) {
+ break;
+ }
+ if (!channel->handle_message(channel, message)) {
+ snd_disconnect_channel(channel);
+ return;
+ }
+ channel->recive_data.message = (RedDataHeader *)((uint8_t *)message +
+ sizeof(RedDataHeader) +
+ message->size);
+ }
+ if (channel->recive_data.now == (uint8_t *)channel->recive_data.message) {
+ channel->recive_data.now = channel->recive_data.buf;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ } else if (channel->recive_data.now == channel->recive_data.end) {
+ memcpy(channel->recive_data.buf, channel->recive_data.message, n);
+ channel->recive_data.now = channel->recive_data.buf + n;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ }
+ }
+ }
+}
+
+static inline void __snd_add_buf(SndChannel *channel, void *data, uint32_t size)
+{
+ int pos = channel->send_data.n_bufs++;
+ ASSERT(pos < MAX_SEND_BUFS);
+ channel->send_data.bufs[pos].size = size;
+ channel->send_data.bufs[pos].data = data;
+}
+
+static void snd_add_buf(SndChannel *channel, void *data, uint32_t size)
+{
+ __snd_add_buf(channel, data, size);
+ channel->send_data.header.size += size;
+}
+
+static inline int snd_reset_send_data(SndChannel *channel, uint16_t verb)
+{
+ if (!channel) {
+ return FALSE;
+ }
+
+ channel->send_data.pos = 0;
+ channel->send_data.n_bufs = 0;
+ channel->send_data.header.sub_list = 0;
+ channel->send_data.header.size = 0;
+ channel->send_data.header.type = verb;
+ ++channel->send_data.header.serial;
+ __snd_add_buf(channel, &channel->send_data.header, sizeof(RedDataHeader));
+ return TRUE;
+}
+
+static int snd_playback_send_migrate(PlaybackChannel *channel)
+{
+ if (!snd_reset_send_data((SndChannel *)channel, RED_MIGRATE)) {
+ return FALSE;
+ }
+ channel->send_data.u.migrate.flags = 0;
+ snd_add_buf((SndChannel *)channel, &channel->send_data.u.migrate,
+ sizeof(channel->send_data.u.migrate));
+ channel->base.send_data.size = channel->base.send_data.header.size + sizeof(RedDataHeader);
+ return snd_send_data((SndChannel *)channel);
+}
+
+static int snd_playback_send_start(PlaybackChannel *playback_channel)
+{
+ SndChannel *channel = (SndChannel *)playback_channel;
+ RedPlaybackStart *start;
+ if (!snd_reset_send_data(channel, RED_PLAYBACK_START)) {
+ return FALSE;
+ }
+
+ start = &playback_channel->send_data.u.start;
+ start->channels = VD_INTERFACE_PLAYBACK_CHAN;
+ start->frequency = VD_INTERFACE_PLAYBACK_FREQ;
+ ASSERT(VD_INTERFACE_PLAYBACK_FMT == VD_INTERFACE_AUDIO_FMT_S16);
+ start->format = RED_AUDIO_FMT_S16;
+ start->time = reds_get_mm_time();
+ snd_add_buf(channel, start, sizeof(*start));
+
+ channel->send_data.size = sizeof(RedDataHeader) + sizeof(*start);
+ return snd_send_data(channel);
+}
+
+static int snd_playback_send_stop(PlaybackChannel *playback_channel)
+{
+ SndChannel *channel = (SndChannel *)playback_channel;
+ if (!snd_reset_send_data(channel, RED_PLAYBACK_STOP)) {
+ return FALSE;
+ }
+ channel->send_data.size = sizeof(RedDataHeader);
+ return snd_send_data(channel);
+}
+
+static int snd_playback_send_ctl(PlaybackChannel *playback_channel)
+{
+ SndChannel *channel = (SndChannel *)playback_channel;
+
+ if ((channel->client_active = channel->active)) {
+ return snd_playback_send_start(playback_channel);
+ } else {
+ return snd_playback_send_stop(playback_channel);
+ }
+}
+
+static int snd_record_send_start(RecordChannel *record_channel)
+{
+ SndChannel *channel = (SndChannel *)record_channel;
+ RedRecordStart *start;
+ if (!snd_reset_send_data(channel, RED_RECORD_START)) {
+ return FALSE;
+ }
+
+ start = &record_channel->send_data.u.start;
+ start->channels = VD_INTERFACE_RECORD_CHAN;
+ start->frequency = VD_INTERFACE_RECORD_FREQ;
+ ASSERT(VD_INTERFACE_RECORD_FMT == VD_INTERFACE_AUDIO_FMT_S16);
+ start->format = RED_AUDIO_FMT_S16;
+ snd_add_buf(channel, start, sizeof(*start));
+
+ channel->send_data.size = sizeof(RedDataHeader) + sizeof(*start);
+ return snd_send_data(channel);
+}
+
+static int snd_record_send_stop(RecordChannel *record_channel)
+{
+ SndChannel *channel = (SndChannel *)record_channel;
+ if (!snd_reset_send_data(channel, RED_RECORD_STOP)) {
+ return FALSE;
+ }
+ channel->send_data.size = sizeof(RedDataHeader);
+ return snd_send_data(channel);
+}
+
+static int snd_record_send_ctl(RecordChannel *record_channel)
+{
+ SndChannel *channel = (SndChannel *)record_channel;
+
+ if ((channel->client_active = channel->active)) {
+ return snd_record_send_start(record_channel);
+ } else {
+ return snd_record_send_stop(record_channel);
+ }
+}
+
+static int snd_record_send_migrate(RecordChannel *record_channel)
+{
+ SndChannel *channel = (SndChannel *)record_channel;
+ RecordMigrateMessage* migrate;
+
+ if (!snd_reset_send_data(channel, RED_MIGRATE)) {
+ return FALSE;
+ }
+
+ migrate = &record_channel->send_data.u.migrate;
+ migrate->migrate.flags = RED_MIGRATE_NEED_DATA_TRANSFER;
+ migrate->header.type = RED_MIGRATE_DATA;
+ migrate->header.size = sizeof(RecordMigrateData);
+ migrate->header.serial = ++channel->send_data.header.serial;
+ migrate->header.sub_list = 0;
+
+ migrate->data.version = RECORD_MIG_VERSION;
+ migrate->data.serial = channel->send_data.header.serial;
+ migrate->data.start_time = record_channel->start_time;
+ migrate->data.mode = record_channel->mode;
+ migrate->data.mode_time = record_channel->mode_time;
+
+ snd_add_buf(channel, migrate, sizeof(*migrate));
+ channel->send_data.size = channel->send_data.header.size + sizeof(RedDataHeader);
+ channel->send_data.header.size -= sizeof(migrate->header);
+ channel->send_data.header.size -= sizeof(migrate->data);
+ return snd_send_data(channel);
+}
+
+static int snd_playback_send_write(PlaybackChannel *playback_channel)
+{
+ SndChannel *channel = (SndChannel *)playback_channel;
+ AudioFrame *frame;
+
+ if (!snd_reset_send_data(channel, RED_PLAYBACK_DATA)) {
+ return FALSE;
+ }
+
+ frame = playback_channel->in_progress;
+ snd_add_buf(channel, &frame->time, sizeof(frame->time));
+ if (playback_channel->mode == RED_AUDIO_DATA_MODE_CELT_0_5_1) {
+ int n = celt051_encode(playback_channel->celt_encoder, (celt_int16_t *)frame->samples, NULL,
+ playback_channel->send_data.u.celt_buf, CELT_COMPRESSED_FRAME_BYTES);
+ if (n < 0) {
+ red_printf("celt encode failed");
+ snd_disconnect_channel(channel);
+ return FALSE;
+ }
+ snd_add_buf(channel, playback_channel->send_data.u.celt_buf, n);
+ } else {
+ snd_add_buf(channel, frame->samples, sizeof(frame->samples));
+ }
+
+ channel->send_data.size = channel->send_data.header.size + sizeof(RedDataHeader);
+
+ return snd_send_data(channel);
+}
+
+static int playback_send_mode(PlaybackChannel *playback_channel)
+{
+ SndChannel *channel = (SndChannel *)playback_channel;
+ RedPlaybackMode *mode;
+
+ if (!snd_reset_send_data(channel, RED_PLAYBACK_MODE)) {
+ return FALSE;
+ }
+ mode = &playback_channel->send_data.u.mode;
+ mode->time = reds_get_mm_time();
+ mode->mode = playback_channel->mode;
+ snd_add_buf(channel, mode, sizeof(*mode));
+
+ channel->send_data.size = channel->send_data.header.size + sizeof(RedDataHeader);
+ return snd_send_data(channel);
+}
+
+static void snd_playback_send(void* data)
+{
+ PlaybackChannel *playback_channel = (PlaybackChannel*)data;
+ SndChannel *channel = (SndChannel*)playback_channel;
+
+ if (!playback_channel || !snd_send_data(data)) {
+ return;
+ }
+
+ while (channel->command) {
+ if (channel->command & SND_PLAYBACK_MODE_MASK) {
+ if (!playback_send_mode(playback_channel)) {
+ return;
+ }
+ channel->command &= ~SND_PLAYBACK_MODE_MASK;
+ }
+ if (channel->command & SND_PLAYBACK_PCM_MASK) {
+ ASSERT(!playback_channel->in_progress && playback_channel->pending_frame);
+ playback_channel->in_progress = playback_channel->pending_frame;
+ playback_channel->pending_frame = NULL;
+ channel->command &= ~SND_PLAYBACK_PCM_MASK;
+ if (!snd_playback_send_write(playback_channel)) {
+ red_printf("snd_send_playback_write failed");
+ return;
+ }
+ }
+ if (channel->command & SND_PLAYBACK_CTRL_MASK) {
+ if (!snd_playback_send_ctl(playback_channel)) {
+ return;
+ }
+ channel->command &= ~SND_PLAYBACK_CTRL_MASK;
+ }
+ if (channel->command & SND_PLAYBACK_MIGRATE_MASK) {
+ if (!snd_playback_send_migrate(playback_channel)) {
+ return;
+ }
+ channel->command &= ~SND_PLAYBACK_MIGRATE_MASK;
+ }
+ }
+}
+
+static void snd_record_send(void* data)
+{
+ RecordChannel *record_channel = (RecordChannel*)data;
+ SndChannel *channel = (SndChannel*)record_channel;
+
+ if (!record_channel || !snd_send_data(data)) {
+ return;
+ }
+
+ while (channel->command) {
+ if (channel->command & SND_RECORD_CTRL_MASK) {
+ if (!snd_record_send_ctl(record_channel)) {
+ return;
+ }
+ channel->command &= ~SND_RECORD_CTRL_MASK;
+ }
+ if (channel->command & SND_RECORD_MIGRATE_MASK) {
+ if (!snd_record_send_migrate(record_channel)) {
+ return;
+ }
+ channel->command &= ~SND_RECORD_MIGRATE_MASK;
+ }
+ }
+}
+
+static SndChannel *__new_channel(SndWorker *worker, int size, RedsStreamContext *peer,
+ int migrate, send_messages_proc send_messages,
+ handle_message_proc handle_message,
+ on_message_done_proc on_message_done,
+ cleanup_channel_proc cleanup)
+{
+ SndChannel *channel;
+ int delay_val;
+ int flags;
+ int priority;
+ int tos;
+
+ if ((flags = fcntl(peer->socket, F_GETFL)) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ priority = 6;
+ if (setsockopt(peer->socket, SOL_SOCKET, SO_PRIORITY, (void*)&priority,
+ sizeof(priority)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ tos = IPTOS_LOWDELAY;
+ if (setsockopt(peer->socket, IPPROTO_IP, IP_TOS, (void*)&tos, sizeof(tos)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ delay_val = IS_LOW_BANDWIDTH() ? 0 : 1;
+ if (setsockopt(peer->socket, IPPROTO_TCP, TCP_NODELAY, &delay_val, sizeof(delay_val)) == -1) {
+ red_printf("setsockopt failed, %s", strerror(errno));
+ }
+
+ if (fcntl(peer->socket, F_SETFL, flags | O_NONBLOCK) == -1) {
+ red_printf("accept failed, %s", strerror(errno));
+ goto error1;
+ }
+
+ ASSERT(size >= sizeof(*channel));
+ if (!(channel = malloc(size))) {
+ red_printf("malloc failed");
+ goto error1;
+ }
+ memset(channel, 0, size);
+ channel->peer = peer;
+ channel->worker = worker;
+ channel->recive_data.message = (RedDataHeader *)channel->recive_data.buf;
+ channel->recive_data.now = channel->recive_data.buf;
+ channel->recive_data.end = channel->recive_data.buf + sizeof(channel->recive_data.buf);
+
+ if (core->set_file_handlers(core, peer->socket, snd_receive, NULL, channel) == -1) {
+ red_printf("qemu_set_fd_handler failed, %s", strerror(errno));
+ goto error2;
+ }
+
+ channel->migrate = migrate;
+ channel->send_messages = send_messages;
+ channel->handle_message = handle_message;
+ channel->on_message_done = on_message_done;
+ channel->cleanup = cleanup;
+ return channel;
+
+error2:
+ free(channel);
+
+error1:
+ peer->cb_free(peer);
+ return NULL;
+}
+
+static void snd_shutdown(Channel *channel)
+{
+ SndWorker *worker = (SndWorker *)channel;
+ snd_disconnect_channel(worker->connection);
+}
+
+static void snd_set_command(SndChannel *channel, uint32_t command)
+{
+ if (!channel) {
+ return;
+ }
+ channel->command |= command;
+}
+
+static void snd_playback_start(PlaybackPlug *plug)
+{
+ PlaybackChannel *playback_channel = CONTAINEROF(plug, PlaybackChannel, plug);
+
+ ASSERT(!playback_channel->base.active);
+ reds_desable_mm_timer();
+ playback_channel->base.active = TRUE;
+ if (!playback_channel->base.client_active) {
+ snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
+ snd_playback_send(&playback_channel->base);
+ } else {
+ playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
+ }
+}
+
+static void snd_playback_stop(PlaybackPlug *plug)
+{
+ PlaybackChannel *playback_channel = CONTAINEROF(plug, PlaybackChannel, plug);
+
+ ASSERT(playback_channel->base.active);
+ reds_enable_mm_timer();
+ playback_channel->base.active = FALSE;
+ if (playback_channel->base.client_active) {
+ snd_set_command(&playback_channel->base, SND_PLAYBACK_CTRL_MASK);
+ snd_playback_send(&playback_channel->base);
+ } else {
+ playback_channel->base.command &= ~SND_PLAYBACK_CTRL_MASK;
+ playback_channel->base.command &= ~SND_PLAYBACK_PCM_MASK;
+
+ if (playback_channel->pending_frame) {
+ ASSERT(!playback_channel->in_progress);
+ snd_playback_free_frame(playback_channel,
+ playback_channel->pending_frame);
+ playback_channel->pending_frame = NULL;
+ }
+ }
+}
+
+static void snd_playback_get_frame(PlaybackPlug *plug, uint32_t **frame, uint32_t *num_samples)
+{
+ PlaybackChannel *playback_channel = CONTAINEROF(plug, PlaybackChannel, plug);
+
+ ASSERT(playback_channel->base.active);
+ if (!playback_channel->free_frames) {
+ *frame = NULL;
+ *num_samples = 0;
+ return;
+ }
+
+ *frame = playback_channel->free_frames->samples;
+ playback_channel->free_frames = playback_channel->free_frames->next;
+ *num_samples = FRAME_SIZE;
+}
+
+static void snd_playback_put_frame(PlaybackPlug *plug, uint32_t *samples)
+{
+ PlaybackChannel *playback_channel = CONTAINEROF(plug, PlaybackChannel, plug);
+ AudioFrame *frame;
+
+ ASSERT(playback_channel->base.active);
+
+ if (playback_channel->pending_frame) {
+ snd_playback_free_frame(playback_channel, playback_channel->pending_frame);
+ }
+ frame = CONTAINEROF(samples, AudioFrame, samples);
+ frame->time = reds_get_mm_time();
+ red_dispatcher_set_mm_time(frame->time);
+ playback_channel->pending_frame = frame;
+ snd_set_command(&playback_channel->base, SND_PLAYBACK_PCM_MASK);
+ snd_playback_send(&playback_channel->base);
+}
+
+static void on_new_playback_channel(SndWorker *worker)
+{
+ PlaybackChannel *playback_channel = (PlaybackChannel *)worker->connection;
+ PlaybackInterface *interface = (PlaybackInterface *)worker->interface;
+ ASSERT(playback_channel);
+
+ playback_channel->plug_ref = interface->plug(interface, &playback_channel->plug,
+ &playback_channel->base.active);
+ snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_MODE_MASK);
+ if (!playback_channel->base.migrate && playback_channel->base.active) {
+ snd_set_command((SndChannel *)playback_channel, SND_PLAYBACK_CTRL_MASK);
+ }
+ if (playback_channel->base.active) {
+ reds_desable_mm_timer();
+ }
+}
+
+static void snd_playback_cleanup(SndChannel *channel)
+{
+ PlaybackChannel *playback_channel = (PlaybackChannel *)channel;
+ PlaybackInterface *interface = (PlaybackInterface *)channel->worker->interface;
+
+ if (playback_channel->base.active) {
+ reds_enable_mm_timer();
+ }
+ interface->unplug(interface, playback_channel->plug_ref);
+
+ celt051_encoder_destroy(playback_channel->celt_encoder);
+ celt051_mode_destroy(playback_channel->celt_mode);
+}
+
+static void snd_set_playback_peer(Channel *channel, RedsStreamContext *peer, int migration,
+ int num_common_caps, uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ SndWorker *worker = (SndWorker *)channel;
+ PlaybackChannel *playback_channel;
+ CELTEncoder *celt_encoder;
+ CELTMode *celt_mode;
+ int celt_error;
+
+ snd_disconnect_channel(worker->connection);
+
+ if (!(celt_mode = celt051_mode_create(VD_INTERFACE_PLAYBACK_FREQ, VD_INTERFACE_PLAYBACK_CHAN,
+ FRAME_SIZE, &celt_error))) {
+ red_printf("create celt mode failed %d", celt_error);
+ return;
+ }
+
+ if (!(celt_encoder = celt051_encoder_create(celt_mode))) {
+ red_printf("create celt encoder failed");
+ goto error_1;
+ }
+
+ if (!(playback_channel = (PlaybackChannel *)__new_channel(worker,
+ sizeof(*playback_channel),
+ peer,
+ migration,
+ snd_playback_send,
+ snd_playback_handle_message,
+ snd_playback_on_message_done,
+ snd_playback_cleanup))) {
+ goto error_2;
+ }
+ worker->connection = &playback_channel->base;
+ snd_playback_free_frame(playback_channel, &playback_channel->frames[0]);
+ snd_playback_free_frame(playback_channel, &playback_channel->frames[1]);
+ snd_playback_free_frame(playback_channel, &playback_channel->frames[2]);
+
+ playback_channel->plug.major_version = VD_INTERFACE_PLAYBACK_MAJOR;
+ playback_channel->plug.minor_version = VD_INTERFACE_PLAYBACK_MINOR;
+ playback_channel->plug.start = snd_playback_start;
+ playback_channel->plug.stop = snd_playback_stop;
+ playback_channel->plug.get_frame = snd_playback_get_frame;
+ playback_channel->plug.put_frame = snd_playback_put_frame;
+ playback_channel->celt_mode = celt_mode;
+ playback_channel->celt_encoder = celt_encoder;
+ playback_channel->celt_allowed = num_caps > 0 && (caps[0] & (1 << RED_PLAYBACK_CAP_CELT_0_5_1));
+ playback_channel->mode = playback_channel->celt_allowed ? playback_compression :
+ RED_AUDIO_DATA_MODE_RAW;
+
+ on_new_playback_channel(worker);
+ snd_playback_send(worker->connection);
+ return;
+
+error_2:
+ celt051_encoder_destroy(celt_encoder);
+
+error_1:
+ celt051_mode_destroy(celt_mode);
+}
+
+static void snd_record_migrate(Channel *channel)
+{
+ SndWorker *worker = (SndWorker *)channel;
+ if (worker->connection) {
+ snd_set_command(worker->connection, SND_RECORD_MIGRATE_MASK);
+ snd_record_send(worker->connection);
+ }
+}
+
+static void snd_record_start(RecordPlug *plug)
+{
+ RecordChannel *record_channel = CONTAINEROF(plug, RecordChannel, plug);
+
+ ASSERT(!record_channel->base.active);
+ record_channel->base.active = TRUE;
+ record_channel->read_pos = record_channel->write_pos = 0; //todo: improve by
+ //stream generation
+ if (!record_channel->base.client_active) {
+ snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
+ snd_record_send(&record_channel->base);
+ } else {
+ record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
+ }
+}
+
+static void snd_record_stop(RecordPlug *plug)
+{
+ RecordChannel *record_channel = CONTAINEROF(plug, RecordChannel, plug);
+
+ ASSERT(record_channel->base.active);
+ record_channel->base.active = FALSE;
+ if (record_channel->base.client_active) {
+ snd_set_command(&record_channel->base, SND_RECORD_CTRL_MASK);
+ snd_record_send(&record_channel->base);
+ } else {
+ record_channel->base.command &= ~SND_RECORD_CTRL_MASK;
+ }
+}
+
+static uint32_t snd_record_read(RecordPlug *plug, uint32_t num_samples, uint32_t *samples)
+{
+ RecordChannel *record_channel = CONTAINEROF(plug, RecordChannel, plug);
+ uint32_t read_pos;
+ uint32_t now;
+ uint32_t len;
+
+ ASSERT(record_channel->base.active);
+
+ if (record_channel->write_pos < RECORD_SAMPLES_SIZE / 2) {
+ return 0;
+ }
+
+ len = MIN(record_channel->write_pos - record_channel->read_pos, num_samples);
+
+ if (len < num_samples) {
+ SndWorker *worker = record_channel->base.worker;
+ snd_receive(record_channel);
+ if (!worker->connection) {
+ return 0;
+ }
+ len = MIN(record_channel->write_pos - record_channel->read_pos, num_samples);
+ }
+
+ read_pos = record_channel->read_pos % RECORD_SAMPLES_SIZE;
+ record_channel->read_pos += len;
+ now = MIN(len, RECORD_SAMPLES_SIZE - read_pos);
+ memcpy(samples, &record_channel->samples[read_pos], now * 4);
+ if (now < len) {
+ memcpy(samples + now, record_channel->samples, (len - now) * 4);
+ }
+ return len;
+}
+
+static void on_new_record_channel(SndWorker *worker)
+{
+ RecordChannel *record_channel = (RecordChannel *)worker->connection;
+ RecordInterface *interface = (RecordInterface *)worker->interface;
+ ASSERT(record_channel);
+
+ record_channel->plug_ref = interface->plug(interface, &record_channel->plug,
+ &record_channel->base.active);
+ if (!record_channel->base.migrate) {
+ if (record_channel->base.active) {
+ snd_set_command((SndChannel *)record_channel, SND_RECORD_CTRL_MASK);
+ }
+ }
+}
+
+static void snd_record_cleanup(SndChannel *channel)
+{
+ RecordChannel *record_channel = (RecordChannel *)channel;
+ RecordInterface *interface = (RecordInterface *)channel->worker->interface;
+ interface->unplug(interface, record_channel->plug_ref);
+
+ celt051_decoder_destroy(record_channel->celt_decoder);
+ celt051_mode_destroy(record_channel->celt_mode);
+}
+
+static void snd_set_record_peer(Channel *channel, RedsStreamContext *peer, int migration,
+ int num_common_caps, uint32_t *common_caps, int num_caps,
+ uint32_t *caps)
+{
+ SndWorker *worker = (SndWorker *)channel;
+ RecordChannel *record_channel;
+ CELTDecoder *celt_decoder;
+ CELTMode *celt_mode;
+ int celt_error;
+
+ snd_disconnect_channel(worker->connection);
+
+ if (!(celt_mode = celt051_mode_create(VD_INTERFACE_RECORD_FREQ, VD_INTERFACE_RECORD_CHAN,
+ FRAME_SIZE, &celt_error))) {
+ red_printf("create celt mode failed %d", celt_error);
+ return;
+ }
+
+ if (!(celt_decoder = celt051_decoder_create(celt_mode))) {
+ red_printf("create celt decoder failed");
+ goto error_1;
+ }
+
+ if (!(record_channel = (RecordChannel *)__new_channel(worker,
+ sizeof(*record_channel),
+ peer,
+ migration,
+ snd_record_send,
+ snd_record_handle_message,
+ snd_record_on_message_done,
+ snd_record_cleanup))) {
+ goto error_2;
+ }
+
+ worker->connection = &record_channel->base;
+
+ record_channel->plug.major_version = VD_INTERFACE_RECORD_MAJOR;
+ record_channel->plug.minor_version = VD_INTERFACE_RECORD_MINOR;
+ record_channel->plug.start = snd_record_start;
+ record_channel->plug.stop = snd_record_stop;
+ record_channel->plug.read = snd_record_read;
+ record_channel->celt_mode = celt_mode;
+ record_channel->celt_decoder = celt_decoder;
+
+ on_new_record_channel(worker);
+ snd_record_send(worker->connection);
+ return;
+
+error_1:
+ celt051_decoder_destroy(celt_decoder);
+
+error_2:
+ celt051_mode_destroy(celt_mode);
+}
+
+static void snd_playback_migrate(Channel *channel)
+{
+ SndWorker *worker = (SndWorker *)channel;
+
+ if (worker->connection) {
+ snd_set_command(worker->connection, SND_PLAYBACK_MIGRATE_MASK);
+ snd_playback_send(worker->connection);
+ }
+}
+
+static void add_worker(SndWorker *worker)
+{
+ worker->next = workers;
+ workers = worker;
+}
+
+static void remove_worker(SndWorker *worker)
+{
+ SndWorker **now = &workers;
+ while (*now) {
+ if (*now == worker) {
+ *now = worker->next;
+ return;
+ }
+ now = &(*now)->next;
+ }
+ red_printf("not found");
+}
+
+static SndWorker *find_worker(VDInterface *interface)
+{
+ SndWorker *worker = workers;
+ while (worker) {
+ if (worker->interface == interface) {
+ break;
+ }
+ worker = worker->next;
+ }
+ return worker;
+}
+
+void snd_attach_playback(PlaybackInterface *interface)
+{
+ SndWorker *playback_worker;
+ if (!(playback_worker = (SndWorker *)malloc(sizeof(*playback_worker)))) {
+ red_error("playback channel malloc failed");
+ }
+ memset(playback_worker, 0, sizeof(*playback_worker));
+ playback_worker->base.type = RED_CHANNEL_PLAYBACK;
+ playback_worker->base.link = snd_set_playback_peer;
+ playback_worker->base.shutdown = snd_shutdown;
+ playback_worker->base.migrate = snd_playback_migrate;
+ playback_worker->base.data = NULL;
+
+ playback_worker->interface = &interface->base;
+ playback_worker->base.num_caps = 1;
+ if (!(playback_worker->base.caps = malloc(sizeof(uint32_t)))) {
+ PANIC("malloc failed");
+ }
+ playback_worker->base.caps[0] = (1 << RED_PLAYBACK_CAP_CELT_0_5_1);
+
+ add_worker(playback_worker);
+ reds_register_channel(&playback_worker->base);
+}
+
+void snd_attach_record(RecordInterface *interface)
+{
+ SndWorker *record_worker;
+ if (!(record_worker = (SndWorker *)malloc(sizeof(*record_worker)))) {
+ PANIC("malloc failed");
+ }
+
+ memset(record_worker, 0, sizeof(*record_worker));
+ record_worker->base.type = RED_CHANNEL_RECORD;
+ record_worker->base.link = snd_set_record_peer;
+ record_worker->base.shutdown = snd_shutdown;
+ record_worker->base.migrate = snd_record_migrate;
+ record_worker->base.data = NULL;
+
+ record_worker->interface = &interface->base;
+
+ record_worker->base.num_caps = 1;
+ if (!(record_worker->base.caps = malloc(sizeof(uint32_t)))) {
+ PANIC("malloc failed");
+ }
+ record_worker->base.caps[0] = (1 << RED_RECORD_CAP_CELT_0_5_1);
+ add_worker(record_worker);
+ reds_register_channel(&record_worker->base);
+}
+
+static void snd_detach_common(VDInterface *interface)
+{
+ SndWorker *worker = find_worker(interface);
+
+ if (!worker) {
+ return;
+ }
+ remove_worker(worker);
+ snd_disconnect_channel(worker->connection);
+ reds_unregister_channel(&worker->base);
+
+ free(worker->base.common_caps);
+ free(worker->base.caps);
+ free(worker);
+}
+
+void snd_detach_playback(PlaybackInterface *interface)
+{
+ snd_detach_common(&interface->base);
+}
+
+void snd_detach_record(RecordInterface *interface)
+{
+ snd_detach_common(&interface->base);
+}
+
+void snd_set_playback_compression(int on)
+{
+ SndWorker *now = workers;
+
+ playback_compression = on ? RED_AUDIO_DATA_MODE_CELT_0_5_1 : RED_AUDIO_DATA_MODE_RAW;
+ for (; now; now = now->next) {
+ if (now->base.type == RED_CHANNEL_PLAYBACK && now->connection) {
+ PlaybackChannel* playback = (PlaybackChannel*)now->connection;
+ if (!playback->celt_allowed) {
+ ASSERT(playback->mode == RED_AUDIO_DATA_MODE_RAW);
+ continue;
+ }
+ if (playback->mode != playback_compression) {
+ playback->mode = playback_compression;
+ snd_set_command(now->connection, SND_PLAYBACK_MODE_MASK);
+ }
+ }
+ }
+}
+
+int snd_get_playback_compression()
+{
+ return (playback_compression == RED_AUDIO_DATA_MODE_RAW) ? FALSE : TRUE;
+}
+
diff --git a/server/snd_worker.h b/server/snd_worker.h
new file mode 100644
index 00000000..ddcad1c0
--- /dev/null
+++ b/server/snd_worker.h
@@ -0,0 +1,33 @@
+/*
+ Copyright (C) 2009 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 _H_SND_WORKER
+#define _H_SND_WORKER
+
+#include "vd_interface.h"
+
+void snd_attach_playback(PlaybackInterface *interface);
+void snd_detach_playback(PlaybackInterface *interface);
+
+void snd_attach_record(RecordInterface *interface);
+void snd_detach_record(RecordInterface *interface);
+
+void snd_set_playback_compression(int on);
+int snd_get_playback_compression();
+
+#endif
+
diff --git a/server/spice.h b/server/spice.h
new file mode 100644
index 00000000..26de4f73
--- /dev/null
+++ b/server/spice.h
@@ -0,0 +1,29 @@
+/*
+ Copyright (C) 2009 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 _H_SPICE
+#define _H_SPICE
+
+#include "vd_interface.h"
+
+extern const char *spice_usage_str[];
+
+int spice_parse_args(const char *args);
+void spice_init(CoreInterface *core);
+
+#endif
+
diff --git a/server/stat.h b/server/stat.h
new file mode 100644
index 00000000..6e5d7e79
--- /dev/null
+++ b/server/stat.h
@@ -0,0 +1,48 @@
+/*
+ Copyright (C) 2009 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 _H_STAT
+#define _H_STAT
+
+#include <stdint.h>
+
+typedef uint32_t StatNodeRef;
+#define INVALID_STAT_REF (~(StatNodeRef)0)
+
+#ifdef RED_STATISTICS
+
+StatNodeRef stat_add_node(StatNodeRef parent, const char *name, int visible);
+void stat_remove_node(StatNodeRef node);
+uint64_t *stat_add_counter(StatNodeRef parent, const char *name, int visible);
+void stat_remove_counter(uint64_t *counter);
+
+#define stat_inc_counter(counter, value) { \
+ if (counter) { \
+ *(counter) += (value); \
+ } \
+}
+
+#else
+#define stat_add_node(p, n, v) INVALID_STAT_REF
+#define stat_remove_node(n)
+#define stat_add_counter(p, n, v) NULL
+#define stat_remove_counter(c)
+#define stat_inc_counter(c, v)
+#endif
+
+#endif
+
diff --git a/server/vd_interface.h b/server/vd_interface.h
new file mode 100644
index 00000000..932c0b13
--- /dev/null
+++ b/server/vd_interface.h
@@ -0,0 +1,334 @@
+/*
+ Copyright (C) 2009 Red Hat, Inc.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions are
+ met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in
+ the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived
+ from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER AND CONTRIBUTORS "AS
+ IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#ifndef _H_VD_INTERFACE
+#define _H_VD_INTERFACE
+
+#include <stdint.h>
+
+#define VM_INTERFACE_VERSION 1
+typedef unsigned long VDObjectRef;
+#define INVALID_VD_OBJECT_REF 0
+typedef struct VDInterface VDInterface;
+
+struct VDInterface {
+ uint32_t base_version;
+ const char *type;
+ unsigned int id;
+ const char *description;
+ //todo: swap minor major order on VM_INTERFACE_VERSION change
+ // (here and in spacific interfaces)
+ uint32_t minor_version;
+ uint32_t major_version;
+};
+
+#define VD_INTERFACE_CORE "core"
+#define VD_INTERFACE_CORE_MAJOR 1
+#define VD_INTERFACE_CORE_MINOR 2
+typedef struct CoreInterface CoreInterface;
+typedef enum {
+ VD_INTERFACE_ADDING,
+ VD_INTERFACE_REMOVING,
+} VDInterfaceChangeType;
+
+typedef enum {
+ VD_LOG_ERROR = 1,
+ VD_LOG_WARN,
+ VD_LOG_INFO,
+} LogLevel;
+
+typedef void (*vd_interface_change_notifier_t)(void *opaque, VDInterface *interface,
+ VDInterfaceChangeType change);
+typedef void (*timer_callback_t)(void *opaque);
+
+struct CoreInterface {
+ VDInterface base;
+
+ VDInterface *(*next)(CoreInterface *core, VDInterface *prev);
+
+ VDObjectRef (*register_change_notifiers)(CoreInterface *core, void *opaque,
+ vd_interface_change_notifier_t in_notifier);
+ void (*unregister_change_notifiers)(CoreInterface *core, VDObjectRef notifier);
+
+ VDObjectRef (*create_timer)(CoreInterface *core, timer_callback_t, void *opaue);
+ void (*arm_timer)(CoreInterface *core, VDObjectRef timer, uint32_t ms);
+ void (*disarm_timer)(CoreInterface *core, VDObjectRef timer);
+ void (*destroy_timer)(CoreInterface *core, VDObjectRef timer);
+
+ int (*set_file_handlers)(CoreInterface *core, int fd,
+ void (*on_read)(void *),
+ void (*on_write)(void *),
+ void *opaque);
+
+ void (*term_printf)(CoreInterface *core, const char* str, ...);
+ void (*log)(CoreInterface *core, LogLevel level, const char* component,
+ const char* format, ...);
+};
+
+#define VD_INTERFACE_QXL "qxl"
+#define VD_INTERFACE_QXL_MAJOR 1
+#define VD_INTERFACE_QXL_MINOR 2
+typedef struct QXLInterface QXLInterface;
+typedef void (*qxl_mode_change_notifier_t)(void *opaque);
+typedef struct QXLWorker QXLWorker;
+union QXLReleaseInfo;
+struct QXLCommand;
+struct QXLWorker {
+ uint32_t minor_version;
+ uint32_t major_version;
+ void (*attach)(QXLWorker *worker);
+ void (*detach)(QXLWorker *worker);
+ void (*wakeup)(QXLWorker *worker);
+ void (*oom)(QXLWorker *worker);
+ void (*save)(QXLWorker *worker);
+ void (*load)(QXLWorker *worker);
+ void (*start)(QXLWorker *worker);
+ void (*stop)(QXLWorker *worker);
+ void (*update_area)(QXLWorker *worker);
+};
+
+typedef struct DrawArea {
+ uint8_t *buf;
+ uint32_t size;
+ uint8_t *line_0;
+ uint32_t width;
+ uint32_t heigth;
+ int stride;
+} DrawArea;
+
+typedef struct QXLDevInfo {
+ long phys_delta;
+ unsigned long phys_start;
+ unsigned long phys_end;
+
+ uint32_t x_res;
+ uint32_t y_res;
+ uint32_t bits;
+ uint32_t use_hardware_cursor;
+
+ DrawArea draw_area;
+
+ uint32_t ram_size;
+} QXLDevInfo;
+
+struct QXLInterface {
+ VDInterface base;
+
+ uint16_t pci_vendor;
+ uint16_t pci_id;
+ uint8_t pci_revision;
+
+ void (*attache_worker)(QXLInterface *qxl, QXLWorker *qxl_worker);
+ void (*set_compression_level)(QXLInterface *qxl, int level);
+ void (*set_mm_time)(QXLInterface *qxl, uint32_t mm_time);
+ VDObjectRef (*register_mode_change)(QXLInterface *qxl, qxl_mode_change_notifier_t,
+ void *opaque);
+ void (*unregister_mode_change)(QXLInterface *qxl, VDObjectRef notifier);
+
+ void (*get_info)(QXLInterface *qxl, QXLDevInfo *info);
+ int (*get_command)(QXLInterface *qxl, struct QXLCommand *cmd);
+ int (*req_cmd_notification)(QXLInterface *qxl);
+ int (*has_command)(QXLInterface *qxl);
+ void (*release_resource)(QXLInterface *qxl, union QXLReleaseInfo *release_info);
+ int (*get_cursor_command)(QXLInterface *qxl, struct QXLCommand *cmd);
+ int (*req_cursor_notification)(QXLInterface *qxl);
+ const struct Rect *(*get_update_area)(QXLInterface *qxl);
+ void (*notify_update)(QXLInterface *qxl, uint32_t update_id);
+ void (*set_save_data)(QXLInterface *qxl, void *data, int size);
+ void *(*get_save_data)(QXLInterface *qxl);
+ int (*flush_resources)(QXLInterface *qxl);
+};
+
+#define VD_INTERFACE_KEYBOARD "keyboard"
+#define VD_INTERFACE_KEYBOARD_MAJOR 1
+#define VD_INTERFACE_KEYBOARD_MINOR 1
+typedef struct KeyboardInterface KeyboardInterface;
+typedef void (*keyborad_leads_notifier_t)(void *opaque, uint8_t leds);
+
+struct KeyboardInterface {
+ VDInterface base;
+
+ void (*push_scan_freg)(KeyboardInterface *keyboard, uint8_t frag);
+ uint8_t (*get_leds)(KeyboardInterface *keyboard);
+ VDObjectRef (*register_leds_notifier)(KeyboardInterface *keyboard,
+ keyborad_leads_notifier_t notifier, void *opaque);
+ void (*unregister_leds_notifayer)(KeyboardInterface *keyboard, VDObjectRef notifier);
+};
+
+#define VD_INTERFACE_MOUSE "mouse"
+#define VD_INTERFACE_MOUSE_MAJOR 1
+#define VD_INTERFACE_MOUSE_MINOR 1
+typedef struct MouseInterface MouseInterface;
+
+struct MouseInterface {
+ VDInterface base;
+
+ void (*moution)(MouseInterface* mouse, int dx, int dy, int dz,
+ uint32_t buttons_state);
+ void (*buttons)(MouseInterface* mouse, uint32_t buttons_state);
+};
+
+#define VD_INTERFACE_TABLET "tablet"
+#define VD_INTERFACE_TABLET_MAJOR 1
+#define VD_INTERFACE_TABLET_MINOR 1
+typedef struct TabletInterface TabletInterface;
+
+struct TabletInterface {
+ VDInterface base;
+
+ void (*set_logical_size)(TabletInterface* tablet, int width, int height);
+ void (*position)(TabletInterface* tablet, int x, int y, uint32_t buttons_state);
+ void (*wheel)(TabletInterface* tablet, int wheel_moution, uint32_t buttons_state);
+ void (*buttons)(TabletInterface* tablet, uint32_t buttons_state);
+};
+
+#define VD_INTERFACE_MIGRATION "migration"
+#define VD_INTERFACE_MIGRATION_MAJOR 1
+#define VD_INTERFACE_MIGRATION_MINOR 1
+typedef struct MigrationInterface MigrationInterface;
+typedef void (*migration_notify_started_t)(void *opaque, const char *args);
+typedef void (*migration_notify_finished_t)(void *opaque, int completed);
+typedef void (*migration_notify_recv_t)(void *opaque, int fd);
+
+struct MigrationInterface {
+ VDInterface base;
+
+ VDObjectRef (*register_notifiers)(MigrationInterface* mig, const char *key,
+ migration_notify_started_t,
+ migration_notify_finished_t,
+ migration_notify_recv_t,
+ void *opaque);
+ void (*unregister_notifiers)(MigrationInterface* mig, VDObjectRef notifier);
+ void (*notifier_done)(MigrationInterface *mig, VDObjectRef notifier);
+ int (*begin_hook)(MigrationInterface *mig, VDObjectRef notifier);
+};
+
+#define VD_INTERFACE_QTERM "qemu_terminal"
+#define VD_INTERFACE_QTERM_MAJOR 1
+#define VD_INTERFACE_QTERM_MINOR 1
+typedef struct QTermInterface QTermInterface;
+
+struct QTermInterface {
+ VDInterface base;
+
+ VDObjectRef (*add_action_command_handler)(QTermInterface *term, const char *module_name,
+ const char *name,
+ const char *args_type,
+ void *handler,
+ const char *params,
+ const char *help);
+ void (*remove_action_command_handler)(QTermInterface *term, VDObjectRef obj);
+
+ VDObjectRef (*add_info_command_handler)(QTermInterface *term, const char *module_name,
+ const char *name,
+ void *handler,
+ const char *help);
+ void (*remove_info_command_handler)(QTermInterface *term, VDObjectRef obj);
+};
+
+#define VD_INTERFACE_PLAYBACK "playback"
+#define VD_INTERFACE_PLAYBACK_MAJOR 1
+#define VD_INTERFACE_PLAYBACK_MINOR 1
+typedef struct PlaybackInterface PlaybackInterface;
+
+enum {
+ VD_INTERFACE_AUDIO_FMT_S16 = 1,
+};
+
+#define VD_INTERFACE_PLAYBACK_FREQ 44100
+#define VD_INTERFACE_PLAYBACK_CHAN 2
+#define VD_INTERFACE_PLAYBACK_FMT VD_INTERFACE_AUDIO_FMT_S16
+
+typedef struct PlaybackPlug PlaybackPlug;
+struct PlaybackPlug {
+ uint32_t minor_version;
+ uint32_t major_version;
+ void (*start)(PlaybackPlug *plug);
+ void (*stop)(PlaybackPlug *plug);
+ void (*get_frame)(PlaybackPlug *plug, uint32_t **frame, uint32_t *samples);
+ void (*put_frame)(PlaybackPlug *plug, uint32_t *frame);
+};
+
+struct PlaybackInterface {
+ VDInterface base;
+
+ VDObjectRef (*plug)(PlaybackInterface *playback, PlaybackPlug* plug, int *enable);
+ void (*unplug)(PlaybackInterface *playback, VDObjectRef);
+};
+
+#define VD_INTERFACE_RECORD "record"
+#define VD_INTERFACE_RECORD_MAJOR 2
+#define VD_INTERFACE_RECORD_MINOR 1
+typedef struct RecordInterface RecordInterface;
+
+#define VD_INTERFACE_RECORD_FREQ 44100
+#define VD_INTERFACE_RECORD_CHAN 2
+#define VD_INTERFACE_RECORD_FMT VD_INTERFACE_AUDIO_FMT_S16
+
+
+typedef struct RecordPlug RecordPlug;
+struct RecordPlug {
+ uint32_t minor_version;
+ uint32_t major_version;
+ void (*start)(RecordPlug *plug);
+ void (*stop)(RecordPlug *plug);
+ uint32_t (*read)(RecordPlug *plug, uint32_t num_samples, uint32_t *samples);
+};
+
+struct RecordInterface {
+ VDInterface base;
+
+ VDObjectRef (*plug)(RecordInterface *recorder, RecordPlug* plug, int *enable);
+ void (*unplug)(RecordInterface *recorder, VDObjectRef);
+};
+
+#define VD_INTERFACE_VDI_PORT "vdi_port"
+#define VD_INTERFACE_VDI_PORT_MAJOR 1
+#define VD_INTERFACE_VDI_PORT_MINOR 1
+typedef struct VDIPortInterface VDIPortInterface;
+
+typedef struct VDIPortPlug VDIPortPlug;
+struct VDIPortPlug {
+ uint32_t minor_version;
+ uint32_t major_version;
+ void (*wakeup)(VDIPortPlug *plug);
+};
+
+struct VDIPortInterface {
+ VDInterface base;
+
+ VDObjectRef (*plug)(VDIPortInterface *port, VDIPortPlug* plug);
+ void (*unplug)(VDIPortInterface *port, VDObjectRef plug);
+ int (*write)(VDIPortInterface *port, VDObjectRef plug, const uint8_t *buf, int len);
+ int (*read)(VDIPortInterface *port, VDObjectRef plug, uint8_t *buf, int len);
+};
+
+#endif
+