diff --git a/README.md b/README.md index 68dfe18..0ce4096 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ with rotation: - [ ] 8 bit/pixel - [x] 16 bit/pixel - [ ] 24 bit/pixel -- [ ] 32 bit/pixel +- [x] 32 bit/pixel The code is based on a LibVNC example for Android: https://github.com/LibVNC/libvncserver/blob/master/examples/androidvncserver.c @@ -53,6 +53,9 @@ Using qmake: ./framebuffer-vncserver [-f device] [-p port] [-t touchscreen] [-m mouse] [-k keyboard] [-r rotation] [-R touchscreen rotation] [-F FPS] [-v] [-h] -p port: VNC port, default is 5900 + -a authentication: path to password file generated with 'storepasswd' + -A authentication: plain text password + -c cursor: shared memory name for cursor data -f device: framebuffer device node, default is /dev/fb0 -k device: keyboard device node (example: /dev/input/event0) -t device: touchscreen device node (example:/dev/input/event2) @@ -63,6 +66,78 @@ Using qmake: -v: verbose -h: print this help +### Handling the mouse cursor + +In case the app you're running on your server isn't relying on an X server (that is, it manipulates the framebuffer directly), you will encounter the absolute mouse coordinates problem. More specifically, if it is impossible for you to retrieve the mouse absolute coordinates with `EV_ABS` and `ABS_X`/`ABS_Y`, you will have to do the following: + +* Write your own mouse driver to keep track of the cursor's absolute coordinates +* Make the driver write the mouse coordinates in a shared memory of chosen name (e.g `/cursor_pos`) using this struct: +```cpp +typedef struct { + int x; + int y; +} CursorPosition; +``` +* Use the flag `-c` when starting the vnc server. + +Here is a practical example using [lvgl](https://github.com/lvgl/lvgl) to manipulate the framebuffer: + +```cpp +// ------------------------- +// Driver's code + +CursorPosition *cursor_shared_mem = NULL; + +// Initialize shared memory +int init_shared_cursor_mem() { + + int fd = shm_open("/cursor_pos", O_CREAT | O_RDWR, 0666); + + if(fd < 0) + { + fprintf(stderr, "%s(): error while opening shared memory : %s", __FUNCTION__, strerror(errno)); + } + else + { + ftruncate(fd, sizeof(CursorPosition)); + cursor_shared_mem = (CursorPosition*) mmap(0, sizeof(CursorPosition), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + } + return fd; +} + +// Custom driver +static void mouse_custom_driver(lv_indev_t * indev, lv_indev_data_t * data) +{ + // ... some custom computation ... + + // Writing the cursor position in the shared memory : + if (cursor_shared_mem) { + cursor_shared_mem->x = data->point.x; + cursor_shared_mem->y = data->point.y; + } +} + +int main() +{ + // .. some init code ... + init_shared_cursor_mem(); + lv_indev_t *touch = lv_indev_create(); + if(touch != nullptr){ + lv_indev_set_type(touch, LV_INDEV_TYPE_POINTER); + lv_indev_set_read_cb(touch, mouse_custom_driver); // attaching custom driver as read callback + lv_indev_set_display(touch, disp); + } + + // ... + return 0; +} +``` + +Invoking the vnc server: +```bash +framebuffer-vncserver -c /cursor_pos -m /dev/input/event0 ... +``` + ## Run on startup as service To run at startup as a service using systemd, edit the file `fbvnc.service` make sure the path and command line arguments are correct and then run: diff --git a/src/framebuffer-vncserver.c b/src/framebuffer-vncserver.c index b62ff20..f4087d6 100644 --- a/src/framebuffer-vncserver.c +++ b/src/framebuffer-vncserver.c @@ -54,6 +54,7 @@ static char fb_device[256] = "/dev/fb0"; static char touch_device[256] = ""; static char kbd_device[256] = ""; static char mouse_device[256] = ""; +static char cursor_pos[256] = ""; static struct fb_var_screeninfo var_scrinfo; static struct fb_fix_screeninfo fix_scrinfo; @@ -72,7 +73,19 @@ static unsigned int bits_per_pixel; static unsigned int frame_size; static unsigned int fb_xres; static unsigned int fb_yres; + +static int shared_cursor_data_fd; +typedef struct { + int x; + int y; +} CursorPosition; + +CursorPosition *cursor_shared_mem = NULL; + int verbose = 0; +/* Can be either a path to a password file or a plain text password. */ +static char *authData = NULL; +static rfbBool authByFile = TRUE; #define UNUSED(x) (void)(x) @@ -222,8 +235,69 @@ a press and release of button 5. injectMouseEvent(&var_scrinfo, buttonMask, x, y); } -/*****************************************************************************/ +static rfbBool rfbDefaultPasswordCheck(rfbClientPtr cl, const char* response, int len) +{ + int i; + char *passwd=rfbDecryptPasswdFromFile(cl->screen->authPasswdData); + + if(!passwd) { + rfbErr("Couldn't read password file: %s\n",cl->screen->authPasswdData); + return(FALSE); + } + + rfbEncryptBytes(cl->authChallenge, passwd); + + /* Lose the password from memory */ + for (i = strlen(passwd); i >= 0; i--) { + passwd[i] = '\0'; + } + + free(passwd); + + if (memcmp(cl->authChallenge, response, len) != 0) { + rfbErr("authProcessClientMessage: authentication failed from %s\n", + cl->host); + return(FALSE); + } + + return(TRUE); +} + +rfbBool rfbCheckPasswordByList(rfbClientPtr cl, const char* response, int len) +{ + char **passwds; + int i=0; + + for(passwds=(char**)cl->screen->authPasswdData;*passwds;passwds++,i++) { + uint8_t auth_tmp[CHALLENGESIZE]; + memcpy((char *)auth_tmp, (char *)cl->authChallenge, CHALLENGESIZE); + rfbEncryptBytes(auth_tmp, *passwds); + + if (memcmp(auth_tmp, response, len) == 0) { + if(i>=cl->screen->authPasswdFirstViewOnly) + cl->viewOnly=TRUE; + return(TRUE); + } + } + rfbErr("authProcessClientMessage: authentication failed from %s\n", + cl->host); + return(FALSE); +} + +enum rfbNewClientAction onClientHook(struct _rfbClientRec* cl) +{ + if(strlen(mouse_device)>0) + { + if(cursor_shared_mem) + { + init_mouse_pos(cursor_shared_mem->x, cursor_shared_mem->y); + } + injectMouseEvent(&var_scrinfo, 0, cl->cursorX, cl->cursorY); + } +} + +/*****************************************************************************/ static void init_fb_server(int argc, char **argv, rfbBool enable_touch, rfbBool enable_mouse) { info_print("Initializing server...\n"); @@ -250,6 +324,9 @@ static void init_fb_server(int argc, char **argv, rfbBool enable_touch, rfbBool server->alwaysShared = TRUE; server->httpDir = NULL; server->port = vnc_port; + server->passwordCheck = authByFile ? rfbDefaultPasswordCheck : rfbCheckPasswordByList; + server->authPasswdData = (void *)authData; + server->newClientHook = onClientHook; server->kbdAddEvent = keyevent; if (enable_touch) @@ -482,11 +559,25 @@ static void update_screen(void) } } } - else if (bits_per_pixel == 16) + else if(vnc_rotate > 0 && (bits_per_pixel == 16 || bits_per_pixel == 32)) { - uint16_t *f = (uint16_t *)fbmmap; /* -> framebuffer */ - uint16_t *c = (uint16_t *)fbbuf; /* -> compare framebuffer */ - uint16_t *r = (uint16_t *)vncbuf; /* -> remote framebuffer */ + union + { + uint16_t * val16b; + uint32_t * val32b; + } f, c, r; + + if (bits_per_pixel == 16) + { + f.val16b = (uint16_t *)fbmmap; /* -> framebuffer */ + c.val16b = (uint16_t *)fbbuf; /* -> compare framebuffer */ + r.val16b = (uint16_t *)vncbuf; /* -> remote framebuffer */ + } else if (bits_per_pixel == 32) + { + f.val32b = (uint32_t *)fbmmap; /* -> framebuffer */ + c.val32b = (uint32_t *)fbbuf; /* -> compare framebuffer */ + r.val32b = (uint32_t *)vncbuf; /* -> remote framebuffer */ + } switch (vnc_rotate) { @@ -514,13 +605,30 @@ static void update_screen(void) int x; for (x = 0; x < (int)fb_xres; x++) { - uint16_t pixel = *f; + union { + uint16_t val16b; + uint32_t val32b; + } pixel; - if (pixel != *c) + if (bits_per_pixel == 16) { - int x2, y2; + pixel.val16b = *f.val16b; + } else if (bits_per_pixel == 32) + { + pixel.val32b = *f.val32b; + } - *c = pixel; + if (bits_per_pixel == 16 && pixel.val16b != *c.val16b + || bits_per_pixel == 32 && pixel.val32b != *c.val32b) + { + int x2, y2; + if (bits_per_pixel == 16) + { + *c.val16b = pixel.val16b; + } else if (bits_per_pixel == 32) + { + *c.val32b = pixel.val32b; + } switch (vnc_rotate) { case 0: @@ -547,7 +655,13 @@ static void update_screen(void) exit(EXIT_FAILURE); } - r[y2 * server->width + x2] = PIXEL_FB_TO_RFB(pixel, varblock.r_offset, varblock.g_offset, varblock.b_offset); + if (bits_per_pixel == 16) + { + r.val16b[y2 * server->width + x2] = PIXEL_FB_TO_RFB(pixel.val16b, varblock.r_offset, varblock.g_offset, varblock.b_offset); + } else if (bits_per_pixel == 32) + { + r.val32b[y2 * server->width + x2] = PIXEL_FB_TO_RFB(pixel.val32b, varblock.r_offset, varblock.g_offset, varblock.b_offset); + } if (x2 < varblock.min_i) varblock.min_i = x2; @@ -563,8 +677,15 @@ static void update_screen(void) } } - f++; - c++; + if (bits_per_pixel == 16) + { + f.val16b++; + c.val16b++; + } else if (bits_per_pixel == 32) + { + f.val32b++; + c.val32b++; + } } } } @@ -592,12 +713,27 @@ static void update_screen(void) } } +int init_cursor_reader() { + int fd = shm_open(cursor_pos, O_RDONLY, 0666); + if (fd < 0) { + error_print("Error when trying to open %s: %s\n", cursor_pos, strerror(errno)); + exit(1); + } + + cursor_shared_mem = (CursorPosition*) mmap(0, sizeof(CursorPosition), PROT_READ, MAP_SHARED, fd, 0); + // close(fd); + return fd; +} + /*****************************************************************************/ void print_usage(char **argv) { info_print("%s [-f device] [-p port] [-t touchscreen] [-m mouse] [-k keyboard] [-r rotation] [-R touchscreen rotation] [-F FPS] [-v] [-h]\n" "-p port: VNC port, default is 5900\n" + "-a authentication: path to password file generated with 'storepasswd'\n" + "-A authentication: plain text password\n" + "-c cursor: shared memory name for cursor data\n" "-f device: framebuffer device node, default is /dev/fb0\n" "-k device: keyboard device node (example: /dev/input/event0)\n" "-t device: touchscreen device node (example:/dev/input/event2)\n" @@ -649,6 +785,31 @@ int main(int argc, char **argv) if (argv[i]) vnc_port = atoi(argv[i]); break; + case 'a': + i++; + if (argv[i]) + authData = argv[i]; + break; + case 'A': { + i++; + char **passwds = malloc(sizeof(char**)*2); + if (passwds && argv[i]) { + passwds[0] = argv[i]; + passwds[1] = NULL; + authByFile = FALSE; + authData = (char *)passwds; + } + break; + } + case 'c': + i++; + if (argv[i]) + { + info_print("Shared cursor data : %s\n", argv[i]); + strcpy(cursor_pos, argv[i]); + shared_cursor_data_fd = init_cursor_reader(); + } + break; case 'r': i++; if (argv[i]) @@ -706,7 +867,17 @@ int main(int argc, char **argv) { // init touch only if there is a mouse device defined int ret = init_mouse(mouse_device, touch_rotate); - enable_mouse = (ret > 0); + if(ret == 3) + { + info_print("Trying to init with relative coordinates... "); + ret = init_mouse_rel(fb_xres, fb_yres, mouse_device, touch_rotate); + if(!ret) + { + info_print("Successful\n"); + } + } + + enable_mouse = (!ret); } else { @@ -718,6 +889,7 @@ int main(int argc, char **argv) info_print(" height: %d\n", (int)fb_yres); info_print(" bpp: %d\n", (int)var_scrinfo.bits_per_pixel); info_print(" port: %d\n", (int)vnc_port); + info_print(" authentication mode: %s\n", authByFile ? "file" : "password"); info_print(" rotate: %d\n", (int)vnc_rotate); info_print(" mouse/touch rotate: %d\n", (int)touch_rotate); info_print(" target FPS: %d\n", (int)target_fps); diff --git a/src/mouse.c b/src/mouse.c index 854b09d..e4234c5 100644 --- a/src/mouse.c +++ b/src/mouse.c @@ -23,13 +23,10 @@ #include "logging.h" -static int mousefd = -1; - -static int xmin, xmax; -static int ymin, ymax; -static int rotate; -static int trkg_id = -1; -static bool is_wheel_hires = false; +#define CHECK_BIT(var,pos) ((var) & (1<<(pos))) +#define MAP(x) { #x,x } +#define WHEEL_UP 3 +#define WHEEL_DOWN 4 #ifndef input_event_sec #define input_event_sec time.tv_sec @@ -42,10 +39,26 @@ typedef struct const int value; } map_t; -#define CHECK_BIT(var,pos) ((var) & (1<<(pos))) -#define MAP(x) { #x,x } -#define WHEEL_UP 3 -#define WHEEL_DOWN 4 +static int mousefd = -1; + +static int xmin, xmax; +static int ymin, ymax; +static int rotate; +static int trkg_id = -1; +static bool is_wheel_hires = false; +static bool is_relative_coords = false; +static const map_t mouseButtonMap[] = { + MAP(BTN_LEFT), + MAP(BTN_MIDDLE), + MAP(BTN_RIGHT), +}; +static const int buttonsNumber = sizeof(mouseButtonMap) / sizeof(mouseButtonMap[0]); +static int last_buttonMask; + +// last_x and last_y should be initialized with the cursor coordinates at the moment of the server start-up. +static int last_x = 0; +static int last_y = 0; +static int wheel_tick; int init_mouse(const char *touch_device, int vnc_rotate) { @@ -59,13 +72,13 @@ int init_mouse(const char *touch_device, int vnc_rotate) if ((mousefd = open(touch_device, O_RDWR)) == -1) { error_print("cannot open mouse device %s\n", touch_device); - return 0; + return 1; } //REL_WHEEL_HI_RES if (ioctl(mousefd, EVIOCGBIT(EV_REL, sizeof(evtype_bitmask)), evtype_bitmask) < 0) { error_print("%s can't get evdev features: %s",touch_device, strerror(errno)); - return 0; + return 2; } #ifdef REL_WHEEL_HI_RES @@ -82,21 +95,42 @@ int init_mouse(const char *touch_device, int vnc_rotate) if (ioctl(mousefd, EVIOCGABS(ABS_X), &info)) { error_print("cannot get ABS_X info, %s\n", strerror(errno)); - return 0; + return 3; } xmin = info.minimum; xmax = info.maximum; if (ioctl(mousefd, EVIOCGABS(ABS_Y), &info)) { error_print("cannot get ABS_Y, %s\n", strerror(errno)); - return 0; + return 3; } ymin = info.minimum; ymax = info.maximum; rotate = vnc_rotate; info_print(" x:(%d %d) y:(%d %d) \n", xmin, xmax, ymin, ymax); - return 1; + return 0; +} + +int init_mouse_rel(int fb_xres, int fb_yres, const char *touch_device, int vnc_rotate) +{ + xmin = 0; + xmax = fb_xres; + ymin = 0; + ymax = fb_yres; + + rotate = vnc_rotate; + + is_relative_coords = true; + info_print(" x:(%d %d) y:(%d %d) \n", xmin, xmax, ymin, ymax); + + return 0; +} + +int init_mouse_pos(int first_x, int first_y) +{ + last_x = first_x; + last_y = first_y; } void cleanup_mouse() @@ -117,17 +151,6 @@ void injectMouseEvent(struct fb_var_screeninfo *scrinfo, int buttonMask, int x, 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 */ - - static map_t mouseButtonMap[] = { - MAP(BTN_LEFT), - MAP(BTN_MIDDLE), - MAP(BTN_RIGHT), - }; - static int buttonsNumber = sizeof(mouseButtonMap) / sizeof(mouseButtonMap[0]); - static int last_buttonMask; - static int last_x; - static int last_y; - static int wheel_tick; int last_wheel_tick = 0; @@ -238,9 +261,18 @@ void injectMouseEvent(struct fb_var_screeninfo *scrinfo, int buttonMask, int x, gettimeofday(&time, 0); ev.input_event_sec = time.tv_sec; ev.input_event_usec = time.tv_usec; - ev.type = EV_ABS; - ev.code = ABS_X; - ev.value = x; + + if(is_relative_coords) + { + ev.type = EV_REL; + ev.code = REL_X; + ev.value = dx; + } else + { + ev.type = EV_ABS; + ev.code = ABS_X; + ev.value = x; + } if (write(mousefd, &ev, sizeof(ev)) < 0) { error_print("write event failed, %s\n", strerror(errno)); @@ -254,9 +286,17 @@ void injectMouseEvent(struct fb_var_screeninfo *scrinfo, int buttonMask, int x, gettimeofday(&time, 0); ev.input_event_sec = time.tv_sec; ev.input_event_usec = time.tv_usec; - ev.type = EV_ABS; - ev.code = ABS_Y; - ev.value = y; + if(is_relative_coords) + { + ev.type = EV_REL; + ev.code = REL_Y; + ev.value = dy; + } else + { + ev.type = EV_ABS; + ev.code = ABS_Y; + ev.value = y; + } if (write(mousefd, &ev, sizeof(ev)) < 0) { error_print("write event failed, %s\n", strerror(errno)); diff --git a/src/mouse.h b/src/mouse.h index 4a919f9..96ac7ad 100644 --- a/src/mouse.h +++ b/src/mouse.h @@ -1,5 +1,7 @@ #pragma once int init_mouse(const char *touch_device, int vnc_rotate); +int init_mouse_rel(int fb_xres, int fb_yres, const char *touch_device, int vnc_rotate); +int init_mouse_pos(int first_x, int first_y); void cleanup_mouse(); void injectMouseEvent(struct fb_var_screeninfo *scrinfo, int buttonMask, int x, int y); diff --git a/src/touch.c b/src/touch.c index 8c8bb76..afd19bc 100644 --- a/src/touch.c +++ b/src/touch.c @@ -98,10 +98,10 @@ void injectTouchEvent(enum MouseAction mouseAction, int x, int y, struct fb_var_ // 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); - } + // { + // x = xmin + (x * (xmax - xmin)) / (scrinfo->xres); + // y = ymin + (y * (ymax - ymin)) / (scrinfo->yres); + // } memset(&ev, 0, sizeof(ev)); @@ -109,6 +109,7 @@ void injectTouchEvent(enum MouseAction mouseAction, int x, int y, struct fb_var_ bool sendTouch; int trkIdValue; int touchValue; + int pressureValue; struct timeval time; switch (mouseAction) @@ -118,12 +119,14 @@ void injectTouchEvent(enum MouseAction mouseAction, int x, int y, struct fb_var_ sendTouch = true; trkIdValue = ++trkg_id; touchValue = 1; + pressureValue = 11; break; case MouseRelease: sendPos = false; sendTouch = true; trkIdValue = -1; touchValue = 0; + pressureValue = 0; break; case MouseDrag: sendPos = true; @@ -210,8 +213,31 @@ void injectTouchEvent(enum MouseAction mouseAction, int x, int y, struct fb_var_ { error_print("write event failed, %s\n", strerror(errno)); } + + gettimeofday(&time, 0); + ev.input_event_sec = time.tv_sec; + ev.input_event_usec = time.tv_usec; + ev.type = EV_ABS; + ev.code = ABS_PRESSURE; + ev.value = pressureValue; + if (write(touchfd, &ev, sizeof(ev)) < 0) + { + error_print("write event failed, %s\n", strerror(errno)); + } + + gettimeofday(&time, 0); + ev.input_event_sec = time.tv_sec; + ev.input_event_usec = time.tv_usec; + ev.type = EV_ABS; + ev.code = ABS_MT_PRESSURE; + ev.value = pressureValue; + if (write(touchfd, &ev, sizeof(ev)) < 0) + { + error_print("write event failed, %s\n", strerror(errno)); + } } + // Finally send the SYN gettimeofday(&time, 0); ev.input_event_sec = time.tv_sec; @@ -223,5 +249,5 @@ void injectTouchEvent(enum MouseAction mouseAction, int x, int y, struct fb_var_ { error_print("write event failed, %s\n", strerror(errno)); } - debug_print("injectTouchEvent (screen(%d,%d) -> touch(%d,%d), mouse=%d)\n", xin, yin, x, y, mouseAction); + info_print("injectTouchEvent (screen(%d,%d) -> touch(%d,%d), mouse=%d)\n", xin, yin, x, y, mouseAction); }