From 24236869fbff210b356b6626e6e9e9eadc8a976c Mon Sep 17 00:00:00 2001 From: bellard Date: Sun, 30 Apr 2006 21:28:36 +0000 Subject: [PATCH] VNC server (Anthony Liguori) git-svn-id: svn://svn.savannah.nongnu.org/qemu/trunk@1869 c046a42c-6fe2-441c-8c8c-71466251a162 --- Changelog | 1 + Makefile.target | 4 + hw/cirrus_vga.c | 83 +++++- keymaps.c | 2 + qemu-doc.texi | 7 + vl.c | 13 + vl.h | 13 + vnc.c | 894 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vnc_keysym.h | 275 +++++++++++++++++ vnchextile.h | 189 ++++++++++++ 10 files changed, 1477 insertions(+), 4 deletions(-) create mode 100644 vnc.c create mode 100644 vnc_keysym.h create mode 100644 vnchextile.h diff --git a/Changelog b/Changelog index d6f34fe..27c6009 100644 --- a/Changelog +++ b/Changelog @@ -7,6 +7,7 @@ version 0.8.1: - SSE3 support - Solaris port (Ben Taylor) - Preliminary SH4 target (Samuel Tardieu) + - VNC server (Anthony Liguori) version 0.8.0: diff --git a/Makefile.target b/Makefile.target index f335b02..77eaa1e 100644 --- a/Makefile.target +++ b/Makefile.target @@ -357,6 +357,7 @@ endif ifdef CONFIG_SDL VL_OBJS+=sdl.o endif +VL_OBJS+=vnc.o ifdef CONFIG_COCOA VL_OBJS+=cocoa.o COCOA_LIBS=-F/System/Library/Frameworks -framework Cocoa -framework IOKit @@ -409,6 +410,9 @@ cocoa.o: cocoa.m sdl.o: sdl.c keymaps.c sdl_keysym.h $(CC) $(CFLAGS) $(DEFINES) $(SDL_CFLAGS) -c -o $@ $< +vnc.o: vnc.c keymaps.c sdl_keysym.h vnchextile.h + $(CC) $(CFLAGS) $(DEFINES) -c -o $@ $< + sdlaudio.o: sdlaudio.c $(CC) $(CFLAGS) $(DEFINES) $(SDL_CFLAGS) -c -o $@ $< diff --git a/hw/cirrus_vga.c b/hw/cirrus_vga.c index 934cde9..d186d79 100644 --- a/hw/cirrus_vga.c +++ b/hw/cirrus_vga.c @@ -644,15 +644,90 @@ static int cirrus_bitblt_videotovideo_patterncopy(CirrusVGAState * s) (s->cirrus_blt_srcaddr & ~7)); } -static int cirrus_bitblt_videotovideo_copy(CirrusVGAState * s) +static void cirrus_do_copy(CirrusVGAState *s, int dst, int src, int w, int h) { + int sx, sy; + int dx, dy; + int width, height; + int depth; + int notify = 0; + + depth = s->get_bpp((VGAState *)s) / 8; + s->get_resolution((VGAState *)s, &width, &height); + + /* extra x, y */ + sx = (src % (width * depth)) / depth; + sy = (src / (width * depth)); + dx = (dst % (width *depth)) / depth; + dy = (dst / (width * depth)); + + /* normalize width */ + w /= depth; + + /* if we're doing a backward copy, we have to adjust + our x/y to be the upper left corner (instead of the lower + right corner) */ + if (s->cirrus_blt_dstpitch < 0) { + sx -= (s->cirrus_blt_width / depth) - 1; + dx -= (s->cirrus_blt_width / depth) - 1; + sy -= s->cirrus_blt_height - 1; + dy -= s->cirrus_blt_height - 1; + } + + /* are we in the visible portion of memory? */ + if (sx >= 0 && sy >= 0 && dx >= 0 && dy >= 0 && + (sx + w) <= width && (sy + h) <= height && + (dx + w) <= width && (dy + h) <= height) { + notify = 1; + } + + /* make to sure only copy if it's a plain copy ROP */ + if (*s->cirrus_rop != cirrus_bitblt_rop_fwd_src && + *s->cirrus_rop != cirrus_bitblt_rop_bkwd_src) + notify = 0; + + /* we have to flush all pending changes so that the copy + is generated at the appropriate moment in time */ + if (notify) + vga_hw_update(); + (*s->cirrus_rop) (s, s->vram_ptr + s->cirrus_blt_dstaddr, s->vram_ptr + s->cirrus_blt_srcaddr, s->cirrus_blt_dstpitch, s->cirrus_blt_srcpitch, s->cirrus_blt_width, s->cirrus_blt_height); - cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, - s->cirrus_blt_dstpitch, s->cirrus_blt_width, - s->cirrus_blt_height); + + if (notify) + s->ds->dpy_copy(s->ds, + sx, sy, dx, dy, + s->cirrus_blt_width / depth, + s->cirrus_blt_height); + + /* we don't have to notify the display that this portion has + changed since dpy_copy implies this */ + + if (!notify) + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_width, + s->cirrus_blt_height); +} + +static int cirrus_bitblt_videotovideo_copy(CirrusVGAState * s) +{ + if (s->ds->dpy_copy) { + cirrus_do_copy(s, s->cirrus_blt_dstaddr - s->start_addr, + s->cirrus_blt_srcaddr - s->start_addr, + s->cirrus_blt_width, s->cirrus_blt_height); + } else { + (*s->cirrus_rop) (s, s->vram_ptr + s->cirrus_blt_dstaddr, + s->vram_ptr + s->cirrus_blt_srcaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_srcpitch, + s->cirrus_blt_width, s->cirrus_blt_height); + + cirrus_invalidate_region(s, s->cirrus_blt_dstaddr, + s->cirrus_blt_dstpitch, s->cirrus_blt_width, + s->cirrus_blt_height); + } + return 1; } diff --git a/keymaps.c b/keymaps.c index c3de8d7..bd89328 100644 --- a/keymaps.c +++ b/keymaps.c @@ -99,8 +99,10 @@ static kbd_layout_t *parse_keyboard_layout(const char *language, "Warning: Could not assign keysym %s (0x%x) because of memory constraints.\n", line, keysym); } else { +#if 0 fprintf(stderr, "Setting %d: %d,%d\n", k->extra_count, keysym, keycode); +#endif k->keysym2keycode_extra[k->extra_count]. keysym = keysym; k->keysym2keycode_extra[k->extra_count]. diff --git a/qemu-doc.texi b/qemu-doc.texi index 89ae6be..7068d2a 100644 --- a/qemu-doc.texi +++ b/qemu-doc.texi @@ -185,6 +185,13 @@ command line application. The emulated serial port is redirected on the console. Therefore, you can still use QEMU to debug a Linux kernel with a serial console. +@item -vnc d + +Normally, QEMU uses SDL to display the VGA output. With this option, +you can have QEMU listen on VNC display d and redirect the VGA display +over the VNC session. It is very useful to enable the usb tablet device +when using this option (option @option{-usbdevice tablet}). + @item -k language Use keyboard layout @var{language} (for example @code{fr} for diff --git a/vl.c b/vl.c index ef5bee5..64fcfb4 100644 --- a/vl.c +++ b/vl.c @@ -149,6 +149,7 @@ USBPort *vm_usb_ports[MAX_VM_USB_PORTS]; USBDevice *vm_usb_hub; static VLANState *first_vlan; int smp_cpus = 1; +int vnc_display = -1; #if defined(TARGET_SPARC) #define MAX_CPUS 16 #elif defined(TARGET_I386) @@ -4638,6 +4639,7 @@ void help(void) " (default is CL-GD5446 PCI VGA)\n" #endif "-loadvm file start right away with a saved state (loadvm in monitor)\n" + "-vnc display start a VNC server on display\n" "\n" "During emulation, the following keys are useful:\n" "ctrl-alt-f toggle full screen\n" @@ -4721,6 +4723,7 @@ enum { QEMU_OPTION_usb, QEMU_OPTION_usbdevice, QEMU_OPTION_smp, + QEMU_OPTION_vnc, }; typedef struct QEMUOption { @@ -4788,6 +4791,7 @@ const QEMUOption qemu_options[] = { { "win2k-hack", 0, QEMU_OPTION_win2k_hack }, { "usbdevice", HAS_ARG, QEMU_OPTION_usbdevice }, { "smp", HAS_ARG, QEMU_OPTION_smp }, + { "vnc", HAS_ARG, QEMU_OPTION_vnc }, /* temporary options */ { "usb", 0, QEMU_OPTION_usb }, @@ -5386,6 +5390,13 @@ int main(int argc, char **argv) exit(1); } break; + case QEMU_OPTION_vnc: + vnc_display = atoi(optarg); + if (vnc_display < 0) { + fprintf(stderr, "Invalid VNC display\n"); + exit(1); + } + break; } } } @@ -5551,6 +5562,8 @@ int main(int argc, char **argv) /* terminal init */ if (nographic) { dumb_display_init(ds); + } if (vnc_display != -1) { + vnc_display_init(ds, vnc_display); } else { #if defined(CONFIG_SDL) sdl_display_init(ds, full_screen); diff --git a/vl.h b/vl.h index 4ae3d61..ddf8892 100644 --- a/vl.h +++ b/vl.h @@ -82,6 +82,13 @@ static inline char *realpath(const char *path, char *resolved_path) #define tostring(s) #s #endif +#ifndef MIN +#define MIN(a, b) (((a) < (b)) ? (a) : (b)) +#endif +#ifndef MAX +#define MAX(a, b) (((a) > (b)) ? (a) : (b)) +#endif + /* vl.c */ uint64_t muldiv64(uint64_t a, uint32_t b, uint32_t c); @@ -672,9 +679,12 @@ struct DisplayState { int depth; int width; int height; + void *opaque; + void (*dpy_update)(struct DisplayState *s, int x, int y, int w, int h); void (*dpy_resize)(struct DisplayState *s, int w, int h); void (*dpy_refresh)(struct DisplayState *s); + void (*dpy_copy)(struct DisplayState *s, int src_x, int src_y, int dst_x, int dst_y, int w, int h); }; static inline void dpy_update(DisplayState *s, int x, int y, int w, int h) @@ -703,6 +713,9 @@ void sdl_display_init(DisplayState *ds, int full_screen); /* cocoa.m */ void cocoa_display_init(DisplayState *ds, int full_screen); +/* vnc.c */ +void vnc_display_init(DisplayState *ds, int display); + /* ide.c */ #define MAX_DISKS 4 diff --git a/vnc.c b/vnc.c new file mode 100644 index 0000000..f461de6 --- /dev/null +++ b/vnc.c @@ -0,0 +1,894 @@ +#include "vl.h" + +#include +#include +#include +#include + +#define VNC_REFRESH_INTERVAL (1000 / 30) + +#include "vnc_keysym.h" +#include "keymaps.c" + +typedef struct Buffer +{ + size_t capacity; + size_t offset; + char *buffer; +} Buffer; + +typedef struct VncState VncState; + +typedef int VncReadEvent(VncState *vs, char *data, size_t len); + +struct VncState +{ + QEMUTimer *timer; + int lsock; + int csock; + DisplayState *ds; + int need_update; + int width; + int height; + uint64_t dirty_row[768]; + char *old_data; + int depth; + int has_resize; + int has_hextile; + Buffer output; + Buffer input; + kbd_layout_t *kbd_layout; + + VncReadEvent *read_handler; + size_t read_handler_expect; +}; + +/* TODO + 1) Get the queue working for IO. + 2) there is some weirdness when using the -S option (the screen is grey + and not totally invalidated + 3) resolutions > 1024 +*/ + +static void vnc_write(VncState *vs, const void *data, size_t len); +static void vnc_write_u32(VncState *vs, uint32_t value); +static void vnc_write_s32(VncState *vs, int32_t value); +static void vnc_write_u16(VncState *vs, uint16_t value); +static void vnc_write_u8(VncState *vs, uint8_t value); +static void vnc_flush(VncState *vs); +static void vnc_update_client(void *opaque); +static void vnc_client_read(void *opaque); + +static void vnc_dpy_update(DisplayState *ds, int x, int y, int w, int h) +{ + VncState *vs = ds->opaque; + int i; + + h += y; + + for (; y < h; y++) + for (i = 0; i < w; i += 16) + vs->dirty_row[y] |= (1ULL << ((x + i) / 16)); +} + +static void vnc_framebuffer_update(VncState *vs, int x, int y, int w, int h, + int32_t encoding) +{ + vnc_write_u16(vs, x); + vnc_write_u16(vs, y); + vnc_write_u16(vs, w); + vnc_write_u16(vs, h); + + vnc_write_s32(vs, encoding); +} + +static void vnc_dpy_resize(DisplayState *ds, int w, int h) +{ + VncState *vs = ds->opaque; + + ds->data = realloc(ds->data, w * h * vs->depth); + vs->old_data = realloc(vs->old_data, w * h * vs->depth); + + if (ds->data == NULL || vs->old_data == NULL) { + fprintf(stderr, "vnc: memory allocation failed\n"); + exit(1); + } + + ds->depth = vs->depth * 8; + ds->width = w; + ds->height = h; + ds->linesize = w * vs->depth; + if (vs->csock != -1 && vs->has_resize) { + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); /* number of rects */ + vnc_framebuffer_update(vs, 0, 0, ds->width, ds->height, -223); + vnc_flush(vs); + vs->width = ds->width; + vs->height = ds->height; + } +} + +static void send_framebuffer_update_raw(VncState *vs, int x, int y, int w, int h) +{ + int i; + char *row; + + vnc_framebuffer_update(vs, x, y, w, h, 0); + + row = vs->ds->data + y * vs->ds->linesize + x * vs->depth; + for (i = 0; i < h; i++) { + vnc_write(vs, row, w * vs->depth); + row += vs->ds->linesize; + } +} + +static void hextile_enc_cord(uint8_t *ptr, int x, int y, int w, int h) +{ + ptr[0] = ((x & 0x0F) << 4) | (y & 0x0F); + ptr[1] = (((w - 1) & 0x0F) << 4) | ((h - 1) & 0x0F); +} + +#define BPP 8 +#include "vnchextile.h" +#undef BPP + +#define BPP 16 +#include "vnchextile.h" +#undef BPP + +#define BPP 32 +#include "vnchextile.h" +#undef BPP + +static void send_framebuffer_update_hextile(VncState *vs, int x, int y, int w, int h) +{ + int i, j; + int has_fg, has_bg; + uint32_t last_fg32, last_bg32; + uint16_t last_fg16, last_bg16; + uint8_t last_fg8, last_bg8; + + vnc_framebuffer_update(vs, x, y, w, h, 5); + + has_fg = has_bg = 0; + for (j = y; j < (y + h); j += 16) { + for (i = x; i < (x + w); i += 16) { + switch (vs->depth) { + case 1: + send_hextile_tile_8(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j), + &last_bg8, &last_fg8, &has_bg, &has_fg); + break; + case 2: + send_hextile_tile_16(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j), + &last_bg16, &last_fg16, &has_bg, &has_fg); + break; + case 4: + send_hextile_tile_32(vs, i, j, MIN(16, x + w - i), MIN(16, y + h - j), + &last_bg32, &last_fg32, &has_bg, &has_fg); + break; + default: + break; + } + } + } +} + +static void send_framebuffer_update(VncState *vs, int x, int y, int w, int h) +{ + if (vs->has_hextile) + send_framebuffer_update_hextile(vs, x, y, w, h); + else + send_framebuffer_update_raw(vs, x, y, w, h); +} + +static void vnc_copy(DisplayState *ds, int src_x, int src_y, int dst_x, int dst_y, int w, int h) +{ + int src, dst; + char *src_row; + char *dst_row; + char *old_row; + int y = 0; + int pitch = ds->linesize; + VncState *vs = ds->opaque; + + vnc_update_client(vs); + + if (dst_y > src_y) { + y = h - 1; + pitch = -pitch; + } + + src = (ds->linesize * (src_y + y) + vs->depth * src_x); + dst = (ds->linesize * (dst_y + y) + vs->depth * dst_x); + + src_row = ds->data + src; + dst_row = ds->data + dst; + old_row = vs->old_data + dst; + + for (y = 0; y < h; y++) { + memmove(old_row, src_row, w * vs->depth); + memmove(dst_row, src_row, w * vs->depth); + src_row += pitch; + dst_row += pitch; + old_row += pitch; + } + + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + vnc_write_u16(vs, 1); /* number of rects */ + vnc_framebuffer_update(vs, dst_x, dst_y, w, h, 1); + vnc_write_u16(vs, src_x); + vnc_write_u16(vs, src_y); + vnc_flush(vs); +} + +static int find_dirty_height(VncState *vs, int y, int last_x, int x) +{ + int h; + + for (h = 1; h < (vs->height - y); h++) { + int tmp_x; + if (!(vs->dirty_row[y + h] & (1ULL << last_x))) + break; + for (tmp_x = last_x; tmp_x < x; tmp_x++) + vs->dirty_row[y + h] &= ~(1ULL << tmp_x); + } + + return h; +} + +static void vnc_update_client(void *opaque) +{ + VncState *vs = opaque; + + if (vs->need_update && vs->csock != -1) { + int y; + char *row; + char *old_row; + uint64_t width_mask; + int n_rectangles; + int saved_offset; + int has_dirty = 0; + + width_mask = (1ULL << (vs->width / 16)) - 1; + + if (vs->width == 1024) + width_mask = ~(0ULL); + + /* Walk through the dirty map and eliminate tiles that + really aren't dirty */ + row = vs->ds->data; + old_row = vs->old_data; + + for (y = 0; y < vs->height; y++) { + if (vs->dirty_row[y] & width_mask) { + int x; + char *ptr, *old_ptr; + + ptr = row; + old_ptr = old_row; + + for (x = 0; x < vs->ds->width; x += 16) { + if (memcmp(old_ptr, ptr, 16 * vs->depth) == 0) { + vs->dirty_row[y] &= ~(1ULL << (x / 16)); + } else { + has_dirty = 1; + memcpy(old_ptr, ptr, 16 * vs->depth); + } + + ptr += 16 * vs->depth; + old_ptr += 16 * vs->depth; + } + } + + row += vs->ds->linesize; + old_row += vs->ds->linesize; + } + + if (!has_dirty) { + qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); + return; + } + + /* Count rectangles */ + n_rectangles = 0; + vnc_write_u8(vs, 0); /* msg id */ + vnc_write_u8(vs, 0); + saved_offset = vs->output.offset; + vnc_write_u16(vs, 0); + + for (y = 0; y < vs->height; y++) { + int x; + int last_x = -1; + for (x = 0; x < vs->width / 16; x++) { + if (vs->dirty_row[y] & (1ULL << x)) { + if (last_x == -1) { + last_x = x; + } + vs->dirty_row[y] &= ~(1ULL << x); + } else { + if (last_x != -1) { + int h = find_dirty_height(vs, y, last_x, x); + send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); + n_rectangles++; + } + last_x = -1; + } + } + if (last_x != -1) { + int h = find_dirty_height(vs, y, last_x, x); + send_framebuffer_update(vs, last_x * 16, y, (x - last_x) * 16, h); + n_rectangles++; + } + } + vs->output.buffer[saved_offset] = (n_rectangles >> 8) & 0xFF; + vs->output.buffer[saved_offset + 1] = n_rectangles & 0xFF; + vnc_flush(vs); + + } + qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock) + VNC_REFRESH_INTERVAL); +} + +static void vnc_timer_init(VncState *vs) +{ + if (vs->timer == NULL) { + vs->timer = qemu_new_timer(rt_clock, vnc_update_client, vs); + qemu_mod_timer(vs->timer, qemu_get_clock(rt_clock)); + } +} + +static void vnc_dpy_refresh(DisplayState *ds) +{ + VncState *vs = ds->opaque; + vnc_timer_init(vs); + vga_hw_update(); +} + +static int vnc_listen_poll(void *opaque) +{ + VncState *vs = opaque; + if (vs->csock == -1) + return 1; + return 0; +} + +static void buffer_reserve(Buffer *buffer, size_t len) +{ + if ((buffer->capacity - buffer->offset) < len) { + buffer->capacity += (len + 1024); + buffer->buffer = realloc(buffer->buffer, buffer->capacity); + if (buffer->buffer == NULL) { + fprintf(stderr, "vnc: out of memory\n"); + exit(1); + } + } +} + +static int buffer_empty(Buffer *buffer) +{ + return buffer->offset == 0; +} + +static char *buffer_end(Buffer *buffer) +{ + return buffer->buffer + buffer->offset; +} + +static void buffer_reset(Buffer *buffer) +{ + buffer->offset = 0; +} + +static void buffer_append(Buffer *buffer, const void *data, size_t len) +{ + memcpy(buffer->buffer + buffer->offset, data, len); + buffer->offset += len; +} + +static int vnc_client_io_error(VncState *vs, int ret) +{ + if (ret == 0 || ret == -1) { + if (ret == -1 && (errno == EINTR || errno == EAGAIN)) + return 0; + + qemu_set_fd_handler2(vs->csock, NULL, NULL, NULL, NULL); + close(vs->csock); + vs->csock = -1; + buffer_reset(&vs->input); + buffer_reset(&vs->output); + vs->need_update = 0; + return 0; + } + return ret; +} + +static void vnc_client_error(VncState *vs) +{ + errno = EINVAL; + vnc_client_io_error(vs, -1); +} + +static void vnc_client_write(void *opaque) +{ + ssize_t ret; + VncState *vs = opaque; + + ret = write(vs->csock, vs->output.buffer, vs->output.offset); + ret = vnc_client_io_error(vs, ret); + if (!ret) + return; + + memmove(vs->output.buffer, vs->output.buffer + ret, (vs->output.offset - ret)); + vs->output.offset -= ret; + + if (vs->output.offset == 0) { + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, vs); + } +} + +static void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting) +{ + vs->read_handler = func; + vs->read_handler_expect = expecting; +} + +static void vnc_client_read(void *opaque) +{ + VncState *vs = opaque; + ssize_t ret; + + buffer_reserve(&vs->input, 4096); + + ret = read(vs->csock, buffer_end(&vs->input), 4096); + ret = vnc_client_io_error(vs, ret); + if (!ret) + return; + + vs->input.offset += ret; + + while (vs->read_handler && vs->input.offset >= vs->read_handler_expect) { + size_t len = vs->read_handler_expect; + int ret; + + ret = vs->read_handler(vs, vs->input.buffer, len); + if (vs->csock == -1) + return; + + if (!ret) { + memmove(vs->input.buffer, vs->input.buffer + len, (vs->input.offset - len)); + vs->input.offset -= len; + } else { + vs->read_handler_expect = ret; + } + } +} + +static void vnc_write(VncState *vs, const void *data, size_t len) +{ + buffer_reserve(&vs->output, len); + + if (buffer_empty(&vs->output)) { + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, vnc_client_write, vs); + } + + buffer_append(&vs->output, data, len); +} + +static void vnc_write_s32(VncState *vs, int32_t value) +{ + vnc_write_u32(vs, *(uint32_t *)&value); +} + +static void vnc_write_u32(VncState *vs, uint32_t value) +{ + uint8_t buf[4]; + + buf[0] = (value >> 24) & 0xFF; + buf[1] = (value >> 16) & 0xFF; + buf[2] = (value >> 8) & 0xFF; + buf[3] = value & 0xFF; + + vnc_write(vs, buf, 4); +} + +static void vnc_write_u16(VncState *vs, uint16_t value) +{ + char buf[2]; + + buf[0] = (value >> 8) & 0xFF; + buf[1] = value & 0xFF; + + vnc_write(vs, buf, 2); +} + +static void vnc_write_u8(VncState *vs, uint8_t value) +{ + vnc_write(vs, (char *)&value, 1); +} + +static void vnc_flush(VncState *vs) +{ + if (vs->output.offset) + vnc_client_write(vs); +} + +static uint8_t read_u8(char *data, size_t offset) +{ + return data[offset]; +} + +static uint16_t read_u16(char *data, size_t offset) +{ + return ((data[offset] & 0xFF) << 8) | (data[offset + 1] & 0xFF); +} + +static int32_t read_s32(char *data, size_t offset) +{ + return (int32_t)((data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]); +} + +static uint32_t read_u32(char *data, size_t offset) +{ + return ((data[offset] << 24) | (data[offset + 1] << 16) | + (data[offset + 2] << 8) | data[offset + 3]); +} + +static void client_cut_text(VncState *vs, size_t len, char *text) +{ +} + +static void pointer_event(VncState *vs, int button_mask, int x, int y) +{ + int buttons = 0; + int dz = 0; + + if (button_mask & 0x01) + buttons |= MOUSE_EVENT_LBUTTON; + if (button_mask & 0x02) + buttons |= MOUSE_EVENT_MBUTTON; + if (button_mask & 0x04) + buttons |= MOUSE_EVENT_RBUTTON; + if (button_mask & 0x08) + dz = -1; + if (button_mask & 0x10) + dz = 1; + + if (kbd_mouse_is_absolute()) { + kbd_mouse_event(x * 0x7FFF / vs->ds->width, + y * 0x7FFF / vs->ds->height, + dz, buttons); + } else { + static int last_x = -1; + static int last_y = -1; + + if (last_x != -1) + kbd_mouse_event(x - last_x, y - last_y, dz, buttons); + + last_x = x; + last_y = y; + } +} + +static void key_event(VncState *vs, int down, uint32_t sym) +{ + int keycode; + + keycode = keysym2scancode(vs->kbd_layout, sym & 0xFFFF); + + if (keycode & 0x80) + kbd_put_keycode(0xe0); + if (down) + kbd_put_keycode(keycode & 0x7f); + else + kbd_put_keycode(keycode | 0x80); +} + +static void framebuffer_update_request(VncState *vs, int incremental, + int x_position, int y_position, + int w, int h) +{ + int i; + vs->need_update = 1; + if (!incremental) { + char *old_row = vs->old_data + y_position * vs->ds->linesize; + + for (i = 0; i < h; i++) { + vs->dirty_row[y_position + i] = (1ULL << (vs->ds->width / 16)) - 1; + if (vs->ds->width == 1024) { + vs->dirty_row[y_position + i] = ~(0ULL); + } + memset(old_row, 42, vs->ds->width * vs->depth); + old_row += vs->ds->linesize; + } + } +} + +static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings) +{ + int i; + + vs->has_hextile = 0; + vs->has_resize = 0; + vs->ds->dpy_copy = NULL; + + for (i = n_encodings - 1; i >= 0; i--) { + switch (encodings[i]) { + case 0: /* Raw */ + vs->has_hextile = 0; + break; + case 1: /* CopyRect */ + vs->ds->dpy_copy = vnc_copy; + break; + case 5: /* Hextile */ + vs->has_hextile = 1; + break; + case -223: /* DesktopResize */ + vs->has_resize = 1; + break; + default: + break; + } + } +} + +static void set_pixel_format(VncState *vs, + int bits_per_pixel, int depth, + int big_endian_flag, int true_color_flag, + int red_max, int green_max, int blue_max, + int red_shift, int green_shift, int blue_shift) +{ + switch (bits_per_pixel) { + case 32: + case 24: + vs->depth = 4; + break; + case 16: + vs->depth = 2; + break; + case 8: + vs->depth = 1; + break; + default: + vnc_client_error(vs); + break; + } + + if (!true_color_flag) + vnc_client_error(vs); + + vnc_dpy_resize(vs->ds, vs->ds->width, vs->ds->height); + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + memset(vs->old_data, 42, vs->ds->linesize * vs->ds->height); + + vga_hw_invalidate(); + vga_hw_update(); +} + +static int protocol_client_msg(VncState *vs, char *data, size_t len) +{ + int i; + uint16_t limit; + + switch (data[0]) { + case 0: + if (len == 1) + return 20; + + set_pixel_format(vs, read_u8(data, 4), read_u8(data, 5), + read_u8(data, 6), read_u8(data, 7), + read_u16(data, 8), read_u16(data, 10), + read_u16(data, 12), read_u8(data, 14), + read_u8(data, 15), read_u8(data, 16)); + break; + case 2: + if (len == 1) + return 4; + + if (len == 4) + return 4 + (read_u16(data, 2) * 4); + + limit = read_u16(data, 2); + for (i = 0; i < limit; i++) { + int32_t val = read_s32(data, 4 + (i * 4)); + memcpy(data + 4 + (i * 4), &val, sizeof(val)); + } + + set_encodings(vs, (int32_t *)(data + 4), limit); + break; + case 3: + if (len == 1) + return 10; + + framebuffer_update_request(vs, + read_u8(data, 1), read_u16(data, 2), read_u16(data, 4), + read_u16(data, 6), read_u16(data, 8)); + break; + case 4: + if (len == 1) + return 8; + + key_event(vs, read_u8(data, 1), read_u32(data, 4)); + break; + case 5: + if (len == 1) + return 6; + + pointer_event(vs, read_u8(data, 1), read_u16(data, 2), read_u16(data, 4)); + break; + case 6: + if (len == 1) + return 8; + + if (len == 8) + return 8 + read_u32(data, 4); + + client_cut_text(vs, read_u32(data, 4), data + 8); + break; + default: + printf("Msg: %d\n", data[0]); + vnc_client_error(vs); + break; + } + + vnc_read_when(vs, protocol_client_msg, 1); + return 0; +} + +static int protocol_client_init(VncState *vs, char *data, size_t len) +{ + char pad[3] = { 0, 0, 0 }; + + vs->width = vs->ds->width; + vs->height = vs->ds->height; + vnc_write_u16(vs, vs->ds->width); + vnc_write_u16(vs, vs->ds->height); + + vnc_write_u8(vs, vs->depth * 8); /* bits-per-pixel */ + vnc_write_u8(vs, vs->depth * 8); /* depth */ + vnc_write_u8(vs, 0); /* big-endian-flag */ + vnc_write_u8(vs, 1); /* true-color-flag */ + if (vs->depth == 4) { + vnc_write_u16(vs, 0xFF); /* red-max */ + vnc_write_u16(vs, 0xFF); /* green-max */ + vnc_write_u16(vs, 0xFF); /* blue-max */ + vnc_write_u8(vs, 16); /* red-shift */ + vnc_write_u8(vs, 8); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + } else if (vs->depth == 2) { + vnc_write_u16(vs, 31); /* red-max */ + vnc_write_u16(vs, 63); /* green-max */ + vnc_write_u16(vs, 31); /* blue-max */ + vnc_write_u8(vs, 11); /* red-shift */ + vnc_write_u8(vs, 5); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + } else if (vs->depth == 1) { + vnc_write_u16(vs, 3); /* red-max */ + vnc_write_u16(vs, 7); /* green-max */ + vnc_write_u16(vs, 3); /* blue-max */ + vnc_write_u8(vs, 5); /* red-shift */ + vnc_write_u8(vs, 2); /* green-shift */ + vnc_write_u8(vs, 0); /* blue-shift */ + } + + vnc_write(vs, pad, 3); /* padding */ + + vnc_write_u32(vs, 4); + vnc_write(vs, "QEMU", 4); + vnc_flush(vs); + + vnc_read_when(vs, protocol_client_msg, 1); + + return 0; +} + +static int protocol_version(VncState *vs, char *version, size_t len) +{ + char local[13]; + int maj, min; + + memcpy(local, version, 12); + local[12] = 0; + + if (sscanf(local, "RFB %03d.%03d\n", &maj, &min) != 2) { + vnc_client_error(vs); + return 0; + } + + vnc_write_u32(vs, 1); /* None */ + vnc_flush(vs); + + vnc_read_when(vs, protocol_client_init, 1); + + return 0; +} + +static void vnc_listen_read(void *opaque) +{ + VncState *vs = opaque; + struct sockaddr_in addr; + socklen_t addrlen = sizeof(addr); + + vs->csock = accept(vs->lsock, (struct sockaddr *)&addr, &addrlen); + if (vs->csock != -1) { + fcntl(vs->csock, F_SETFL, O_NONBLOCK); + qemu_set_fd_handler2(vs->csock, NULL, vnc_client_read, NULL, opaque); + vnc_write(vs, "RFB 003.003\n", 12); + vnc_flush(vs); + vnc_read_when(vs, protocol_version, 12); + memset(vs->old_data, 0, vs->ds->linesize * vs->ds->height); + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + vs->has_resize = 0; + vs->has_hextile = 0; + vs->ds->dpy_copy = NULL; + } +} + +void vnc_display_init(DisplayState *ds, int display) +{ + struct sockaddr_in addr; + int reuse_addr, ret; + VncState *vs; + + vs = qemu_mallocz(sizeof(VncState)); + if (!vs) + exit(1); + + ds->opaque = vs; + + vs->lsock = -1; + vs->csock = -1; + vs->depth = 4; + + vs->ds = ds; + + if (!keyboard_layout) + keyboard_layout = "en-us"; + + vs->kbd_layout = init_keyboard_layout(keyboard_layout); + if (!vs->kbd_layout) + exit(1); + + vs->lsock = socket(PF_INET, SOCK_STREAM, 0); + if (vs->lsock == -1) { + fprintf(stderr, "Could not create socket\n"); + exit(1); + } + + addr.sin_family = AF_INET; + addr.sin_port = htons(5900 + display); + memset(&addr.sin_addr, 0, sizeof(addr.sin_addr)); + + reuse_addr = 1; + ret = setsockopt(vs->lsock, SOL_SOCKET, SO_REUSEADDR, + &reuse_addr, sizeof(reuse_addr)); + if (ret == -1) { + fprintf(stderr, "setsockopt() failed\n"); + exit(1); + } + + if (bind(vs->lsock, (struct sockaddr *)&addr, sizeof(addr)) == -1) { + fprintf(stderr, "bind() failed\n"); + exit(1); + } + + if (listen(vs->lsock, 1) == -1) { + fprintf(stderr, "listen() failed\n"); + exit(1); + } + + ret = qemu_set_fd_handler2(vs->lsock, vnc_listen_poll, vnc_listen_read, NULL, vs); + if (ret == -1) { + exit(1); + } + + vs->ds->data = NULL; + vs->ds->dpy_update = vnc_dpy_update; + vs->ds->dpy_resize = vnc_dpy_resize; + vs->ds->dpy_refresh = vnc_dpy_refresh; + + memset(vs->dirty_row, 0xFF, sizeof(vs->dirty_row)); + + vnc_dpy_resize(vs->ds, 640, 400); +} diff --git a/vnc_keysym.h b/vnc_keysym.h new file mode 100644 index 0000000..a4ac688 --- /dev/null +++ b/vnc_keysym.h @@ -0,0 +1,275 @@ +typedef struct { + const char* name; + int keysym; +} name2keysym_t; +static name2keysym_t name2keysym[]={ +/* ascii */ + { "space", 0x020}, + { "exclam", 0x021}, + { "quotedbl", 0x022}, + { "numbersign", 0x023}, + { "dollar", 0x024}, + { "percent", 0x025}, + { "ampersand", 0x026}, + { "apostrophe", 0x027}, + { "parenleft", 0x028}, + { "parenright", 0x029}, + { "asterisk", 0x02a}, + { "plus", 0x02b}, + { "comma", 0x02c}, + { "minus", 0x02d}, + { "period", 0x02e}, + { "slash", 0x02f}, + { "0", 0x030}, + { "1", 0x031}, + { "2", 0x032}, + { "3", 0x033}, + { "4", 0x034}, + { "5", 0x035}, + { "6", 0x036}, + { "7", 0x037}, + { "8", 0x038}, + { "9", 0x039}, + { "colon", 0x03a}, + { "semicolon", 0x03b}, + { "less", 0x03c}, + { "equal", 0x03d}, + { "greater", 0x03e}, + { "question", 0x03f}, + { "at", 0x040}, + { "A", 0x041}, + { "B", 0x042}, + { "C", 0x043}, + { "D", 0x044}, + { "E", 0x045}, + { "F", 0x046}, + { "G", 0x047}, + { "H", 0x048}, + { "I", 0x049}, + { "J", 0x04a}, + { "K", 0x04b}, + { "L", 0x04c}, + { "M", 0x04d}, + { "N", 0x04e}, + { "O", 0x04f}, + { "P", 0x050}, + { "Q", 0x051}, + { "R", 0x052}, + { "S", 0x053}, + { "T", 0x054}, + { "U", 0x055}, + { "V", 0x056}, + { "W", 0x057}, + { "X", 0x058}, + { "Y", 0x059}, + { "Z", 0x05a}, + { "bracketleft", 0x05b}, + { "backslash", 0x05c}, + { "bracketright", 0x05d}, + { "asciicircum", 0x05e}, + { "underscore", 0x05f}, + { "grave", 0x060}, + { "a", 0x061}, + { "b", 0x062}, + { "c", 0x063}, + { "d", 0x064}, + { "e", 0x065}, + { "f", 0x066}, + { "g", 0x067}, + { "h", 0x068}, + { "i", 0x069}, + { "j", 0x06a}, + { "k", 0x06b}, + { "l", 0x06c}, + { "m", 0x06d}, + { "n", 0x06e}, + { "o", 0x06f}, + { "p", 0x070}, + { "q", 0x071}, + { "r", 0x072}, + { "s", 0x073}, + { "t", 0x074}, + { "u", 0x075}, + { "v", 0x076}, + { "w", 0x077}, + { "x", 0x078}, + { "y", 0x079}, + { "z", 0x07a}, + { "braceleft", 0x07b}, + { "bar", 0x07c}, + { "braceright", 0x07d}, + { "asciitilde", 0x07e}, + +/* latin 1 extensions */ +{ "nobreakspace", 0x0a0}, +{ "exclamdown", 0x0a1}, +{ "cent", 0x0a2}, +{ "sterling", 0x0a3}, +{ "currency", 0x0a4}, +{ "yen", 0x0a5}, +{ "brokenbar", 0x0a6}, +{ "section", 0x0a7}, +{ "diaeresis", 0x0a8}, +{ "copyright", 0x0a9}, +{ "ordfeminine", 0x0aa}, +{ "guillemotleft", 0x0ab}, +{ "notsign", 0x0ac}, +{ "hyphen", 0x0ad}, +{ "registered", 0x0ae}, +{ "macron", 0x0af}, +{ "degree", 0x0b0}, +{ "plusminus", 0x0b1}, +{ "twosuperior", 0x0b2}, +{ "threesuperior", 0x0b3}, +{ "acute", 0x0b4}, +{ "mu", 0x0b5}, +{ "paragraph", 0x0b6}, +{ "periodcentered", 0x0b7}, +{ "cedilla", 0x0b8}, +{ "onesuperior", 0x0b9}, +{ "masculine", 0x0ba}, +{ "guillemotright", 0x0bb}, +{ "onequarter", 0x0bc}, +{ "onehalf", 0x0bd}, +{ "threequarters", 0x0be}, +{ "questiondown", 0x0bf}, +{ "Agrave", 0x0c0}, +{ "Aacute", 0x0c1}, +{ "Acircumflex", 0x0c2}, +{ "Atilde", 0x0c3}, +{ "Adiaeresis", 0x0c4}, +{ "Aring", 0x0c5}, +{ "AE", 0x0c6}, +{ "Ccedilla", 0x0c7}, +{ "Egrave", 0x0c8}, +{ "Eacute", 0x0c9}, +{ "Ecircumflex", 0x0ca}, +{ "Ediaeresis", 0x0cb}, +{ "Igrave", 0x0cc}, +{ "Iacute", 0x0cd}, +{ "Icircumflex", 0x0ce}, +{ "Idiaeresis", 0x0cf}, +{ "ETH", 0x0d0}, +{ "Eth", 0x0d0}, +{ "Ntilde", 0x0d1}, +{ "Ograve", 0x0d2}, +{ "Oacute", 0x0d3}, +{ "Ocircumflex", 0x0d4}, +{ "Otilde", 0x0d5}, +{ "Odiaeresis", 0x0d6}, +{ "multiply", 0x0d7}, +{ "Ooblique", 0x0d8}, +{ "Oslash", 0x0d8}, +{ "Ugrave", 0x0d9}, +{ "Uacute", 0x0da}, +{ "Ucircumflex", 0x0db}, +{ "Udiaeresis", 0x0dc}, +{ "Yacute", 0x0dd}, +{ "THORN", 0x0de}, +{ "Thorn", 0x0de}, +{ "ssharp", 0x0df}, +{ "agrave", 0x0e0}, +{ "aacute", 0x0e1}, +{ "acircumflex", 0x0e2}, +{ "atilde", 0x0e3}, +{ "adiaeresis", 0x0e4}, +{ "aring", 0x0e5}, +{ "ae", 0x0e6}, +{ "ccedilla", 0x0e7}, +{ "egrave", 0x0e8}, +{ "eacute", 0x0e9}, +{ "ecircumflex", 0x0ea}, +{ "ediaeresis", 0x0eb}, +{ "igrave", 0x0ec}, +{ "iacute", 0x0ed}, +{ "icircumflex", 0x0ee}, +{ "idiaeresis", 0x0ef}, +{ "eth", 0x0f0}, +{ "ntilde", 0x0f1}, +{ "ograve", 0x0f2}, +{ "oacute", 0x0f3}, +{ "ocircumflex", 0x0f4}, +{ "otilde", 0x0f5}, +{ "odiaeresis", 0x0f6}, +{ "division", 0x0f7}, +{ "oslash", 0x0f8}, +{ "ooblique", 0x0f8}, +{ "ugrave", 0x0f9}, +{ "uacute", 0x0fa}, +{ "ucircumflex", 0x0fb}, +{ "udiaeresis", 0x0fc}, +{ "yacute", 0x0fd}, +{ "thorn", 0x0fe}, +{ "ydiaeresis", 0x0ff}, +{"EuroSign", 0x20ac}, /* XK_EuroSign */ + + /* modifiers */ +{"Control_L", 0xffe3}, /* XK_Control_L */ +{"Control_R", 0xffe4}, /* XK_Control_R */ +{"Alt_L", 0xffe9}, /* XK_Alt_L */ +{"Alt_R", 0xffea}, /* XK_Alt_R */ +{"Caps_Lock", 0xffe5}, /* XK_Caps_Lock */ +{"Meta_L", 0xffe7}, /* XK_Meta_L */ +{"Meta_R", 0xffe8}, /* XK_Meta_R */ +{"Shift_L", 0xffe1}, /* XK_Shift_L */ +{"Shift_R", 0xffe2}, /* XK_Shift_R */ +{"Super_L", 0xffeb}, /* XK_Super_L */ +{"Super_R", 0xffec}, /* XK_Super_R */ + + /* special keys */ +{"BackSpace", 0xff08}, /* XK_BackSpace */ +{"Tab", 0xff09}, /* XK_Tab */ +{"Return", 0xff0d}, /* XK_Return */ +{"Right", 0xff53}, /* XK_Right */ +{"Left", 0xff51}, /* XK_Left */ +{"Up", 0xff52}, /* XK_Up */ +{"Down", 0xff54}, /* XK_Down */ +{"Page_Down", 0xff56}, /* XK_Page_Down */ +{"Page_Up", 0xff55}, /* XK_Page_Up */ +{"Insert", 0xff63}, /* XK_Insert */ +{"Delete", 0xffff}, /* XK_Delete */ +{"Home", 0xff50}, /* XK_Home */ +{"End", 0xff57}, /* XK_End */ +{"Scroll_Lock", 0xff14}, /* XK_Scroll_Lock */ +{"F1", 0xffbe}, /* XK_F1 */ +{"F2", 0xffbf}, /* XK_F2 */ +{"F3", 0xffc0}, /* XK_F3 */ +{"F4", 0xffc1}, /* XK_F4 */ +{"F5", 0xffc2}, /* XK_F5 */ +{"F6", 0xffc3}, /* XK_F6 */ +{"F7", 0xffc4}, /* XK_F7 */ +{"F8", 0xffc5}, /* XK_F8 */ +{"F9", 0xffc6}, /* XK_F9 */ +{"F10", 0xffc7}, /* XK_F10 */ +{"F11", 0xffc8}, /* XK_F11 */ +{"F12", 0xffc9}, /* XK_F12 */ +{"F13", 0xffca}, /* XK_F13 */ +{"F14", 0xffcb}, /* XK_F14 */ +{"F15", 0xffcc}, /* XK_F15 */ +{"Sys_Req", 0xff15}, /* XK_Sys_Req */ +{"KP_0", 0xffb0}, /* XK_KP_0 */ +{"KP_1", 0xffb1}, /* XK_KP_1 */ +{"KP_2", 0xffb2}, /* XK_KP_2 */ +{"KP_3", 0xffb3}, /* XK_KP_3 */ +{"KP_4", 0xffb4}, /* XK_KP_4 */ +{"KP_5", 0xffb5}, /* XK_KP_5 */ +{"KP_6", 0xffb6}, /* XK_KP_6 */ +{"KP_7", 0xffb7}, /* XK_KP_7 */ +{"KP_8", 0xffb8}, /* XK_KP_8 */ +{"KP_9", 0xffb9}, /* XK_KP_9 */ +{"KP_Add", 0xffab}, /* XK_KP_Add */ +{"KP_Decimal", 0xffae}, /* XK_KP_Decimal */ +{"KP_Divide", 0xffaf}, /* XK_KP_Divide */ +{"KP_Enter", 0xff8d}, /* XK_KP_Enter */ +{"KP_Equal", 0xffbd}, /* XK_KP_Equal */ +{"KP_Multiply", 0xffaa}, /* XK_KP_Multiply */ +{"KP_Subtract", 0xffad}, /* XK_KP_Subtract */ +{"help", 0xff6a}, /* XK_Help */ +{"Menu", 0xff67}, /* XK_Menu */ +{"Print", 0xff61}, /* XK_Print */ +{"Mode_switch", 0xff7e}, /* XK_Mode_switch */ +{"Num_Lock", 0xff7f}, /* XK_Num_Lock */ +{"Pause", 0xff13}, /* XK_Pause */ +{"Escape", 0xff1b}, /* XK_Escape */ +{0,0}, +}; diff --git a/vnchextile.h b/vnchextile.h new file mode 100644 index 0000000..7277670 --- /dev/null +++ b/vnchextile.h @@ -0,0 +1,189 @@ +#define CONCAT_I(a, b) a ## b +#define CONCAT(a, b) CONCAT_I(a, b) +#define pixel_t CONCAT(uint, CONCAT(BPP, _t)) + +static void CONCAT(send_hextile_tile_, BPP)(VncState *vs, + int x, int y, int w, int h, + pixel_t *last_bg, pixel_t *last_fg, + int *has_bg, int *has_fg) +{ + char *row = (vs->ds->data + y * vs->ds->linesize + x * vs->depth); + pixel_t *irow = (pixel_t *)row; + int j, i; + pixel_t bg = 0; + pixel_t fg = 0; + int n_colors = 0; + int bg_count = 0; + int fg_count = 0; + int flags = 0; + uint8_t data[(sizeof(pixel_t) + 2) * 16 * 16]; + int n_data = 0; + int n_subtiles = 0; + + for (j = 0; j < h; j++) { + for (i = 0; i < w; i++) { + switch (n_colors) { + case 0: + bg = irow[i]; + n_colors = 1; + break; + case 1: + if (irow[i] != bg) { + fg = irow[i]; + n_colors = 2; + } + break; + case 2: + if (irow[i] != bg && irow[i] != fg) { + n_colors = 3; + } else { + if (irow[i] == bg) + bg_count++; + else if (irow[i] == fg) + fg_count++; + } + break; + default: + break; + } + } + if (n_colors > 2) + break; + irow += vs->ds->linesize / sizeof(pixel_t); + } + + if (n_colors > 1 && fg_count > bg_count) { + pixel_t tmp = fg; + fg = bg; + bg = tmp; + } + + if (!*has_bg || *last_bg != bg) { + flags |= 0x02; + *has_bg = 1; + *last_bg = bg; + } + + if (!*has_fg || *last_fg != fg) { + flags |= 0x04; + *has_fg = 1; + *last_fg = fg; + } + + switch (n_colors) { + case 1: + n_data = 0; + break; + case 2: + flags |= 0x08; + + irow = (pixel_t *)row; + + for (j = 0; j < h; j++) { + int min_x = -1; + for (i = 0; i < w; i++) { + if (irow[i] == fg) { + if (min_x == -1) + min_x = i; + } else if (min_x != -1) { + hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); + n_data += 2; + n_subtiles++; + min_x = -1; + } + } + if (min_x != -1) { + hextile_enc_cord(data + n_data, min_x, j, i - min_x, 1); + n_data += 2; + n_subtiles++; + } + irow += vs->ds->linesize / sizeof(pixel_t); + } + break; + case 3: + flags |= 0x18; + + irow = (pixel_t *)row; + + if (!*has_bg || *last_bg != bg) + flags |= 0x02; + + for (j = 0; j < h; j++) { + int has_color = 0; + int min_x = -1; + pixel_t color; + + for (i = 0; i < w; i++) { + if (!has_color) { + if (irow[i] == bg) + continue; + color = irow[i]; + min_x = i; + has_color = 1; + } else if (irow[i] != color) { + has_color = 0; + + memcpy(data + n_data, &color, sizeof(color)); + hextile_enc_cord(data + n_data + sizeof(pixel_t), min_x, j, i - min_x, 1); + n_data += 2 + sizeof(pixel_t); + n_subtiles++; + + min_x = -1; + if (irow[i] != bg) { + color = irow[i]; + min_x = i; + has_color = 1; + } + } + } + if (has_color) { + memcpy(data + n_data, &color, sizeof(color)); + hextile_enc_cord(data + n_data + sizeof(pixel_t), min_x, j, i - min_x, 1); + n_data += 2 + sizeof(pixel_t); + n_subtiles++; + } + irow += vs->ds->linesize / sizeof(pixel_t); + } + + /* A SubrectsColoured subtile invalidates the foreground color */ + *has_fg = 0; + if (n_data > (w * h * sizeof(pixel_t))) { + n_colors = 4; + flags = 0x01; + *has_bg = 0; + + /* we really don't have to invalidate either the bg or fg + but we've lost the old values. oh well. */ + } + default: + break; + } + + if (n_colors > 3) { + flags = 0x01; + *has_fg = 0; + *has_bg = 0; + n_colors = 4; + } + + vnc_write_u8(vs, flags); + if (n_colors < 4) { + if (flags & 0x02) + vnc_write(vs, last_bg, sizeof(pixel_t)); + if (flags & 0x04) + vnc_write(vs, last_fg, sizeof(pixel_t)); + if (n_subtiles) { + vnc_write_u8(vs, n_subtiles); + vnc_write(vs, data, n_data); + } + } else { + for (j = 0; j < h; j++) { + vnc_write(vs, row, w * vs->depth); + row += vs->ds->linesize; + } + } +} + +#undef pixel_t +#undef CONCAT_I +#undef CONCAT -- 2.7.4