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>
23 #include <netinet/in.h>
32 #include <sys/socket.h>
34 #include <sys/types.h>
35 #include <linux/input.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"
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
;
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
))
71 fd
= socket(addrinfo
->ai_family
, addrinfo
->ai_socktype
,
72 addrinfo
->ai_protocol
);
74 if (connect(fd
, addrinfo
->ai_addr
, addrinfo
->ai_addrlen
) == -1) {
76 freeaddrinfo(addrinfo
);
79 freeaddrinfo(addrinfo
);
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
)
95 while (nr
< len
&& (n
= read(fd
, buf
+ nr
, len
- nr
)) > 0)
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
);
107 printf("fbvnc: partial vnc write!\n");
109 return nw
< len
? -1 : nw
;
112 static int vnc_init(int fd
)
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
;
124 if (vread(fd
, vncver
, 12) < 0)
126 strcpy(vncver
, "RFB 003.003\n");
127 vwrite(fd
, vncver
, 12);
128 if (vread(fd
, &connstat
, sizeof(connstat
)) < 0)
130 if (ntohl(connstat
) != VNC_CONN_NOAUTH
)
132 clientinit
.shared
= 1;
133 vwrite(fd
, &clientinit
, sizeof(clientinit
));
134 if (vread(fd
, &serverinit
, sizeof(serverinit
)) < 0)
136 if (vread(fd
, buf
, ntohl(serverinit
.len
)) < 0)
138 srv_cols
= ntohs(serverinit
.w
);
139 srv_rows
= ntohs(serverinit
.h
);
141 /* set up the framebuffer */
142 if (fb_init(getenv("FBDEV")))
144 cols
= MIN(srv_cols
, fb_cols());
145 rows
= MIN(srv_rows
, fb_rows());
146 bpp
= FBM_BPP(fb_mode());
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
;
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]));
176 static int vnc_free(void)
182 static int vnc_refresh(int fd
, int inc
)
184 struct vnc_updaterequest fbup_req
;
185 fbup_req
.type
= VNC_UPDATEREQUEST
;
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 */
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
)
215 if (x
< 0 || x
+ w
>= srv_cols
|| y
< 0 || y
+ h
>= srv_rows
)
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
;
228 if (vread(fd
, &uprect
, sizeof(uprect
)) < 0)
234 if (x
< 0 || w
< 0 || x
+ w
> srv_cols
)
236 if (y
< 0 || h
< 0 || y
+ h
> srv_rows
)
238 if (uprect
.enc
== htonl(VNC_ENC_RAW
)) {
239 for (i
= 0; i
< h
; i
++) {
240 if (vread(fd
, buf
, w
* bpp
) < 0)
243 drawfb(buf
, x
, y
+ i
, w
, 1);
246 if (uprect
.enc
== htonl(VNC_ENC_RRE
)) {
250 vread(fd
, pixel
, bpp
);
252 drawrect(pixel
, x
, y
, w
, h
);
253 for (i
= 0; i
< ntohl(n
); i
++) {
255 vread(fd
, pixel
, bpp
);
258 drawrect(pixel
, x
+ ntohs(pos
[0]), y
+ ntohs(pos
[1]),
259 ntohs(pos
[2]), ntohs(pos
[3]));
265 static int vnc_event(int fd
)
268 struct vnc_update
*fbup
= (void *) msg
;
269 struct vnc_servercuttext
*cuttext
= (void *) msg
;
270 struct vnc_setcolormapentries
*colormap
= (void *) msg
;
274 if (vread(fd
, msg
, 1) < 0)
278 vread(fd
, msg
+ 1, sizeof(*fbup
) - 1);
280 for (i
= 0; i
< n
; i
++)
286 case VNC_SERVERCUTTEXT
:
287 vread(fd
, msg
+ 1, sizeof(*cuttext
) - 1);
288 vread(fd
, buf
, ntohl(cuttext
->len
));
290 case VNC_SETCOLORMAPENTRIES
:
291 vread(fd
, msg
+ 1, sizeof(*colormap
) - 1);
292 vread(fd
, buf
, ntohs(colormap
->n
) * 3 * 2);
295 fprintf(stderr
, "fbvnc: unknown vnc msg %d\n", msg
[0]);
301 static int rat_event(int fd
, int ratfd
)
304 struct vnc_pointerevent me
= {VNC_POINTEREVENT
};
306 int or_
= or, oc_
= oc
;
307 if (ratfd
> 0 && read(ratfd
, &ie
, sizeof(ie
)) != 4)
309 /* ignore mouse movements when nodraw */
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
);
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
));
326 mask
|= VNC_BUTTON1_MASK
;
328 mask
|= VNC_BUTTON2_MASK
;
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
;
339 vwrite(fd
, &me
, sizeof(me
));
340 if (or != or_
|| oc
!= oc_
)
341 if (vnc_refresh(fd
, 0))
346 static int press(int fd
, int key
, int down
)
348 struct vnc_keyevent ke
= {VNC_KEYEVENT
};
351 vwrite(fd
, &ke
, sizeof(ke
));
355 static void showmsg(void)
358 sprintf(msg
, "\x1b[HFBVNC \t\t nr=%-8ld\tnw=%-8ld\r", vnc_nr
, vnc_nw
);
362 static void nodraw_set(int val
)
371 static int kbd_event(int fd
, int kbdfd
)
376 if ((nr
= read(kbdfd
, key
, sizeof(key
))) <= 0 )
378 for (i
= 0; i
< nr
; i
++) {
391 if (i
+ 2 < nr
&& key
[i
+ 1] == '[') {
392 if (key
[i
+ 2] == 'A')
394 if (key
[i
+ 2] == 'B')
396 if (key
[i
+ 2] == 'C')
398 if (key
[i
+ 2] == 'D')
400 if (key
[i
+ 2] == 'H')
409 mod
[nmod
++] = 0xffe9;
411 if (k
== 0x03) /* esc-^C: quit */
418 case 0x0: /* c-space: stop/start drawing */
419 nodraw_set(1 - nodraw
);
421 k
= (unsigned char) key
[i
];
423 if ((k
>= 'A' && k
<= 'Z') || strchr(":\"<>?{}|+_()*&^%$#@!~", k
))
424 mod
[nmod
++] = 0xffe1;
425 if (k
>= 1 && k
<= 26) {
427 mod
[nmod
++] = 0xffe3;
431 for (j
= 0; j
< nmod
; j
++)
432 press(fd
, mod
[j
], 1);
435 for (j
= 0; j
< nmod
; j
++)
436 press(fd
, mod
[j
], 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 */
448 tcgetattr(0, &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];
466 ufds
[0].events
= POLLIN
;
468 ufds
[1].events
= POLLIN
;
470 ufds
[2].events
= POLLIN
;
471 rat_event(vnc_fd
, -1);
472 if (vnc_refresh(vnc_fd
, 0))
475 err
= poll(ufds
, 3, 500);
476 if (err
== -1 && errno
!= EINTR
)
480 if (ufds
[0].revents
& POLLIN
)
481 if (kbd_event(vnc_fd
, kbd_fd
) == -1)
483 if (ufds
[1].revents
& POLLIN
) {
484 if (vnc_event(vnc_fd
) == -1)
488 if (ufds
[2].revents
& POLLIN
)
489 if (rat_event(vnc_fd
, rat_fd
) == -1)
491 if (!nodraw
&& nodraw_ref
) {
493 if (vnc_refresh(vnc_fd
, 0))
497 if (vnc_refresh(vnc_fd
, 1))
502 static void signalreceived(int sig
)
504 if (sig
== SIGUSR1
&& !nodraw
) /* disable drawing */
506 if (sig
== SIGUSR2
&& nodraw
) /* enable drawing */
510 int main(int argc
, char * argv
[])
512 char *port
= VNC_PORT
;
513 char *host
= "127.0.0.1";
516 if (argc
>= 2 && argv
[1][0] && strcmp("-", argv
[1]))
520 if ((vnc_fd
= vnc_connect(host
, port
)) < 0) {
521 fprintf(stderr
, "fbvnc: could not connect!\n");
524 if (vnc_init(vnc_fd
) < 0) {
526 fprintf(stderr
, "fbvnc: vnc init failed!\n");
529 if (getenv("TERM_PGID") != NULL
&& atoi(getenv("TERM_PGID")) == getppid())
530 if (tcsetpgrp(0, getppid()) == 0)
531 setpgid(0, getppid());
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
);