fbvnc: let the server send the protocol version message first
[fbvnc.git] / fbvnc.c
blobbbb4a90953b60b684989cc5315f4aa80dcbbd299
1 /*
2 * fbvnc - a small linux framebuffer vnc viewer
4 * Copyright (C) 2009-2013 Ali Gholami Rudi
6 * This program is released under the modified BSD license.
7 */
8 #include <arpa/inet.h>
9 #include <ctype.h>
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <netdb.h>
13 #include <netinet/in.h>
14 #include <poll.h>
15 #include <pwd.h>
16 #include <stdio.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <termios.h>
20 #include <unistd.h>
21 #include <sys/socket.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <linux/input.h>
25 #include "draw.h"
26 #include "vnc.h"
28 #define MIN(a, b) ((a) < (b) ? (a) : (b))
29 #define MAX(a, b) ((a) > (b) ? (a) : (b))
30 #define OUT(msg) write(1, (msg), strlen(msg))
32 #define VNC_PORT "5900"
33 #define SCRSCRL 2
34 #define MAXRES (1 << 16)
36 static int cols, rows; /* framebuffer dimensions */
37 static int bpp; /* bytes per pixel */
38 static int srv_cols, srv_rows; /* server screen dimensions */
39 static int or, oc; /* visible screen offset */
40 static int mr, mc; /* mouse position */
41 static int nodraw; /* don't draw anything */
43 static char buf[MAXRES];
45 static int vnc_connect(char *addr, char *port)
47 struct addrinfo hints, *addrinfo;
48 int fd;
50 memset(&hints, 0, sizeof(hints));
51 hints.ai_family = AF_UNSPEC;
52 hints.ai_socktype = SOCK_STREAM;
53 hints.ai_flags = AI_PASSIVE;
55 if (getaddrinfo(addr, port, &hints, &addrinfo))
56 return -1;
57 fd = socket(addrinfo->ai_family, addrinfo->ai_socktype,
58 addrinfo->ai_protocol);
60 if (connect(fd, addrinfo->ai_addr, addrinfo->ai_addrlen) == -1) {
61 close(fd);
62 freeaddrinfo(addrinfo);
63 return -1;
65 freeaddrinfo(addrinfo);
66 return fd;
69 static void fbmode_bits(int *rr, int *rg, int *rb)
71 int mode = FBM_COLORS(fb_mode());
72 *rr = (mode >> 8) & 0xf;
73 *rg = (mode >> 4) & 0xf;
74 *rb = (mode >> 0) & 0xf;
77 static int vnc_init(int fd)
79 char vncver[16];
80 int rr, rg, rb;
82 struct vnc_client_init clientinit;
83 struct vnc_server_init serverinit;
84 struct vnc_client_pixelfmt pixfmt_cmd;
85 int connstat = VNC_CONN_FAILED;
87 read(fd, vncver, 12);
88 strcpy(vncver, "RFB 003.003\n");
89 write(fd, vncver, 12);
91 read(fd, &connstat, sizeof(connstat));
93 if (ntohl(connstat) != VNC_CONN_NOAUTH)
94 return -1;
96 clientinit.shared = 1;
97 write(fd, &clientinit, sizeof(clientinit));
98 read(fd, &serverinit, sizeof(serverinit));
100 if (fb_init())
101 return -1;
102 srv_cols = ntohs(serverinit.w);
103 srv_rows = ntohs(serverinit.h);
104 cols = MIN(srv_cols, fb_cols());
105 rows = MIN(srv_rows, fb_rows());
106 bpp = FBM_BPP(fb_mode());
107 mr = rows / 2;
108 mc = cols / 2;
110 read(fd, buf, ntohl(serverinit.len));
111 pixfmt_cmd.type = VNC_CLIENT_PIXFMT;
112 pixfmt_cmd.format.bpp = bpp << 3;
113 pixfmt_cmd.format.depth = bpp << 3;
114 pixfmt_cmd.format.bigendian = 0;
115 pixfmt_cmd.format.truecolor = 1;
117 fbmode_bits(&rr, &rg, &rb);
118 pixfmt_cmd.format.rmax = htons((1 << rr) - 1);
119 pixfmt_cmd.format.gmax = htons((1 << rg) - 1);
120 pixfmt_cmd.format.bmax = htons((1 << rb) - 1);
121 /* assuming colors packed as RGB; shall handle other cases later */
122 pixfmt_cmd.format.rshl = rg + rb;
123 pixfmt_cmd.format.gshl = rb;
124 pixfmt_cmd.format.bshl = 0;
125 write(fd, &pixfmt_cmd, sizeof(pixfmt_cmd));
126 return 0;
129 static int vnc_free(void)
131 fb_free();
132 return 0;
135 static int vnc_refresh(int fd, int inc)
137 struct vnc_client_fbup fbup_req;
138 fbup_req.type = VNC_CLIENT_FBUP;
139 fbup_req.inc = inc;
140 fbup_req.x = htons(oc);
141 fbup_req.y = htons(or);
142 fbup_req.w = htons(cols);
143 fbup_req.h = htons(rows);
144 return write(fd, &fbup_req, sizeof(fbup_req)) != sizeof(fbup_req);
147 static void drawfb(char *s, int x, int y, int w, int h)
149 int sc; /* screen column offset */
150 int bc, bw; /* buffer column offset / row width */
151 int i;
152 sc = MAX(0, x - oc);
153 bc = x > oc ? 0 : oc - x;
154 bw = x + w < oc + cols ? w - bc : w - bc - (x + w - oc - cols);
155 for (i = y; i < y + h; i++)
156 if (i - or >= 0 && i - or < rows && bw > 0)
157 fb_set(i - or, sc, s + ((i - y) * w + bc) * bpp, bw);
160 static void xread(int fd, void *buf, int len)
162 int nr = 0;
163 int n;
164 while (nr < len && (n = read(fd, buf + nr, len - nr)) > 0)
165 nr += n;
166 if (nr < len) {
167 printf("partial vnc read!\n");
168 exit(1);
172 static int vnc_event(int fd)
174 struct vnc_rect uprect;
175 char msg[1 << 12];
176 struct vnc_server_fbup *fbup = (void *) msg;
177 struct vnc_server_cuttext *cuttext = (void *) msg;
178 struct vnc_server_colormap *colormap = (void *) msg;
179 int i, j;
180 int n;
182 if (read(fd, msg, 1) != 1)
183 return -1;
184 switch (msg[0]) {
185 case VNC_SERVER_FBUP:
186 xread(fd, msg + 1, sizeof(*fbup) - 1);
187 n = ntohs(fbup->n);
188 for (j = 0; j < n; j++) {
189 int x, y, w, h;
190 xread(fd, &uprect, sizeof(uprect));
191 x = ntohs(uprect.x);
192 y = ntohs(uprect.y);
193 w = ntohs(uprect.w);
194 h = ntohs(uprect.h);
195 if (x >= srv_cols || x + w > srv_cols)
196 return -1;
197 if (y >= srv_rows || y + h > srv_rows)
198 return -1;
199 for (i = 0; i < h; i++) {
200 xread(fd, buf, w * bpp);
201 if (!nodraw)
202 drawfb(buf, x, y + i, w, 1);
205 break;
206 case VNC_SERVER_BELL:
207 break;
208 case VNC_SERVER_CUTTEXT:
209 xread(fd, msg + 1, sizeof(*cuttext) - 1);
210 xread(fd, buf, ntohl(cuttext->len));
211 break;
212 case VNC_SERVER_COLORMAP:
213 xread(fd, msg + 1, sizeof(*colormap) - 1);
214 xread(fd, buf, ntohs(colormap->n) * 3 * 2);
215 break;
216 default:
217 fprintf(stderr, "unknown vnc msg: %d\n", msg[0]);
218 return -1;
220 return 0;
223 static int rat_event(int fd, int ratfd)
225 char ie[4];
226 struct vnc_client_ratevent me = {VNC_CLIENT_RATEVENT};
227 int mask = 0;
228 int or_ = or, oc_ = oc;
229 if (read(ratfd, &ie, sizeof(ie)) != 4)
230 return -1;
231 /* ignore mouse movements when nodraw */
232 if (nodraw)
233 return 0;
234 mc += ie[1];
235 mr -= ie[2];
237 if (mc < oc)
238 oc = MAX(0, oc - cols / SCRSCRL);
239 if (mc >= oc + cols && oc + cols < srv_cols)
240 oc = MIN(srv_cols - cols, oc + cols / SCRSCRL);
241 if (mr < or)
242 or = MAX(0, or - rows / SCRSCRL);
243 if (mr >= or + rows && or + rows < srv_rows)
244 or = MIN(srv_rows - rows, or + rows / SCRSCRL);
245 mc = MAX(oc, MIN(oc + cols - 1, mc));
246 mr = MAX(or, MIN(or + rows - 1, mr));
247 if (ie[0] & 0x01)
248 mask |= VNC_BUTTON1_MASK;
249 if (ie[0] & 0x04)
250 mask |= VNC_BUTTON2_MASK;
251 if (ie[0] & 0x02)
252 mask |= VNC_BUTTON3_MASK;
253 if (ie[3] > 0) /* wheel up */
254 mask |= VNC_BUTTON4_MASK;
255 if (ie[3] < 0) /* wheel down */
256 mask |= VNC_BUTTON5_MASK;
258 me.y = htons(mr);
259 me.x = htons(mc);
260 me.mask = mask;
261 write(fd, &me, sizeof(me));
262 if (or != or_ || oc != oc_)
263 if (vnc_refresh(fd, 0))
264 return -1;
265 return 0;
268 static int press(int fd, int key, int down)
270 struct vnc_client_keyevent ke = {VNC_CLIENT_KEYEVENT};
271 ke.key = htonl(key);
272 ke.down = down;
273 return write(fd, &ke, sizeof(ke));
276 static void showmsg(void)
278 OUT("\x1b[H\t\t\t*** fbvnc ***\r");
281 static int kbd_event(int fd, int kbdfd)
283 char key[1024];
284 int i, nr;
286 if ((nr = read(kbdfd, key, sizeof(key))) <= 0 )
287 return -1;
288 for (i = 0; i < nr; i++) {
289 int k = -1;
290 int mod[4];
291 int nmod = 0;
292 switch (key[i]) {
293 case 0x08:
294 case 0x7f:
295 k = 0xff08;
296 break;
297 case 0x09:
298 k = 0xff09;
299 break;
300 case 0x1b:
301 if (i + 2 < nr && key[i + 1] == '[') {
302 if (key[i + 2] == 'A')
303 k = 0xff52;
304 if (key[i + 2] == 'B')
305 k = 0xff54;
306 if (key[i + 2] == 'C')
307 k = 0xff53;
308 if (key[i + 2] == 'D')
309 k = 0xff51;
310 if (key[i + 2] == 'H')
311 k = 0xff50;
312 if (k > 0) {
313 i += 2;
314 break;
317 k = 0xff1b;
318 if (i + 1 < nr) {
319 mod[nmod++] = 0xffe9;
320 k = key[++i];
321 if (k == 0x03) /* esc-^C: quit */
322 return -1;
324 break;
325 case 0x0d:
326 k = 0xff0d;
327 break;
328 case 0x0: /* c-space: stop/start drawing */
329 if (!nodraw) {
330 nodraw = 1;
331 showmsg();
332 } else {
333 nodraw = 0;
334 if (vnc_refresh(fd, 0))
335 return -1;
337 default:
338 k = (unsigned char) key[i];
340 if (k >= 'A' && k <= 'Z' || strchr(":\"<>?{}|+_()*&^%$#@!~", k))
341 mod[nmod++] = 0xffe1;
342 if (k >= 1 && k <= 26) {
343 k = 'a' + k - 1;
344 mod[nmod++] = 0xffe3;
346 if (k > 0) {
347 int j;
348 for (j = 0; j < nmod; j++)
349 press(fd, mod[j], 1);
350 press(fd, k, 1);
351 press(fd, k, 0);
352 for (j = 0; j < nmod; j++)
353 press(fd, mod[j], 0);
356 return 0;
359 static void term_setup(struct termios *ti)
361 struct termios termios;
362 OUT("\033[2J"); /* clear the screen */
363 OUT("\033[?25l"); /* hide the cursor */
364 showmsg();
365 tcgetattr(0, &termios);
366 *ti = termios;
367 cfmakeraw(&termios);
368 tcsetattr(0, TCSANOW, &termios);
371 static void term_cleanup(struct termios *ti)
373 tcsetattr(0, TCSANOW, ti);
374 OUT("\r\n\033[?25h"); /* show the cursor */
377 static void mainloop(int vnc_fd, int kbd_fd, int rat_fd)
379 struct pollfd ufds[3];
380 int pending = 0;
381 int err;
382 ufds[0].fd = kbd_fd;
383 ufds[0].events = POLLIN;
384 ufds[1].fd = vnc_fd;
385 ufds[1].events = POLLIN;
386 ufds[2].fd = rat_fd;
387 ufds[2].events = POLLIN;
388 if (vnc_refresh(vnc_fd, 0))
389 return;
390 while (1) {
391 err = poll(ufds, 3, 500);
392 if (err == -1 && errno != EINTR)
393 break;
394 if (!err)
395 continue;
396 if (ufds[0].revents & POLLIN)
397 if (kbd_event(vnc_fd, kbd_fd) == -1)
398 break;
399 if (ufds[1].revents & POLLIN) {
400 if (vnc_event(vnc_fd) == -1)
401 break;
402 pending = 0;
404 if (ufds[2].revents & POLLIN)
405 if (rat_event(vnc_fd, rat_fd) == -1)
406 break;
407 if (!pending++)
408 if (vnc_refresh(vnc_fd, 1))
409 break;
413 int main(int argc, char * argv[])
415 char *port = VNC_PORT;
416 char *host = "127.0.0.1";
417 struct termios ti;
418 int vnc_fd, rat_fd;
419 if (argc >= 2)
420 host = argv[1];
421 if (argc >= 3)
422 port = argv[2];
423 if ((vnc_fd = vnc_connect(host, port)) < 0) {
424 fprintf(stderr, "could not connect!\n");
425 return 1;
427 if (vnc_init(vnc_fd) < 0) {
428 close(vnc_fd);
429 fprintf(stderr, "vnc init failed!\n");
430 return 1;
432 term_setup(&ti);
434 /* entering intellimouse for using mouse wheel */
435 rat_fd = open("/dev/input/mice", O_RDWR);
436 write(rat_fd, "\xf3\xc8\xf3\x64\xf3\x50", 6);
437 read(rat_fd, buf, 1);
439 mainloop(vnc_fd, 0, rat_fd);
441 term_cleanup(&ti);
442 vnc_free();
443 close(vnc_fd);
444 close(rat_fd);
445 return 0;