3 /* Purpose: Support for "term.c" using "termcap" calls */
12 * This file is a total hack, but is often very helpful. :-)
14 * This file allows use of the terminal without requiring the
15 * "curses" routines. In fact, if "USE_HARDCODE" is defined,
16 * this file will attempt to use various hard-coded "vt100"
17 * escape sequences to also avoid the use of the "termcap"
18 * routines. I do not know if this will work on System V.
20 * This file is intended for use only on those machines which are
21 * unable, for whatever reason, to compile the "main-gcu.c" file,
22 * but which seem to be able to support the "termcap" library, or
23 * which at least seem able to support "vt100" terminals.
25 * Large portions of this file were stolen from "main-gcu.c"
27 * 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.
44 #if !defined(USE_TPOSIX)
45 # if !defined(USE_TERMIO) && !defined(USE_TCHARS)
46 # if defined(_POSIX_VERSION)
49 # if defined(USG) || defined(linux) || defined(SOLARIS)
64 # include <sys/ioctl.h>
69 * One version needs these files
72 # include <sys/ioctl.h>
77 * The other needs these files
80 # include <sys/ioctl.h>
81 # include <sys/resource.h>
82 # include <sys/param.h>
83 # include <sys/file.h>
84 # include <sys/types.h>
89 * XXX XXX Hack -- POSIX uses "O_NONBLOCK" instead of "O_NDELAY"
91 * They should both work due to the "(i != 1)" test in the code
92 * which checks for the result of the "read()" command.
95 # define O_NDELAY O_NONBLOCK
104 * Termcap string information
107 static char blob[1024]; /* The "termcap" entry */
108 static char area[1024]; /* The string extraction buffer */
109 static char *next = area; /* The current "index" into "area" */
110 static char *desc; /* The terminal name */
116 * Pointers into the "area"
119 static char *cm; /* Move cursor */
120 static char *ch; /* Move cursor to horizontal location */
121 static char *cv; /* Move cursor to vertical location */
122 static char *ho; /* Move cursor to top left */
123 static char *ll; /* Move cursor to bottom left */
124 static char *cs; /* Set scroll area */
125 static char *cl; /* Clear screen */
126 static char *cd; /* Clear to end of display */
127 static char *ce; /* Clear to end of line */
128 static char *cr; /* Move to start of line */
129 static char *so; /* Turn on standout */
130 static char *se; /* Turn off standout */
131 static char *md; /* Turn on bold */
132 static char *me; /* Turn off bold */
133 static char *vi; /* Cursor - invisible */
134 static char *ve; /* Cursor - normal */
135 static char *vs; /* Cursor - bright */
142 static int rows; /* Screen size (Y) */
143 static int cols; /* Screen size (X) */
144 static int curx; /* Cursor location (X) */
145 static int cury; /* Cursor location (Y) */
146 static int curv; /* Cursor visibility */
152 extern char *getenv();
153 extern char *tgoto();
154 extern char *tgetstr();
158 * Write some chars to the terminal
160 static void ewrite(char *str)
162 int numtowrite, numwritten;
164 /* See how much work we have */
165 numtowrite = strlen(str);
167 /* Write until done */
168 while (numtowrite > 0)
170 /* Try to write the chars */
171 numwritten = write(1, str, numtowrite);
173 /* Handle FIFOs and EINTR */
174 if (numwritten < 0) numwritten = 0;
176 /* See what we completed */
177 numtowrite -= numwritten;
180 /* Hack -- sleep if not done */
181 if (numtowrite > 0) sleep(1);
189 static char write_buffer[128];
190 static char *write_buffer_ptr;
192 static void output_one(char c)
194 *write_buffer_ptr++ = c;
197 static void tp(char *s)
199 /* Dump the string into us */
200 write_buffer_ptr = write_buffer;
202 /* Write the string with padding */
203 tputs (s, 1, output_one);
205 /* Finish the string */
206 *write_buffer_ptr = '\0';
208 /* Dump the recorded buffer */
209 ewrite (write_buffer);
216 static void tp(char *s)
232 static void do_cl(void)
238 * Clear to the end of the line
240 static void do_ce(void)
247 * Set the cursor visibility (0 = invis, 1 = normal, 2 = bright)
249 static void curs_set(int vis)
272 * Restrict scrolling to within these rows
274 static void do_cs(int y1, int y2)
278 if (cs) tp(tgoto(cs, y2, y1));
283 sprintf(temp, cs, y1, y2);
292 * Go to the given screen location directly
294 static void do_cm(int x, int y)
298 if (cm) tp(tgoto(cm, x, y));
303 sprintf(temp, cm, y+1, x+1);
311 * Go to the given screen location in a "clever" manner
313 * XXX XXX XXX This function could use some work!
315 static void do_move(int x1, int y1, int x2, int y2)
317 /* Hack -- unknown start location */
318 if ((x1 == x2) && (y1 == y2)) do_cm(x2, y2);
323 if ((y2 <= 0) && ho) tp(ho);
324 else if ((y2 >= rows-1) && ll) tp(ll);
325 else if ((y2 == y1) && cr) tp(cr);
327 else if ((y2 == y1+1) && cr && dn)
329 else if ((y2 == y1-1) && cr && up)
336 /* Up/Down one line */
337 else if ((x2 == x1) && (y2 == y1+1) && dn) tp(dn);
338 else if ((x2 == x1) && (y2 == y1-1) && up) tp(up);
341 /* Default -- go directly there */
349 * Help initialize this file (see below)
351 errr init_cap_aux(void)
356 /* Get the terminal name (if possible) */
357 desc = getenv("TERM");
358 if (!desc) return (1);
360 /* Get the terminal info */
361 if (tgetent(blob, desc) != 1) return (2);
363 /* Get the (initial) columns and rows, or default */
364 if ((cols = tgetnum("co")) == -1) cols = 80;
365 if ((rows = tgetnum("li")) == -1) rows = 24;
367 /* Find out how to move the cursor to a given location */
368 cm = tgetstr("cm", &next);
369 if (!cm) return (10);
371 /* Find out how to move the cursor to a given position */
372 ch = tgetstr("ch", &next);
373 cv = tgetstr("cv", &next);
375 /* Find out how to "home" the screen */
376 ho = tgetstr("ho", &next);
378 /* Find out how to "last-line" the screen */
379 ll = tgetstr("ll", &next);
381 /* Find out how to do a "carriage return" */
382 cr = tgetstr("cr", &next);
385 /* Find out how to clear the screen */
386 cl = tgetstr("cl", &next);
387 if (!cl) return (11);
389 /* Find out how to clear to the end of display */
390 cd = tgetstr("cd", &next);
392 /* Find out how to clear to the end of the line */
393 ce = tgetstr("ce", &next);
395 /* Find out how to scroll (set the scroll region) */
396 cs = tgetstr("cs", &next);
398 /* Find out how to hilite */
399 so = tgetstr("so", &next);
400 se = tgetstr("se", &next);
401 if (!so || !se) so = se = NULL;
403 /* Find out how to bold */
404 md = tgetstr("md", &next);
405 me = tgetstr("me", &next);
406 if (!md || !me) md = me = NULL;
408 /* Check the cursor visibility stuff */
409 vi = tgetstr("vi", &next);
410 vs = tgetstr("vs", &next);
411 ve = tgetstr("ve", &next);
417 /* Assume some defualt information */
422 cl = "\033[2J\033[H"; /* --]--]-- */
424 /* Clear to end of line */
425 ce = "\033[K"; /* --]-- */
428 so = "\033[7m"; /* --]-- */
429 se = "\033[m"; /* --]-- */
432 cs = "\033[%d;%dr"; /* --]-- */
435 cm = "\033[%d;%dH"; /* --]-- */
450 * Save the "normal" and "angband" terminal settings
455 static struct termios norm_termios;
457 static struct termios game_termios;
463 static struct termio norm_termio;
465 static struct termio game_termio;
471 static struct sgttyb norm_ttyb;
472 static struct tchars norm_tchars;
473 static struct ltchars norm_ltchars;
474 static int norm_local_chars;
476 static struct sgttyb game_ttyb;
477 static struct tchars game_tchars;
478 static struct ltchars game_ltchars;
479 static int game_local_chars;
486 * Are we active? Not really needed.
488 static int active = FALSE;
492 * The main screen (no sub-screens)
494 static term term_screen_body;
499 * Place the "keymap" into its "normal" state
501 static void keymap_norm(void)
506 /* restore the saved values of the special chars */
507 (void)tcsetattr(0, TCSAFLUSH, &norm_termios);
513 /* restore the saved values of the special chars */
514 (void)ioctl(0, TCSETA, (char *)&norm_termio);
520 /* restore the saved values of the special chars */
521 (void)ioctl(0, TIOCSETP, (char *)&norm_ttyb);
522 (void)ioctl(0, TIOCSETC, (char *)&norm_tchars);
523 (void)ioctl(0, TIOCSLTC, (char *)&norm_ltchars);
524 (void)ioctl(0, TIOCLSET, (char *)&norm_local_chars);
532 * Place the "keymap" into the "game" state
534 static void keymap_game(void)
539 /* restore the saved values of the special chars */
540 (void)tcsetattr(0, TCSAFLUSH, &game_termios);
546 /* restore the saved values of the special chars */
547 (void)ioctl(0, TCSETA, (char *)&game_termio);
553 /* restore the saved values of the special chars */
554 (void)ioctl(0, TIOCSETP, (char *)&game_ttyb);
555 (void)ioctl(0, TIOCSETC, (char *)&game_tchars);
556 (void)ioctl(0, TIOCSLTC, (char *)&game_ltchars);
557 (void)ioctl(0, TIOCLSET, (char *)&game_local_chars);
565 * Save the normal keymap
567 static void keymap_norm_prepare(void)
572 /* Get the normal keymap */
573 tcgetattr(0, &norm_termios);
579 /* Get the normal keymap */
580 (void)ioctl(0, TCGETA, (char *)&norm_termio);
586 /* Get the normal keymap */
587 (void)ioctl(0, TIOCGETP, (char *)&norm_ttyb);
588 (void)ioctl(0, TIOCGETC, (char *)&norm_tchars);
589 (void)ioctl(0, TIOCGLTC, (char *)&norm_ltchars);
590 (void)ioctl(0, TIOCLGET, (char *)&norm_local_chars);
598 * Save the keymaps (normal and game)
600 static void keymap_game_prepare(void)
605 /* Acquire the current mapping */
606 tcgetattr(0, &game_termios);
608 /* Force "Ctrl-C" to interupt */
609 game_termios.c_cc[VINTR] = (char)3;
611 /* Force "Ctrl-Z" to suspend */
612 game_termios.c_cc[VSUSP] = (char)26;
614 /* Hack -- Leave "VSTART/VSTOP" alone */
616 /* Disable the standard control characters */
617 game_termios.c_cc[VQUIT] = (char)-1;
618 game_termios.c_cc[VERASE] = (char)-1;
619 game_termios.c_cc[VKILL] = (char)-1;
620 game_termios.c_cc[VEOF] = (char)-1;
621 game_termios.c_cc[VEOL] = (char)-1;
623 /* Normally, block until a character is read */
624 game_termios.c_cc[VMIN] = 1;
625 game_termios.c_cc[VTIME] = 0;
627 /* Hack -- Turn off "echo" and "canonical" mode */
628 game_termios.c_lflag &= ~(ECHO | ICANON);
630 /* Turn off flow control */
631 game_termios.c_iflag &= ~IXON;
637 /* Acquire the current mapping */
638 (void)ioctl(0, TCGETA, (char *)&game_termio);
640 /* Force "Ctrl-C" to interupt */
641 game_termio.c_cc[VINTR] = (char)3;
643 /* Force "Ctrl-Z" to suspend */
644 game_termio.c_cc[VSUSP] = (char)26;
646 /* Hack -- Leave "VSTART/VSTOP" alone */
648 /* Disable the standard control characters */
649 game_termio.c_cc[VQUIT] = (char)-1;
650 game_termio.c_cc[VERASE] = (char)-1;
651 game_termio.c_cc[VKILL] = (char)-1;
652 game_termio.c_cc[VEOF] = (char)-1;
653 game_termio.c_cc[VEOL] = (char)-1;
656 /* Disable the non-posix control characters */
657 game_termio.c_cc[VEOL2] = (char)-1;
658 game_termio.c_cc[VSWTCH] = (char)-1;
659 game_termio.c_cc[VDSUSP] = (char)-1;
660 game_termio.c_cc[VREPRINT] = (char)-1;
661 game_termio.c_cc[VDISCARD] = (char)-1;
662 game_termio.c_cc[VWERASE] = (char)-1;
663 game_termio.c_cc[VLNEXT] = (char)-1;
664 game_termio.c_cc[VSTATUS] = (char)-1;
667 /* Normally, block until a character is read */
668 game_termio.c_cc[VMIN] = 1;
669 game_termio.c_cc[VTIME] = 0;
671 /* Hack -- Turn off "echo" and "canonical" mode */
672 game_termio.c_lflag &= ~(ECHO | ICANON);
674 /* Turn off flow control */
675 game_termio.c_iflag &= ~IXON;
681 /* Get the default game characters */
682 (void)ioctl(0, TIOCGETP, (char *)&game_ttyb);
683 (void)ioctl(0, TIOCGETC, (char *)&game_tchars);
684 (void)ioctl(0, TIOCGLTC, (char *)&game_ltchars);
685 (void)ioctl(0, TIOCLGET, (char *)&game_local_chars);
687 /* Force interupt (^C) */
688 game_tchars.t_intrc = (char)3;
690 /* Force start/stop (^Q, ^S) */
691 game_tchars.t_startc = (char)17;
692 game_tchars.t_stopc = (char)19;
694 /* Cancel some things */
695 game_tchars.t_quitc = (char)-1;
696 game_tchars.t_eofc = (char)-1;
697 game_tchars.t_brkc = (char)-1;
699 /* Force suspend (^Z) */
700 game_ltchars.t_suspc = (char)26;
702 /* Cancel some things */
703 game_ltchars.t_dsuspc = (char)-1;
704 game_ltchars.t_rprntc = (char)-1;
705 game_ltchars.t_flushc = (char)-1;
706 game_ltchars.t_werasc = (char)-1;
707 game_ltchars.t_lnextc = (char)-1;
709 /* XXX XXX XXX XXX Verify this before use */
710 /* Hack -- Turn off "echo" and "canonical" mode */
711 /* game_termios.c_lflag &= ~(ECHO | ICANON); */
712 game_ttyb.flag &= ~(ECHO | ICANON);
714 /* XXX XXX XXX Should maybe turn off flow control too. How? */
730 static errr Term_xtra_cap_alive(int v)
735 if (!active) return (1);
737 /* Hack -- make sure the cursor is visible */
740 /* Move to bottom right */
741 do_move(0, rows - 1, 0, rows - 1);
743 /* Go to normal keymap mode */
746 /* No longer active */
753 if (active) return (1);
755 /* Hack -- restore the cursor location */
756 do_move(curx, cury, curx, cury);
758 /* Hack -- restore the cursor visibility */
761 /* Go to angband keymap mode */
764 /* Now we are active */
777 static errr Term_xtra_cap_event(int v)
785 /* Wait for one byte */
788 /* Hack -- Handle "errors" */
789 if ((i <= 0) && (errno != EINTR)) exit_game_panic();
795 /* Get the current flags for stdin */
796 if ((arg = fcntl(0, F_GETFL, 0)) < 1) return (1);
798 /* Tell stdin not to block */
799 if (fcntl(0, F_SETFL, arg | O_NDELAY) < 0) return (1);
801 /* Read one byte, if possible */
804 /* Replace the flags for stdin */
805 if (fcntl(0, F_SETFL, arg)) return (1);
809 if ((i != 1) || (!buf[0])) return (1);
811 /* Enqueue the keypress */
812 Term_keypress(buf[0]);
822 * Actually move the hardware cursor
824 static errr Term_curs_cap(int x, int y)
826 /* Literally move the cursor */
827 do_move(curx, cury, x, y);
829 /* Save the cursor location */
839 * Erase a grid of space
841 * XXX XXX XXX Note that we will never be asked to clear the
842 * bottom line all the way to the bottom right edge, since we
843 * have set the "avoid the bottom right corner" flag.
845 static errr Term_wipe_cap(int x, int y, int n)
849 /* Place the cursor */
852 /* Wipe to end of line */
861 for (dx = 0; dx < n; ++dx)
874 * Place some text on the screen using an attribute
876 static errr Term_text_cap(int x, int y, int n, byte a, cptr s)
880 /* Move the cursor */
883 /* Dump the text, advance the cursor */
884 for (i = 0; s[i]; i++)
889 /* Advance cursor 'X', and wrap */
892 /* Reset cursor 'X' */
895 /* Hack -- Advance cursor 'Y', and wrap */
896 if (++cury == rows) cury = 0;
906 * Handle a "special request"
908 static errr Term_xtra_cap(int n, int v)
910 /* Analyze the request */
913 /* Clear the screen */
914 case TERM_XTRA_CLEAR:
920 case TERM_XTRA_NOISE:
921 (void)write(1, "\007", 1);
924 /* Change the cursor visibility */
925 case TERM_XTRA_SHAPE:
931 case TERM_XTRA_ALIVE:
932 return (Term_xtra_cap_alive(v));
935 case TERM_XTRA_EVENT:
936 return (Term_xtra_cap_event(v));
939 case TERM_XTRA_FLUSH:
940 while (!Term_xtra_cap_event(FALSE));
944 case TERM_XTRA_DELAY:
957 * Init a "term" for this file
959 static void Term_init_cap(term *t)
963 /* Assume cursor at top left */
967 /* Assume visible cursor */
970 /* Clear the screen */
973 /* Hack -- visible cursor */
982 * Nuke a "term" for this file
984 static void Term_nuke_cap(term *t)
988 /* Hack -- make sure the cursor is visible */
991 /* Move to bottom right */
992 do_move(0, rows - 1, 0, rows - 1);
997 /* No longer active */
1011 * Prepare this file for Angband usage
1015 term *t = &term_screen_body;
1018 /*** Initialize ***/
1020 /* Initialize the screen */
1021 if (init_cap_aux()) return (-1);
1023 /* Hack -- Require large screen, or Quit with message */
1024 if ((rows < 24) || (cols < 80)) quit("Screen too small!");
1027 /*** Prepare to play ***/
1029 /* Extract the normal keymap */
1030 keymap_norm_prepare();
1032 /* Extract the game keymap */
1033 keymap_game_prepare();
1035 /* Hack -- activate the game keymap */
1038 /* Hack -- Do NOT buffer stdout */
1039 setbuf(stdout, NULL);
1042 /*** Now prepare the term ***/
1044 /* Initialize the term */
1045 term_init(t, 80, 24, 256);
1047 /* Avoid the bottom right corner */
1048 t->icky_corner = TRUE;
1050 /* Erase with "white space" */
1051 t->attr_blank = TERM_WHITE;
1052 t->char_blank = ' ';
1054 /* Set some hooks */
1055 t->init_hook = Term_init_cap;
1056 t->nuke_hook = Term_nuke_cap;
1058 /* Set some more hooks */
1059 t->text_hook = Term_text_cap;
1060 t->wipe_hook = Term_wipe_cap;
1061 t->curs_hook = Term_curs_cap;
1062 t->xtra_hook = Term_xtra_cap;
1068 Term_activate(term_screen);
1075 #endif /* USE_CAP */