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