Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 76 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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:
Expand Down
198 changes: 185 additions & 13 deletions src/framebuffer-vncserver.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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)

Expand Down Expand Up @@ -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");
Expand All @@ -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)
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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:
Expand All @@ -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;
Expand All @@ -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++;
}
}
}
}
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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
{
Expand All @@ -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);
Expand Down
Loading