draw: read FBDEV environment variable
[fbvnc.git] / fbvnc.c
blob850bb102a942742ae20483d477c4c0bc642c9581
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_CLR(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(getenv("FBDEV")))
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 fb_set(int r, int c, void *mem, int len)
194 memcpy(fb_mem(r) + c * bpp, mem, len * bpp);
197 static void drawfb(char *s, int x, int y, int w, int h)
199 int sc; /* screen column offset */
200 int bc, bw; /* buffer column offset / row width */
201 int i;
202 sc = MAX(0, x - oc);
203 bc = x > oc ? 0 : oc - x;
204 bw = x + w < oc + cols ? w - bc : w - bc - (x + w - oc - cols);
205 for (i = y; i < y + h; i++)
206 if (i - or >= 0 && i - or < rows && bw > 0)
207 fb_set(i - or, sc, s + ((i - y) * w + bc) * bpp, bw);
210 static void drawrect(char *pixel, int x, int y, int w, int h)
212 int i;
213 if (x < 0 || x + w >= srv_cols || y < 0 || y + h >= srv_rows)
214 return;
215 for (i = 0; i < w; i++)
216 memcpy(buf + i * bpp, pixel, bpp);
217 for (i = 0; i < h; i++)
218 drawfb(buf, x, y + i, w, 1);
221 static int readrect(int fd)
223 struct vnc_rect uprect;
224 int x, y, w, h;
225 int i;
226 if (vread(fd, &uprect, sizeof(uprect)) < 0)
227 return -1;
228 x = ntohs(uprect.x);
229 y = ntohs(uprect.y);
230 w = ntohs(uprect.w);
231 h = ntohs(uprect.h);
232 if (x < 0 || w < 0 || x + w > srv_cols)
233 return -1;
234 if (y < 0 || h < 0 || y + h > srv_rows)
235 return -1;
236 if (uprect.enc == htonl(VNC_ENC_RAW)) {
237 for (i = 0; i < h; i++) {
238 if (vread(fd, buf, w * bpp) < 0)
239 return -1;
240 if (!nodraw)
241 drawfb(buf, x, y + i, w, 1);
244 if (uprect.enc == htonl(VNC_ENC_RRE)) {
245 char pixel[8];
246 u32 n;
247 vread(fd, &n, 4);
248 vread(fd, pixel, bpp);
249 if (!nodraw)
250 drawrect(pixel, x, y, w, h);
251 for (i = 0; i < ntohl(n); i++) {
252 u16 pos[4];
253 vread(fd, pixel, bpp);
254 vread(fd, pos, 8);
255 if (!nodraw)
256 drawrect(pixel, x + ntohs(pos[0]), y + ntohs(pos[1]),
257 ntohs(pos[2]), ntohs(pos[3]));
260 return 0;
263 static int vnc_event(int fd)
265 char msg[1 << 12];
266 struct vnc_update *fbup = (void *) msg;
267 struct vnc_servercuttext *cuttext = (void *) msg;
268 struct vnc_setcolormapentries *colormap = (void *) msg;
269 int i;
270 int n;
272 if (vread(fd, msg, 1) < 0)
273 return -1;
274 switch (msg[0]) {
275 case VNC_UPDATE:
276 vread(fd, msg + 1, sizeof(*fbup) - 1);
277 n = ntohs(fbup->n);
278 for (i = 0; i < n; i++)
279 if (readrect(fd))
280 return -1;
281 break;
282 case VNC_BELL:
283 break;
284 case VNC_SERVERCUTTEXT:
285 vread(fd, msg + 1, sizeof(*cuttext) - 1);
286 vread(fd, buf, ntohl(cuttext->len));
287 break;
288 case VNC_SETCOLORMAPENTRIES:
289 vread(fd, msg + 1, sizeof(*colormap) - 1);
290 vread(fd, buf, ntohs(colormap->n) * 3 * 2);
291 break;
292 default:
293 fprintf(stderr, "fbvnc: unknown vnc msg %d\n", msg[0]);
294 return -1;
296 return 0;
299 static int rat_event(int fd, int ratfd)
301 char ie[4] = {0};
302 struct vnc_pointerevent me = {VNC_POINTEREVENT};
303 int mask = 0;
304 int or_ = or, oc_ = oc;
305 if (ratfd > 0 && read(ratfd, &ie, sizeof(ie)) != 4)
306 return -1;
307 /* ignore mouse movements when nodraw */
308 if (nodraw)
309 return 0;
310 mc += ie[1];
311 mr -= ie[2];
313 if (mc < oc)
314 oc = MAX(0, oc - cols / SCRSCRL);
315 if (mc >= oc + cols && oc + cols < srv_cols)
316 oc = MIN(srv_cols - cols, oc + cols / SCRSCRL);
317 if (mr < or)
318 or = MAX(0, or - rows / SCRSCRL);
319 if (mr >= or + rows && or + rows < srv_rows)
320 or = MIN(srv_rows - rows, or + rows / SCRSCRL);
321 mc = MAX(oc, MIN(oc + cols - 1, mc));
322 mr = MAX(or, MIN(or + rows - 1, mr));
323 if (ie[0] & 0x01)
324 mask |= VNC_BUTTON1_MASK;
325 if (ie[0] & 0x04)
326 mask |= VNC_BUTTON2_MASK;
327 if (ie[0] & 0x02)
328 mask |= VNC_BUTTON3_MASK;
329 if (ie[3] > 0) /* wheel up */
330 mask |= VNC_BUTTON4_MASK;
331 if (ie[3] < 0) /* wheel down */
332 mask |= VNC_BUTTON5_MASK;
334 me.y = htons(mr);
335 me.x = htons(mc);
336 me.mask = mask;
337 vwrite(fd, &me, sizeof(me));
338 if (or != or_ || oc != oc_)
339 if (vnc_refresh(fd, 0))
340 return -1;
341 return 0;
344 static int press(int fd, int key, int down)
346 struct vnc_keyevent ke = {VNC_KEYEVENT};
347 ke.key = htonl(key);
348 ke.down = down;
349 vwrite(fd, &ke, sizeof(ke));
350 return 0;
353 static void showmsg(void)
355 char msg[128];
356 sprintf(msg, "\x1b[HFBVNC \t\t nr=%-8ld\tnw=%-8ld\r", vnc_nr, vnc_nw);
357 OUT(msg);
360 static int kbd_event(int fd, int kbdfd)
362 char key[1024];
363 int i, nr;
365 if ((nr = read(kbdfd, key, sizeof(key))) <= 0 )
366 return -1;
367 for (i = 0; i < nr; i++) {
368 int k = -1;
369 int mod[4];
370 int nmod = 0;
371 switch (key[i]) {
372 case 0x08:
373 case 0x7f:
374 k = 0xff08;
375 break;
376 case 0x09:
377 k = 0xff09;
378 break;
379 case 0x1b:
380 if (i + 2 < nr && key[i + 1] == '[') {
381 if (key[i + 2] == 'A')
382 k = 0xff52;
383 if (key[i + 2] == 'B')
384 k = 0xff54;
385 if (key[i + 2] == 'C')
386 k = 0xff53;
387 if (key[i + 2] == 'D')
388 k = 0xff51;
389 if (key[i + 2] == 'H')
390 k = 0xff50;
391 if (k > 0) {
392 i += 2;
393 break;
396 k = 0xff1b;
397 if (i + 1 < nr) {
398 mod[nmod++] = 0xffe9;
399 k = key[++i];
400 if (k == 0x03) /* esc-^C: quit */
401 return -1;
403 break;
404 case 0x0d:
405 k = 0xff0d;
406 break;
407 case 0x0: /* c-space: stop/start drawing */
408 if (!nodraw) {
409 nodraw = 1;
410 showmsg();
411 } else {
412 nodraw = 0;
413 if (vnc_refresh(fd, 0))
414 return -1;
416 default:
417 k = (unsigned char) key[i];
419 if ((k >= 'A' && k <= 'Z') || strchr(":\"<>?{}|+_()*&^%$#@!~", k))
420 mod[nmod++] = 0xffe1;
421 if (k >= 1 && k <= 26) {
422 k = 'a' + k - 1;
423 mod[nmod++] = 0xffe3;
425 if (k > 0) {
426 int j;
427 for (j = 0; j < nmod; j++)
428 press(fd, mod[j], 1);
429 press(fd, k, 1);
430 press(fd, k, 0);
431 for (j = 0; j < nmod; j++)
432 press(fd, mod[j], 0);
435 return 0;
438 static void term_setup(struct termios *ti)
440 struct termios termios;
441 OUT("\033[2J"); /* clear the screen */
442 OUT("\033[?25l"); /* hide the cursor */
443 showmsg();
444 tcgetattr(0, &termios);
445 *ti = termios;
446 cfmakeraw(&termios);
447 tcsetattr(0, TCSANOW, &termios);
450 static void term_cleanup(struct termios *ti)
452 tcsetattr(0, TCSANOW, ti);
453 OUT("\r\n\033[?25h"); /* show the cursor */
456 static void mainloop(int vnc_fd, int kbd_fd, int rat_fd)
458 struct pollfd ufds[3];
459 int pending = 0;
460 int err;
461 ufds[0].fd = kbd_fd;
462 ufds[0].events = POLLIN;
463 ufds[1].fd = vnc_fd;
464 ufds[1].events = POLLIN;
465 ufds[2].fd = rat_fd;
466 ufds[2].events = POLLIN;
467 rat_event(vnc_fd, -1);
468 if (vnc_refresh(vnc_fd, 0))
469 return;
470 while (1) {
471 err = poll(ufds, 3, 500);
472 if (err == -1 && errno != EINTR)
473 break;
474 if (!err)
475 continue;
476 if (ufds[0].revents & POLLIN)
477 if (kbd_event(vnc_fd, kbd_fd) == -1)
478 break;
479 if (ufds[1].revents & POLLIN) {
480 if (vnc_event(vnc_fd) == -1)
481 break;
482 pending = 0;
484 if (ufds[2].revents & POLLIN)
485 if (rat_event(vnc_fd, rat_fd) == -1)
486 break;
487 if (!pending++)
488 if (vnc_refresh(vnc_fd, 1))
489 break;
493 int main(int argc, char * argv[])
495 char *port = VNC_PORT;
496 char *host = "127.0.0.1";
497 struct termios ti;
498 int vnc_fd, rat_fd;
499 if (argc >= 2 && argv[1][0] && strcmp("-", argv[1]))
500 host = argv[1];
501 if (argc >= 3)
502 port = argv[2];
503 if ((vnc_fd = vnc_connect(host, port)) < 0) {
504 fprintf(stderr, "fbvnc: could not connect!\n");
505 return 1;
507 if (vnc_init(vnc_fd) < 0) {
508 close(vnc_fd);
509 fprintf(stderr, "fbvnc: vnc init failed!\n");
510 return 1;
512 term_setup(&ti);
514 /* entering intellimouse for using mouse wheel */
515 rat_fd = open("/dev/input/mice", O_RDWR);
516 write(rat_fd, "\xf3\xc8\xf3\x64\xf3\x50", 6);
517 read(rat_fd, buf, 1);
519 mainloop(vnc_fd, 0, rat_fd);
521 term_cleanup(&ti);
522 vnc_free();
523 close(vnc_fd);
524 close(rat_fd);
525 return 0;