xref: /plan9/sys/src/cmd/vt/main.c (revision ec59a3ddbfceee0efe34584c2c9981a5e5ff1ec4)
1 #include <u.h>
2 #include <libc.h>
3 #include <draw.h>
4 #include <event.h>
5 #include <bio.h>
6 #include <keyboard.h>
7 #include "cons.h"
8 
9 enum{
10 	Ehost		= 4,
11 };
12 
13 char	*menutext2[] = {
14 	"backup",
15 	"forward",
16 	"reset",
17 	"clear",
18 	"send",
19 	"page",
20 	0
21 };
22 
23 char	*menutext3[] = {
24 	"24x80",
25 	"crnl",
26 	"nl",
27 	"raw",
28 	"exit",
29 	0
30 };
31 
32 /* variables associated with the screen */
33 
34 int	x, y;	/* character positions */
35 char	*backp;
36 int	backc;
37 int	atend;
38 int	nbacklines;
39 int	xmax, ymax;
40 int	blocked;
41 int	resize_flag;
42 int	pagemode;
43 int	olines;
44 int	peekc;
45 int	cursoron = 1;
46 Menu	menu2;
47 Menu	menu3;
48 char	*histp;
49 char	hist[HISTSIZ];
50 int	yscrmin, yscrmax;
51 int	attr, defattr;
52 int	wctlout;
53 
54 Image	*bordercol;
55 Image	*cursback;
56 Image	*colors[8];
57 Image	*hicolors[8];
58 Image	*red;
59 Image	*fgcolor;
60 Image	*bgcolor;
61 Image	*fgdefault;
62 Image	*bgdefault;
63 
64 uint rgbacolors[8] = {
65 	0x000000FF,	/* black */
66 	0xAA0000FF,	/* red */
67 	0x00AA00FF,	/* green */
68 	0xFF5500FF,	/* brown */
69 	0x0000FFFF,	/* blue */
70 	0xAA00AAFF,	/* purple */
71 	0x00AAAAFF,	/* cyan */
72 	0x7F7F7FFF,	/* white */
73 };
74 
75 ulong rgbahicolors[8] = {
76 	0x555555FF,	/* light black aka grey */
77 	0xFF5555FF,	/* light red */
78 	0x55FF55FF,	/* light green */
79 	0xFFFF55FF,	/* light brown aka yellow */
80 	0x5555FFFF,	/* light blue */
81 	0xFF55FFFF,	/* light purple */
82 	0x55FFFFFF,	/* light cyan */
83 	0xFFFFFFFF,	/* light grey aka white */
84 };
85 
86 /* terminal control */
87 struct ttystate ttystate[2] = { {0, 1}, {0, 1} };
88 
89 int	NS;
90 int	CW;
91 Consstate *cs;
92 Mouse	mouse;
93 
94 int	outfd = -1;
95 Biobuf	*snarffp = 0;
96 
97 char	*host_buf;
98 char	*hostp;			/* input from host */
99 int	host_bsize = 2*BSIZE;
100 int	hostlength;			/* amount of input from host */
101 char	echo_input[BSIZE];
102 char	*echop = echo_input;	/* characters to echo, after canon */
103 char	sendbuf[BSIZE];	/* hope you can't type ahead more than BSIZE chars */
104 char	*sendp = sendbuf;
105 
106 /* functions */
107 void	initialize(int, char **);
108 void	ebegin(int);
109 int	waitchar(void);
110 int	rcvchar(void);
111 void	set_input(char *);
112 void	set_host(Event *);
113 void	bigscroll(void);
114 void	readmenu(void);
115 void	eresized(int);
116 void	resize(void);
117 void	send_interrupt(void);
118 int	alnum(int);
119 void	escapedump(int,uchar *,int);
120 char *term;
121 struct funckey *fk;
122 
123 int	debug;
124 int	logfd = -1;
125 void
126 main(int argc, char **argv)
127 {
128 	initialize(argc, argv);
129 	emulate();
130 }
131 
132 void
133 usage(void)
134 {
135 	fprint(2, "usage: %s [-2s] [-l logfile]\n", argv0);
136 	exits("usage");
137 }
138 
139 void
140 initialize(int argc, char **argv)
141 {
142 	int i;
143 	char *fontname, *p;
144 
145 	rfork(RFNAMEG|RFNOTEG);
146 
147 	fontname = nil;
148 	term = "vt100";
149 	fk = vt100fk;
150 	ARGBEGIN{
151 	case 'f':
152 		fontname = EARGF(usage());
153 		break;
154 	case 'a':
155 		term = "ansi";
156 		fk = ansifk;
157 		break;
158 	case '2':
159 		term = "vt220";
160 		fk = vt220fk;
161 		break;
162 	case 'x':
163 		fk = xtermfk;
164 		term = "xterm";
165 		break;
166 	case 'l':
167 		p = EARGF(usage());
168 		logfd = create(p, OWRITE, 0666);
169 		if(logfd < 0)
170 			sysfatal("could not create log file: %s: %r", p);
171 		break;
172 	}ARGEND;
173 
174 	host_buf = malloc(host_bsize);
175 	hostp = host_buf;
176 	hostlength = 0;
177 
178 	if(initdraw(0, fontname, term) < 0){
179 		fprint(2, "%s: initdraw failed: %r\n", term);
180 		exits("initdraw");
181 	}
182 	ebegin(Ehost);
183 
184 	histp = hist;
185 	menu2.item = menutext2;
186 	menu3.item = menutext3;
187 	pagemode = 0;
188 	blocked = 0;
189 	NS = font->height;
190 	CW = stringwidth(font, "m");
191 
192 	red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, DRed);
193 	bordercol = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCCC);
194 	cursback = allocimage(display, Rect(0, 0, CW+1, NS+1), screen->chan, 0, DNofill);
195 
196 	for(i=0; i<8; i++){
197 		colors[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, rgbacolors[i]);
198 		hicolors[i] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, rgbahicolors[i]);
199 	}
200 
201 	bgdefault = display->white;
202 	fgdefault = display->black;
203 	bgcolor = bgdefault;
204 	fgcolor = fgdefault;
205 
206 	resize();
207 
208 	if(argc > 0) {
209 		sendnchars(strlen(argv[0]),argv[0]);
210 		sendnchars(1,"\n");
211 	}
212 }
213 
214 void
215 clear(Rectangle r)
216 {
217 	draw(screen, r, bgcolor, nil, ZP);
218 }
219 
220 void
221 newline(void)
222 {
223 	nbacklines--;
224 	if(y >= yscrmax) {
225 		y = yscrmax;
226 		if(pagemode && olines >= yscrmax) {
227 			blocked = 1;
228 			return;
229 		}
230 		scroll(yscrmin+1, yscrmax+1, yscrmin, yscrmax);
231 	} else
232 		y++;
233 	olines++;
234 }
235 
236 void
237 cursoff(void)
238 {
239 	draw(screen, Rpt(pt(x, y), addpt(pt(x, y), Pt(CW,NS))),
240 		cursback, nil, cursback->r.min);
241 }
242 
243 void
244 curson(int bl)
245 {
246 	Image *col;
247 
248 	if(!cursoron){
249 		cursoff();
250 		return;
251 	}
252 
253 	draw(cursback, cursback->r, screen, nil, pt(x, y));
254 	if(bl)
255 		col = red;
256 	else
257 		col = bordercol;
258 	border(screen, Rpt(pt(x, y), addpt(pt(x, y), Pt(CW,NS))), 2, col, ZP);
259 }
260 
261 int
262 get_next_char(void)
263 {
264 	int c = peekc;
265 	uchar buf[1];
266 	peekc = 0;
267 	if(c > 0)
268 		return(c);
269 	while(c <= 0) {
270 		if(backp) {
271 			c = *backp;
272 			if(c && nbacklines >= 0) {
273 				backp++;
274 				if(backp >= &hist[HISTSIZ])
275 					backp = hist;
276 				return(c);
277 			}
278 			backp = 0;
279 		}
280 		c = (uchar)waitchar();
281 		if(c > 0 && logfd >= 0) {
282 			buf[0] = c;
283 			write(logfd, buf, 1);
284 		}
285 	}
286 	*histp++ = c;
287 	if(histp >= &hist[HISTSIZ])
288 		histp = hist;
289 	*histp = '\0';
290 	return(c);
291 }
292 
293 int
294 canon(char *ep, int c)
295 {
296 	if(c&0200)
297 		return(SCROLL);
298 	switch(c) {
299 		case '\b':
300 			if(sendp > sendbuf)
301 				sendp--;
302 			*ep++ = '\b';
303 			*ep++ = ' ';
304 			*ep++ = '\b';
305 			break;
306 		case 0x15:	/* ^U line kill */
307 			sendp = sendbuf;
308 			*ep++ = '^';
309 			*ep++ = 'U';
310 			*ep++ = '\n';
311 			break;
312 		case 0x17:	/* ^W word kill */
313 			while(sendp > sendbuf && !alnum(*sendp)) {
314 				*ep++ = '\b';
315 				*ep++ = ' ';
316 				*ep++ = '\b';
317 				sendp--;
318 			}
319 			while(sendp > sendbuf && alnum(*sendp)) {
320 				*ep++ = '\b';
321 				*ep++ = ' ';
322 				*ep++ = '\b';
323 				sendp--;
324 			}
325 			break;
326 		case '\177':	/* interrupt */
327 			sendp = sendbuf;
328 			send_interrupt();
329 			return(NEWLINE);
330 		case '\021':	/* quit */
331 		case '\r':
332 		case '\n':
333 			if(sendp < &sendbuf[512])
334 				*sendp++ = '\n';
335 			sendnchars((int)(sendp-sendbuf), sendbuf);
336 			sendp = sendbuf;
337 			if(c == '\n' || c == '\r') {
338 				*ep++ = '\n';
339 			}
340 			*ep = 0;
341 			return(NEWLINE);
342 		case '\004':	/* EOT */
343 			if(sendp == sendbuf) {
344 				sendnchars(0,sendbuf);
345 				*ep = 0;
346 				return(NEWLINE);
347 			}
348 			/* fall through */
349 		default:
350 			if(sendp < &sendbuf[512])
351 				*sendp++ = c;
352 			*ep++ = c;
353 			break;
354 
355 	}
356 	*ep = 0;
357 	return(OTHER);
358 }
359 
360 void
361 sendfk(char *name)
362 {
363 	int i;
364 	static int fd;
365 
366 	for(i=0; fk[i].name; i++)
367 		if(strcmp(name, fk[i].name)==0){
368 			sendnchars2(strlen(fk[i].sequence), fk[i].sequence);
369 			return;
370 		}
371 }
372 
373 int
374 waitchar(void)
375 {
376 	Event e;
377 	int c;
378 	char c2;
379 	int newmouse;
380 	int wasblocked;
381 	int kbdchar = -1;
382 	char echobuf[3*BSIZE];
383 	static int lastc = -1;
384 
385 
386 	for(;;) {
387 		if(resize_flag)
388 			resize();
389 		wasblocked = blocked;
390 		if(backp)
391 			return(0);
392 		if(ecanmouse() && (button2() || button3()))
393 			readmenu();
394 		if(snarffp) {
395 			if((c = Bgetc(snarffp)) < 0) {
396 				if(lastc != '\n')
397 					write(outfd,"\n",1);
398 				Bterm(snarffp);
399 				snarffp = 0;
400 				if(lastc != '\n') {
401 					lastc = -1;
402 					return('\n');
403 				}
404 				lastc = -1;
405 				continue;
406 			}
407 			lastc = c;
408 			c2 = c;
409 			write(outfd, &c2, 1);
410 			return(c);
411 		}
412 		if(!blocked && host_avail())
413 			return(rcvchar());
414 		if(kbdchar > 0) {
415 			if(blocked)
416 				resize();
417 			if(cs->raw) {
418 				switch(kbdchar){
419 				case Kup:
420 					sendfk("up key");
421 					break;
422 				case Kdown:
423 					sendfk("down key");
424 					break;
425 				case Kleft:
426 					sendfk("left key");
427 					break;
428 				case Kright:
429 					sendfk("right key");
430 					break;
431 				case Kpgup:
432 					sendfk("page up");
433 					break;
434 				case Kpgdown:
435 					sendfk("page down");
436 					break;
437 				case KF|1:
438 					sendfk("F1");
439 					break;
440 				case KF|2:
441 					sendfk("F2");
442 					break;
443 				case KF|3:
444 					sendfk("F3");
445 					break;
446 				case KF|4:
447 					sendfk("F4");
448 					break;
449 				case KF|5:
450 					sendfk("F5");
451 					break;
452 				case KF|6:
453 					sendfk("F6");
454 					break;
455 				case KF|7:
456 					sendfk("F7");
457 					break;
458 				case KF|8:
459 					sendfk("F8");
460 					break;
461 				case KF|9:
462 					sendfk("F9");
463 					break;
464 				case KF|10:
465 					sendfk("F10");
466 					break;
467 				case KF|11:
468 					sendfk("F11");
469 					break;
470 				case KF|12:
471 					sendfk("F12");
472 					break;
473 				case '\n':
474 					echobuf[0] = '\r';
475 					sendnchars(1, echobuf);
476 					break;
477 				case '\r':
478 					echobuf[0] = '\n';
479 					sendnchars(1, echobuf);
480 					break;
481 				default:
482 					echobuf[0] = kbdchar;
483 					sendnchars(1, echobuf);
484 					break;
485 				}
486 			} else if(canon(echobuf,kbdchar) == SCROLL) {
487 				if(!blocked)
488 					bigscroll();
489 			} else
490 				strcat(echo_input,echobuf);
491 			blocked = 0;
492 			kbdchar = -1;
493 			continue;
494 		}
495 		curson(wasblocked);	/* turn on cursor while we're waiting */
496 		do {
497 			newmouse = 0;
498 			switch(eread(blocked ? Emouse|Ekeyboard :
499 					       Emouse|Ekeyboard|Ehost, &e)) {
500 			case Emouse:
501 				mouse = e.mouse;
502 				if(button2() || button3())
503 					readmenu();
504 				else if(resize_flag == 0) {
505 					/* eresized() is triggered by special mouse event */
506 					newmouse = 1;
507 				}
508 				break;
509 			case Ekeyboard:
510 				kbdchar = e.kbdc;
511 				break;
512 			case Ehost:
513 				set_host(&e);
514 				break;
515 			default:
516 				perror("protocol violation");
517 				exits("protocol violation");
518 			}
519 		} while(newmouse == 1);
520 		cursoff();	/* turn cursor back off */
521 	}
522 }
523 
524 void
525 eresized(int new)
526 {
527 	resize_flag = 1+new;
528 }
529 
530 void
531 putenvint(char *name, int x)
532 {
533 	char buf[20];
534 
535 	snprint(buf, sizeof buf, "%d", x);
536 	putenv(name, buf);
537 }
538 
539 void
540 exportsize(void)
541 {
542 	putenvint("XPIXELS", Dx(screen->r)-2*XMARGIN);
543 	putenvint("YPIXELS", Dy(screen->r)-2*XMARGIN);
544 	putenvint("LINES", ymax+1);
545 	putenvint("COLS", xmax+1);
546 	putenv("TERM", term);
547 }
548 
549 void
550 resize(void)
551 {
552 	if(resize_flag > 1 && getwindow(display, Refnone) < 0){
553 		fprint(2, "can't reattach to window: %r\n");
554 		exits("can't reattach to window");
555 	}
556 	xmax = (Dx(screen->r)-2*XMARGIN)/CW-1;
557 	ymax = (Dy(screen->r)-2*YMARGIN)/NS-1;
558 	if(xmax == 0 || ymax == 0)
559 		exits("window gone");
560 	x = 0;
561 	y = 0;
562 	yscrmin = 0;
563 	yscrmax = ymax;
564 	olines = 0;
565 	exportsize();
566 	clear(screen->r);
567 	resize_flag = 0;
568 }
569 
570 void
571 setdim(int ht, int wid)
572 {
573 	int fd;
574 	Rectangle r;
575 
576 	if(ht != -1)
577 		ymax = ht-1;
578 	if(wid != -1)
579 		xmax = wid-1;
580 
581 	r.min = screen->r.min;
582 	r.max = addpt(screen->r.min,
583 			Pt((xmax+1)*CW+2*XMARGIN+2*INSET,
584 				(ymax+1)*NS+2*YMARGIN+2*INSET));
585 	fd = open("/dev/wctl", OWRITE);
586 	if(fd < 0 || fprint(fd, "resize -dx %d -dy %d\n", Dx(r)+2*Borderwidth, Dy(r)+2*Borderwidth) < 0){
587 		border(screen, r, INSET, bordercol, ZP);
588 		exportsize();
589 	}
590 	if(fd >= 0)
591 		close(fd);
592 }
593 
594 void
595 readmenu(void)
596 {
597 	if(button3()) {
598 		menu3.item[1] = ttystate[cs->raw].crnl ? "cr" : "crnl";
599 		menu3.item[2] = ttystate[cs->raw].nlcr ? "nl" : "nlcr";
600 		menu3.item[3] = cs->raw ? "cooked" : "raw";
601 
602 		switch(emenuhit(3, &mouse, &menu3)) {
603 		case 0:		/* 24x80 */
604 			setdim(24, 80);
605 			return;
606 		case 1:		/* newline after cr? */
607 			ttystate[cs->raw].crnl = !ttystate[cs->raw].crnl;
608 			return;
609 		case 2:		/* cr after newline? */
610 			ttystate[cs->raw].nlcr = !ttystate[cs->raw].nlcr;
611 			return;
612 		case 3:		/* switch raw mode */
613 			cs->raw = !cs->raw;
614 			return;
615 		case 4:
616 			exits(0);
617 		}
618 		return;
619 	}
620 
621 	menu2.item[5] = pagemode? "scroll": "page";
622 
623 	switch(emenuhit(2, &mouse, &menu2)) {
624 
625 	case 0:		/* back up */
626 		if(atend == 0) {
627 			backc++;
628 			backup(backc);
629 		}
630 		return;
631 
632 	case 1:		/* move forward */
633 		backc--;
634 		if(backc >= 0)
635 			backup(backc);
636 		else
637 			backc = 0;
638 		return;
639 
640 	case 2:		/* reset */
641 		backc = 0;
642 		backup(0);
643 		return;
644 
645 	case 3:		/* clear screen */
646 		eresized(0);
647 		return;
648 
649 	case 4:		/* send the snarf buffer */
650 		snarffp = Bopen("/dev/snarf",OREAD);
651 		return;
652 
653 	case 5:		/* pause and clear at end of screen */
654 		pagemode = 1-pagemode;
655 		if(blocked && !pagemode) {
656 			eresized(0);
657 			blocked = 0;
658 		}
659 		return;
660 	}
661 }
662 
663 void
664 backup(int count)
665 {
666 	register n;
667 	register char *cp;
668 
669 	eresized(0);
670 	n = 3*(count+1)*ymax/4;
671 	cp = histp;
672 	atend = 0;
673 	while (n >= 0) {
674 		cp--;
675 		if(cp < hist)
676 			cp = &hist[HISTSIZ-1];
677 		if(*cp == '\0') {
678 			atend = 1;
679 			break;
680 		}
681 		if(*cp == '\n')
682 			n--;
683 	}
684 	cp++;
685 	if(cp >= &hist[HISTSIZ])
686 		cp = hist;
687 	backp = cp;
688 	nbacklines = ymax-2;
689 }
690 
691 Point
692 pt(int x, int y)
693 {
694 	return addpt(screen->r.min, Pt(x*CW+XMARGIN,y*NS+YMARGIN));
695 }
696 
697 void
698 scroll(int sy, int ly, int dy, int cy)	/* source, limit, dest, which line to clear */
699 {
700 	draw(screen, Rpt(pt(0, dy), pt(xmax+1, dy+ly-sy)), screen, nil, pt(0, sy));
701 	clear(Rpt(pt(0, cy), pt(xmax+1, cy+1)));
702 	flushimage(display, 1);
703 }
704 
705 void
706 bigscroll(void)			/* scroll up half a page */
707 {
708 	int half = ymax/3;
709 
710 	if(x == 0 && y == 0)
711 		return;
712 	if(y < half) {
713 		clear(Rpt(pt(0,0),pt(xmax+1,ymax+1)));
714 		x = y = 0;
715 		return;
716 	}
717 	draw(screen, Rpt(pt(0, 0), pt(xmax+1, ymax+1)), screen, nil, pt(0, half));
718 	clear(Rpt(pt(0,y-half+1),pt(xmax+1,ymax+1)));
719 	y -= half;
720 	if(olines)
721 		olines -= half;
722 	flushimage(display, 1);
723 }
724 
725 int
726 number(char *p, int *got)
727 {
728 	int c, n = 0;
729 
730 	if(got)
731 		*got = 0;
732 	while ((c = get_next_char()) >= '0' && c <= '9'){
733 		if(got)
734 			*got = 1;
735 		n = n*10 + c - '0';
736 	}
737 	*p = c;
738 	return(n);
739 }
740 
741 /* stubs */
742 
743 void
744 sendnchars(int n,char *p)
745 {
746 	sendnchars2(n, p);
747 	p[n+1] = 0;
748 }
749 
750 void
751 sendnchars2(int n,char *p)
752 {
753 	if(write(outfd,p,n) < 0) {
754 		close(outfd);
755 		close(0);
756 		close(1);
757 		close(2);
758 		exits("write");
759 	}
760 }
761 
762 int
763 host_avail(void)
764 {
765 	return(*echop || ((hostp - host_buf) < hostlength));
766 }
767 
768 int
769 rcvchar(void)
770 {
771 	int c;
772 	if(*echop) {
773 		c = *echop++;
774 		if(!*echop) {
775 			echop = echo_input;
776 			*echop = 0;
777 		}
778 		return c;
779 	}
780 	return *hostp++;
781 }
782 
783 void
784 set_host(Event *e)
785 {
786 	hostlength = e->n;
787 	if(hostlength > host_bsize) {
788 		host_bsize *= 2;
789 		host_buf = realloc(host_buf,host_bsize);
790 	}
791 	hostp = host_buf;
792 	memmove(host_buf,e->data,hostlength);
793 	host_buf[hostlength]=0;
794 }
795 
796 void
797 ringbell(void){
798 }
799 
800 int
801 alnum(int c)
802 {
803 	if(c >= 'a' && c <= 'z')
804 		return 1;
805 	if(c >= 'A' && c <= 'Z')
806 		return 1;
807 	if(c >= '0' && c <= '9')
808 		return 1;
809 	return 0;
810 }
811 
812 void
813 escapedump(int fd,uchar *str,int len)
814 {
815 	int i;
816 
817 	for(i = 0; i < len; i++) {
818 		if((str[i] < ' ' || str[i] > '\177') &&
819 			str[i] != '\n' && str[i] != '\t') fprint(fd,"^%c",str[i]+64);
820 		else if(str[i] == '\177') fprint(fd,"^$");
821 		else if(str[i] == '\n') fprint(fd,"^J\n");
822 		else fprint(fd,"%c",str[i]);
823 	}
824 }
825 
826 void
827 funckey(int key)
828 {
829 	if(key >= NKEYS)
830 		return;
831 	if(fk[key].name == 0)
832 		return;
833 	sendnchars2(strlen(fk[key].sequence), fk[key].sequence);
834 }
835 
836 
837 void
838 drawstring(Point p, char *str, int attr)
839 {
840 	int i;
841 	Image *txt, *bg, *tmp;
842 
843 	txt = fgcolor;
844 	bg = bgcolor;
845 	if(attr & TReverse){
846 		tmp = txt;
847 		txt = bg;
848 		bg = tmp;
849 	}
850 	if(attr & THighIntensity){
851 		for(i=0; i<8; i++)
852 			if(txt == colors[i])
853 				txt = hicolors[i];
854 	}
855 
856 	draw(screen, Rpt(p, addpt(p, stringsize(font, str))), bg, nil, p);
857 	string(screen, p, txt, ZP, font, str);
858 }
859