1 /*
2  * Copyright (c) 2019 Georg Brein. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are met:
6  *
7  * 1. Redistributions of source code must retain the above copyright notice,
8  *    this list of conditions and the following disclaimer.
9  *
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * 3. Neither the name of the copyright holder nor the names of its
15  *    contributors may be used to endorse or promote products derived from
16  *    this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
22  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28  * POSSIBILITY OF SUCH DAMAGE.
29  */
30 
31 
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <stddef.h>
35 #include <string.h>
36 #include <wchar.h>
37 #include <signal.h>
38 
39 #include <unistd.h>
40 #include <sys/select.h>
41 #include <ncurses/ncurses.h>
42 
43 #include "tnylpo.h"
44 
45 
46 /*
47  * size of the terminal input queue
48  */
49 #define IN_SIZE 128
50 
51 
52 /*
53  * state of the escape sequence parser
54  */
55 static enum term_state {
56 	ST_NORMAL, /* not in escape sequence */
57 	ST_ESCAPE, /* escape seen */
58 	ST_ESCAPEY, /* escape-Y seen */
59 	ST_ESCAPEYL, /* escape-Y-<line> seen */
60 	ST_ESCAPES, /* escape-S seen */
61 	ST_ESCAPET /* escape-T seen */
62 } state = ST_NORMAL;
63 static int escape_y_line = 0, escape_y_col = 0;
64 
65 
66 /*
67  * terminal input queue
68  */
69 static unsigned char in_buffer[IN_SIZE];
70 static int in_count = 0, in_in = 0, in_out = 0;
71 
72 
73 /*
74  * current logical cursor position
75  */
76 static int cursor_x = 0, cursor_y = 0;
77 
78 
79 /*
80  * actual size of the visible screen/window (can change at random)
81  */
82 static int screen_lines = 0, screen_cols = 0;
83 
84 
85 /*
86  * current output attributes
87  */
88 static int is_graphics = 0, is_reverse = 0, is_bold = 0, is_standout = 0,
89 	is_blink = 0, is_underline = 0;
90 
91 
92 /*
93  * current state of the "hold screen" mechanism
94  */
95 static int hold_screen = 0, hold_allow = 0;
96 
97 
98 /*
99  * state of the cursor visibility
100  */
101 static int cursor_off = 0, old_cursor = 0;
102 
103 
104 /*
105  * state of the application keypad (not really implemented)
106  */
107 static int app_keypad = 0;
108 
109 
110 /*
111  * color output valiables
112  */
113 static int use_color = 0;
114 static int foreground = 0, background = 0;
115 static short pairs[8][8] = {
116     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
117     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
118     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
119     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
120     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
121     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
122     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) },
123     { (-1), (-1), (-1), (-1), (-1), (-1), (-1), (-1) }
124 };
125 
126 
127 /*
128  * is input currently non-blocking?
129  */
130 static int noblock = 0;
131 
132 
133 /*
134  * Curses descriptors of the actual screen/window and of the representation
135  * of the VT52 screen contents
136  */
137 static WINDOW *win_p = NULL, *pad_p = NULL;
138 
139 
140 /*
141  * Curses-compatible representation of the blank character
142  */
143 static cchar_t blank;
144 
145 
146 /*
147  * conversion table between tnylpo's color numbers and curses
148  */
149 static short curses_colors[8] = {
150     COLOR_BLACK, COLOR_BLUE, COLOR_RED, COLOR_MAGENTA,
151     COLOR_GREEN, COLOR_CYAN, COLOR_YELLOW, COLOR_WHITE
152 };
153 
154 
155 /*
156  * cleanup/restore output on leaving the terminal emulation
157  */
158 static void
reset_curses(void)159 reset_curses(void) {
160 	if (pad_p) {
161 		/*
162 		 * cleanup of the VT52 screen contents
163 		 */
164 		/*
165 		 * restore cursor visibility
166 		 */
167 		if (cursor_off && old_cursor != ERR) curs_set(old_cursor);
168 		/*
169 		 * restore blocking input
170 		 */
171 		if (noblock) nodelay(pad_p, 0);
172 		/*
173 		 * turn off hardware supported insert/delete line
174 		 */
175 		idlok(pad_p, 0);
176 		/*
177 		 * restore keypad
178 		 */
179 		keypad(pad_p, 0);
180 		/*
181 		 * remove the VT52 screen contents
182 		 */
183 		delwin(pad_p);
184 	}
185 	if (win_p) {
186 		/*
187 		 * cleanup curses
188 		 */
189 		/*
190 		 * restore terminal default parameters, turn on echo
191 		 */
192 		noraw();
193 		nl();
194 		echo();
195 		/*
196 		 * position cursor to the start of the last line
197 		 * for curses implementations not cleaning/restoring
198 		 * the screen on exit (e. g. NetBSD)
199 		 */
200 		wmove(win_p, screen_lines - 1, 0);
201 		wrefresh(win_p);
202 		/*
203 		 * remove the screen handle
204 		 */
205 		delwin(win_p);
206 		/*
207 		 * leave curses
208 		 */
209 		endwin();
210 		refresh();
211 	}
212 }
213 
214 
215 /*
216  * store a character in the terminal input queue (if there is room)
217  */
218 static void
in_put(unsigned char c)219 in_put(unsigned char c) {
220 	if (in_count < IN_SIZE) {
221 		in_buffer[in_in++] = c;
222 		if (in_in == IN_SIZE) in_in = 0;
223 		in_count++;
224 	}
225 }
226 
227 
228 /*
229  * get a character (or (-1), if none is present) from the terminal
230  * input queue
231  */
232 static int
in_get(void)233 in_get(void) {
234 	int rc = (-1);
235 	if (in_count) {
236 		rc = in_buffer[in_out++];
237 		if (in_out == IN_SIZE) in_out = 0;
238 		in_count--;
239 	}
240 	return rc;
241 }
242 
243 
244 /*
245  * refresh the screen to show the contents of the VT52 screen
246  */
247 static void
show_pad(void)248 show_pad(void) {
249 	wmove(pad_p, cursor_y, cursor_x);
250 	prefresh(pad_p, 0, 0, 0, 0,
251 	    (screen_lines > lines ? lines : screen_lines) - 1,
252 	    (screen_cols > cols ? cols : screen_cols) - 1);
253 }
254 
255 
256 /*
257  * read a keycode from the screen, translate it to VT52 code(s) and
258  * put it/them in the terminal input queue
259  */
260 static void
try_read(void)261 try_read(void) {
262 	wint_t wc;
263 	int t;
264 	/*
265 	 * get a keycode from the screen
266 	 */
267 	switch (wget_wch(pad_p, &wc)) {
268 	case ERR:
269 		/*
270 		 * ignore errors (these are generated by e. g. signals
271 		 * like SIGWINCH interrupting a read from the screen)
272 		 */
273 		break;
274 	case KEY_CODE_YES:
275 		/*
276 		 * it is a function key
277 		 */
278 		switch (wc) {
279 		case KEY_RESIZE:
280 			/*
281 			 * resize event: get new screen/window size and
282 			 * redraw the screen contents
283 			 */
284 			getmaxyx(win_p, screen_lines, screen_cols);
285 			show_pad();
286 			break;
287 		case KEY_BACKSPACE:
288 			/*
289 			 * backspace key
290 			 */
291 			if (reverse_bs_del) {
292 				in_put(0x7f /* DEL */);
293 			} else {
294 				in_put(0x08 /* BS */);
295 			}
296 			break;
297 		case KEY_UP:
298 			/*
299 			 * cursor up key
300 			 */
301 			if (altkeys) {
302 				in_put(0x05 /* ^E */);
303 			} else {
304 				in_put(0x1b /* ESC */);
305 				in_put(0x41 /* A */);
306 			}
307 			break;
308 		case KEY_DOWN:
309 			/*
310 			 * cursor down key
311 			 */
312 			if (altkeys) {
313 				in_put(0x18 /* ^X */);
314 			} else {
315 				in_put(0x1b /* ESC */);
316 				in_put(0x42 /* B */);
317 			}
318 			break;
319 		case KEY_RIGHT:
320 			/*
321 			 * cursor right key
322 			 */
323 			if (altkeys) {
324 				in_put(0x04 /* ^D */);
325 			} else {
326 				in_put(0x1b /* ESC */);
327 				in_put(0x43 /* C */);
328 			}
329 			break;
330 		case KEY_LEFT:
331 			/*
332 			 * cursor left key
333 			 */
334 			if (altkeys) {
335 				in_put(0x13 /* ^S */);
336 			} else {
337 				in_put(0x1b /* ESC */);
338 				in_put(0x44 /* D */);
339 			}
340 			break;
341 		case KEY_F(1):
342 			/*
343 			 * F1 emulates the first blank key of the VT52
344 			 */
345 			in_put(0x1b /* ESC */);
346 			in_put(0x50 /* P */);
347 			break;
348 		case KEY_F(2):
349 			/*
350 			 * F2 emulates the second blank key of the VT52
351 			 */
352 			in_put(0x1b /* ESC */);
353 			in_put(0x51 /* Q */);
354 			break;
355 		case KEY_F(3):
356 			/*
357 			 * F3 emulates the third blank key of the VT52
358 			 */
359 			in_put(0x1b /* ESC */);
360 			in_put(0x52 /* R */);
361 			break;
362 		case KEY_F(4):
363 			/*
364 			 * F4 forces a screen refresh
365 			 */
366 			wrefresh(curscr);
367 			break;
368 		case KEY_F(5):
369 			/*
370 			 * F5 switches "hold screen" mode on or off
371 			 */
372 			hold_screen = ! hold_screen;
373 			if (hold_screen) hold_allow = 0;
374 			break;
375 		case KEY_F(6):
376 			/*
377 			 * F6 allows one further screenfull in
378 			 * "hold screen" mode
379 			 */
380 			if (hold_screen && ! hold_allow) hold_allow += lines;
381 			break;
382 		case KEY_F(7):
383 			/*
384 			 * F7 allows one further line in "hold screen" mode
385 			 */
386 			if (hold_screen && ! hold_allow) hold_allow++;
387 			break;
388 		case KEY_F(10):
389 			/*
390 			 * F10 is the reset switch: the emulation will
391 			 * be stopped by raising SIGINT
392 			 */
393 			plog("F10 key pressed --- raising SIGINT");
394 			raise(SIGINT);
395 			break;
396 		default:
397 			/*
398 			 * all other function keys are ignored
399 			 */
400 			break;
401 		}
402 		break;
403 	default:
404 		/*
405 		 * if the character from the screen can be represented in
406 		 * the CP/M character set, save this representation in the
407 		 * terminal input queue
408 		 */
409 		t = to_cpm(wc);
410 		if (t != (-1)) {
411 			if (reverse_bs_del) {
412 				if (t == 0x80 /* BS */) {
413 					t = 0x7f /* DEL */;
414 				} else if (t == 0x7f /* DEL */) {
415 					t = 0x08 /* BS */;
416 				}
417 			}
418 			in_put((unsigned char) t);
419 		}
420 		break;
421 	}
422 }
423 
424 
425 /*
426  * Get the color pair for a given color combination; if it does not
427  * exist, allocate it; if it cannot be allocated, return the standard
428  * pair.
429  */
430 static inline short
get_pair(int fg,int bg)431 get_pair(int fg, int bg) {
432 	static int out_of_pairs = 0;
433 	/*
434 	 * color pair 0 is reserved
435 	 */
436 	static short free_pair = 1;
437 	short p;
438 	p = pairs[fg][bg];
439 	if (p == (-1)) {
440 		if (free_pair >= COLOR_PAIRS || init_pair(free_pair,
441                     curses_colors[fg], curses_colors[bg]) == ERR) {
442 		    	/*
443 			 * complain if we run out of color pairs, but only once
444 			 */
445 		    	if (! out_of_pairs) {
446 				plog("out of color pairs");
447 				out_of_pairs = 1;
448 			}
449 			p = 0;
450 		} else {
451 			p = free_pair++;
452 		}
453 		pairs[fg][bg] = p;
454 	}
455 	return p;
456 }
457 
458 
459 /*
460  * initializes the VT52 terminal emulation; must be called before all
461  * other crt_xxx() functions
462  */
463 int
crt_init(void)464 crt_init(void) {
465 	int rc = 0;
466 	wchar_t wcs[2];
467 	wint_t wc;
468 	short int pair;
469 	/*
470 	 * redirections are not allowed
471 	 */
472 	if (! isatty(fileno(stdin)) || ! isatty(fileno(stdout))) {
473 		rc = 1;
474 		goto premature_exit;
475 	}
476 	/*
477 	 * initialize curses; in current implementations, any error
478 	 * during initialization will abort the program, so the
479 	 * error checking is futile.
480 	 */
481 	win_p = initscr();
482 	if (! win_p) {
483 		rc = 2;
484 		goto premature_exit;
485 	}
486 	/*
487 	 * set up colors if requested and available
488 	 */
489 	if (conf_color && has_colors()) {
490 		use_color = 1;
491 		foreground = conf_foreground;
492 		background = conf_background;
493 		if (start_color() == ERR) {
494 			rc = 4;
495 			goto premature_exit;
496 		}
497 	}
498 	/*
499 	 * get physical screen dimensions; if requested, these will be
500 	 * used to dimension the VT52 emulation screen, but only as
501 	 * far as they are within the legal maximal and minimal values.
502 	 */
503 	getmaxyx(win_p, screen_lines, screen_cols);
504 	if (cols == (-1)) {
505 		cols = screen_cols;
506 		if (cols < MIN_COLS) cols = MIN_COLS;
507 		if (cols > MAX_COLS) cols = MAX_COLS;
508 	}
509 	if (lines == (-1)) {
510 		lines = screen_lines;
511 		if (lines < MIN_LINES) lines = MIN_LINES;
512 		if (lines > MAX_LINES) lines = MAX_LINES;
513 	}
514 	/*
515 	 * turn off input echoing and CR/NL translation; switch of
516 	 * input postprocessing
517 	 */
518 	noecho();
519 	nonl();
520 	raw();
521 	/*
522 	 * create the representation of the VT52 screen contents
523 	 */
524 	pad_p = newpad(lines, cols);
525 	if (! pad_p) {
526 		rc = 3;
527 		goto premature_exit;
528 	}
529 	/*
530 	 * initialize the blank character
531 	 */
532 	wc = from_cpm(0x20 /* SPC */);
533 	if (wc != (-1)) wc = L' ';
534 	wcs[0] = wc;
535 	wcs[1] = L'\0';
536 	pair = use_color ? get_pair(foreground, background) : 0;
537 	setcchar(&blank, wcs, 0, pair, NULL);
538 	bkgrnd(&blank);
539 	wbkgrnd(pad_p, &blank);
540 	/*
541 	 * switch to application keypad mode, allow hardware assisted
542 	 * line insertion/deletion; show the current (empty) VT52 screen
543 	 */
544 	keypad(pad_p, 1);
545 	idlok(pad_p, 1);
546 	erase();
547 	refresh();
548 	werase(pad_p);
549 	show_pad();
550 premature_exit:
551 	/*
552 	 * in case of errors, reset the screen; error messages from
553 	 * screen initialization are deferred until after resetting the
554 	 * screen
555 	 */
556 	if (rc) reset_curses();
557 	switch (rc) {
558 	case 1:
559 		perr("stdin or stdout must be a terminal");
560 		break;
561 	case 2:
562 		perr("initscr() failed, TERM undefined?");
563 		break;
564 	case 3:
565 		perr("newpad() failed");
566 		break;
567 	case 4:
568 		perr("cannot initialize colors");
569 		break;
570 	}
571 	return rc ? (-1) : 0;
572 }
573 
574 
575 /*
576  * helper function for crt_out(): set foreground or background color
577  */
578 static inline int
set_color(unsigned char c,int curr_color,int def_color)579 set_color(unsigned char c, int curr_color, int def_color) {
580 	switch (c) {
581 	case 0x30 /* 0 */: return 0;
582 	case 0x31 /* 1 */: return 1;
583 	case 0x32 /* 2 */: return 2;
584 	case 0x33 /* 3 */: return 3;
585 	case 0x34 /* 4 */: return 4;
586 	case 0x35 /* 5 */: return 5;
587 	case 0x36 /* 6 */: return 6;
588 	case 0x37 /* 7 */: return 7;
589 	case 0x3d /* = */: return def_color;
590 	}
591 	return curr_color;
592 }
593 
594 
595 /*
596  * display a character on the emulated VT52 screen, handle escape sequences
597  */
598 void
crt_out(unsigned char c)599 crt_out(unsigned char c) {
600 	int t;
601 	wint_t wc;
602 	short pair;
603 	cchar_t cc;
604 	wchar_t wcs[2];
605 	/*
606 	 * handle ASCII control characters
607 	 */
608 	if (c <= 0x1f /* US */) {
609 		switch (c) {
610 		case 0x07 /* BEL */:
611 			beep();
612 			break;
613 		case 0x08 /* BS */:
614 			if (cursor_x > 0) {
615 				cursor_x--;
616 				goto move_cursor;
617 			}
618 			break;
619 		case 0x09 /* TAB */:
620 			/*
621 			 * This is the VT52 way of TAB expansion: every
622 			 * eighth columns below column 72, then a single
623 			 * column up to column 80, where TABs are ignored.
624 			 */
625 			t = ((cursor_x / 8) + 1) * 8;
626 			if (t >= cols) t = cursor_x + 1;
627 			if (t < cols) {
628 				cursor_x = t;
629 				goto move_cursor;
630 			}
631 			break;
632 		case 0x0a /* LF */:
633 			if (cursor_y + 1 < lines) {
634 				/*
635 				 * before last line: LF always means
636 				 * "cursor down"
637 				 */
638 				cursor_y++;
639 				goto move_cursor;
640 			}
641 			/*
642 			 * In "hold screen" mode, scrolling is only allowed
643 			 * hold_allow times; if hold_allow is decreased to
644 			 * zero, output is blocked until the "hold screen"
645 			 * mode is ended by pressing the "hold screen"
646 			 * key or by allowing a further line resp. a further
647 			 * screenful by pressing the respectve keys.
648 			 */
649 			if (hold_screen) {
650 				while (hold_screen && ! hold_allow) {
651 					if (noblock) {
652 						nodelay(pad_p, 0);
653 						noblock = 0;
654 					}
655 					try_read();
656 				}
657 				if (hold_screen) hold_allow--;
658 			}
659 			/*
660 			 * scroll down one line
661 			 */
662 			scrollok(pad_p, 1);
663 			wscrl(pad_p, 1);
664 			scrollok(pad_p, 0);
665 			goto redraw_screen;
666 		case 0x0d /* CR */:
667 			if (cursor_x > 0) {
668 				cursor_x = 0;
669 				goto move_cursor;
670 			}
671 			break;
672 		case 0x1b /* ESC */:
673 			/*
674 			 * start a new escape sequence
675 			 */
676 			state = ST_ESCAPE;
677 			break;
678 		}
679 		/*
680 		 * ignore all other control characters below 0x20/SPC
681 		 */
682 		goto do_nothing;
683 	}
684 	/*
685 	 * ignore the DEL character
686 	 */
687 	if (c == 0x7f /* DEL */) goto do_nothing;
688 	/*
689 	 * (potentially) printable character
690 	 */
691 	switch (state) {
692 	case ST_NORMAL:
693 		/*
694 		 * character is not part of an escape sequence
695 		 */
696 		/*
697 		 * translate to Unix wide character
698 		 */
699 		wc = is_graphics ? from_graph(c) : from_cpm(c);
700 		/*
701 		 * ignore untranslateable characters
702 		 */
703 		if (wc == (-1)) goto do_nothing;
704 		/*
705 		 * create curses representation of current character
706 		 * with all currently active attributes...
707 		 */
708 		wcs[0] = wc;
709 		wcs[1] = L'\0';
710 		pair = use_color ? get_pair(foreground, background) : 0;
711 		setcchar(&cc, wcs, ((is_standout ? A_STANDOUT : 0) |
712 		    (is_underline ? A_UNDERLINE : 0) |
713 		    (is_blink ? A_BLINK : 0) |
714 		    (is_reverse ? A_REVERSE : 0) |
715 		    (is_bold ? A_BOLD : 0)), pair, NULL);
716 		/*
717 		 * ... and add it to the VT52 screen
718 		 */
719 		wadd_wch(pad_p, &cc);
720 		if (cursor_x + 1 < cols) cursor_x++;
721 		goto redraw_screen;
722 	case ST_ESCAPE:
723 		/*
724 		 * second (and apart from escape-Y, last) character of
725 		 * an escape sequence
726 		 */
727 		state = ST_NORMAL;
728 		switch (c) {
729 		case 0x29 /* ) */:
730 			/*
731 			 * switch to application keypad mode (not implemented)
732 			 */
733 			app_keypad = 0;
734 			break;
735 		case 0x3d /* = */:
736 			/*
737 			 * switch to regular keypad mode (not implemented)
738 			 */
739 			app_keypad = 1;
740 			break;
741 		case 0x41 /* A */:
742 			/*
743 			 * cursor up, stop at first line
744 			 */
745 			if (cursor_y > 0) {
746 				cursor_y--;
747 				goto move_cursor;
748 			}
749 			break;
750 		case 0x42 /* B */:
751 			/*
752 			 * cursor down, stop at last line
753 			 */
754 			if (cursor_y + 1 < lines) {
755 				cursor_y++;
756 				goto move_cursor;
757 			}
758 			break;
759 		case 0x43 /* C */:
760 			/*
761 			 * cursor right, stop at last column
762 			 */
763 			if (cursor_x + 1 < cols) {
764 				cursor_x++;
765 				goto move_cursor;
766 			}
767 			break;
768 		case 0x44 /* D */:
769 			/*
770 			 * cursor left, stop at first column
771 			 */
772 			if (cursor_x > 0) {
773 				cursor_x--;
774 				goto move_cursor;
775 			}
776 			break;
777 		case 0x45 /* E */:
778 			/*
779 			 * clear screen, cursor home; extension to VT52
780 			 */
781 			cursor_x = 0;
782 			cursor_y = 0;
783 			werase(pad_p);
784 			goto redraw_screen;
785 		case 0x46 /* F */:
786 			/*
787 			 * switch codes 0x5e-0x7e to "graphics" mode
788 			 */
789 			is_graphics = 1;
790 			break;
791 		case 0x47 /* G */:
792 			/*
793 			 * switch codes 0x5e-0x7e to ASCII mode
794 			 */
795 			is_graphics = 0;
796 			break;
797 		case 0x48 /* H */:
798 			/*
799 			 * cursor home
800 			 */
801 			if (! cursor_x && ! cursor_y) goto do_nothing;
802 			cursor_x = 0;
803 			cursor_y = 0;
804 			goto move_cursor;
805 		case 0x49 /* I */:
806 			/*
807 			 * cursor up, scroll back at first line
808 			 */
809 			if (cursor_y > 0) {
810 				cursor_y--;
811 				goto move_cursor;
812 			}
813 			scrollok(pad_p, 1);
814 			wscrl(pad_p, (-1));
815 			scrollok(pad_p, 0);
816 			goto redraw_screen;
817 		case 0x4a /* J */:
818 			/*
819 			 * clear to end of screen
820 			 */
821 			wclrtobot(pad_p);
822 			goto redraw_screen;
823 		case 0x4b /* K */:
824 			/*
825 			 * clear to end of line
826 			 */
827 			wclrtoeol(pad_p);
828 			goto redraw_screen;
829 		case 0x4c /* L */:
830 			/*
831 			 * insert empty line at cursor; extension to VT52
832 			 */
833 			winsertln(pad_p);
834 			goto redraw_screen;
835 		case 0x4d /* M */:
836 			/*
837 			 * delete line at cursor; extension to VT52
838 			 */
839 			wdeleteln(pad_p);
840 			goto redraw_screen;
841 		case 0x4e /* N */:
842 			/*
843 			 * insert blank character at cursor; extension to VT52
844 			 */
845 			wins_wch(pad_p, &blank);
846 			goto redraw_screen;
847 		case 0x4f /* O */:
848 			/*
849 			 * delete character at cursor: extension to VT52
850 			 */
851 			wdelch(pad_p);
852 			goto redraw_screen;
853 		case 0x53 /* S */:
854 			/*
855 			 * set beckground color --- expect color code
856 			 */
857 			state = ST_ESCAPES;
858 			break;
859 		case 0x54 /* T */:
860 			/*
861 			 * set foreground color --- expect color code
862 			 */
863 			state = ST_ESCAPET;
864 			break;
865 		case 0x59 /* Y */:
866 			/*
867 			 * direct cursor positioning --- expect line number
868 			 */
869 			state = ST_ESCAPEY;
870 			break;
871 		case 0x5a /* Z */:
872 			/*
873 			 * send terminal identification: VT52 without
874 			 * hardcopy device
875 			 */
876 			in_put(0x1b /* ESC */);
877 			in_put(0x2f /* / */);
878 			in_put(0x4b /* K */);
879 			break;
880 		case 0x5b /* [ */:
881 			/*
882 			 * enter "hold screen" mode
883 			 */
884 			hold_screen = 1;
885 			hold_allow = cursor_y;
886 			break;
887 		case 0x5c /* \ */:
888 			/*
889 			 * exit "hold screen" mode
890 			 */
891 			hold_screen = 0;
892 			break;
893 		case 0x61 /* a */:
894 			/*
895 			 * turn off cursor; extension to VT52
896 			 */
897 			if (cursor_off) break;
898 			cursor_off = 1;
899 			if (old_cursor == ERR) break;
900 			old_cursor = curs_set(0);
901 			goto redraw_screen;
902 		case 0x62 /* b */:
903 			/*
904 			 * restore cursor; extension to VT52
905 			 */
906 			if (! cursor_off) break;
907 			if (old_cursor == ERR) break;
908 			curs_set(old_cursor);
909 			goto redraw_screen;
910 		case 0x63 /* c */:
911 			/*
912 			 * switch to alternate character set; extension to VT52
913 			 */
914 			charset = 1;
915 			break;
916 		case 0x64 /* d */:
917 			/*
918 			 * switch to regular character set; extension to VT52
919 			 */
920 			charset = 0;
921 			break;
922 		case 0x65 /* e */:
923 			/*
924 			 * bold on; extension to VT52
925 			 */
926 			is_bold = 1;
927 			break;
928 		case 0x66 /* f */:
929 			/*
930 			 * bold off; extension to VT52
931 			 */
932 			is_bold = 0;
933 			break;
934 		case 0x67 /* g */:
935 			/*
936 			 * underline on; extension to VT52
937 			 */
938 			is_underline = 1;
939 			break;
940 		case 0x68 /* h */:
941 			/*
942 			 * underline off; extension to VT52
943 			 */
944 			is_underline = 0;
945 			break;
946 		case 0x69 /* i */:
947 			/*
948 			 * reverse video on; extension to VT52
949 			 */
950 			is_reverse = 1;
951 			break;
952 		case 0x6a /* j */:
953 			/*
954 			 * reverse video off; extension to VT52
955 			 */
956 			is_reverse = 0;
957 			break;
958 		case 0x6b /* k */:
959 			/*
960 			 * blinking characters on; extension to VT52
961 			 */
962 			is_blink = 1;
963 			break;
964 		case 0x6c /* l */:
965 			/*
966 			 * blinking characters off; extension to VT52
967 			 */
968 			is_blink = 0;
969 			break;
970 		case 0x6d /* m */:
971 			/*
972 			 * all attributes off, reset colors to default; extension to VT52
973 			 */
974 			is_bold = 0;
975 			is_blink = 0;
976 			is_reverse = 0;
977 			is_underline = 0;
978 			is_standout = 0;
979 			foreground = conf_foreground;
980 			background = conf_background;
981 			break;
982 		case 0x6e /* n */:
983 			/*
984 			 * use alternate cursor keys; extension to VT52
985 			 */
986 			altkeys = 1;
987 			break;
988 		case 0x6f /* o */:
989 			/*
990 			 * use VT52 cursor keys; extension to VT52
991 			 */
992 			altkeys = 0;
993 			break;
994 		case 0x70 /* p */:
995 			/*
996 			 * standout (usually reverse) on; extension to VT52
997 			 */
998 			is_standout = 1;
999 			break;
1000 		case 0x71 /* q */:
1001 			/*
1002 			 * standout (usually reverse) off; extension to VT52
1003 			 */
1004 			is_standout = 0;
1005 			break;
1006 		/*
1007 		 * all other characters terminate the current escape
1008 		 * sequence, but otherwise have no effect
1009 		 */
1010 		}
1011 		break;
1012 	case ST_ESCAPEY:
1013 		/*
1014 		 * third character of an escape-Y sequence: line number
1015 		 */
1016 		state = ST_ESCAPEYL;
1017 		escape_y_line = c - 32;
1018 		break;
1019 	case ST_ESCAPEYL:
1020 		/*
1021 		 * fourth and last character of an escape-Y sequence:
1022 		 * column number
1023 		 */
1024 		state = ST_NORMAL;
1025 		escape_y_col = c - 32;
1026 		/*
1027 		 * a line number greater than the screen size positions
1028 		 * the cursor on the last line of the screen
1029 		 */
1030 		if (escape_y_line >= lines) escape_y_line = lines - 1;
1031 		/*
1032 		 * a column number greater than the screen size leaves
1033 		 * the column position unchanged
1034 		 */
1035 		if (escape_y_col >= cols) escape_y_col = cursor_x;
1036 		/*
1037 		 * ignore positioning to the current position
1038 		 */
1039 		if (escape_y_line != cursor_y || escape_y_col != cursor_x) {
1040 			cursor_y = escape_y_line;
1041 			cursor_x = escape_y_col;
1042 			goto move_cursor;
1043 		}
1044 		break;
1045 	case ST_ESCAPES:
1046 		/*
1047 		 * set background color
1048 		 */
1049 		state = ST_NORMAL;
1050 		background = set_color(c, background, conf_background);
1051 		break;
1052 	case ST_ESCAPET:
1053 		/*
1054 		 * set foreground color
1055 		 */
1056 		state = ST_NORMAL;
1057 		foreground = set_color(c, foreground, conf_foreground);
1058 		break;
1059 	}
1060 	goto do_nothing;
1061 redraw_screen:
1062 move_cursor:
1063 	/*
1064 	 * changing the screen contents and cursor positioning are likewise
1065 	 * handled by a screen refresh
1066 	 */
1067 	show_pad();
1068 do_nothing:
1069 	return;
1070 }
1071 
1072 
1073 /*
1074  * polls the keyboard to keep things like "hold screen" feature and
1075  * screen redrawing functional, even in the absence of regular
1076  * calls to crt_in() or crt_status()
1077  */
1078 void
crt_poll(void)1079 crt_poll(void) {
1080 	/*
1081 	 * do a non-blocking read from the screen
1082 	 */
1083 	if (! noblock) {
1084 		nodelay(pad_p, 1);
1085 		noblock = 1;
1086 	}
1087 	try_read();
1088 }
1089 
1090 
1091 /*
1092  * return 1 if a character can be read from the emulated terminal,
1093  * otherwise return 0
1094  */
1095 int
crt_status(void)1096 crt_status(void) {
1097 	int rc = 0;
1098 	if (in_count) {
1099 		/*
1100 		 * input queue not empty
1101 		 */
1102 		rc = 1;
1103 	} else {
1104 		/*
1105 		 * poll the keyboard
1106 		 */
1107 		crt_poll();
1108 		/*
1109 		 * this might result in characters being deposited
1110 		 * in the input queue.
1111 		 */
1112 		if (in_count) rc = 1;
1113 	}
1114 	return rc;
1115 }
1116 
1117 
1118 /*
1119  * return a character from the emulated terminal, block until one is available
1120  */
1121 unsigned char
crt_in(void)1122 crt_in(void) {
1123 	int t;
1124 	while ((t = in_get()) == (-1)) {
1125 		/*
1126 		 * do a blocking read from the screen
1127 		 */
1128 		if (noblock) {
1129 			nodelay(pad_p, 0);
1130 			noblock = 0;
1131 		}
1132 		try_read();
1133 	}
1134 	return (unsigned char) t;
1135 }
1136 
1137 
1138 /*
1139  * reset emulated terminal
1140  */
1141 void
crt_exit(void)1142 crt_exit(void) {
1143 	struct timeval tv;
1144 	/*
1145 	 * insert delay to allow reading of the final screen contents
1146 	 */
1147 	switch (screen_delay) {
1148 	case (-1):
1149 		/*
1150 		 * wait for a keypress
1151 		 */
1152 		crt_in();
1153 		break;
1154 	case 0:
1155 		/*
1156 		 * no delay
1157 		 */
1158 		break;
1159 	default:
1160 		/*
1161 		 * wait the specified number of seconds
1162 		 */
1163 		tv.tv_sec = screen_delay;
1164 		tv.tv_usec = 0;
1165 		select(0, NULL, NULL, NULL, &tv);
1166 		break;
1167 	}
1168 	reset_curses();
1169 }
1170