home *** CD-ROM | disk | FTP | other *** search
- /* Terminal dependent routines (well, most of them ...)
- *
- * Author: Bob Baldwin, October 1986.
- */
-
- /*
- * Routines in terminal abstraction:
- *
- * set_term()
- * Initialize terminal, clear the screen.
- *
- * unset_term()
- * Return terminal to state before set_term().
- *
- * char2sym(char)
- * Return the symbol used to display the given char in the
- * decryption window.
- *
- * putsym(symbol)
- * Displays the given symbol on the terminal. Handles
- * entering and exiting graphics mode.
- *
- * getcmd()
- * Reads stdin for a keystroke and returns
- * a command integer.
- *
- * beep()
- * Cause the terminal to beep or flash.
- */
-
- /* The trick to device independence is to clearly separate
- * internal and external representations. On the inbound
- * side, we separate KEYSTOKES which generate a sequence
- * of ascii characters (one per keystroke in the simple case),
- * from COMMAND-KEYS such as move-cursor-up. On the outbound
- * side we separate SYMBOLS from the sequence of ascii characters,
- * called GRAPHICS, used to display the symbol. Each symbol
- * has a single use by the code, though two symbols might
- * appear the same on the user's terminal.
- */
-
- /* Symbols are represented by integers. If the integer is
- * greater than 256, then it denotes one of the symbols defined
- * in terminal.h.
- *
- * Commands are represented by two byte integers. The high byte
- * describes the command (see terminal.h), the low byte is the
- * argument to the command. For example, the insert char command
- * sets the low byte to the character to be inserted.
- */
-
- /* INTERNALS: symbols and graphics
- *
- * The terminal is assumed to be in one of three modes: normal, graphics,
- * or standout (inverse video). This abstraction takes care of all the
- * switching between modes and tries to avoid sending redundant escape
- * sequences to the terminal.
- *
- * Part of the terminal initialization is to build a table that describes
- * how to display each symbol. Ascii characters (including control chars)
- * pass through unchanged, but symbols are looked up in graphtab.
- * Each entry in the graphics table specifies a terminal mode and a
- * string to print that will display that symbol in the given mode.
- * If necessary a mode switch sequence will be sent before printing the
- * string.
- *
- * The graphics corresponding to symbols can be set by the shell variable
- * 'GRAHPICS' using a format similar to the termcap format. The two
- * character names of the symbols are defined in the table symnames[],
- * which can be indexed by the symbol codes (see terminal.h).
- * The mapping from symbols to graphics is first set to a default value,
- * and then updated by the values found in the shell variable (if any).
- *
- * The GRAPHICS string consists of a number of entries separated by colon
- * characters. Each entry have the form xx=\Mccc, where xx is the name
- * of a symbol (see symnames[] below), \M indicates the mode for displaying
- * this symbol (it must be one of \N (for normal), \G (for graphics), or \S
- * (for standout or inverse video)), and ccc is a sequence of characters
- * to send. To include a colon character in the ccc portion, preceed it
- * by a backslash (i.e., '\'). If the \M is ommitted, it defaults to normal.
- */
-
- /* INTERNALS: keystrokes and commands
- *
- * The table keycmdtab is used to convert a sequence of ascii characters
- * into a command integer. The table specifies the escape sequences that
- * might be generated by the different command keys. The getcmd routine
- * will read characters from stdin until it has uniquely identified a
- * command, or decided that there is no match. If no match is found,
- * the terminal is beeped and matching is restarted with the next character.
- * By default keystrokes turn into self-insert commands.
- *
- * In general the last character of the sequence is returned as the arg.
- * This helps windows assign different meanings to keystokes. For example
- * The return key can be either an insert-return command or an execute-
- * command-line command.
- */
-
-
- #include <curses.h>
- #include <sgtty.h>
- #include <strings.h>
- #include "window.h"
- #include "terminal.h"
- #include "specs.h"
-
-
- /* Routines from termcap library.
- */
- extern char *getenv(), *tgetstr(), *tgoto();
-
-
- /* Screen control strings from termcap entry.
- */
- char *term_is; /* Terminal initializaion string. */
- char *erase_eol; /* Erase to end of line. */
- char *erase_eos; /* Erase to end of screen. */
- char *erase_scr; /* Erase whole screen. */
- char *cm; /* Cursor motion. */
- char *start_kp; /* Start graphics mode. */
- char *end_kp; /* End graphics mode. */
- char *start_alt; /* Start graphics mode. */
- char *end_alt; /* End graphics mode. */
- char *start_so; /* Start standout mode. */
- char *end_so; /* End standout mode. */
-
- /* Keymap strings from termcap file.
- */
- char *term_f1; /* The f1 key. */
- char *term_f2; /* The f2 key. */
- char *term_f3; /* The f3 key. */
- char *term_f4; /* The f4 key. */
- char *term_up; /* Up arrow. */
- char *term_down; /* Down arrow. */
- char *term_left; /* Left arrow. */
- char *term_right; /* Right arrow. */
-
-
- /* Symbol names for the shell variable 'GRAPHICS'
- * The values must be and'ed with SYMBOLM to make
- * suitable indices for graphtab[].
- */
- labelv symnames[NSYMC + 1] = {
- {"tb", STAB}, /* Tab */
- {"na", SNOTASCII}, /* Not ascii */
- {"lf", SLINEFEED}, /* Linefeed */
- {"cr", SCARETURN}, /* Carriage return */
- {"ff", SFORMFEED}, /* Formfeed */
- {"cc", SCONTCODE}, /* Other control characters */
- {"uk", SUNKNOWN}, /* Plaintext unknown */
- {"ul", SUNDERLINE}, /* Pseudo underline char */
- {"hb", SHORZBAR}, /* Horizontal bar */
- {"vb", SVERTBAR}, /* Vertical bar */
- {"ll", SLLCORNER}, /* Lower left corner */
- {NULL, 0}, /* End flag. */
- };
-
- /* Table of graphics characters initialized for ordinary CRT.
- */
- symgraph graphtab[NSYMC];
-
-
- /* Symbol names for the shell variable KEYMAPVAR
- * A command's index in this table should be one less
- * than the command code.
- */
- labelv cmdnames[] = {
- {"up", CGO_UP},
- {"do", CGO_DOWN},
- {"le", CGO_LEFT},
- {"ri", CGO_RIGHT},
- {"re", CREFRESH},
- {"un", CUNDO},
- {"cl", CCLRLINE},
- {"ws", CWRDSRCH},
- {"df", CDELF},
- {"db", CDELB},
- {"pr", CPREVBLOCK},
- {"ne", CNEXTBLOCK},
- {"ac", CACCEPT},
- {"ex", CEXECUTE},
- {"--", CINSERT}, /* Should not be in keymap var. */
- {"ta", CTRYALL},
- {"jc", CJUMPCMD},
- {NULL, 0},
- };
-
- /* Table of keystroke commands.
- * Self-insert commands are the default.
- * There can be several keystrokes that generate the same command.
- * To insert control chars, they must be quoted, see QUOTEC.
- * The table is terminated by an entry with c_seq == NULL.
- */
- #define QUOTEC (CNTRL & 'Q')
- keycmd keycmdtab[100];
- keycmd *keycmdp; /* Pointer to next free entry. */
-
-
-
- /* Saved process control characters.
- */
- struct tchars saved_tchars;
-
-
- /* Buffer for termcap entry. */
- #define TBUFSIZ 1024
- char buf[TBUFSIZ];
- char free_buf[1000], *fr; /* Must be global, see tgetstr() */
-
-
- /* Current terminal mode. */
- int termmode = -1;
-
-
- /* Set up the terminal. This package now makes calls to both curses
- * and termcap subroutine packages, although the old code is used
- * for screen refresh.
- */
- set_term()
- {
- printf("\n\nInitializing terminal ...");
- fflush(stdout);
-
- get_termstrs();
- get_genv();
- get_kenv();
- savetty();
- crmode();
- noecho();
- noflow();
- Puts(term_is);
- Puts(start_kp);
- enter_mode(SMNORMAL);
-
- printf(" done.\n");
-
- clrscreen();
- }
-
-
- /* Get keystroke characters, build keymap.
- * The earlier entries have priority, so fill them in from
- * the shell var, then add defaults from a string then termcap.
- */
- get_kenv()
- {
- char *kenv;
- char tcapstr[1000];
-
- keycmdp = keycmdtab;
- kenv = getenv(KEYMAPVAR);
- if (kenv != NULL)
- read_keymap(kenv);
- kenv_termcap(tcapstr);
- read_keymap(tcapstr);
- read_keymap(DKEYMAP);
- }
-
-
- /* Build a keymap string in the given buffer from the info
- * in the termcap file.
- * The string format is like: "up=\Eu:do=\033d".
- */
- kenv_termcap(str)
- char *str;
- {
- *str = NULL;
-
- if (term_up != NULL) {
- strcat(str, cmdnames[CGO_UP - 1].label);
- strcat(str, "=");
- strcat(str, term_up);
- strcat(str, ":");
- }
- if (term_down != NULL) {
- strcat(str, cmdnames[CGO_DOWN - 1].label);
- strcat(str, "=");
- strcat(str, term_down);
- strcat(str, ":");
- }
- if (term_left != NULL) {
- strcat(str, cmdnames[CGO_LEFT - 1].label);
- strcat(str, "=");
- strcat(str, term_left);
- strcat(str, ":");
- }
- if (term_right != NULL) {
- strcat(str, cmdnames[CGO_RIGHT - 1].label);
- strcat(str, "=");
- strcat(str, term_right);
- strcat(str, ":");
- }
- if (term_f1 != NULL) {
- strcat(str, cmdnames[CPREVBLOCK - 1].label);
- strcat(str, "=");
- strcat(str, term_f1);
- strcat(str, ":");
- }
- if (term_f2 != NULL) {
- strcat(str, cmdnames[CNEXTBLOCK - 1].label);
- strcat(str, "=");
- strcat(str, term_f2);
- strcat(str, ":");
- }
- if (term_f3 != NULL) {
- strcat(str, cmdnames[CACCEPT - 1].label);
- strcat(str, "=");
- strcat(str, term_f3);
- strcat(str, ":");
- }
- if (term_f4 != NULL) {
- strcat(str, cmdnames[CJUMPCMD - 1].label);
- strcat(str, "=");
- strcat(str, term_f4);
- strcat(str, ":");
- }
- }
-
-
-
- /* Add key bindings from the given string to the keycmdtab.
- */
- read_keymap(var)
- char *var;
- {
- int cmd_code;
-
- while (*var != NULL) {
- if (! read_varlabel(&var, cmdnames, &cmd_code)) {
- if (*var == NULL)
- break;
- disperr("Can't parse label in read_keymap.");
- exit(1);
- }
-
- keycmdp->c_code = cmd_code;
- if (! read_varval(&var, &(keycmdp->c_seq))) {
- disperr("keymap value has bad format.");
- exit(1);
- }
- keycmdp++;
- }
- }
-
-
- /* Get graphics characters.
- * Set to defaults, then read changes from shell var (if any).
- */
- get_genv()
- {
- char *genv;
-
- read_graphics(DGRAPHICS);
- genv = getenv(GRAPHICSVAR);
- if (genv == NULL)
- return;
- read_graphics(genv);
- }
-
-
- /* Read graphics map from the given string.
- */
- read_graphics(var)
- char *var;
- {
- int sym_idx;
-
- while (*var != NULL) {
- if (! read_varlabel(&var, symnames, &sym_idx)) {
- if (*var == NULL)
- break;
- disperr("Can't parse label in GRAPHICSMAP.");
- exit(1);
- }
-
- if ((var[0] != '\\') || (index(GVARMODES, var[1]) == 0)) {
- disperr("GRAPHICSMAP value has bad mode.");
- exit(1);
- }
- sym_idx &= SYMBOLM;
- graphtab[sym_idx].s_mode = var[1] & CHARM;
- var++, var++;
-
- if (! read_varval(&var, &(graphtab[sym_idx].s_seq))) {
- disperr("GRAPHICSMAP val has bad format.");
- exit(1);
- }
- }
- }
-
-
- /* Advance to the next label in strp, look the label up in the
- * labeltab, and set *valp to the value in the labeltab.
- * Return with *strp pointing after '=' that follows the label.
- * Return TRUE is parses ok, else return FALSE.
- * If string empty, return false and set *strp to point to a NULL.
- */
- int read_varlabel(strp, labeltab, valp)
- char **strp;
- labelv *labeltab;
- int *valp;
- {
- char *str;
- labelv *lp;
-
- for (str = *strp ; *str && index(VARSEP, *str) != 0 ; str++ );
-
- for (lp = labeltab ; lp->label != NULL ; lp++) {
- if (substrp(str, lp->label)) {
- *valp = lp->value;
- str = index(str, '=');
- if (str == NULL)
- return(FALSE);
- str++;
- *strp = str;
- return(TRUE);
- }
- }
- *strp = str;
- return(FALSE);
- }
-
-
- /* Read a string value from a shell var string.
- * Return with *strp pointing after the string,
- * fill in valp with a pointer to a copy of the value string
- * on the heap.
- * Return TRUE if things go ok.
- */
- int read_varval(strp, valp)
- char **strp;
- char **valp;
- {
- char buf[100], *bp;
- char *var;
-
- var = *strp;
- bp = buf;
- while ((*var != NULL) && (index(VARTERM, *var) == NULL)) {
- *bp = read_slashed(&var);
- bp++;
- }
- *bp = 0;
- *valp = savestr(buf);
- *strp = var;
- return (TRUE);
- }
-
-
- /* Read the given (slashified) string and return a character.
- * Advance *strp over chars read.
- * Handle \\, \001, \n, \t, \r.
- * The symbol \E maps to escape (\033).
- * An unexpected char after the slash is just returned.
- */
- int read_slashed(strp)
- char **strp;
- {
- char *str;
- char c;
-
- str = *strp;
- if (*str != '\\') {
- *strp = (*strp) + 1;
- return (*str);
- }
- str++;
- c = *str;
- switch (c) {
- default:
- if (index(DIGITS, c) == NULL)
- break;
- c -= '0';
- if (index(DIGITS, str[1]) == NULL)
- break;
- str++;
- c = (c * 8) + (*str - '0');
- if (index(DIGITS, str[1]) == NULL)
- break;
- str++;
- c = (c * 8) + (*str - '0');
- break;
-
- case 'n':
- c = '\n';
- break;
-
- case 't':
- c = '\t';
- break;
-
- case 'E':
- c = '\033';
- break;
-
- case 'f':
- c = '\f';
- break;
-
- case 'r':
- c = '\r';
- break;
- }
- *strp = str + 1;
- return (c);
- }
-
-
- /* Turn off flow control characters.
- */
- noflow()
- {
- struct tchars new_tchars;
-
- /* Turn off C-Q, C-S flow control. */
- if (ioctl(0, TIOCGETC, &saved_tchars) < 0) {
- perror("noflow iocl get");
- exit(1);
- }
- new_tchars = saved_tchars;
- new_tchars.t_stopc = -1;
- new_tchars.t_startc = -1;
- if (ioctl(0, TIOCSETC, &new_tchars) < 0) {
- perror("noflow iocl set");
- exit(1);
- }
- }
-
-
- /* Restore the flow control characters.
- */
- restore_flow()
- {
- if (ioctl(0, TIOCSETC, &saved_tchars) < 0) {
- perror("restore_flow iocl set");
- exit(1);
- }
- }
-
-
- /* Read in the termcap strings.
- */
- get_termstrs()
- {
- char *term;
- int res;
-
- fr=free_buf;
- term = getenv("TERM");
- if (term == NULL) {
- disperr("The shell variable TERM is not defined.");
- exit(1);
- }
- res = tgetent(buf,term);
- switch (res) {
- case -1:
- disperr("Can't open termcap file.");
- exit(1);
-
- case 0:
- disperr("No termcap entry for your terminal.");
- exit(1);
-
- default:
- break;
- }
-
- term_is = tgetstr("is", &fr);
- if (term_is == NULL)
- term_is = "";
- erase_eol = tgetstr("ce", &fr);
- erase_eos = tgetstr("cd", &fr);
- erase_scr = tgetstr("cl", &fr);
- start_so = tgetstr("so", &fr);
- end_so = tgetstr("se", &fr);
- start_alt = tgetstr("as", &fr);
- end_alt = tgetstr("ae", &fr);
- if (start_alt == 0 || end_alt == 0) {
- start_alt = "\033F"; /* VT100 default. */
- end_alt = "\033G";
- }
- cm = tgetstr("cm", &fr); /* for cursor positioning */
- start_kp = tgetstr("ks", &fr);
- end_kp = tgetstr("ke", &fr);
-
- if ((term_is == NULL) || (erase_eol == NULL) ||
- (erase_eos == NULL) || (erase_scr == NULL) ||
- (cm == NULL) ||
- (end_kp == NULL) || (start_kp == NULL) ||
- (end_alt == NULL) || (start_alt == NULL) ||
- (end_so == NULL) || (start_so == NULL) ) {
- disperr("A required termcap capability is missing.");
- disperr("\t one of: ce, cd, cl, so, se, cm, ks, ke.");
- exit(1);
- }
-
- /* Now get entries for keymap, NULL means no such entry.
- */
- term_f1 = tgetstr("k1", &fr);
- term_f2 = tgetstr("k2", &fr);
- term_f3 = tgetstr("k3", &fr);
- term_f4 = tgetstr("k4", &fr);
- term_up = tgetstr("ku", &fr);
- term_down = tgetstr("kd", &fr);
- term_left = tgetstr("kl", &fr);
- term_right = tgetstr("kr", &fr);
- }
-
-
- /* Restore the terminal to its original mode.
- */
- unset_term()
- {
- enter_mode(SMNORMAL);
- Puts(end_kp); /* Can't tell if this is original. */
- fflush(stdout);
- nocrmode();
- echo();
- restore_flow();
- resetty();
- }
-
-
- /* Return the symbol used to display the given char in the
- * decryption window.
- */
- int char2sym(pchar)
- int pchar;
- {
- int gchar;
-
- if (printable(pchar)) {gchar = pchar;}
- else if (pchar == -1) {gchar = SUNKNOWN;}
- else if (notascii(pchar)) {gchar = SNOTASCII;}
- else if (pchar == '\n') {gchar = SLINEFEED;}
- else if (pchar == '\r') {gchar = SCARETURN;}
- else if (pchar == '\f') {gchar = SFORMFEED;}
- else if (pchar == '\t') {gchar = STAB;}
- else {gchar = SCONTCODE;}
- return (gchar);
- }
-
-
- /* Displays the given symbol on the terminal. Handles
- * entering and exiting graphics or standout mode.
- */
- putsym(symbol)
- int symbol;
- {
- int symcode;
- symgraph *gp;
-
- if (! graphic(symbol)) {
- enter_mode(SMNORMAL);
- putchar(symbol & CHARM);
- return;
- }
- symcode = symbol & SYMBOLM;
- if (symcode >= NSYMC) {
- disperr("Bad symbol code in putsym.");
- return;
- }
- gp = &graphtab[symcode];
- enter_mode(gp->s_mode);
- Puts(gp->s_seq);
- }
-
-
- /* Enter a particular mode. If necessary send escape sequence
- * to the terminal. Handle terminating the previous mode.
- */
- enter_mode(mode)
- int mode;
- {
- if (termmode == mode)
- return;
-
- switch (termmode) {
- case SMNORMAL:
- break;
-
- case SMGRAPHIC:
- Puts(end_alt);
- break;
-
- case SMSTANDOUT:
- Puts(end_so);
- break;
-
- default:
- Puts(end_so);
- Puts(end_alt);
- break;
- }
-
- termmode = mode;
-
- switch (termmode) {
- case SMNORMAL:
- break;
-
- case SMGRAPHIC:
- Puts(start_alt);
- break;
-
- case SMSTANDOUT:
- Puts(start_so);
- break;
-
- default:
- disperr("Bad terminal mode.");
- break;
- }
- }
-
-
- /* Return values from srch_ktab().
- */
- #define SK_SUBSTR -1
- #define SK_NOMATCH -2
-
-
- /* Search the key command table for the given keystroke.
- * If no match, return SK_NOMATCH (which is < 0).
- * If the keystroke is the prefix for one or more commands,
- * return SK_SUBSTR (which is < 0).
- * If an exact match is found, return the index ( >= 0) of
- * the entry that matched.
- */
- int srch_ktab(ktab, stroke)
- keycmd ktab[];
- char *stroke;
- {
- int i;
- int nsubstr = 0; /* Number of close entries */
-
- for (i = 0 ; ktab[i].c_seq != NULL ; i++) {
- if (strcmp(ktab[i].c_seq, stroke) == 0)
- return(i);
- if (substrp(ktab[i].c_seq, stroke))
- nsubstr++;
- }
- if (nsubstr > 0)
- return (SK_SUBSTR);
- return (SK_NOMATCH);
- }
-
-
- /* Return TRUE if the model string starts with the given string.
- * Return false if strlen(given) > strlen(model).
- */
- int substrp(model, given)
- char *model;
- char *given;
- {
- for ( ; (*model != 0) && (*given != 0) ; model++, given++) {
- if (*model != *given)
- return (FALSE);
- }
- if (*given == 0)
- return (TRUE);
- return (FALSE);
- }
-
-
- /* Read a keystroke from stdin and return the command for it.
- *
- * Single character keystrokes not found in the table generate
- * self-insert commands.
- * Control characters other than \n, and \t must be quoted in order
- * to generate self-insert commands. To quote a char, preceed it
- * by the QUOTEC character.
- * Multi character keystrokes should end in an exact match. If not,
- * throw away the char that caused no matches in the keycmdtab,
- * beep the terminal, and start over.
- */
- int getcmd()
- {
- char keystroke[10]; /* Chars in keystroke. */
- int nchars; /* Length of keystroke[]. */
- int c;
- int index; /* Cmd index in keycmdtab. */
- int code; /* Cmd code. */
-
- start_over:
- nchars = 0;
- keystroke[0] = 0;
-
- while (TRUE) {
- c = getchar();
- keystroke[nchars++] = c;
- keystroke[nchars] = 0;
- index = srch_ktab(keycmdtab, keystroke);
- switch (index) {
- case SK_SUBSTR:
- continue;
-
- case SK_NOMATCH:
- if (nchars != 1) {
- beep();
- goto start_over;
- }
- code = CINSERT;
- if (c == QUOTEC) {
- c = getchar();
- break;
- }
- else if (printable(c)) {
- break;
- }
- else if ((c != LINEFEED) && (c != TAB)) {
- beep();
- goto start_over;
- }
- break;
-
- default:
- if (index < 0) {
- disperr("Bad keycmdtab index.");
- }
- code = keycmdtab[index].c_code;
- break;
- }
- return ((code << CMDSHIFT) | (c & CHARM));
- }
- }
-
-
- /* Cause the terminal to beep.
- */
- beep()
- {
- Puts("\007"); /* C-G */
- }
-
-
- /* Save a copy of the given string on the heap.
- * Return pointer to copy.
- */
- char *savestr(s)
- register char *s;
- {
- char *p;
- register char *t;
-
- if (s == NULL)
- return( NULL );
- t = p = (char*) calloc(strlen(s) + 1, 1);
- if (t == NULL)
- return(NULL);
- while (*t++ = *s++);
- return(p);
- }
-