/* * This example VNC server for Android is adopted from * http://code.google.com/p/android-vnc-server/ with some additional * fixes applied. * * To build, you'll need the Android Native Development Kit from * http://developer.android.com/sdk/ndk/. * * 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, 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. * * This project is an adaptation of the original fbvncserver for the iPAQ * and Zaurus. */ #include #include #include #include #include #include #include #include /* For makedev() */ #include #include #include #include #include /* libvncserver */ #include "rfb/rfb.h" #include "rfb/keysym.h" /*****************************************************************************/ /* Android does not use /dev/fb0. */ #define FB_DEVICE "/dev/graphics/fb0" static char KBD_DEVICE[256] = "/dev/input/event3"; static char TOUCH_DEVICE[256] = "/dev/input/event1"; static struct fb_var_screeninfo scrinfo; static int fbfd = -1; static int kbdfd = -1; static int touchfd = -1; static unsigned short int *fbmmap = MAP_FAILED; static unsigned short int *vncbuf; static unsigned short int *fbbuf; /* Android already has 5900 bound natively. */ #define VNC_PORT 5901 static rfbScreenInfoPtr vncscr; static int xmin, xmax; static int ymin, ymax; /* No idea, just copied from fbvncserver as part of the frame differerencing * algorithm. I will probably be later rewriting all of this. */ static struct varblock_t { int min_i; int min_j; int max_i; int max_j; int r_offset; int g_offset; int b_offset; int rfb_xres; int rfb_maxy; } varblock; /*****************************************************************************/ static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl); static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl); /*****************************************************************************/ static void init_fb(void) { size_t pixels; size_t bytespp; if ((fbfd = open(FB_DEVICE, O_RDONLY)) == -1) { printf("cannot open fb device %s\n", FB_DEVICE); exit(EXIT_FAILURE); } if (ioctl(fbfd, FBIOGET_VSCREENINFO, &scrinfo) != 0) { printf("ioctl error\n"); exit(EXIT_FAILURE); } pixels = scrinfo.xres * scrinfo.yres; bytespp = scrinfo.bits_per_pixel / 8; fprintf(stderr, "xres=%d, yres=%d, xresv=%d, yresv=%d, xoffs=%d, yoffs=%d, bpp=%d\n", (int)scrinfo.xres, (int)scrinfo.yres, (int)scrinfo.xres_virtual, (int)scrinfo.yres_virtual, (int)scrinfo.xoffset, (int)scrinfo.yoffset, (int)scrinfo.bits_per_pixel); fbmmap = mmap(NULL, pixels * bytespp, PROT_READ, MAP_SHARED, fbfd, 0); if (fbmmap == MAP_FAILED) { printf("mmap failed\n"); exit(EXIT_FAILURE); } } static void cleanup_fb(void) { if(fbfd != -1) { close(fbfd); } } static void init_kbd() { if((kbdfd = open(KBD_DEVICE, O_RDWR)) == -1) { printf("cannot open kbd device %s\n", KBD_DEVICE); exit(EXIT_FAILURE); } } static void cleanup_kbd() { if(kbdfd != -1) { close(kbdfd); } } static void init_touch() { struct input_absinfo info; if((touchfd = open(TOUCH_DEVICE, O_RDWR)) == -1) { printf("cannot open touch device %s\n", TOUCH_DEVICE); exit(EXIT_FAILURE); } // Get the Range of X and Y if(ioctl(touchfd, EVIOCGABS(ABS_X), &info)) { printf("cannot get ABS_X info, %s\n", strerror(errno)); exit(EXIT_FAILURE); } xmin = info.minimum; xmax = info.maximum; if(ioctl(touchfd, EVIOCGABS(ABS_Y), &info)) { printf("cannot get ABS_Y, %s\n", strerror(errno)); exit(EXIT_FAILURE); } ymin = info.minimum; ymax = info.maximum; } static void cleanup_touch() { if(touchfd != -1) { close(touchfd); } } /*****************************************************************************/ static void init_fb_server(int argc, char **argv) { printf("Initializing server...\n"); /* Allocate the VNC server buffer to be managed (not manipulated) by * libvncserver. */ vncbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 8); assert(vncbuf != NULL); /* Allocate the comparison buffer for detecting drawing updates from frame * to frame. */ fbbuf = calloc(scrinfo.xres * scrinfo.yres, scrinfo.bits_per_pixel / 8); assert(fbbuf != NULL); /* TODO: This assumes scrinfo.bits_per_pixel is 16. */ vncscr = rfbGetScreen(&argc, argv, scrinfo.xres, scrinfo.yres, 5, 2, (scrinfo.bits_per_pixel / 8)); assert(vncscr != NULL); vncscr->desktopName = "Android"; vncscr->frameBuffer = (char *)vncbuf; vncscr->alwaysShared = TRUE; vncscr->httpDir = NULL; vncscr->port = VNC_PORT; vncscr->kbdAddEvent = keyevent; vncscr->ptrAddEvent = ptrevent; rfbInitServer(vncscr); /* Mark as dirty since we haven't sent any updates at all yet. */ rfbMarkRectAsModified(vncscr, 0, 0, scrinfo.xres, scrinfo.yres); /* No idea. */ varblock.r_offset = scrinfo.red.offset + scrinfo.red.length - 5; varblock.g_offset = scrinfo.green.offset + scrinfo.green.length - 5; varblock.b_offset = scrinfo.blue.offset + scrinfo.blue.length - 5; varblock.rfb_xres = scrinfo.yres; varblock.rfb_maxy = scrinfo.xres - 1; } /*****************************************************************************/ void injectKeyEvent(uint16_t code, uint16_t value) { struct input_event ev; memset(&ev, 0, sizeof(ev)); gettimeofday(&ev.time,0); ev.type = EV_KEY; ev.code = code; ev.value = value; if(write(kbdfd, &ev, sizeof(ev)) < 0) { printf("write event failed, %s\n", strerror(errno)); } printf("injectKey (%d, %d)\n", code , value); } static int keysym2scancode(rfbBool down, rfbKeySym key, rfbClientPtr cl) { int scancode = 0; int code = (int)key; if (code>='0' && code<='9') { scancode = (code & 0xF) - 1; if (scancode<0) scancode += 10; scancode += KEY_1; } else if (code>=0xFF50 && code<=0xFF58) { static const uint16_t map[] = { KEY_HOME, KEY_LEFT, KEY_UP, KEY_RIGHT, KEY_DOWN, KEY_END, 0 }; scancode = map[code & 0xF]; } else if (code>=0xFFE1 && code<=0xFFEE) { static const uint16_t map[] = { KEY_LEFTSHIFT, KEY_LEFTSHIFT, KEY_COMPOSE, KEY_COMPOSE, KEY_LEFTSHIFT, KEY_LEFTSHIFT, 0,0, KEY_LEFTALT, KEY_RIGHTALT, 0, 0, 0, 0 }; scancode = map[code & 0xF]; } else if ((code>='A' && code<='Z') || (code>='a' && code<='z')) { static const uint16_t map[] = { KEY_A, KEY_B, KEY_C, KEY_D, KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z }; scancode = map[(code & 0x5F) - 'A']; } else { switch (code) { case 0x0020: scancode = KEY_SPACE; break; case 0x002C: scancode = KEY_COMMA; break; case 0x003C: scancode = KEY_COMMA; break; case 0x002E: scancode = KEY_DOT; break; case 0x003E: scancode = KEY_DOT; break; case 0x002F: scancode = KEY_SLASH; break; case 0x003F: scancode = KEY_SLASH; break; case 0x0032: scancode = KEY_EMAIL; break; case 0x0040: scancode = KEY_EMAIL; break; case 0xFF08: scancode = KEY_BACKSPACE; break; case 0xFF1B: scancode = KEY_BACK; break; case 0xFF09: scancode = KEY_TAB; break; case 0xFF0D: scancode = KEY_ENTER; break; case 0xFFBE: scancode = KEY_F1; break; // F1 case 0xFFBF: scancode = KEY_F2; break; // F2 case 0xFFC0: scancode = KEY_F3; break; // F3 case 0xFFC5: scancode = KEY_F4; break; // F8 case 0xFFC8: rfbShutdownServer(cl->screen,TRUE); break; // F11 } } return scancode; } static void keyevent(rfbBool down, rfbKeySym key, rfbClientPtr cl) { int scancode; printf("Got keysym: %04x (down=%d)\n", (unsigned int)key, (int)down); if ((scancode = keysym2scancode(down, key, cl))) { injectKeyEvent(scancode, down); } } void injectTouchEvent(int down, int x, int y) { struct input_event ev; // Calculate the final x and y /* Fake touch screen always reports zero */ if (xmin != 0 && xmax != 0 && ymin != 0 && ymax != 0) { x = xmin + (x * (xmax - xmin)) / (scrinfo.xres); y = ymin + (y * (ymax - ymin)) / (scrinfo.yres); } memset(&ev, 0, sizeof(ev)); // Then send a BTN_TOUCH gettimeofday(&ev.time,0); ev.type = EV_KEY; ev.code = BTN_TOUCH; ev.value = down; if(write(touchfd, &ev, sizeof(ev)) < 0) { printf("write event failed, %s\n", strerror(errno)); } // Then send the X gettimeofday(&ev.time,0); ev.type = EV_ABS; ev.code = ABS_X; ev.value = x; if(write(touchfd, &ev, sizeof(ev)) < 0) { printf("write event failed, %s\n", strerror(errno)); } // Then send the Y gettimeofday(&ev.time,0); ev.type = EV_ABS; ev.code = ABS_Y; ev.value = y; if(write(touchfd, &ev, sizeof(ev)) < 0) { printf("write event failed, %s\n", strerror(errno)); } // Finally send the SYN gettimeofday(&ev.time,0); ev.type = EV_SYN; ev.code = 0; ev.value = 0; if(write(touchfd, &ev, sizeof(ev)) < 0) { printf("write event failed, %s\n", strerror(errno)); } printf("injectTouchEvent (x=%d, y=%d, down=%d)\n", x , y, down); } static void ptrevent(int buttonMask, int x, int y, rfbClientPtr cl) { /* Indicates either pointer movement or a pointer button press or release. The pointer is now at (x-position, y-position), and the current state of buttons 1 to 8 are represented by bits 0 to 7 of button-mask respectively, 0 meaning up, 1 meaning down (pressed). On a conventional mouse, buttons 1, 2 and 3 correspond to the left, middle and right buttons on the mouse. On a wheel mouse, each step of the wheel upwards is represented by a press and release of button 4, and each step downwards is represented by a press and release of button 5. From: http://www.vislab.usyd.edu.au/blogs/index.php/2009/05/22/an-headerless-indexed-protocol-for-input-1?blog=61 */ //printf("Got ptrevent: %04x (x=%d, y=%d)\n", buttonMask, x, y); if(buttonMask & 1) { // Simulate left mouse event as touch event injectTouchEvent(1, x, y); injectTouchEvent(0, x, y); } } #define PIXEL_FB_TO_RFB(p,r,g,b) ((p>>r)&0x1f001f)|(((p>>g)&0x1f001f)<<5)|(((p>>b)&0x1f001f)<<10) static void update_screen(void) { unsigned int *f, *c, *r; int x, y; varblock.min_i = varblock.min_j = 9999; varblock.max_i = varblock.max_j = -1; f = (unsigned int *)fbmmap; /* -> framebuffer */ c = (unsigned int *)fbbuf; /* -> compare framebuffer */ r = (unsigned int *)vncbuf; /* -> remote framebuffer */ for (y = 0; y < scrinfo.yres; y++) { /* Compare every 2 pixels at a time, assuming that changes are likely * in pairs. */ for (x = 0; x < scrinfo.xres; x += 2) { unsigned int pixel = *f; if (pixel != *c) { *c = pixel; /* XXX: Undo the checkered pattern to test the efficiency * gain using hextile encoding. */ if (pixel == 0x18e320e4 || pixel == 0x20e418e3) pixel = 0x18e318e3; *r = PIXEL_FB_TO_RFB(pixel, varblock.r_offset, varblock.g_offset, varblock.b_offset); if (x < varblock.min_i) varblock.min_i = x; else { if (x > varblock.max_i) varblock.max_i = x; if (y > varblock.max_j) varblock.max_j = y; else if (y < varblock.min_j) varblock.min_j = y; } } f++, c++; r++; } } if (varblock.min_i < 9999) { if (varblock.max_i < 0) varblock.max_i = varblock.min_i; if (varblock.max_j < 0) varblock.max_j = varblock.min_j; fprintf(stderr, "Dirty page: %dx%d+%d+%d...\n", (varblock.max_i+2) - varblock.min_i, (varblock.max_j+1) - varblock.min_j, varblock.min_i, varblock.min_j); rfbMarkRectAsModified(vncscr, varblock.min_i, varblock.min_j, varblock.max_i + 2, varblock.max_j + 1); rfbProcessEvents(vncscr, 10000); } } /*****************************************************************************/ void print_usage(char **argv) { printf("%s [-k device] [-t device] [-h]\n" "-k device: keyboard device node, default is /dev/input/event3\n" "-t device: touch device node, default is /dev/input/event1\n" "-h : print this help\n", argv[0]); } int main(int argc, char **argv) { if(argc > 1) { int i=1; while(i < argc) { if(*argv[i] == '-') { switch(*(argv[i] + 1)) { case 'h': print_usage(argv); exit(0); break; case 'k': i++; strcpy(KBD_DEVICE, argv[i]); break; case 't': i++; strcpy(TOUCH_DEVICE, argv[i]); break; } } i++; } } printf("Initializing framebuffer device " FB_DEVICE "...\n"); init_fb(); printf("Initializing keyboard device %s ...\n", KBD_DEVICE); init_kbd(); printf("Initializing touch device %s ...\n", TOUCH_DEVICE); init_touch(); printf("Initializing VNC server:\n"); printf(" width: %d\n", (int)scrinfo.xres); printf(" height: %d\n", (int)scrinfo.yres); printf(" bpp: %d\n", (int)scrinfo.bits_per_pixel); printf(" port: %d\n", (int)VNC_PORT); init_fb_server(argc, argv); /* Implement our own event loop to detect changes in the framebuffer. */ while (1) { while (vncscr->clientHead == NULL) rfbProcessEvents(vncscr, 100000); rfbProcessEvents(vncscr, 100000); update_screen(); } printf("Cleaning up...\n"); cleanup_fb(); cleanup_kbd(); cleanup_touch(); }