README: basic introduction
[fbvnc.git] / fbvnc.c
blob5933afe44f42b767b327fbd7af49f83d356bbea3
1 /*
2 * FBVNC: a small Linux framebuffer VNC viewer
4 * Copyright (C) 2009-2021 Ali Gholami Rudi
6 * Permission to use, copy, modify, and/or distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include <arpa/inet.h>
19 #include <ctype.h>
20 #include <errno.h>
21 #include <fcntl.h>
22 #include <netdb.h>
23 #include <netinet/in.h>
24 #include <poll.h>
25 #include <pwd.h>
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <termios.h>
30 #include <unistd.h>
31 #include <sys/socket.h>
32 #include <sys/stat.h>
33 #include <sys/types.h>
34 #include <linux/input.h>
35 #include "draw.h"
36 #include "vnc.h"
38 #define MIN(a, b) ((a) < (b) ? (a) : (b))
39 #define MAX(a, b) ((a) > (b) ? (a) : (b))
40 #define OUT(msg) write(1, (msg), strlen(msg))
42 #define VNC_PORT "5900"
43 #define SCRSCRL 2
44 #define MAXRES (1 << 16)
46 static int cols, rows; /* framebuffer dimensions */
47 static int bpp; /* bytes per pixel */
48 static int srv_cols, srv_rows; /* server screen dimensions */
49 static int or, oc; /* visible screen offset */
50 static int mr, mc; /* mouse position */
51 static int nodraw; /* don't draw anything */
52 static long vnc_nr; /* number of bytes received */
53 static long vnc_nw; /* number of bytes sent */
55 static char buf[MAXRES];
57 static int vnc_connect(char *addr, char *port)
59 struct addrinfo hints, *addrinfo;
60 int fd;
62 memset(&hints, 0, sizeof(hints));
63 hints.ai_family = AF_UNSPEC;
64 hints.ai_socktype = SOCK_STREAM;
65 hints.ai_flags = AI_PASSIVE;
67 if (getaddrinfo(addr, port, &hints, &addrinfo))
68 return -1;
69 fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
70 addrinfo->ai_protocol);
72 if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) == -1) {
73 close(fd);
74 freeaddrinfo(addrinfo);
75 return -1;
77 freeaddrinfo(addrinfo);
78 return fd;
81 static void fbmode_bits(int *rr, int *rg, int *rb)
83 int mode = FBM_COLORS(fb_mode());
84 *rr = (mode >> 8) & 0xf;
85 *rg = (mode >> 4) & 0xf;
86 *rb = (mode >> 0) & 0xf;
89 static int vread(int fd, void *buf, long len)
91 long nr = 0;
92 long n;
93 while (nr < len && (n = read(fd, buf + nr, len - nr)) > 0)
94 nr += n;
95 vnc_nr += nr;
96 if (nr < len)
97 printf("fbvnc: partial vnc read!\n");
98 return nr < len ? -1 : len;
101 static int vwrite(int fd, void *buf, long len)
103 int nw = write(fd, buf, len);
104 if (nw != len)
105 printf("fbvnc: partial vnc write!\n");
106 vnc_nw += len;
107 return nw < len ? -1 : nw;
110 static int vnc_init(int fd)
112 char vncver[16];
113 int rr, rg, rb;
114 struct vnc_clientinit clientinit;
115 struct vnc_serverinit serverinit;
116 struct vnc_setpixelformat pixfmt_cmd;
117 struct vnc_setencoding enc_cmd;
118 u32 enc[] = {htonl(VNC_ENC_RAW), htonl(VNC_ENC_RRE)};
119 int connstat = VNC_CONN_FAILED;
121 /* handshake */
122 if (vread(fd, vncver, 12) < 0)
123 return -1;
124 strcpy(vncver, "RFB 003.003\n");
125 vwrite(fd, vncver, 12);
126 if (vread(fd, &connstat, sizeof(connstat)) < 0)
127 return -1;
128 if (ntohl(connstat) != VNC_CONN_NOAUTH)
129 return -1;
130 clientinit.shared = 1;
131 vwrite(fd, &clientinit, sizeof(clientinit));
132 if (vread(fd, &serverinit, sizeof(serverinit)) < 0)
133 return -1;
134 if (vread(fd, buf, ntohl(serverinit.len)) < 0)
135 return -1;
136 srv_cols = ntohs(serverinit.w);
137 srv_rows = ntohs(serverinit.h);
139 /* set up the framebuffer */
140 if (fb_init())
141 return -1;
142 cols = MIN(srv_cols, fb_cols());
143 rows = MIN(srv_rows, fb_rows());
144 bpp = FBM_BPP(fb_mode());
145 mr = rows / 2;
146 mc = cols / 2;
148 /* send framebuffer configuration */
149 pixfmt_cmd.type = VNC_SETPIXELFORMAT;
150 pixfmt_cmd.format.bpp = bpp << 3;
151 pixfmt_cmd.format.depth = bpp << 3;
152 pixfmt_cmd.format.bigendian = 0;
153 pixfmt_cmd.format.truecolor = 1;
154 fbmode_bits(&rr, &rg, &rb);
155 pixfmt_cmd.format.rmax = htons((1 << rr) - 1);
156 pixfmt_cmd.format.gmax = htons((1 << rg) - 1);
157 pixfmt_cmd.format.bmax = htons((1 << rb) - 1);
159 /* assuming colors packed as RGB; shall handle other cases later */
160 pixfmt_cmd.format.rshl = rg + rb;
161 pixfmt_cmd.format.gshl = rb;
162 pixfmt_cmd.format.bshl = 0;
163 vwrite(fd, &pixfmt_cmd, sizeof(pixfmt_cmd));
165 /* send pixel format */
166 enc_cmd.type = VNC_SETENCODING;
167 enc_cmd.pad = 0;
168 enc_cmd.n = htons(2);
169 vwrite(fd, &enc_cmd, sizeof(enc_cmd));
170 vwrite(fd, enc, ntohs(enc_cmd.n) * sizeof(enc[0]));
171 return 0;
174 static int vnc_free(void)
176 fb_free();
177 return 0;
180 static int vnc_refresh(int fd, int inc)
182 struct vnc_updaterequest fbup_req;
183 fbup_req.type = VNC_UPDATEREQUEST;
184 fbup_req.inc = inc;
185 fbup_req.x = htons(oc);
186 fbup_req.y = htons(or);
187 fbup_req.w = htons(cols);
188 fbup_req.h = htons(rows);
189 return vwrite(fd, &fbup_req, sizeof(fbup_req)) < 0 ? -1 : 0;
192 static void drawfb(char *s, int x, int y, int w, int h)
194 int sc; /* screen column offset */
195 int bc, bw; /* buffer column offset / row width */
196 int i;
197 sc = MAX(0, x - oc);
198 bc = x > oc ? 0 : oc - x;
199 bw = x + w < oc + cols ? w - bc : w - bc - (x + w - oc - cols);
200 for (i = y; i < y + h; i++)
201 if (i - or >= 0 && i - or < rows && bw > 0)
202 fb_set(i - or, sc, s + ((i - y) * w + bc) * bpp, bw);
205 static void drawrect(char *pixel, int x, int y, int w, int h)
207 int i;
208 if (x < 0 || x + w >= srv_cols || y < 0 || y + h >= srv_rows)
209 return;
210 for (i = 0; i < w; i++)
211 memcpy(buf + i * bpp, pixel, bpp);
212 for (i = 0; i < h; i++)
213 drawfb(buf, x, y + i, w, 1);
216 static int readrect(int fd)
218 struct vnc_rect uprect;
219 int x, y, w, h;
220 int i;
221 if (vread(fd, &uprect, sizeof(uprect)) < 0)
222 return -1;
223 x = ntohs(uprect.x);
224 y = ntohs(uprect.y);
225 w = ntohs(uprect.w);
226 h = ntohs(uprect.h);
227 if (x < 0 || w < 0 || x + w > srv_cols)
228 return -1;
229 if (y < 0 || h < 0 || y + h > srv_rows)
230 return -1;
231 if (uprect.enc == htonl(VNC_ENC_RAW)) {
232 for (i = 0; i < h; i++) {
233 if (vread(fd, buf, w * bpp) < 0)
234 return -1;
235 if (!nodraw)
236 drawfb(buf, x, y + i, w, 1);
239 if (uprect.enc == htonl(VNC_ENC_RRE)) {
240 char pixel[8];
241 u32 n;
242 vread(fd, &n, 4);
243 vread(fd, pixel, bpp);
244 if (!nodraw)
245 drawrect(pixel, x, y, w, h);
246 for (i = 0; i < ntohl(n); i++) {
247 u16 pos[4];
248 vread(fd, pixel, bpp);
249 vread(fd, pos, 8);
250 if (!nodraw)
251 drawrect(pixel, x + ntohs(pos[0]), y + ntohs(pos[1]),
252 ntohs(pos[2]), ntohs(pos[3]));
255 return 0;
258 static int vnc_event(int fd)
260 char msg[1 << 12];
261 struct vnc_update *fbup = (void *) msg;
262 struct vnc_servercuttext *cuttext = (void *) msg;
263 struct vnc_setcolormapentries *colormap = (void *) msg;
264 int i;
265 int n;
267 if (vread(fd, msg, 1) < 0)
268 return -1;
269 switch (msg[0]) {
270 case VNC_UPDATE:
271 vread(fd, msg + 1, sizeof(*fbup) - 1);
272 n = ntohs(fbup->n);
273 for (i = 0; i < n; i++)
274 if (readrect(fd))
275 return -1;
276 break;
277 case VNC_BELL:
278 break;
279 case VNC_SERVERCUTTEXT:
280 vread(fd, msg + 1, sizeof(*cuttext) - 1);
281 vread(fd, buf, ntohl(cuttext->len));
282 break;
283 case VNC_SETCOLORMAPENTRIES:
284 vread(fd, msg + 1, sizeof(*colormap) - 1);
285 vread(fd, buf, ntohs(colormap->n) * 3 * 2);
286 break;
287 default:
288 fprintf(stderr, "fbvnc: unknown vnc msg %d\n", msg[0]);
289 return -1;
291 return 0;
294 static int rat_event(int fd, int ratfd)
296 char ie[4] = {0};
297 struct vnc_pointerevent me = {VNC_POINTEREVENT};
298 int mask = 0;
299 int or_ = or, oc_ = oc;
300 if (ratfd > 0 && read(ratfd, &ie, sizeof(ie)) != 4)
301 return -1;
302 /* ignore mouse movements when nodraw */
303 if (nodraw)
304 return 0;
305 mc += ie[1];
306 mr -= ie[2];
308 if (mc < oc)
309 oc = MAX(0, oc - cols / SCRSCRL);
310 if (mc >= oc + cols && oc + cols < srv_cols)
311 oc = MIN(srv_cols - cols, oc + cols / SCRSCRL);
312 if (mr < or)
313 or = MAX(0, or - rows / SCRSCRL);
314 if (mr >= or + rows && or + rows < srv_rows)
315 or = MIN(srv_rows - rows, or + rows / SCRSCRL);
316 mc = MAX(oc, MIN(oc + cols - 1, mc));
317 mr = MAX(or, MIN(or + rows - 1, mr));
318 if (ie[0] & 0x01)
319 mask |= VNC_BUTTON1_MASK;
320 if (ie[0] & 0x04)
321 mask |= VNC_BUTTON2_MASK;
322 if (ie[0] & 0x02)
323 mask |= VNC_BUTTON3_MASK;
324 if (ie[3] > 0) /* wheel up */
325 mask |= VNC_BUTTON4_MASK;
326 if (ie[3] < 0) /* wheel down */
327 mask |= VNC_BUTTON5_MASK;
329 me.y = htons(mr);
330 me.x = htons(mc);
331 me.mask = mask;
332 vwrite(fd, &me, sizeof(me));
333 if (or != or_ || oc != oc_)
334 if (vnc_refresh(fd, 0))
335 return -1;
336 return 0;
339 static int press(int fd, int key, int down)
341 struct vnc_keyevent ke = {VNC_KEYEVENT};
342 ke.key = htonl(key);
343 ke.down = down;
344 vwrite(fd, &ke, sizeof(ke));
345 return 0;
348 static void showmsg(void)
350 char msg[128];
351 sprintf(msg, "\x1b[HFBVNC \t\t nr=%-8ld\tnw=%-8ld\r", vnc_nr, vnc_nw);
352 OUT(msg);
355 static int kbd_event(int fd, int kbdfd)
357 char key[1024];
358 int i, nr;
360 if ((nr = read(kbdfd, key, sizeof(key))) <= 0 )
361 return -1;
362 for (i = 0; i < nr; i++) {
363 int k = -1;
364 int mod[4];
365 int nmod = 0;
366 switch (key[i]) {
367 case 0x08:
368 case 0x7f:
369 k = 0xff08;
370 break;
371 case 0x09:
372 k = 0xff09;
373 break;
374 case 0x1b:
375 if (i + 2 < nr && key[i + 1] == '[') {
376 if (key[i + 2] == 'A')
377 k = 0xff52;
378 if (key[i + 2] == 'B')
379 k = 0xff54;
380 if (key[i + 2] == 'C')
381 k = 0xff53;
382 if (key[i + 2] == 'D')
383 k = 0xff51;
384 if (key[i + 2] == 'H')
385 k = 0xff50;
386 if (k > 0) {
387 i += 2;
388 break;
391 k = 0xff1b;
392 if (i + 1 < nr) {
393 mod[nmod++] = 0xffe9;
394 k = key[++i];
395 if (k == 0x03) /* esc-^C: quit */
396 return -1;
398 break;
399 case 0x0d:
400 k = 0xff0d;
401 break;
402 case 0x0: /* c-space: stop/start drawing */
403 if (!nodraw) {
404 nodraw = 1;
405 showmsg();
406 } else {
407 nodraw = 0;
408 if (vnc_refresh(fd, 0))
409 return -1;
411 default:
412 k = (unsigned char) key[i];
414 if ((k >= 'A' && k <= 'Z') || strchr(":\"<>?{}|+_()*&^%$#@!~", k))
415 mod[nmod++] = 0xffe1;
416 if (k >= 1 && k <= 26) {
417 k = 'a' + k - 1;
418 mod[nmod++] = 0xffe3;
420 if (k > 0) {
421 int j;
422 for (j = 0; j < nmod; j++)
423 press(fd, mod[j], 1);
424 press(fd, k, 1);
425 press(fd, k, 0);
426 for (j = 0; j < nmod; j++)
427 press(fd, mod[j], 0);
430 return 0;
433 static void term_setup(struct termios *ti)
435 struct termios termios;
436 OUT("\033[2J"); /* clear the screen */
437 OUT("\033[?25l"); /* hide the cursor */
438 showmsg();
439 tcgetattr(0, &termios);
440 *ti = termios;
441 cfmakeraw(&termios);
442 tcsetattr(0, TCSANOW, &termios);
445 static void term_cleanup(struct termios *ti)
447 tcsetattr(0, TCSANOW, ti);
448 OUT("\r\n\033[?25h"); /* show the cursor */
451 static void mainloop(int vnc_fd, int kbd_fd, int rat_fd)
453 struct pollfd ufds[3];
454 int pending = 0;
455 int err;
456 ufds[0].fd = kbd_fd;
457 ufds[0].events = POLLIN;
458 ufds[1].fd = vnc_fd;
459 ufds[1].events = POLLIN;
460 ufds[2].fd = rat_fd;
461 ufds[2].events = POLLIN;
462 rat_event(vnc_fd, -1);
463 if (vnc_refresh(vnc_fd, 0))
464 return;
465 while (1) {
466 err = poll(ufds, 3, 500);
467 if (err == -1 && errno != EINTR)
468 break;
469 if (!err)
470 continue;
471 if (ufds[0].revents & POLLIN)
472 if (kbd_event(vnc_fd, kbd_fd) == -1)
473 break;
474 if (ufds[1].revents & POLLIN) {
475 if (vnc_event(vnc_fd) == -1)
476 break;
477 pending = 0;
479 if (ufds[2].revents & POLLIN)
480 if (rat_event(vnc_fd, rat_fd) == -1)
481 break;
482 if (!pending++)
483 if (vnc_refresh(vnc_fd, 1))
484 break;
488 int main(int argc, char * argv[])
490 char *port = VNC_PORT;
491 char *host = "127.0.0.1";
492 struct termios ti;
493 int vnc_fd, rat_fd;
494 if (argc >= 2 && argv[1][0] && strcmp("-", argv[1]))
495 host = argv[1];
496 if (argc >= 3)
497 port = argv[2];
498 if ((vnc_fd = vnc_connect(host, port)) < 0) {
499 fprintf(stderr, "fbvnc: could not connect!\n");
500 return 1;
502 if (vnc_init(vnc_fd) < 0) {
503 close(vnc_fd);
504 fprintf(stderr, "fbvnc: vnc init failed!\n");
505 return 1;
507 term_setup(&ti);
509 /* entering intellimouse for using mouse wheel */
510 rat_fd = open("/dev/input/mice", O_RDWR);
511 write(rat_fd, "\xf3\xc8\xf3\x64\xf3\x50", 6);
512 read(rat_fd, buf, 1);
514 mainloop(vnc_fd, 0, rat_fd);
516 term_cleanup(&ti);
517 vnc_free();
518 close(vnc_fd);
519 close(rat_fd);
520 return 0;