From 5a5cfbe24c307c842a736ac222307f8f63950296 Mon Sep 17 00:00:00 2001 From: Christian Beier Date: Sat, 23 Jul 2011 17:56:05 +0200 Subject: [PATCH] Add androidvncserver example. --- examples/android/README | 54 +++ examples/android/jni/Android.mk | 65 ++++ examples/android/jni/fbvncserver.c | 518 +++++++++++++++++++++++++++++ 3 files changed, 637 insertions(+) create mode 100644 examples/android/README create mode 100644 examples/android/jni/Android.mk create mode 100644 examples/android/jni/fbvncserver.c diff --git a/examples/android/README b/examples/android/README new file mode 100644 index 0000000..5baea22 --- /dev/null +++ b/examples/android/README @@ -0,0 +1,54 @@ + +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/. + + +Building with autotools +----------------------- + +This has the advantage that the LibVNCServer sources are properly set up +using the configure script. + +1. Read /docs/STANDALONE-TOOLCHAIN.html. + +2. Setup your toolchain according to step 3 in the above file. + +3. Execute + + ./configure --host=arm-eabi CC=arm-linux-androideabi-gcc + + in the LibVNCServer root directory. + +4. Execute + + make + + in the LibVNCServer root directory. This will build the whole + LibVNCServer distribution for Android, including androidvncserver. + + + + +Building with the NDK build system +---------------------------------- + +This is probably easier than the autotools method, but you'll have to edit +some files manually. + +1. Edit rfb/rfbconfig.h to match your Android target. For instance, comment out + LIBVNCSERVER_HAVE_LIBJPEG if you don't have libjpeg for Android. + +2. Edit the HAVE_X variables in jni/Android.mk accordingly. + +3. Execute + + ndk-build -C . + + in the examples/android directory. The resulting binary will be in libs/. + + + diff --git a/examples/android/jni/Android.mk b/examples/android/jni/Android.mk new file mode 100644 index 0000000..731a790 --- /dev/null +++ b/examples/android/jni/Android.mk @@ -0,0 +1,65 @@ +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LIBVNCSERVER_ROOT:=../../.. + +HAVE_LIBZ=1 +#HAVE_LIBJPEG=1 + +ifdef HAVE_LIBZ +ZLIBSRCS := \ + $(LIBVNCSERVER_ROOT)/libvncserver/zlib.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/zrle.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/zrleoutstream.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/zrlepalettehelper.c \ + $(LIBVNCSERVER_ROOT)/common/zywrletemplate.c +ifdef HAVE_LIBJPEG +TIGHTSRCS := $(LIBVNCSERVER_ROOT)/libvncserver/tight.c +endif +endif + +LOCAL_SRC_FILES:= \ + fbvncserver.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/main.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/rfbserver.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/rfbregion.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/auth.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/sockets.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/stats.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/corre.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/hextile.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/rre.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/translate.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/cutpaste.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/httpd.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/cursor.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/font.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/draw.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/selbox.c \ + $(LIBVNCSERVER_ROOT)/common/d3des.c \ + $(LIBVNCSERVER_ROOT)/common/vncauth.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/cargs.c \ + $(LIBVNCSERVER_ROOT)/common/minilzo.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/ultra.c \ + $(LIBVNCSERVER_ROOT)/libvncserver/scale.c \ + $(ZLIBSRCS) \ + $(TIGHTSRCS) + +LOCAL_C_INCLUDES := \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/libvncserver \ + $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT)/common \ + $(LOCAL_PATH)/$(LIBVNCSERVER_ROOT) \ + external/jpeg + +ifdef HAVE_LIBZ +LOCAL_SHARED_LIBRARIES := libz +LOCAL_LDLIBS := -lz +endif +ifdef HAVE_LIBJPEG +LOCAL_STATIC_LIBRARIES := libjpeg +endif + +LOCAL_MODULE:= androidvncserver + +include $(BUILD_EXECUTABLE) diff --git a/examples/android/jni/fbvncserver.c b/examples/android/jni/fbvncserver.c new file mode 100644 index 0000000..dea4d85 --- /dev/null +++ b/examples/android/jni/fbvncserver.c @@ -0,0 +1,518 @@ +/* + * $Id$ + * + * 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 / 2); + 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 / 2); + assert(fbbuf != NULL); + + /* TODO: This assumes scrinfo.bits_per_pixel is 16. */ + vncscr = rfbGetScreen(&argc, argv, scrinfo.xres, scrinfo.yres, 5, 2, 2); + 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_SOFT1, KEY_SOFT2, 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 0x0003: scancode = KEY_CENTER; break; + case 0x0020: scancode = KEY_SPACE; break; + case 0x0023: scancode = KEY_SHARP; break; + case 0x0033: scancode = KEY_SHARP; 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 0x002A: scancode = KEY_STAR; 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 + 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"); +} + +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_kdb(); + cleanup_touch(); +}