You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
524 lines
14 KiB
524 lines
14 KiB
/*
|
|
* 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 <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 / 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();
|
|
}
|