home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1994 January / usenetsourcesnewsgroupsinfomagicjanuary1994.iso / sources / unix / volume10 / cbw / part08 / terminal.c < prev   
Encoding:
C/C++ Source or Header  |  1987-06-17  |  18.8 KB  |  854 lines

  1. /* Terminal dependent routines (well, most of them ...)
  2.  *
  3.  * Author: Bob Baldwin, October 1986.
  4.  */
  5.  
  6. /* 
  7.  * Routines in terminal abstraction:
  8.  *
  9.  *    set_term()
  10.  *        Initialize terminal, clear the screen.
  11.  *
  12.  *    unset_term()
  13.  *        Return terminal to state before set_term().
  14.  *
  15.  *    char2sym(char)
  16.  *        Return the symbol used to display the given char in the
  17.  *        decryption window.
  18.  *
  19.  *    putsym(symbol)
  20.  *        Displays the given symbol on the terminal.  Handles
  21.  *         entering and exiting graphics mode.
  22.  *
  23.  *    getcmd()
  24.  *        Reads stdin for a keystroke and returns
  25.  *        a command integer.
  26.  *
  27.  *    beep()
  28.  *        Cause the terminal to beep or flash.
  29.  */
  30.  
  31. /* The trick to device independence is to clearly separate
  32.  * internal and external representations.  On the inbound
  33.  * side, we separate KEYSTOKES which generate a sequence
  34.  * of ascii characters (one per keystroke in the simple case),
  35.  * from COMMAND-KEYS such as move-cursor-up.  On the outbound
  36.  * side we separate SYMBOLS from the sequence of ascii characters,
  37.  * called GRAPHICS, used to display the symbol.  Each symbol
  38.  * has a single use by the code, though two symbols might
  39.  * appear the same on the user's terminal.
  40.  */
  41.  
  42. /* Symbols are represented by integers.  If the integer is
  43.  * greater than 256, then it denotes one of the symbols defined
  44.  * in terminal.h.
  45.  *
  46.  * Commands are represented by two byte integers.  The high byte
  47.  * describes the command (see terminal.h), the low byte is the
  48.  * argument to the command.  For example, the insert char command
  49.  * sets the low byte to the character to be inserted.
  50.  */
  51.  
  52. /* INTERNALS: symbols and graphics
  53.  *
  54.  * The terminal is assumed to be in one of three modes: normal, graphics,
  55.  * or standout (inverse video).  This abstraction takes care of all the
  56.  * switching between modes and tries to avoid sending redundant escape
  57.  * sequences to the terminal.
  58.  * 
  59.  * Part of the terminal initialization is to build a table that describes
  60.  * how to display each symbol.  Ascii characters (including control chars)
  61.  * pass through unchanged, but symbols are looked up in graphtab.
  62.  * Each entry in the graphics table specifies a terminal mode and a
  63.  * string to print that will display that symbol in the given mode.
  64.  * If necessary a mode switch sequence will be sent before printing the
  65.  * string.
  66.  *
  67.  * The graphics corresponding to symbols can be set by the shell variable
  68.  * 'GRAHPICS' using a format similar to the termcap format.  The two
  69.  * character names of the symbols are defined in the table symnames[],
  70.  * which can be indexed by the symbol codes (see terminal.h).
  71.  * The mapping from symbols to graphics is first set to a default value,
  72.  * and then updated by the values found in the shell variable (if any).
  73.  *
  74.  * The GRAPHICS string consists of a number of entries separated by colon
  75.  * characters.  Each entry have the form xx=\Mccc, where xx is the name
  76.  * of a symbol (see symnames[] below), \M indicates the mode for displaying
  77.  * this symbol (it must be one of \N (for normal), \G (for graphics), or \S
  78.  * (for standout or inverse video)), and ccc is a sequence of characters
  79.  * to send.  To include a colon character in the ccc portion, preceed it
  80.  * by a backslash (i.e., '\').  If the \M is ommitted, it defaults to normal.
  81.  */
  82.  
  83. /* INTERNALS: keystrokes and commands
  84.  *
  85.  * The table keycmdtab is used to convert a sequence of ascii characters
  86.  * into a command integer.  The table specifies the escape sequences that
  87.  * might be generated by the different command keys.  The getcmd routine
  88.  * will read characters from stdin until it has uniquely identified a
  89.  * command, or decided that there is no match.  If no match is found,
  90.  * the terminal is beeped and matching is restarted with the next character.
  91.  * By default keystrokes turn into self-insert commands.
  92.  *
  93.  * In general the last character of the sequence is returned as the arg.
  94.  * This helps windows assign different meanings to keystokes.  For example
  95.  * The return key can be either an insert-return command or an execute-
  96.  * command-line command.
  97.  */
  98.  
  99.  
  100. #include    <curses.h>
  101. #include    <sgtty.h>
  102. #include    <strings.h>
  103. #include    "window.h"
  104. #include    "terminal.h"
  105. #include    "specs.h"
  106.  
  107.  
  108. /* Routines from termcap library.
  109.  */
  110. extern    char *getenv(), *tgetstr(), *tgoto();
  111.  
  112.  
  113. /* Screen control strings from termcap entry.
  114.  */
  115. char    *term_is;        /* Terminal initializaion string. */
  116. char    *erase_eol;        /* Erase to end of line. */
  117. char    *erase_eos;        /* Erase to end of screen. */
  118. char    *erase_scr;        /* Erase whole screen. */
  119. char    *cm;            /* Cursor motion. */
  120. char    *start_kp;        /* Start graphics mode. */
  121. char    *end_kp;        /* End graphics mode. */
  122. char    *start_alt;        /* Start graphics mode. */
  123. char    *end_alt;        /* End graphics mode. */
  124. char    *start_so;        /* Start standout mode. */
  125. char    *end_so;        /* End standout mode. */
  126.  
  127. /* Keymap strings from termcap file.
  128.  */
  129. char    *term_f1;        /* The f1 key. */
  130. char    *term_f2;        /* The f2 key. */
  131. char    *term_f3;        /* The f3 key. */
  132. char    *term_f4;        /* The f4 key. */
  133. char    *term_up;        /* Up arrow. */
  134. char    *term_down;        /* Down arrow. */
  135. char    *term_left;        /* Left arrow. */
  136. char    *term_right;        /* Right arrow. */
  137.  
  138.  
  139. /* Symbol names for the shell variable 'GRAPHICS'
  140.  * The values must be and'ed with SYMBOLM to make
  141.  * suitable indices for graphtab[].
  142.  */
  143. labelv    symnames[NSYMC + 1] = {
  144.     {"tb", STAB},        /* Tab */
  145.     {"na", SNOTASCII},    /* Not ascii */
  146.     {"lf", SLINEFEED},    /* Linefeed */
  147.     {"cr", SCARETURN},    /* Carriage return */
  148.     {"ff", SFORMFEED},    /* Formfeed */
  149.     {"cc", SCONTCODE},    /* Other control characters */
  150.     {"uk", SUNKNOWN},    /* Plaintext unknown */
  151.     {"ul", SUNDERLINE},    /* Pseudo underline char */
  152.     {"hb", SHORZBAR},    /* Horizontal bar */
  153.     {"vb", SVERTBAR},    /* Vertical bar */
  154.     {"ll", SLLCORNER},    /* Lower left corner */
  155.     {NULL, 0},        /* End flag. */
  156. };
  157.  
  158. /* Table of graphics characters initialized for ordinary CRT.
  159.  */
  160. symgraph graphtab[NSYMC];
  161.  
  162.  
  163. /* Symbol names for the shell variable KEYMAPVAR
  164.  * A command's index in this table should be one less
  165.  * than the command code.
  166.  */
  167. labelv    cmdnames[] = {
  168.     {"up", CGO_UP},
  169.     {"do", CGO_DOWN},
  170.     {"le", CGO_LEFT},
  171.     {"ri", CGO_RIGHT},
  172.     {"re", CREFRESH},
  173.     {"un", CUNDO},
  174.     {"cl", CCLRLINE},
  175.     {"ws", CWRDSRCH},
  176.     {"df", CDELF},
  177.     {"db", CDELB},
  178.     {"pr", CPREVBLOCK},
  179.     {"ne", CNEXTBLOCK},
  180.     {"ac", CACCEPT},
  181.     {"ex", CEXECUTE},
  182.     {"--", CINSERT},        /* Should not be in keymap var. */
  183.     {"ta", CTRYALL},
  184.     {"jc", CJUMPCMD},
  185.     {NULL, 0},
  186. };
  187.  
  188. /* Table of keystroke commands.
  189.  * Self-insert commands are the default.
  190.  * There can be several keystrokes that generate the same command.
  191.  * To insert control chars, they must be quoted, see QUOTEC.
  192.  * The table is terminated by an entry with c_seq == NULL.
  193.  */
  194. #define    QUOTEC    (CNTRL & 'Q')
  195. keycmd    keycmdtab[100];
  196. keycmd    *keycmdp;        /* Pointer to next free entry. */
  197.  
  198.  
  199.  
  200. /* Saved process control characters.
  201.  */
  202. struct    tchars    saved_tchars;
  203.  
  204.  
  205. /* Buffer for termcap entry. */
  206. #define TBUFSIZ 1024
  207. char    buf[TBUFSIZ];
  208. char    free_buf[1000], *fr;        /* Must be global, see tgetstr() */
  209.  
  210.  
  211. /* Current terminal mode. */
  212. int    termmode = -1;
  213.  
  214.  
  215. /* Set up the terminal. This package now makes calls to both curses
  216.  * and termcap subroutine packages, although the old code is used
  217.  * for screen refresh.
  218.  */
  219. set_term()
  220. {
  221.     printf("\n\nInitializing terminal ...");
  222.     fflush(stdout);
  223.  
  224.     get_termstrs();
  225.     get_genv();
  226.     get_kenv();
  227.     savetty();
  228.     crmode();
  229.     noecho();
  230.     noflow();
  231.     Puts(term_is);
  232.     Puts(start_kp);
  233.     enter_mode(SMNORMAL);
  234.  
  235.     printf(" done.\n");
  236.  
  237.     clrscreen();
  238. }
  239.  
  240.  
  241. /* Get keystroke characters, build keymap.
  242.  * The earlier entries have priority, so fill them in from
  243.  * the shell var, then add defaults from a string then termcap.
  244.  */
  245. get_kenv()
  246. {
  247.     char    *kenv;
  248.     char    tcapstr[1000];
  249.  
  250.     keycmdp = keycmdtab;
  251.     kenv = getenv(KEYMAPVAR);
  252.     if (kenv != NULL)
  253.           read_keymap(kenv);
  254.     kenv_termcap(tcapstr);
  255.     read_keymap(tcapstr);
  256.     read_keymap(DKEYMAP);
  257. }
  258.  
  259.  
  260. /* Build a keymap string in the given buffer from the info
  261.  * in the termcap file.
  262.  * The string format is like: "up=\Eu:do=\033d".
  263.  */
  264. kenv_termcap(str)
  265. char    *str;
  266. {
  267.     *str = NULL;
  268.  
  269.     if (term_up != NULL)  {
  270.         strcat(str, cmdnames[CGO_UP - 1].label);
  271.         strcat(str, "=");
  272.         strcat(str, term_up);
  273.         strcat(str, ":");
  274.     }
  275.     if (term_down != NULL)  {
  276.         strcat(str, cmdnames[CGO_DOWN - 1].label);
  277.         strcat(str, "=");
  278.         strcat(str, term_down);
  279.         strcat(str, ":");
  280.     }
  281.     if (term_left != NULL)  {
  282.         strcat(str, cmdnames[CGO_LEFT - 1].label);
  283.         strcat(str, "=");
  284.         strcat(str, term_left);
  285.         strcat(str, ":");
  286.     }
  287.     if (term_right != NULL)  {
  288.         strcat(str, cmdnames[CGO_RIGHT - 1].label);
  289.         strcat(str, "=");
  290.         strcat(str, term_right);
  291.         strcat(str, ":");
  292.     }
  293.     if (term_f1 != NULL)  {
  294.         strcat(str, cmdnames[CPREVBLOCK - 1].label);
  295.         strcat(str, "=");
  296.         strcat(str, term_f1);
  297.         strcat(str, ":");
  298.     }
  299.     if (term_f2 != NULL)  {
  300.         strcat(str, cmdnames[CNEXTBLOCK - 1].label);
  301.         strcat(str, "=");
  302.         strcat(str, term_f2);
  303.         strcat(str, ":");
  304.     }
  305.     if (term_f3 != NULL)  {
  306.         strcat(str, cmdnames[CACCEPT - 1].label);
  307.         strcat(str, "=");
  308.         strcat(str, term_f3);
  309.         strcat(str, ":");
  310.     }
  311.     if (term_f4 != NULL)  {
  312.         strcat(str, cmdnames[CJUMPCMD - 1].label);
  313.         strcat(str, "=");
  314.         strcat(str, term_f4);
  315.         strcat(str, ":");
  316.     }
  317. }
  318.  
  319.  
  320.  
  321. /* Add key bindings from the given string to the keycmdtab.
  322.  */
  323. read_keymap(var)
  324. char    *var;
  325. {
  326.     int    cmd_code;
  327.  
  328.     while (*var != NULL)  {
  329.         if (! read_varlabel(&var, cmdnames, &cmd_code))  {
  330.             if (*var == NULL)
  331.                   break;
  332.             disperr("Can't parse label in read_keymap.");
  333.             exit(1);
  334.         }
  335.  
  336.         keycmdp->c_code = cmd_code;
  337.         if (! read_varval(&var, &(keycmdp->c_seq)))  {
  338.             disperr("keymap value has bad format.");
  339.             exit(1);
  340.         }
  341.         keycmdp++;
  342.     }
  343. }
  344.  
  345.  
  346. /* Get graphics characters.
  347.  * Set to defaults, then read changes from shell var (if any).
  348.  */
  349. get_genv()
  350. {
  351.     char    *genv;
  352.  
  353.     read_graphics(DGRAPHICS);
  354.     genv = getenv(GRAPHICSVAR);
  355.     if (genv == NULL)
  356.           return;
  357.     read_graphics(genv);
  358. }
  359.  
  360.  
  361. /* Read graphics map from the given string.
  362.  */
  363. read_graphics(var)
  364. char    *var;
  365. {
  366.     int    sym_idx;
  367.  
  368.     while (*var != NULL)  {
  369.         if (! read_varlabel(&var, symnames, &sym_idx))  {
  370.             if (*var == NULL)
  371.                   break;
  372.             disperr("Can't parse label in GRAPHICSMAP.");
  373.             exit(1);
  374.         }
  375.  
  376.         if ((var[0] != '\\') || (index(GVARMODES, var[1]) == 0))  {
  377.             disperr("GRAPHICSMAP value has bad mode.");
  378.             exit(1);
  379.          }
  380.         sym_idx &= SYMBOLM;
  381.         graphtab[sym_idx].s_mode = var[1] & CHARM;
  382.         var++, var++;
  383.  
  384.         if (! read_varval(&var, &(graphtab[sym_idx].s_seq)))  {
  385.             disperr("GRAPHICSMAP val has bad format.");
  386.             exit(1);
  387.         }
  388.     }
  389. }
  390.  
  391.  
  392. /* Advance to the next label in strp, look the label up in the
  393.  * labeltab, and set *valp to the value in the labeltab.
  394.  * Return with *strp pointing after '=' that follows the label.
  395.  * Return TRUE is parses ok, else return FALSE.
  396.  * If string empty, return false and set *strp to point to a NULL.
  397.  */
  398. int read_varlabel(strp, labeltab, valp)
  399. char    **strp;
  400. labelv    *labeltab;
  401. int    *valp;
  402. {
  403.     char    *str;
  404.     labelv    *lp;
  405.  
  406.     for (str = *strp ; *str && index(VARSEP, *str) != 0 ; str++ );
  407.  
  408.     for (lp = labeltab ; lp->label != NULL ; lp++)  {
  409.         if (substrp(str, lp->label))  {
  410.             *valp = lp->value;
  411.             str = index(str, '=');
  412.             if (str == NULL)
  413.                   return(FALSE);
  414.             str++;
  415.             *strp = str;
  416.             return(TRUE);
  417.         }
  418.     }
  419.     *strp = str;
  420.     return(FALSE);
  421. }
  422.  
  423.  
  424. /* Read a string value from a shell var string.
  425.  * Return with *strp pointing after the string,
  426.  * fill in valp with a pointer to a copy of the value string
  427.  * on the heap.
  428.  * Return TRUE if things go ok.
  429.  */
  430. int read_varval(strp, valp)
  431. char    **strp;
  432. char    **valp;
  433. {
  434.     char    buf[100], *bp;
  435.     char    *var;
  436.  
  437.     var = *strp;
  438.     bp = buf;
  439.     while ((*var != NULL) && (index(VARTERM, *var) == NULL))  {
  440.         *bp = read_slashed(&var);
  441.                bp++;
  442.         }
  443.     *bp = 0;
  444.     *valp = savestr(buf);
  445.     *strp = var;
  446.     return (TRUE);
  447. }
  448.  
  449.  
  450. /* Read the given (slashified) string and return a character.
  451.  * Advance *strp over chars read.
  452.  * Handle \\, \001, \n, \t, \r.
  453.  * The symbol \E maps to escape (\033).
  454.  * An unexpected char after the slash is just returned.
  455.  */
  456. int read_slashed(strp)
  457. char    **strp;
  458. {
  459.     char    *str;
  460.     char    c;
  461.  
  462.     str = *strp;
  463.     if (*str != '\\')  {
  464.         *strp = (*strp) + 1;
  465.           return (*str);
  466.     }
  467.     str++;
  468.     c = *str;
  469.     switch (c)  {
  470.       default:
  471.         if (index(DIGITS, c) == NULL)
  472.               break;
  473.         c -= '0';
  474.         if (index(DIGITS, str[1]) == NULL)
  475.               break;
  476.         str++;
  477.         c = (c * 8) + (*str - '0');
  478.         if (index(DIGITS, str[1]) == NULL)
  479.               break;
  480.         str++;
  481.         c = (c * 8) + (*str - '0');
  482.         break;
  483.  
  484.       case 'n':
  485.         c = '\n';
  486.         break;
  487.  
  488.       case 't':
  489.         c = '\t';
  490.         break;
  491.  
  492.       case 'E':
  493.         c = '\033';
  494.         break;
  495.  
  496.       case 'f':
  497.         c = '\f';
  498.         break;
  499.  
  500.       case 'r':
  501.         c = '\r';
  502.         break;
  503.     }
  504.     *strp = str + 1;
  505.       return (c);
  506. }
  507.  
  508.  
  509. /* Turn off flow control characters.
  510.  */
  511. noflow()
  512. {
  513.     struct    tchars    new_tchars;
  514.     
  515.     /* Turn off C-Q, C-S flow control. */
  516.     if (ioctl(0, TIOCGETC, &saved_tchars) < 0)  {
  517.         perror("noflow iocl get");
  518.         exit(1);
  519.     }
  520.     new_tchars = saved_tchars;
  521.     new_tchars.t_stopc = -1;
  522.     new_tchars.t_startc = -1;
  523.     if (ioctl(0, TIOCSETC, &new_tchars) < 0)  {
  524.         perror("noflow iocl set");
  525.         exit(1);
  526.     }
  527. }
  528.  
  529.  
  530. /* Restore the flow control characters.
  531.  */
  532. restore_flow()
  533. {
  534.     if (ioctl(0, TIOCSETC, &saved_tchars) < 0)  {
  535.         perror("restore_flow iocl set");
  536.         exit(1);
  537.     }
  538. }
  539.  
  540.  
  541. /* Read in the termcap strings.
  542.  */
  543. get_termstrs()
  544. {
  545.     char    *term;
  546.     int    res;
  547.  
  548.     fr=free_buf;
  549.     term = getenv("TERM");
  550.     if (term == NULL)  {
  551.         disperr("The shell variable TERM is not defined.");
  552.         exit(1);
  553.     }
  554.     res = tgetent(buf,term);
  555.     switch (res)  {
  556.       case -1:
  557.         disperr("Can't open termcap file.");
  558.         exit(1);
  559.  
  560.       case 0:
  561.         disperr("No termcap entry for your terminal.");
  562.         exit(1);
  563.  
  564.       default:
  565.         break;
  566.     }           
  567.  
  568.     term_is = tgetstr("is", &fr);
  569.     if (term_is == NULL)
  570.           term_is = "";
  571.     erase_eol = tgetstr("ce", &fr);
  572.     erase_eos = tgetstr("cd", &fr);
  573.     erase_scr = tgetstr("cl", &fr);
  574.     start_so = tgetstr("so", &fr);
  575.     end_so = tgetstr("se", &fr);
  576.     start_alt = tgetstr("as", &fr);
  577.     end_alt = tgetstr("ae", &fr);
  578.     if (start_alt == 0  ||  end_alt == 0) {
  579.         start_alt = "\033F";               /* VT100 default. */
  580.         end_alt = "\033G";
  581.     }
  582.     cm = tgetstr("cm", &fr);        /* for cursor positioning */
  583.     start_kp = tgetstr("ks", &fr);
  584.     end_kp = tgetstr("ke", &fr);
  585.  
  586.     if ((term_is == NULL) || (erase_eol == NULL) ||
  587.         (erase_eos == NULL) || (erase_scr == NULL) ||
  588.         (cm == NULL) ||
  589.         (end_kp == NULL) || (start_kp == NULL) ||
  590.         (end_alt == NULL) || (start_alt == NULL) ||
  591.         (end_so == NULL) || (start_so == NULL) )  {
  592.         disperr("A required termcap capability is missing.");
  593.         disperr("\t one of: ce, cd, cl, so, se, cm, ks, ke.");
  594.         exit(1);
  595.     }
  596.  
  597.     /* Now get entries for keymap, NULL means no such entry.
  598.      */
  599.     term_f1 = tgetstr("k1", &fr);
  600.     term_f2 = tgetstr("k2", &fr);
  601.     term_f3 = tgetstr("k3", &fr);
  602.     term_f4 = tgetstr("k4", &fr);
  603.     term_up = tgetstr("ku", &fr);
  604.     term_down = tgetstr("kd", &fr);
  605.     term_left = tgetstr("kl", &fr);
  606.     term_right = tgetstr("kr", &fr);
  607. }
  608.  
  609.  
  610. /* Restore the terminal to its original mode.
  611.  */
  612. unset_term()
  613. {
  614.     enter_mode(SMNORMAL);
  615.     Puts(end_kp);        /* Can't tell if this is original. */
  616.     fflush(stdout);
  617.     nocrmode();
  618.     echo();
  619.     restore_flow();
  620.     resetty();
  621. }
  622.  
  623.  
  624. /* Return the symbol used to display the given char in the
  625.  * decryption window.
  626.  */
  627. int  char2sym(pchar)
  628. int    pchar;
  629. {
  630.      int    gchar;
  631.  
  632.     if (printable(pchar))        {gchar = pchar;}
  633.     else if (pchar == -1)         {gchar = SUNKNOWN;}
  634.     else if (notascii(pchar))    {gchar = SNOTASCII;}
  635.     else if (pchar == '\n')        {gchar = SLINEFEED;}
  636.     else if (pchar == '\r')        {gchar = SCARETURN;}
  637.     else if (pchar == '\f')        {gchar = SFORMFEED;}
  638.     else if (pchar == '\t')        {gchar = STAB;}
  639.     else                {gchar = SCONTCODE;}
  640.     return (gchar);
  641. }
  642.  
  643.  
  644. /* Displays the given symbol on the terminal.  Handles
  645.  * entering and exiting graphics or standout mode.
  646.  */
  647. putsym(symbol)
  648. int    symbol;
  649. {
  650.     int        symcode;
  651.     symgraph    *gp;
  652.  
  653.     if (! graphic(symbol))  {
  654.         enter_mode(SMNORMAL);
  655.         putchar(symbol & CHARM);
  656.         return;
  657.     }
  658.     symcode = symbol & SYMBOLM;
  659.     if (symcode >= NSYMC)  {
  660.         disperr("Bad symbol code in putsym.");
  661.         return;
  662.     }
  663.     gp = &graphtab[symcode];
  664.     enter_mode(gp->s_mode);
  665.     Puts(gp->s_seq);
  666. }
  667.  
  668.  
  669. /* Enter a particular mode.  If necessary send escape sequence
  670.  * to the terminal.  Handle terminating the previous mode.
  671.  */
  672. enter_mode(mode)
  673. int    mode;
  674. {
  675.     if (termmode == mode)
  676.           return;
  677.  
  678.     switch (termmode)  {
  679.       case SMNORMAL:
  680.         break;
  681.  
  682.       case SMGRAPHIC:
  683.         Puts(end_alt);
  684.         break;
  685.  
  686.       case SMSTANDOUT:
  687.         Puts(end_so);
  688.         break;
  689.  
  690.       default:
  691.         Puts(end_so);
  692.         Puts(end_alt);
  693.         break;
  694.     }
  695.  
  696.     termmode = mode;
  697.  
  698.     switch (termmode)  {
  699.       case SMNORMAL:
  700.         break;
  701.  
  702.       case SMGRAPHIC:
  703.         Puts(start_alt);
  704.         break;
  705.  
  706.       case SMSTANDOUT:
  707.         Puts(start_so);
  708.         break;
  709.  
  710.       default:
  711.         disperr("Bad terminal mode.");
  712.         break;
  713.     }
  714. }
  715.  
  716.  
  717. /* Return values from srch_ktab().
  718.  */
  719. #define SK_SUBSTR    -1
  720. #define SK_NOMATCH    -2
  721.  
  722.  
  723. /* Search the key command table for the given keystroke.
  724.  * If no match, return SK_NOMATCH (which is < 0).
  725.  * If the keystroke is the prefix for one or more commands,
  726.  * return SK_SUBSTR (which is < 0).
  727.  * If an exact match is found, return the index ( >= 0) of
  728.  * the entry that matched.
  729.  */
  730. int srch_ktab(ktab, stroke)
  731. keycmd    ktab[];
  732. char    *stroke;
  733. {
  734.     int    i;
  735.     int    nsubstr = 0;        /* Number of close entries */
  736.  
  737.     for (i = 0 ; ktab[i].c_seq != NULL ; i++)  {
  738.         if (strcmp(ktab[i].c_seq, stroke) == 0)
  739.               return(i);
  740.         if (substrp(ktab[i].c_seq, stroke))
  741.               nsubstr++;
  742.     }
  743.     if (nsubstr > 0)
  744.           return (SK_SUBSTR);
  745.     return (SK_NOMATCH);
  746. }
  747.  
  748.  
  749. /* Return TRUE if the model string starts with the given string.
  750.  * Return false if strlen(given) > strlen(model).
  751.  */
  752. int substrp(model, given)
  753. char    *model;
  754. char    *given;
  755. {
  756.     for ( ; (*model != 0) && (*given != 0) ; model++, given++)  {
  757.         if (*model != *given)
  758.               return (FALSE);
  759.     }
  760.     if (*given == 0)
  761.           return (TRUE);
  762.     return (FALSE);
  763. }
  764.  
  765.  
  766. /* Read a keystroke from stdin and return the command for it.
  767.  *
  768.  * Single character keystrokes not found in the table generate
  769.  * self-insert commands.
  770.  * Control characters other than \n, and \t must be quoted in order
  771.  * to generate self-insert commands.  To quote a char, preceed it
  772.  * by the QUOTEC character.
  773.  * Multi character keystrokes should end in an exact match.  If not,
  774.  * throw away the char that caused no matches in the keycmdtab,
  775.  * beep the terminal, and start over.
  776.  */
  777. int getcmd()
  778. {
  779.     char    keystroke[10];        /* Chars in keystroke. */
  780.     int    nchars;            /* Length of keystroke[]. */
  781.     int    c;
  782.     int    index;            /* Cmd index in keycmdtab. */
  783.     int    code;            /* Cmd code. */
  784.  
  785.   start_over:
  786.     nchars = 0;
  787.     keystroke[0] = 0;
  788.  
  789.     while (TRUE)  {
  790.         c = getchar();
  791.         keystroke[nchars++] = c;
  792.         keystroke[nchars] = 0;
  793.         index = srch_ktab(keycmdtab, keystroke);
  794.         switch (index)  {
  795.           case SK_SUBSTR:
  796.             continue;
  797.  
  798.           case SK_NOMATCH:
  799.             if (nchars != 1)  {
  800.                 beep();
  801.                 goto start_over;
  802.             }
  803.             code = CINSERT;
  804.             if (c == QUOTEC)  {
  805.                 c = getchar();
  806.                 break;
  807.             }
  808.             else if (printable(c))  {
  809.                 break;
  810.             }
  811.             else if ((c != LINEFEED) && (c != TAB))  {
  812.                 beep();
  813.                 goto start_over;
  814.             }
  815.             break;
  816.             
  817.           default:
  818.             if (index < 0)  {
  819.                 disperr("Bad keycmdtab index.");
  820.             }
  821.             code = keycmdtab[index].c_code;
  822.             break;
  823.         }
  824.     return ((code << CMDSHIFT) | (c & CHARM));
  825.     }
  826. }
  827.  
  828.  
  829. /* Cause the terminal to beep.
  830.  */
  831. beep()
  832. {
  833.     Puts("\007");            /* C-G */
  834. }
  835.  
  836.  
  837. /* Save a copy of the given string on the heap.
  838.  * Return pointer to copy.
  839.  */
  840. char *savestr(s)
  841. register char    *s;
  842. {
  843.     char     *p;
  844.     register char    *t;
  845.  
  846.     if (s == NULL)
  847.         return( NULL );
  848.     t = p = (char*) calloc(strlen(s) + 1, 1);
  849.     if (t == NULL)
  850.         return(NULL);
  851.     while (*t++ = *s++);
  852.     return(p); 
  853. }
  854.