/* rfb.c : Remote Frame Buffer / VNC server implementation */ /* Jon Mayo - September 1, 2006 - PUBLIC DOMAIN */ /* History: * 2006-09-06 - first successful connection with uncompressed video */ /* Requirements: * + clients that connect must let server pick pixel format */ /* Bugs: * + sometimes sys_fd[].events is set to 0, then the connection get stuck */ /* TODO: * + make a lookup palette to translate framebuffer to the pixelformat * + remove the coloring format id code, it wasn't a good idea * + process SetPixelFormat client message * + bandwidth throttle * + translate BGR233 into other formats based on SetPixelFormat setting * + use a bitmap to mark non-updated areas * + process SetEncodings client message into client_encoding_flags * + support hextile encoding * + support zlib compression * + support cursor pseudo-encoding * + support DesktopSize pseudo-encoding * + support cut and paste fully * + make keysym macros/enum * + support palette; SetColourMapEntries * + make BGR233 and drawing functions its own library * */ #include #include #include #include #include #include "bo.h" #include "net.h" #define DEFAULT_RFB_VERSION "RFB 003.003\n" #define DEFAULT_RFB_VERSION_LEN 12 #ifdef NDEBUG #define DEBUG(...) #define DEBUG_ONLY(x) #else #define DEBUG(...) fprintf(stderr, __VA_ARGS__) #define DEBUG_ONLY(x) x #endif #define BGR233_BITS_PER_PIXEL 8 #define BGR233_DEPTH 8 #define BGR233_RED_BITS 3 #define BGR233_GREEN_BITS 3 #define BGR233_BLUE_BITS 2 #define BGR233_RED_SHIFT 0 #define BGR233_GREEN_SHIFT 3 #define BGR233_BLUE_SHIFT 6 #define BGR233_RED_MAX ((1<>(8-fmt##_RED_BITS))<>(8-fmt##_GREEN_BITS))<>(8-fmt##_BLUE_BITS))<bits_per_pixel=BGR233_BITS_PER_PIXEL; pf->depth=BGR233_DEPTH; pf->big_endian_flag=0; pf->true_color_flag=1; pf->red_max=BGR233_RED_MAX; pf->green_max=BGR233_GREEN_MAX; pf->blue_max=BGR233_BLUE_MAX; pf->red_shift=BGR233_RED_SHIFT; pf->green_shift=BGR233_GREEN_SHIFT; pf->blue_shift=BGR233_BLUE_SHIFT; } static void pixelformat_encode(struct rfb_pixelformat *pf, unsigned char buf[16]) { assert(pf!=NULL); assert(buf!=NULL); buf[0]=pf->bits_per_pixel; buf[1]=pf->depth; buf[2]=pf->big_endian_flag; buf[3]=pf->true_color_flag; WR_BE16(buf, 4, pf->red_max); WR_BE16(buf, 6, pf->blue_max); WR_BE16(buf, 8, pf->green_max); buf[10]=pf->red_shift; buf[11]=pf->blue_shift; buf[12]=pf->green_shift; buf[13]=buf[14]=buf[15]=0; /* pad */ } static void pixelformat_decode(unsigned char buf[16], struct rfb_pixelformat *pf) { assert(pf!=NULL); assert(buf!=NULL); pf->bits_per_pixel=buf[0]; pf->depth=buf[1]; pf->big_endian_flag=buf[2]; pf->true_color_flag=buf[3]; pf->red_max=RD_BE16(buf, 4); pf->green_max=RD_BE16(buf, 6); pf->blue_max=RD_BE16(buf, 8); pf->red_shift=buf[10]; pf->blue_shift=buf[11]; pf->green_shift=buf[12]; } static void pixelformat_dump(struct rfb_pixelformat *pf) { assert(pf!=NULL); fprintf(stderr, "PixelFormat: %p\n" "\tbits_per_pixel: %u\n" "\tdepth: %u\n" "\tbig_endian_flag: %u\n" "\ttrue_color_flag: %u\n" "\tred_max: %u\n" "\tgreen_max: %u\n" "\tblue_max: %u\n" "\tred_shift: %u\n" "\tgreen_shift: %u\n" "\tblue_shift: %u\n" , (void*)pf, pf->bits_per_pixel, pf->depth, pf->big_endian_flag, pf->true_color_flag, pf->red_max, pf->green_max, pf->blue_max, pf->red_shift, pf->blue_shift, pf->green_shift); } /* Sends the authorization type "public". requring no authentication */ static void rfb_info_auth_public(struct socket_info *si) { unsigned char hdr[] = { 0, 0, 0, 1, /* RFB auth public */ }; sockwrite(si, hdr, sizeof hdr); } /* Sends the security result. (not used with public auth) */ static void rfb_info_auth_securityresult(struct socket_info *si, int result) { unsigned char hdr[] = { 0, 0, 0, 0, }; WR_BE32(hdr, 0, result); sockwrite(si, hdr, sizeof hdr); } /* sends the server initialization to the client. * format is a BGR233 format */ static void rfb_info_initialize(struct socket_info *si) { struct rfb_info *ri=si->userdata; unsigned char hdr[4+16+4]; /* width, height, pixelformat, desktop_name_len */ unsigned desktop_name_len=strlen(ri->desktop_name); DEBUG("server initialization size: %d\n", sizeof hdr); WR_BE16(hdr, 0, ri->width); WR_BE16(hdr, 2, ri->height); pixelformat_encode(&ri->pixelformat, hdr+4); WR_BE32(hdr, 20, desktop_name_len); sockwrite(si, hdr, sizeof hdr); sockwrite(si, ri->desktop_name, desktop_name_len); DEBUG_ONLY(pixelformat_dump(&ri->pixelformat)); } /* sends a FramebufferUpdate with CopyRect encoding */ static void rfb_info_copyrect(struct socket_info *si, int dx, int dy, int w, int h, int sy, int sx) { unsigned char hdr[] = { /* message header */ RFB_MSG_S_FRAMEBUFFERUPDATE, /* 00 - msg type */ 0, /* 01 - pad */ 0, 1, /* nr rects */ /* 02 - number of rects */ /* rect info */ 0, 0, /* 04 - x */ 0, 0, /* 06 - y */ 0, 0, /* 08 - w */ 0, 0, /* 10 - h */ 0, 0, 0, 1, /* 12 - type 1 = CopyRect */ 0, 0, /* 14 - src x */ 0, 0, /* 16 - src y */ }; /* TODO: update local framebuffer to match the copyrect */ WR_BE16(hdr, 4, dx); WR_BE16(hdr, 6, dy); WR_BE16(hdr, 8, w); WR_BE16(hdr, 10, h); WR_BE16(hdr, 14, sx); WR_BE16(hdr, 16, sy); sockwrite(si, hdr, sizeof hdr); } /* sends a FramebufferUpdate message with Raw encoding */ static void rfb_info_frameupdate(struct socket_info *si, int x, int y, int w, int h) { struct rfb_info *ri=si->userdata; unsigned char hdr[] = { /* message header */ RFB_MSG_S_FRAMEBUFFERUPDATE, /* 00 - msg type */ 0, /* 01 - pad */ 0, 1, /* nr rects */ /* 02 - number of rects */ /* rect info */ 0, 0, /* 04 - x */ 0, 0, /* 06 - y */ 0, 0, /* 08 - w */ 0, 0, /* 10 - h */ 0, 0, 0, 0, /* 12 - type = Raw */ /* framebuffer data follows... */ }; unsigned iy, mw, mh; assert(ri!=NULL); /* clamp values */ if(x<0) x=0; if(y<0) y=0; if(w<0) w=ri->width; if(h<0) h=ri->height; mw=x+w; mh=y+h; if(mw>ri->width) { mw=ri->width; w=mw-x; } if(mh>ri->height) { mh=ri->height; h=mh-y; } WR_BE16(hdr, 4, x); WR_BE16(hdr, 6, y); WR_BE16(hdr, 8, w); WR_BE16(hdr, 10, h); DEBUG("FramebufferUpdate: %ux%u @ %u,%u\n", RD_BE16(hdr, 8), RD_BE16(hdr, 10), RD_BE16(hdr, 4), RD_BE16(hdr, 6)); DEBUG("framebuffer update header size: %d\n", sizeof hdr); sockwrite(si, hdr, sizeof hdr); for(iy=y;yfb+x+y*ri->bytes_per_line, w); } } /* sends a ServerCutText message */ /* TODO: force LATIN-1 encoding */ static void rfb_info_servercuttext(struct socket_info *si, const char *text) { unsigned char hdr[] = { RFB_MSG_S_CUTTEXT, /* message type */ 0, 0, 0, /* padding */ 0, 0, 0, 0, /* length */ }; unsigned text_len; text_len=strlen(text); WR_BE32(hdr, 4, text_len); sockwrite(si, hdr, sizeof hdr); sockwrite(si, text, text_len); } /* send a Bell message */ static void rfb_info_bell(struct socket_info *si) { unsigned char pkt = RFB_MSG_S_BELL; sockwrite(si, &pkt, sizeof pkt); } static void rfb_sendcursor(struct socket_info *si) { unsigned char hdr[] = { /* message header */ RFB_MSG_S_FRAMEBUFFERUPDATE, /* 00 - msg type */ 0, /* 01 - pad */ 0, 1, /* nr rects */ /* 02 - number of rects */ /* rect info */ 0, 0, /* 04 - x - hotspot */ 0, 0, /* 06 - y - hotspot */ 0, 8, /* 08 - width*/ 0, 8, /* 10 - height */ 0xff, 0xff, 0xff, 0x11, /* 12 - type -239 = Cursor */ }; const unsigned char cursor[8*8] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, }; const unsigned char cursor_mask[(8+7)/8 * 8] = { 0xF8, 0xC0, 0xE0, 0xB0, 0x98, 0x0C, 0x06, 0x03, }; sockwrite(si, hdr, sizeof hdr); sockwrite(si, cursor, sizeof cursor); sockwrite(si, cursor_mask, sizeof cursor_mask); } static void draw_pattern(struct rfb_info *ri) { unsigned x,y; unsigned char *curr; for(y=0,curr=ri->fb;yheight;y++) { for(x=0;xwidth;x++) { *curr=x^y; curr++; } curr+=ri->bytes_per_line-x; } } static void draw_box(struct rfb_info *ri, int x, int y, int w, int h) { int mw, mh; int ix, iy; unsigned b; DEBUG("draw_box: %d,%d - %dx%d\n", x, y, w, h); b=255*((x&1)|((y&1)<<1))/3; /* blue value */ /* clamp values */ if(x<0) x=0; if(y<0) y=0; if(w<0) w=ri->width; if(h<0) h=ri->height; mw=x+w; mh=y+h; if(mw>ri->width) { mw=ri->width; w=mw-x; } if(mh>ri->height) { mh=ri->height; h=mh-y; } if(w<=1 || h<=1) return; /* colorful pattern */ for(iy=0;iyfb[(x+ix)+(y+iy)*ri->bytes_per_line]=RGB_TO_BGR233(255*ix/(w-1), 255*iy/(h-1), b); } } } static void rfb_info_clear(struct rfb_info *ri, unsigned char color) { unsigned x,y; unsigned char *curr; for(y=0,curr=ri->fb;yheight;y++) { for(x=0;xheight;x++) { *curr=color; curr++; } curr+=ri->bytes_per_line-x; } } static struct rfb_info *rfb_info_alloc(const char *desktop_name, unsigned width, unsigned height) { struct rfb_info *ri; ri=malloc(sizeof *ri); assert(ri!=NULL); ri->state=RFB_STATE_EXCHANGE_VERSION; ri->desktop_name=strdup(desktop_name); ri->width=width; ri->height=height; ri->bytes_per_line=width; ri->fb=malloc(ri->height * ri->bytes_per_line); ri->inbuf_len=0; ri->has_sent_full=0; ri->client_encoding_flags=0; pixelformat_init(&ri->pixelformat); assert(ri->fb!=NULL); draw_pattern(ri); return ri; } static void rfb_info_free(struct rfb_info *ri) { free(ri->desktop_name); free(ri->fb); free(ri); } static void consume_input(struct socket_info *si, unsigned amount) { struct rfb_info *ri=si->userdata; DEBUG("Consume: len=%d amount=%d\n", ri->inbuf_len, amount); ri->inbuf_len-=amount; if(ri->inbuf_len>0) memmove(ri->inbuf, ri->inbuf+amount, ri->inbuf_len); } static int process_message(struct socket_info *si) { struct rfb_info *ri=si->userdata; if(ri->inbuf_len<1) { return 0; } unsigned char message_type; message_type=ri->inbuf[0]; switch((enum rfb_client_message)message_type) { case RFB_MSG_C_SETPIXELFORMAT: /* 20 bytes */ if(ri->inbuf_len<20) return 0; else { struct rfb_pixelformat pf; /* TODO: we must process this */ DEBUG("SetPixelFormat: ignored\n"); pixelformat_decode(ri->inbuf+4, &pf); pixelformat_dump(&pf); consume_input(si, 20); } break; case RFB_MSG_C_SETENCODINGS: /* 4 bytes + 4 * nr_encodings */ /* TODO: load this into ri->client_encoding_flags */ if(ri->inbuf_len<4) return 0; else { unsigned short nr_encodings; int total_size; int i; nr_encodings=RD_BE16(ri->inbuf, 2); total_size=4+4*nr_encodings; DEBUG("SetEncodings: processing ... %d encodings (%d bytes)\n", nr_encodings, total_size); DEBUG("len: %d\n", ri->inbuf_len); if(ri->inbuf_leninbuf, 4 + 4*i)); } consume_input(si, total_size); } break; case RFB_MSG_C_FRAMEBUFFERUPDATEREQUEST: /* */ if(ri->inbuf_len<10) return 0; else { unsigned short x, y, w, h; int incremental_flag; incremental_flag=ri->inbuf[1]; x=RD_BE16(ri->inbuf, 2); y=RD_BE16(ri->inbuf, 4); w=RD_BE16(ri->inbuf, 6); h=RD_BE16(ri->inbuf, 8); consume_input(si, 10); DEBUG("FramebufferUpdateRequest: (%u,%u) %ux%u inc=%d\n", x, y, w, h, incremental_flag); /* TODO: do something with the inc flag */ /* this logic is weird. I don't like it */ if(!incremental_flag) { rfb_info_frameupdate(si, x, y, w, h); ri->has_sent_full=1; } else { if(!ri->has_sent_full) { rfb_info_frameupdate(si, x, y, w, h); ri->has_sent_full=1; } } } break; case RFB_MSG_C_POINTEREVENT: /* 6 bytes */ if(ri->inbuf_len<6) return 0; else { short x, y; unsigned char button; button=ri->inbuf[1]; x=RD_BE16(ri->inbuf, 2); y=RD_BE16(ri->inbuf, 4); consume_input(si, 6); DEBUG("Pointer Event: x=%d y=%d b=0x%x\n", x, y, button); if(button&1) { draw_box(ri, x-32, y-32, 64, 64); rfb_info_frameupdate(si, x-32, y-32, 64, 64); } } break; case RFB_MSG_C_KEYEVENT: /* 8 bytes */ if(ri->inbuf_len<8) return 0; else { unsigned char down_flag; unsigned key_sym; down_flag=ri->inbuf[1]; key_sym=RD_BE32(ri->inbuf, 4); DEBUG("KeyEvent: keysym=%04X state=%u\n", key_sym, down_flag); consume_input(si, 8); } break; case RFB_MSG_C_CUTTEXT: /* 8 + length bytes */ if(ri->inbuf_len<8) return 0; else { unsigned buf_len; buf_len=RD_BE32(ri->inbuf, 4); DEBUG("ClientCutText: processing ... %d characters\n", buf_len); DEBUG("len: %d\n", ri->inbuf_len); if(ri->inbuf_len<(buf_len+8)) return 0; DEBUG("ClientCutText: ignored %d characters\n", buf_len); consume_input(si, buf_len+8); } break; default: DEBUG("message_type unknown: 0x%X\n", message_type); /* we don't know how much to consume */ sockkill(si); return 0; } /* TODO: loop through buffer processing all full packets */ return 1; } static void rfb_process_inbuf(struct socket_info *si) { struct rfb_info *ri=si->userdata; again: if(!ri->inbuf_len) return; /* give up early */ switch(ri->state) { case RFB_STATE_EXCHANGE_VERSION: /* get client version */ if(ri->inbuf_len>=12) { DEBUG("state: EXCHANGE_VERSION\n"); DEBUG("client version: %.12s", ri->inbuf); consume_input(si, 12); rfb_info_auth_public(si); /* not needed for public: rfb_info_auth_securityresult(si,0); */ ri->state=RFB_STATE_CLIENT_INITIALIZATION; goto again; } break; case RFB_STATE_CLIENT_INITIALIZATION: /* get shared flag */ if(ri->inbuf_len>=1) { DEBUG("state: CLIENT_INITIALIZATION\n"); DEBUG("shared flag: %d\n", ri->inbuf[0]); consume_input(si, 1); rfb_info_initialize(si); /* TODO: only send when encoding set */ /* rfb_sendcursor(si); */ rfb_info_frameupdate(si, 0, 0, -1, -1); ri->state=RFB_STATE_MESSAGE_PROTOCOL; } case RFB_STATE_MESSAGE_PROTOCOL: if(process_message(si)) { goto again; } break; /* default: DEBUG("Unknown state %d\n", ri->state); abort(); */ } } static int dump_entry(void *p, struct addrinfo *ai, const char *addr, const char *serv) { fprintf(stderr, "family: %u\tsocktype: %u\tprotocol: %u\taddr: %s/%s\n", ai->ai_family, ai->ai_socktype, ai->ai_protocol, addr, serv); return 1; } static void data_handler(int fd, short ev, struct socket_info *si) { struct rfb_info *ri=si->userdata; size_t len; if(ev&NET_READ_READY) { len=read(fd, ri->inbuf, sizeof ri->inbuf-ri->inbuf_len); if(len<0) { /* close on an error */ perror("read()"); sockkill(si); return; } if(len==0) { sockkill(si); return; } ri->inbuf_len+=len; assert(ri->inbuf_len >= 0); assert(ri->inbuf_len <= sizeof ri->inbuf); if(ri->inbuf_len==sizeof ri->inbuf) { /* we cannot read anymore */ net_clear_event(si, NET_READ_READY); } DEBUG("fd=%d: %zu bytes read\n", fd, len); rfb_process_inbuf(si); /* rfb_info_auth_public(si); rfb_info_initialize(si); rfb_info_frameupdate(si, 0, 0, 64, 64); rfb_info_frameupdate(si, 64, 64, 64, 64); rfb_info_frameupdate(si, 0, 64, 64, 64); rfb_info_frameupdate(si, 64, 0, 64, 64); */ } if(ev&NET_WRITE_READY) { len=write(fd, si->output_buffer, si->output_len); if(len<0) { /* close on an error */ perror("write()"); sockkill(si); return; } DEBUG("fd=%d: %zu bytes written\n", fd, len); si->output_len-=len; if(si->output_len>0) memmove(si->output_buffer, si->output_buffer+len, si->output_len); else /* clear the write bit */ net_clear_event(si, POLLOUT); } } static void client_handler(void *p, int fd, const char *addr, const char *serv) { struct socket_info *si; si=socket_info_fd(fd); assert(si != NULL); fprintf(stderr, "client: %s %s\n", addr, serv); if(p) sockprintf(si, "%s\r\n", p); } static void connect_handler(void *p, int fd, const char *addr, const char *serv) { struct rfb_info *ri; struct socket_info *si; fprintf(stderr, "connect: %s %s\n", addr, serv); /* TODO: do something */ ri=rfb_info_alloc("RFB Demo", 800, 600); if(!(si=socket_insert(fd, NET_READ_READY, data_handler))) { rfb_info_free(ri); close(fd); /* failure */ return; } socket_info_setuserdata(si, ri, (void(*)(void*))rfb_info_free); socket_output_buffer_size(si, RFB_OUTPUT_BUF_LEN); sockwrite(si, DEFAULT_RFB_VERSION, DEFAULT_RFB_VERSION_LEN); } static void usage(const char *argv0) { printf("usage: %s [-p port] \n" " -p sets the port (default is 5900)\n" " -h help\n" , argv0); exit(EXIT_FAILURE); } int main(int argc, char **argv) { int c; const char *sport="5900"; while((c=getopt(argc, argv, "hp:"))!=-1) { switch(c) { case 'p': sport = optarg; break; case 'h': default: usage(argv[0]); } } net_bind(NULL, sport, "tcp", connect_handler, 0); while(net_get_nr_sockets()) net_poll(); return 0; }