Makefile: make it more portable
[fbvnc.git] / fbvnc.c
blobb797f02ecc7cb838d07d0628cfbc1a94761f7819
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 <signal.h>
32 #include <sys/socket.h>
33 #include <sys/stat.h>
34 #include <sys/types.h>
35 #include <linux/input.h>
36 #include "draw.h"
37 #include "vnc.h"
39 #define MIN(a, b) ((a) < (b) ? (a) : (b))
40 #define MAX(a, b) ((a) > (b) ? (a) : (b))
41 #define OUT(msg) write(1, (msg), strlen(msg))
43 #define VNC_PORT "5900"
44 #define SCRSCRL 2
45 #define MAXRES (1 << 16)
47 static int cols, rows; /* framebuffer dimensions */
48 static int bpp; /* bytes per pixel */
49 static int srv_cols, srv_rows; /* server screen dimensions */
50 static int or, oc; /* visible screen offset */
51 static int mr, mc; /* mouse position */
52 static int nodraw; /* do not draw anything */
53 static int nodraw_ref; /* pending screen redraw */
54 static long vnc_nr; /* number of bytes received */
55 static long vnc_nw; /* number of bytes sent */
57 static char buf[MAXRES];
59 static int vnc_connect(char *addr, char *port)
61 struct addrinfo hints, *addrinfo;
62 int fd;
64 memset(&hints, 0, sizeof(hints));
65 hints.ai_family = AF_UNSPEC;
66 hints.ai_socktype = SOCK_STREAM;
67 hints.ai_flags = AI_PASSIVE;
69 if (getaddrinfo(addr, port, &hints, &addrinfo))
70 return -1;
71 fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
72 addrinfo->ai_protocol);
74 if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) == -1) {
75 close(fd);
76 freeaddrinfo(addrinfo);
77 return -1;
79 freeaddrinfo(addrinfo);
80 return fd;
83 static void fbmode_bits(int *rr, int *rg, int *rb)
85 int mode = FBM_CLR(fb_mode());
86 *rr = (mode >> 8) & 0xf;
87 *rg = (mode >> 4) & 0xf;
88 *rb = (mode >> 0) & 0xf;
91 static int vread(int fd, void *buf, long len)
93 long nr = 0;
94 long n;
95 while (nr < len && (n = read(fd, buf + nr, len - nr)) > 0)
96 nr += n;
97 vnc_nr += nr;
98 if (nr < len)
99 printf("fbvnc: partial vnc read!\n");
100 return nr < len ? -1 : len;
103 static int vwrite(int fd, void *buf, long len)
105 int nw = write(fd, buf, len);
106 if (nw != len)
107 printf("fbvnc: partial vnc write!\n");
108 vnc_nw += len;
109 return nw < len ? -1 : nw;
112 static int vnc_init(int fd)
114 char vncver[16];
115 int rr, rg, rb;
116 struct vnc_clientinit clientinit;
117 struct vnc_serverinit serverinit;
118 struct vnc_setpixelformat pixfmt_cmd;
119 struct vnc_setencoding enc_cmd;
120 u32 enc[] = {htonl(VNC_ENC_RAW), htonl(VNC_ENC_RRE)};
121 int connstat = VNC_CONN_FAILED;
123 /* handshake */
124 if (vread(fd, vncver, 12) < 0)
125 return -1;
126 strcpy(vncver, "RFB 003.003\n");
127 vwrite(fd, vncver, 12);
128 if (vread(fd, &connstat, sizeof(connstat)) < 0)
129 return -1;
130 if (ntohl(connstat) != VNC_CONN_NOAUTH)
131 return -1;
132 clientinit.shared = 1;
133 vwrite(fd, &clientinit, sizeof(clientinit));
134 if (vread(fd, &serverinit, sizeof(serverinit)) < 0)
135 return -1;
136 if (vread(fd, buf, ntohl(serverinit.len)) < 0)
137 return -1;
138 srv_cols = ntohs(serverinit.w);
139 srv_rows = ntohs(serverinit.h);
141 /* set up the framebuffer */
142 if (fb_init(getenv("FBDEV")))
143 return -1;
144 cols = MIN(srv_cols, fb_cols());
145 rows = MIN(srv_rows, fb_rows());
146 bpp = FBM_BPP(fb_mode());
147 mr = rows / 2;
148 mc = cols / 2;
150 /* send framebuffer configuration */
151 pixfmt_cmd.type = VNC_SETPIXELFORMAT;
152 pixfmt_cmd.format.bpp = bpp << 3;
153 pixfmt_cmd.format.depth = bpp << 3;
154 pixfmt_cmd.format.bigendian = 0;
155 pixfmt_cmd.format.truecolor = 1;
156 fbmode_bits(&rr, &rg, &rb);
157 pixfmt_cmd.format.rmax = htons((1 << rr) - 1);
158 pixfmt_cmd.format.gmax = htons((1 << rg) - 1);
159 pixfmt_cmd.format.bmax = htons((1 << rb) - 1);
161 /* assuming colors packed as RGB; shall handle other cases later */
162 pixfmt_cmd.format.rshl = rg + rb;
163 pixfmt_cmd.format.gshl = rb;
164 pixfmt_cmd.format.bshl = 0;
165 vwrite(fd, &pixfmt_cmd, sizeof(pixfmt_cmd));
167 /* send pixel format */
168 enc_cmd.type = VNC_SETENCODING;
169 enc_cmd.pad = 0;
170 enc_cmd.n = htons(2);
171 vwrite(fd, &enc_cmd, sizeof(enc_cmd));
172 vwrite(fd, enc, ntohs(enc_cmd.n) * sizeof(enc[0]));
173 return 0;
176 static int vnc_free(void)
178 fb_free();
179 return 0;
182 static int vnc_refresh(int fd, int inc)
184 struct vnc_updaterequest fbup_req;
185 fbup_req.type = VNC_UPDATEREQUEST;
186 fbup_req.inc = inc;
187 fbup_req.x = htons(oc);
188 fbup_req.y = htons(or);
189 fbup_req.w = htons(cols);
190 fbup_req.h = htons(rows);
191 return vwrite(fd, &fbup_req, sizeof(fbup_req)) < 0 ? -1 : 0;
194 static void fb_set(int r, int c, void *mem, int len)
196 memcpy(fb_mem(r) + c * bpp, mem, len * bpp);
199 static void drawfb(char *s, int x, int y, int w, int h)
201 int sc; /* screen column offset */
202 int bc, bw; /* buffer column offset / row width */
203 int i;
204 sc = MAX(0, x - oc);
205 bc = x > oc ? 0 : oc - x;
206 bw = x + w < oc + cols ? w - bc : w - bc - (x + w - oc - cols);
207 for (i = y; i < y + h; i++)
208 if (i - or >= 0 && i - or < rows && bw > 0)
209 fb_set(i - or, sc, s + ((i - y) * w + bc) * bpp, bw);
212 static void drawrect(char *pixel, int x, int y, int w, int h)
214 int i;
215 if (x < 0 || x + w >= srv_cols || y < 0 || y + h >= srv_rows)
216 return;
217 for (i = 0; i < w; i++)
218 memcpy(buf + i * bpp, pixel, bpp);
219 for (i = 0; i < h; i++)
220 drawfb(buf, x, y + i, w, 1);
223 static int readrect(int fd)
225 struct vnc_rect uprect;
226 int x, y, w, h;
227 int i;
228 if (vread(fd, &uprect, sizeof(uprect)) < 0)
229 return -1;
230 x = ntohs(uprect.x);
231 y = ntohs(uprect.y);
232 w = ntohs(uprect.w);
233 h = ntohs(uprect.h);
234 if (x < 0 || w < 0 || x + w > srv_cols)
235 return -1;
236 if (y < 0 || h < 0 || y + h > srv_rows)
237 return -1;
238 if (uprect.enc == htonl(VNC_ENC_RAW)) {
239 for (i = 0; i < h; i++) {
240 if (vread(fd, buf, w * bpp) < 0)
241 return -1;
242 if (!nodraw)
243 drawfb(buf, x, y + i, w, 1);
246 if (uprect.enc == htonl(VNC_ENC_RRE)) {
247 char pixel[8];
248 u32 n;
249 vread(fd, &n, 4);
250 vread(fd, pixel, bpp);
251 if (!nodraw)
252 drawrect(pixel, x, y, w, h);
253 for (i = 0; i < ntohl(n); i++) {
254 u16 pos[4];
255 vread(fd, pixel, bpp);
256 vread(fd, pos, 8);
257 if (!nodraw)
258 drawrect(pixel, x + ntohs(pos[0]), y + ntohs(pos[1]),
259 ntohs(pos[2]), ntohs(pos[3]));
262 return 0;
265 static int vnc_event(int fd)
267 char msg[1 << 12];
268 struct vnc_update *fbup = (void *) msg;
269 struct vnc_servercuttext *cuttext = (void *) msg;
270 struct vnc_setcolormapentries *colormap = (void *) msg;
271 int i;
272 int n;
274 if (vread(fd, msg, 1) < 0)
275 return -1;
276 switch (msg[0]) {
277 case VNC_UPDATE:
278 vread(fd, msg + 1, sizeof(*fbup) - 1);
279 n = ntohs(fbup->n);
280 for (i = 0; i < n; i++)
281 if (readrect(fd))
282 return -1;
283 break;
284 case VNC_BELL:
285 break;
286 case VNC_SERVERCUTTEXT:
287 vread(fd, msg + 1, sizeof(*cuttext) - 1);
288 vread(fd, buf, ntohl(cuttext->len));
289 break;
290 case VNC_SETCOLORMAPENTRIES:
291 vread(fd, msg + 1, sizeof(*colormap) - 1);
292 vread(fd, buf, ntohs(colormap->n) * 3 * 2);
293 break;
294 default:
295 fprintf(stderr, "fbvnc: unknown vnc msg %d\n", msg[0]);
296 return -1;
298 return 0;
301 static int rat_event(int fd, int ratfd)
303 char ie[4] = {0};
304 struct vnc_pointerevent me = {VNC_POINTEREVENT};
305 int mask = 0;
306 int or_ = or, oc_ = oc;
307 if (ratfd > 0 && read(ratfd, &ie, sizeof(ie)) != 4)
308 return -1;
309 /* ignore mouse movements when nodraw */
310 if (nodraw)
311 return 0;
312 mc += ie[1];
313 mr -= ie[2];
315 if (mc < oc)
316 oc = MAX(0, oc - cols / SCRSCRL);
317 if (mc >= oc + cols && oc + cols < srv_cols)
318 oc = MIN(srv_cols - cols, oc + cols / SCRSCRL);
319 if (mr < or)
320 or = MAX(0, or - rows / SCRSCRL);
321 if (mr >= or + rows && or + rows < srv_rows)
322 or = MIN(srv_rows - rows, or + rows / SCRSCRL);
323 mc = MAX(oc, MIN(oc + cols - 1, mc));
324 mr = MAX(or, MIN(or + rows - 1, mr));
325 if (ie[0] & 0x01)
326 mask |= VNC_BUTTON1_MASK;
327 if (ie[0] & 0x04)
328 mask |= VNC_BUTTON2_MASK;
329 if (ie[0] & 0x02)
330 mask |= VNC_BUTTON3_MASK;
331 if (ie[3] > 0) /* wheel up */
332 mask |= VNC_BUTTON4_MASK;
333 if (ie[3] < 0) /* wheel down */
334 mask |= VNC_BUTTON5_MASK;
336 me.y = htons(mr);
337 me.x = htons(mc);
338 me.mask = mask;
339 vwrite(fd, &me, sizeof(me));
340 if (or != or_ || oc != oc_)
341 if (vnc_refresh(fd, 0))
342 return -1;
343 return 0;
346 static int press(int fd, int key, int down)
348 struct vnc_keyevent ke = {VNC_KEYEVENT};
349 ke.key = htonl(key);
350 ke.down = down;
351 vwrite(fd, &ke, sizeof(ke));
352 return 0;
355 static void showmsg(void)
357 char msg[128];
358 sprintf(msg, "\x1b[HFBVNC \t\t nr=%-8ld\tnw=%-8ld\r", vnc_nr, vnc_nw);
359 OUT(msg);
362 static void nodraw_set(int val)
364 if (val && !nodraw)
365 showmsg();
366 if (!val && nodraw)
367 nodraw_ref = 1;
368 nodraw = val;
371 static int kbd_event(int fd, int kbdfd)
373 char key[1024];
374 int i, nr;
376 if ((nr = read(kbdfd, key, sizeof(key))) <= 0 )
377 return -1;
378 for (i = 0; i < nr; i++) {
379 int k = -1;
380 int mod[4];
381 int nmod = 0;
382 switch (key[i]) {
383 case 0x08:
384 case 0x7f:
385 k = 0xff08;
386 break;
387 case 0x09:
388 k = 0xff09;
389 break;
390 case 0x1b:
391 if (i + 2 < nr && key[i + 1] == '[') {
392 if (key[i + 2] == 'A')
393 k = 0xff52;
394 if (key[i + 2] == 'B')
395 k = 0xff54;
396 if (key[i + 2] == 'C')
397 k = 0xff53;
398 if (key[i + 2] == 'D')
399 k = 0xff51;
400 if (key[i + 2] == 'H')
401 k = 0xff50;
402 if (k > 0) {
403 i += 2;
404 break;
407 k = 0xff1b;
408 if (i + 1 < nr) {
409 mod[nmod++] = 0xffe9;
410 k = key[++i];
411 if (k == 0x03) /* esc-^C: quit */
412 return -1;
414 break;
415 case 0x0d:
416 k = 0xff0d;
417 break;
418 case 0x0: /* c-space: stop/start drawing */
419 nodraw_set(1 - nodraw);
420 default:
421 k = (unsigned char) key[i];
423 if ((k >= 'A' && k <= 'Z') || strchr(":\"<>?{}|+_()*&^%$#@!~", k))
424 mod[nmod++] = 0xffe1;
425 if (k >= 1 && k <= 26) {
426 k = 'a' + k - 1;
427 mod[nmod++] = 0xffe3;
429 if (k > 0) {
430 int j;
431 for (j = 0; j < nmod; j++)
432 press(fd, mod[j], 1);
433 press(fd, k, 1);
434 press(fd, k, 0);
435 for (j = 0; j < nmod; j++)
436 press(fd, mod[j], 0);
439 return 0;
442 static void term_setup(struct termios *ti)
444 struct termios termios;
445 OUT("\033[2J"); /* clear the screen */
446 OUT("\033[?25l"); /* hide the cursor */
447 showmsg();
448 tcgetattr(0, &termios);
449 *ti = termios;
450 cfmakeraw(&termios);
451 tcsetattr(0, TCSANOW, &termios);
454 static void term_cleanup(struct termios *ti)
456 tcsetattr(0, TCSANOW, ti);
457 OUT("\r\n\033[?25h"); /* show the cursor */
460 static void mainloop(int vnc_fd, int kbd_fd, int rat_fd)
462 struct pollfd ufds[3];
463 int pending = 0;
464 int err;
465 ufds[0].fd = kbd_fd;
466 ufds[0].events = POLLIN;
467 ufds[1].fd = vnc_fd;
468 ufds[1].events = POLLIN;
469 ufds[2].fd = rat_fd;
470 ufds[2].events = POLLIN;
471 rat_event(vnc_fd, -1);
472 if (vnc_refresh(vnc_fd, 0))
473 return;
474 while (1) {
475 err = poll(ufds, 3, 500);
476 if (err == -1 && errno != EINTR)
477 break;
478 if (!err)
479 continue;
480 if (ufds[0].revents & POLLIN)
481 if (kbd_event(vnc_fd, kbd_fd) == -1)
482 break;
483 if (ufds[1].revents & POLLIN) {
484 if (vnc_event(vnc_fd) == -1)
485 break;
486 pending = 0;
488 if (ufds[2].revents & POLLIN)
489 if (rat_event(vnc_fd, rat_fd) == -1)
490 break;
491 if (!nodraw && nodraw_ref) {
492 nodraw_ref = 0;
493 if (vnc_refresh(vnc_fd, 0))
494 break;
496 if (!pending++)
497 if (vnc_refresh(vnc_fd, 1))
498 break;
502 static void signalreceived(int sig)
504 if (sig == SIGUSR1 && !nodraw) /* disable drawing */
505 nodraw_set(1);
506 if (sig == SIGUSR2 && nodraw) /* enable drawing */
507 nodraw_set(0);
510 int main(int argc, char * argv[])
512 char *port = VNC_PORT;
513 char *host = "127.0.0.1";
514 struct termios ti;
515 int vnc_fd, rat_fd;
516 if (argc >= 2 && argv[1][0] && strcmp("-", argv[1]))
517 host = argv[1];
518 if (argc >= 3)
519 port = argv[2];
520 if ((vnc_fd = vnc_connect(host, port)) < 0) {
521 fprintf(stderr, "fbvnc: could not connect!\n");
522 return 1;
524 if (vnc_init(vnc_fd) < 0) {
525 close(vnc_fd);
526 fprintf(stderr, "fbvnc: vnc init failed!\n");
527 return 1;
529 if (getenv("TERM_PGID") != NULL && atoi(getenv("TERM_PGID")) == getppid())
530 if (tcsetpgrp(0, getppid()) == 0)
531 setpgid(0, getppid());
532 term_setup(&ti);
534 /* entering intellimouse for using mouse wheel */
535 rat_fd = open("/dev/input/mice", O_RDWR);
536 write(rat_fd, "\xf3\xc8\xf3\x64\xf3\x50", 6);
537 read(rat_fd, buf, 1);
538 signal(SIGUSR1, signalreceived);
539 signal(SIGUSR2, signalreceived);
541 mainloop(vnc_fd, 0, rat_fd);
543 term_cleanup(&ti);
544 vnc_free();
545 close(vnc_fd);
546 close(rat_fd);
547 return 0;