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