From c1b79eb035fa158fb2ac3bc8e559809611070016 Mon Sep 17 00:00:00 2001 From: Yaniv Kamay Date: Sat, 19 Sep 2009 21:25:46 +0300 Subject: fresh start --- server/.gitignore | 8 + server/Makefile.am | 81 + server/glz_encode_match_tmpl.c | 151 + server/glz_encode_tmpl.c | 572 ++ server/glz_encoder.c | 308 ++ server/glz_encoder.h | 56 + server/glz_encoder_config.h | 64 + server/glz_encoder_dictionary.c | 618 +++ server/glz_encoder_dictionary.h | 70 + server/glz_encoder_dictionary_protected.h | 187 + server/red_bitmap_utils.h | 162 + server/red_client_cache.h | 136 + server/red_client_shared_cache.h | 216 + server/red_common.h | 99 + server/red_dispatcher.c | 479 ++ server/red_dispatcher.h | 33 + server/red_worker.c | 8492 +++++++++++++++++++++++++++++ server/red_worker.h | 134 + server/red_yuv.h | 154 + server/reds.c | 4996 +++++++++++++++++ server/reds.h | 70 + server/snd_worker.c | 1300 +++++ server/snd_worker.h | 33 + server/spice.h | 29 + server/stat.h | 48 + server/vd_interface.h | 334 ++ 26 files changed, 18830 insertions(+) create mode 100644 server/.gitignore create mode 100644 server/Makefile.am create mode 100644 server/glz_encode_match_tmpl.c create mode 100644 server/glz_encode_tmpl.c create mode 100644 server/glz_encoder.c create mode 100644 server/glz_encoder.h create mode 100644 server/glz_encoder_config.h create mode 100644 server/glz_encoder_dictionary.c create mode 100644 server/glz_encoder_dictionary.h create mode 100644 server/glz_encoder_dictionary_protected.h create mode 100644 server/red_bitmap_utils.h create mode 100644 server/red_client_cache.h create mode 100644 server/red_client_shared_cache.h create mode 100644 server/red_common.h create mode 100644 server/red_dispatcher.c create mode 100644 server/red_dispatcher.h create mode 100644 server/red_worker.c create mode 100644 server/red_worker.h create mode 100644 server/red_yuv.h create mode 100644 server/reds.c create mode 100644 server/reds.h create mode 100644 server/snd_worker.c create mode 100644 server/snd_worker.h create mode 100644 server/spice.h create mode 100644 server/stat.h create mode 100644 server/vd_interface.h (limited to 'server') 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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#include +#include +#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 . +*/ + +#ifndef _H_GLZ_ENCODER +#define _H_GLZ_ENCODER + +/* Manging the lz encoding using a dictionary that is shared among encoders */ + +#include +#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 . +*/ + +#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 . +*/ + +#include +#include +#include + +#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 . +*/ + +#ifndef _H_GLZ_ENCODER_DICTIONARY +#define _H_GLZ_ENCODER_DICTIONARY + +#include +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#ifndef _H_RED_COMMON +#define _H_RED_COMMON + +#include +#include + +#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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . +*/ + +#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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ©.src_bitmap, worker->dev_info.phys_delta); + localize_mask(worker, ©.mask, worker->dev_info.phys_delta); + worker->draw_context.draw_copy(worker->draw_context.canvas, &drawable->bbox, &clip, ©); + unlocalize_mask(©.mask); + unlocalize_bitmap(©.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, ©->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 +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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#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(<ime); + 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(<ime); + 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=][,sport=][,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 port and/or sport ", + " 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, + "", + ""); + mon->add_action_command_handler(mon, "spice", "set_playback_compression", "s", + reds_do_set_playback_compression, + "", + ""); + mon->add_action_command_handler(mon, "spice", "set_ticket", "ss?", + reds_do_set_ticket, + " [expiration=]" + "[,connected=keep|disconnect|fail]", + "set the spice connection ticket"); + mon->add_action_command_handler(mon, "spice", "set_ticket64", "ss?", + reds_do_set_ticket64, + " [expiration=]" + " [,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, + "", + ""); +#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 . +*/ + +#ifndef _H_REDS +#define _H_REDS + +#include +#include +#include + +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 . +*/ + +#include +#include +#include +#include +#include +#include + +#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 . +*/ + +#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 . +*/ + +#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 . +*/ + +#ifndef _H_STAT +#define _H_STAT + +#include + +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 + +#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 + -- cgit