3 /* Purpose: Support for "term.c" using "termcap" calls */
5 #include "io/exit-panic.h"
6 #include "system/angband.h"
7 #include "term/gameterm.h"
8 #include "term/z-form.h"
13 * This file is a total hack, but is often very helpful. :-)
15 * This file allows use of the terminal without requiring the
16 * "curses" routines. In fact, if "USE_HARDCODE" is defined,
17 * this file will attempt to use various hard-coded "vt100"
18 * escape sequences to also avoid the use of the "termcap"
19 * routines. I do not know if this will work on System V.
21 * This file is intended for use only on those machines which are
22 * unable, for whatever reason, to compile the "main-gcu.c" file,
23 * but which seem to be able to support the "termcap" library, or
24 * which at least seem able to support "vt100" terminals.
26 * Large portions of this file were stolen from "main-gcu.c"
28 * This file incorrectly handles output to column 80, I think.
34 #if !defined(USE_TERMCAP) && !defined(USE_HARDCODE)
39 * Hack -- try to guess which systems use what commands
40 * Hack -- allow one of the "USE_Txxxxx" flags to be pre-set.
41 * Mega-Hack -- try to guess when "POSIX" is available.
42 * If the user defines two of these, we will probably crash.
54 #include <sys/ioctl.h>
59 * One version needs these files
62 #include <sys/ioctl.h>
67 * The other needs these files
71 #include <sys/ioctl.h>
72 #include <sys/param.h>
73 #include <sys/resource.h>
74 #include <sys/types.h>
78 * XXX XXX Hack -- POSIX uses "O_NONBLOCK" instead of "O_NDELAY"
80 * They should both work due to the "(i != 1)" test in the code
81 * which checks for the result of the "read()" command.
84 #define O_NDELAY O_NONBLOCK
90 * Termcap string information
93 static char blob[1024]; /* The "termcap" entry */
94 static char area[1024]; /* The string extraction buffer */
95 static char *next = area; /* The current "index" into "area" */
96 static char *desc; /* The terminal name */
101 * Pointers into the "area"
104 static char *cm; /* Move cursor */
105 static char *ch; /* Move cursor to horizontal location */
106 static char *cv; /* Move cursor to vertical location */
107 static char *ho; /* Move cursor to top left */
108 static char *ll; /* Move cursor to bottom left */
109 static char *cs; /* Set scroll area */
110 static char *cl; /* Clear screen */
111 static char *cd; /* Clear to end of display */
112 static char *ce; /* Clear to end of line */
113 static char *cr; /* Move to start of line */
114 static char *so; /* Turn on standout */
115 static char *se; /* Turn off standout */
116 static char *md; /* Turn on bold */
117 static char *me; /* Turn off bold */
118 static char *vi; /* Cursor - invisible */
119 static char *ve; /* Cursor - normal */
120 static char *vs; /* Cursor - bright */
126 static int rows; /* Screen size (Y) */
127 static int cols; /* Screen size (X) */
128 static int curx; /* Cursor location (X) */
129 static int cury; /* Cursor location (Y) */
130 static int curv; /* Cursor visibility */
135 extern char *getenv();
136 extern char *tgoto();
137 extern char *tgetstr();
140 * Write some chars to the terminal
142 static void ewrite(char *str)
144 int numtowrite, numwritten;
146 /* See how much work we have */
147 numtowrite = strlen(str);
149 /* Write until done */
150 while (numtowrite > 0) {
151 /* Try to write the chars */
152 numwritten = write(1, str, numtowrite);
154 /* Handle FIFOs and EINTR */
155 if (numwritten < 0) {
159 /* See what we completed */
160 numtowrite -= numwritten;
163 /* Hack -- sleep if not done */
164 if (numtowrite > 0) {
172 static char write_buffer[128];
173 static char *write_buffer_ptr;
175 static void output_one(char c)
177 *write_buffer_ptr++ = c;
180 static void tp(char *s)
182 /* Dump the string into us */
183 write_buffer_ptr = write_buffer;
185 /* Write the string with padding */
186 tputs(s, 1, output_one);
188 /* Finish the string */
189 *write_buffer_ptr = '\0';
191 /* Dump the recorded buffer */
192 ewrite(write_buffer);
199 static void tp(char *s)
209 static void do_cl(void)
217 * Clear to the end of the line
219 static void do_ce(void)
227 * Set the cursor visibility (0 = invis, 1 = normal, 2 = bright)
229 static void curs_set(int vis)
235 } else if (vis > 1) {
247 * Restrict scrolling to within these rows
249 static void do_cs(int y1, int y2)
254 tp(tgoto(cs, y2, y1));
260 strnfmt(temp, sizeof(temp), cs, y1, y2);
266 * Go to the given screen location directly
268 static void do_cm(int x, int y)
279 strnfmt(temp, sizeof(temp), cm, y + 1, x + 1);
285 * Go to the given screen location in a "clever" manner
287 * XXX XXX XXX This function could use some work!
289 static void do_move(int x1, int y1, int x2, int y2)
291 /* Hack -- unknown start location */
292 if ((x1 == x2) && (y1 == y2)) {
298 if ((y2 <= 0) && ho) {
300 } else if ((y2 >= rows - 1) && ll) {
302 } else if ((y2 == y1) && cr) {
309 /* Default -- go directly there */
316 * Help initialize this file (see below)
318 errr init_cap_aux(void)
323 /* Get the terminal name (if possible) */
324 desc = getenv("TERM");
329 /* Get the terminal info */
330 if (tgetent(blob, desc) != 1) {
334 /* Get the (initial) columns and rows, or default */
335 if ((cols = tgetnum("co")) == -1) {
336 cols = TERM_DEFAULT_COLS;
338 if ((rows = tgetnum("li")) == -1) {
339 rows = TERM_DEFAULT_ROWS;
342 /* Find out how to move the cursor to a given location */
343 cm = tgetstr("cm", &next);
348 /* Find out how to move the cursor to a given position */
349 ch = tgetstr("ch", &next);
350 cv = tgetstr("cv", &next);
352 /* Find out how to "home" the screen */
353 ho = tgetstr("ho", &next);
355 /* Find out how to "last-line" the screen */
356 ll = tgetstr("ll", &next);
358 /* Find out how to do a "carriage return" */
359 cr = tgetstr("cr", &next);
364 /* Find out how to clear the screen */
365 cl = tgetstr("cl", &next);
370 /* Find out how to clear to the end of display */
371 cd = tgetstr("cd", &next);
373 /* Find out how to clear to the end of the line */
374 ce = tgetstr("ce", &next);
376 /* Find out how to scroll (set the scroll region) */
377 cs = tgetstr("cs", &next);
379 /* Find out how to hilite */
380 so = tgetstr("so", &next);
381 se = tgetstr("se", &next);
386 /* Find out how to bold */
387 md = tgetstr("md", &next);
388 me = tgetstr("me", &next);
393 /* Check the cursor visibility stuff */
394 vi = tgetstr("vi", &next);
395 vs = tgetstr("vs", &next);
396 ve = tgetstr("ve", &next);
402 /* Assume some defualt information */
403 rows = TERM_DEFAULT_ROWS;
404 cols = TERM_DEFAULT_COLS;
407 cl = "\033[2J\033[H"; /* --]--]-- */
409 /* Clear to end of line */
410 ce = "\033[K"; /* --]-- */
413 so = "\033[7m"; /* --]-- */
414 se = "\033[m"; /* --]-- */
417 cs = "\033[%d;%dr"; /* --]-- */
420 cm = "\033[%d;%dH"; /* --]-- */
429 * Save the "normal" and "angband" terminal settings
434 static struct termios norm_termios;
436 static struct termios game_termios;
442 static struct termio norm_termio;
444 static struct termio game_termio;
450 static struct sgttyb norm_ttyb;
451 static struct tchars norm_tchars;
452 static struct ltchars norm_ltchars;
453 static int norm_local_chars;
455 static struct sgttyb game_ttyb;
456 static struct tchars game_tchars;
457 static struct ltchars game_ltchars;
458 static int game_local_chars;
463 * Are we active? Not really needed.
465 static int active = false;
468 * The main screen (no sub-screens)
470 static term term_screen_body;
473 * Place the "keymap" into its "normal" state
475 static void keymap_norm(void)
480 /* restore the saved values of the special chars */
481 (void)tcsetattr(0, TCSAFLUSH, &norm_termios);
487 /* restore the saved values of the special chars */
488 (void)ioctl(0, TCSETA, (char *)&norm_termio);
494 /* restore the saved values of the special chars */
495 (void)ioctl(0, TIOCSETP, (char *)&norm_ttyb);
496 (void)ioctl(0, TIOCSETC, (char *)&norm_tchars);
497 (void)ioctl(0, TIOCSLTC, (char *)&norm_ltchars);
498 (void)ioctl(0, TIOCLSET, (char *)&norm_local_chars);
504 * Place the "keymap" into the "game" state
506 static void keymap_game(void)
511 /* restore the saved values of the special chars */
512 (void)tcsetattr(0, TCSAFLUSH, &game_termios);
518 /* restore the saved values of the special chars */
519 (void)ioctl(0, TCSETA, (char *)&game_termio);
525 /* restore the saved values of the special chars */
526 (void)ioctl(0, TIOCSETP, (char *)&game_ttyb);
527 (void)ioctl(0, TIOCSETC, (char *)&game_tchars);
528 (void)ioctl(0, TIOCSLTC, (char *)&game_ltchars);
529 (void)ioctl(0, TIOCLSET, (char *)&game_local_chars);
535 * Save the normal keymap
537 static void keymap_norm_prepare(void)
542 /* Get the normal keymap */
543 tcgetattr(0, &norm_termios);
549 /* Get the normal keymap */
550 (void)ioctl(0, TCGETA, (char *)&norm_termio);
556 /* Get the normal keymap */
557 (void)ioctl(0, TIOCGETP, (char *)&norm_ttyb);
558 (void)ioctl(0, TIOCGETC, (char *)&norm_tchars);
559 (void)ioctl(0, TIOCGLTC, (char *)&norm_ltchars);
560 (void)ioctl(0, TIOCLGET, (char *)&norm_local_chars);
566 * Save the keymaps (normal and game)
568 static void keymap_game_prepare(void)
573 /* Acquire the current mapping */
574 tcgetattr(0, &game_termios);
576 /* Force "Ctrl-C" to interupt */
577 game_termios.c_cc[VINTR] = (char)3;
579 /* Force "Ctrl-Z" to suspend */
580 game_termios.c_cc[VSUSP] = (char)26;
582 /* Hack -- Leave "VSTART/VSTOP" alone */
584 /* Disable the standard control characters */
585 game_termios.c_cc[VQUIT] = (char)-1;
586 game_termios.c_cc[VERASE] = (char)-1;
587 game_termios.c_cc[VKILL] = (char)-1;
588 game_termios.c_cc[VEOF] = (char)-1;
589 game_termios.c_cc[VEOL] = (char)-1;
591 /* Normally, block until a character is read */
592 game_termios.c_cc[VMIN] = 1;
593 game_termios.c_cc[VTIME] = 0;
595 /* Hack -- Turn off "echo" and "canonical" mode */
596 game_termios.c_lflag &= ~(ECHO | ICANON);
598 /* Turn off flow control */
599 game_termios.c_iflag &= ~IXON;
605 /* Acquire the current mapping */
606 (void)ioctl(0, TCGETA, (char *)&game_termio);
608 /* Force "Ctrl-C" to interupt */
609 game_termio.c_cc[VINTR] = (char)3;
611 /* Force "Ctrl-Z" to suspend */
612 game_termio.c_cc[VSUSP] = (char)26;
614 /* Hack -- Leave "VSTART/VSTOP" alone */
616 /* Disable the standard control characters */
617 game_termio.c_cc[VQUIT] = (char)-1;
618 game_termio.c_cc[VERASE] = (char)-1;
619 game_termio.c_cc[VKILL] = (char)-1;
620 game_termio.c_cc[VEOF] = (char)-1;
621 game_termio.c_cc[VEOL] = (char)-1;
623 /* Normally, block until a character is read */
624 game_termio.c_cc[VMIN] = 1;
625 game_termio.c_cc[VTIME] = 0;
627 /* Hack -- Turn off "echo" and "canonical" mode */
628 game_termio.c_lflag &= ~(ECHO | ICANON);
630 /* Turn off flow control */
631 game_termio.c_iflag &= ~IXON;
637 /* Get the default game characters */
638 (void)ioctl(0, TIOCGETP, (char *)&game_ttyb);
639 (void)ioctl(0, TIOCGETC, (char *)&game_tchars);
640 (void)ioctl(0, TIOCGLTC, (char *)&game_ltchars);
641 (void)ioctl(0, TIOCLGET, (char *)&game_local_chars);
643 /* Force interupt (^C) */
644 game_tchars.t_intrc = (char)3;
646 /* Force start/stop (^Q, ^S) */
647 game_tchars.t_startc = (char)17;
648 game_tchars.t_stopc = (char)19;
650 /* Cancel some things */
651 game_tchars.t_quitc = (char)-1;
652 game_tchars.t_eofc = (char)-1;
653 game_tchars.t_brkc = (char)-1;
655 /* Force suspend (^Z) */
656 game_ltchars.t_suspc = (char)26;
658 /* Cancel some things */
659 game_ltchars.t_dsuspc = (char)-1;
660 game_ltchars.t_rprntc = (char)-1;
661 game_ltchars.t_flushc = (char)-1;
662 game_ltchars.t_werasc = (char)-1;
663 game_ltchars.t_lnextc = (char)-1;
665 /* XXX XXX XXX XXX Verify this before use */
666 /* Hack -- Turn off "echo" and "canonical" mode */
667 /* game_termios.c_lflag &= ~(ECHO | ICANON); */
668 game_ttyb.flag &= ~(ECHO | ICANON);
670 /* XXX XXX XXX Should maybe turn off flow control too. How? */
678 static errr game_term_xtra_cap_alive(int v)
686 /* Hack -- make sure the cursor is visible */
689 /* Move to bottom right */
690 do_move(0, rows - 1, 0, rows - 1);
692 /* Go to normal keymap mode */
695 /* No longer active */
705 /* Hack -- restore the cursor location */
706 do_move(curx, cury, curx, cury);
708 /* Hack -- restore the cursor visibility */
711 /* Go to angband keymap mode */
714 /* Now we are active */
725 static errr game_term_xtra_cap_event(int v)
732 /* Wait for one byte */
735 /* Hack -- Handle "errors" */
736 if ((i <= 0) && (errno != EINTR)) {
737 exit_game_panic(p_ptr);
743 /* Get the current flags for stdin */
744 if ((arg = fcntl(0, F_GETFL, 0)) < 1) {
748 /* Tell stdin not to block */
749 if (fcntl(0, F_SETFL, arg | O_NDELAY) < 0) {
753 /* Read one byte, if possible */
756 /* Replace the flags for stdin */
757 if (fcntl(0, F_SETFL, arg)) {
763 if ((i != 1) || (!buf[0])) {
767 /* Enqueue the keypress */
768 game_term_keypress(buf[0]);
775 * Actually move the hardware cursor
777 static errr game_term_curs_cap(int x, int y)
779 /* Literally move the cursor */
780 do_move(curx, cury, x, y);
782 /* Save the cursor location */
791 * Erase a grid of space
793 * XXX XXX XXX Note that we will never be asked to clear the
794 * bottom line all the way to the bottom right edge, since we
795 * have set the "avoid the bottom right corner" flag.
797 static errr game_term_wipe_cap(int x, int y, int n)
801 /* Place the cursor */
802 game_term_curs_cap(x, y);
804 /* Wipe to end of line */
805 if (x + n >= MAIN_TERM_MIN_COLS) {
811 for (dx = 0; dx < n; ++dx) {
822 * Place some text on the screen using an attribute
824 static errr game_term_text_cap(int x, int y, int n, byte a, concptr s)
828 /* Move the cursor */
829 game_term_curs_cap(x, y);
831 /* Dump the text, advance the cursor */
832 for (i = 0; s[i]; i++) {
836 /* Advance cursor 'X', and wrap */
837 if (++curx >= cols) {
838 /* Reset cursor 'X' */
841 /* Hack -- Advance cursor 'Y', and wrap */
842 if (++cury == rows) {
853 * Handle a "special request"
855 static errr game_term_xtra_cap(int n, int v)
857 /* Analyze the request */
859 /* Clear the screen */
860 case TERM_XTRA_CLEAR:
866 case TERM_XTRA_NOISE:
867 (void)write(1, "\007", 1);
870 /* Change the cursor visibility */
871 case TERM_XTRA_SHAPE:
877 case TERM_XTRA_ALIVE:
878 return game_term_xtra_cap_alive(v);
881 case TERM_XTRA_EVENT:
882 return game_term_xtra_cap_event(v);
885 case TERM_XTRA_FLUSH:
886 while (!game_term_xtra_cap_event(false)) {
892 case TERM_XTRA_DELAY:
902 * Init a "term" for this file
904 static void game_term_init_cap(term *t)
910 /* Assume cursor at top left */
914 /* Assume visible cursor */
917 /* Clear the screen */
920 /* Hack -- visible cursor */
928 * Nuke a "term" for this file
930 static void game_term_nuke_cap(term *t)
936 /* Hack -- make sure the cursor is visible */
939 /* Move to bottom right */
940 do_move(0, rows - 1, 0, rows - 1);
945 /* No longer active */
950 * Prepare this file for Angband usage
954 term *t = &term_screen_body;
958 /* Initialize the screen */
959 if (init_cap_aux()) {
963 /* Hack -- Require large screen, or Quit with message */
964 if ((rows < MAIN_TERM_MIN_ROWS) || (cols < MAIN_TERM_MIN_COLS)) {
965 quit("Screen too small!");
968 /*** Prepare to play ***/
970 /* Extract the normal keymap */
971 keymap_norm_prepare();
973 /* Extract the game keymap */
974 keymap_game_prepare();
976 /* Hack -- activate the game keymap */
979 /* Hack -- Do NOT buffer stdout */
980 setbuf(stdout, nullptr);
982 /*** Now prepare the term ***/
984 /* Initialize the term */
985 term_init(t, TERM_DEFAULT_COLS, TERM_DEFAULT_ROWS, 256);
987 /* Avoid the bottom right corner */
988 t->icky_corner = true;
990 /* Erase with "white space" */
991 t->attr_blank = TERM_WHITE;
995 t->init_hook = game_term_init_cap;
996 t->nuke_hook = game_term_nuke_cap;
998 /* Set some more hooks */
999 t->text_hook = game_term_text_cap;
1000 t->wipe_hook = game_term_wipe_cap;
1001 t->curs_hook = game_term_curs_cap;
1002 t->xtra_hook = game_term_xtra_cap;
1008 game_term_activate(term_screen);
1014 #endif /* USE_CAP */