Add androidvncserver example.

pull/1/head
Christian Beier 12 years ago
parent 488ad8a609
commit 5a5cfbe24c

@ -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 <NDK location>/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/.

@ -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)

@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/sysmacros.h> /* For makedev() */
#include <fcntl.h>
#include <linux/fb.h>
#include <linux/input.h>
#include <assert.h>
#include <errno.h>
/* 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();
}
Loading…
Cancel
Save