/* $Id: keyboard.c,v 1.48 2024/12/06 22:02:22 tom Exp $ */

#include <vttest.h>
#include <ttymodes.h>
#include <esc.h>

/* Test of:
     - DECLL   (Load LEDs)
     - Keyboard return messages
     - SM RM   (Set/Reset Mode) - Cursor Keys
                                - Auto repeat
     - DECKPAM (Keypad Application Mode)
     - DECKPNM (Keypad Numeric Mode)

The standard VT100 keyboard layout:

                                                        UP   DN   LE  RI

ESC   1!   2@   3#   4$   5%   6^   7&   8*   9(   0)   -_   =+   `~  BS

TAB*    qQ   wW   eE   rR   tT   yY   uU   iI   oO   pP   [{   ]}      DEL

**   **   aA   sS   dD   fF   gG   hH   jJ   kK   lL   ;:   ,"   RETN  \|

**   ****   zZ   xX   cC   vV   bB   nN   mM   ,<   .>   /?   ****   LF

             ****************SPACE BAR****************

                                                           PF1 PF2 PF3 PF4

                                                           *7* *8* *9* *-*

                                                           *4* *5* *6* *,*

                                                           *1* *2* *3*

                                                           ***0*** *.* ENT

The standard LK401 (VT420) keyboard layout:

F1 F2 F3 F4 F5   F6 F7 F8 F9 F10   F11 F12 F13 F14   Help Do   F17 F18 F19 F20

  `~  1!   2@   3#   4$   5%   6^   7&   8*   9(   0)   -_   =+   DEL

TAB*    qQ   wW   eE   rR   tT   yY   uU   iI   oO   pP   [{   ]}   Return

**   **   aA   sS   dD   fF   gG   hH   jJ   kK   lL   ;:   ,"   \|

*****   <>  zZ   xX   cC   vV   bB   nN   mM   ,<   .>   /?    ******

***** *****  ****************SPACE BAR****************  ****** ******

                       Find  Insert Remove                 PF1 PF2 PF3 PF4

                      Select  Prev   Next                  *7* *8* *9* *-*

                               Up                          *4* *5* *6* *,*

                       Left   Down   Right                 *1* *2* *3*

                                                           ***0*** *.* ENT
*/
/* *INDENT-OFF* */
static struct key {
    char c;
    int  row;
    int  col;
    const char *symbol;
} VT100_keytab [] = {
    { ESC, 1,  0, "ESC" },
    { '1', 1,  6, "1" },    { '!', 1,  7, "!" },
    { '2', 1, 11, "2" },    { '@', 1, 12, "@" },
    { '3', 1, 16, "3" },    { '#', 1, 17, "#" },
    { '4', 1, 21, "4" },    { '$', 1, 22, "$" },
    { '5', 1, 26, "5" },    { '%', 1, 27, "%" },
    { '6', 1, 31, "6" },    { '^', 1, 32, "^" },
    { '7', 1, 36, "7" },    { '&', 1, 37, "&" },
    { '8', 1, 41, "8" },    { '*', 1, 42, "*" },
    { '9', 1, 46, "9" },    { '(', 1, 47, "(" },
    { '0', 1, 51, "0" },    { ')', 1, 52, ")" },
    { '-', 1, 56, "-" },    { '_', 1, 57, "_" },
    { '=', 1, 61, "=" },    { '+', 1, 62, "+" },
    { '`', 1, 66, "`" },    { '~', 1, 67, "~" },
    {   8, 1, 70, "BS" },
    {   9, 2,  0, " TAB " },
    { 'q', 2,  8, "q" },    { 'Q', 2,  9, "Q" },
    { 'w', 2, 13, "w" },    { 'W', 2, 14, "W" },
    { 'e', 2, 18, "e" },    { 'E', 2, 19, "E" },
    { 'r', 2, 23, "r" },    { 'R', 2, 24, "R" },
    { 't', 2, 28, "t" },    { 'T', 2, 29, "T" },
    { 'y', 2, 33, "y" },    { 'Y', 2, 34, "Y" },
    { 'u', 2, 38, "u" },    { 'U', 2, 39, "U" },
    { 'i', 2, 43, "i" },    { 'I', 2, 44, "I" },
    { 'o', 2, 48, "o" },    { 'O', 2, 49, "O" },
    { 'p', 2, 53, "p" },    { 'P', 2, 54, "P" },
    { '[', 2, 58, "[" },    { '{', 2, 59, "{" },
    { ']', 2, 63, "]" },    { '}', 2, 64, "}" },
    { 127, 2, 71, "DEL" },
    { 'a', 3, 10, "a" },    { 'A', 3, 11, "A" },
    { 's', 3, 15, "s" },    { 'S', 3, 16, "S" },
    { 'd', 3, 20, "d" },    { 'D', 3, 21, "D" },
    { 'f', 3, 25, "f" },    { 'F', 3, 26, "F" },
    { 'g', 3, 30, "g" },    { 'G', 3, 31, "G" },
    { 'h', 3, 35, "h" },    { 'H', 3, 36, "H" },
    { 'j', 3, 40, "j" },    { 'J', 3, 41, "J" },
    { 'k', 3, 45, "k" },    { 'K', 3, 46, "K" },
    { 'l', 3, 50, "l" },    { 'L', 3, 51, "L" },
    { ';', 3, 55, ";" },    { ':', 3, 56, ":" },
    {'\'', 3, 60, "'" },    { '"', 3, 61,"\"" },
    {  13, 3, 65, "RETN"},
    {'\\', 3, 71,"\\" },    { '|', 3, 72, "|" },
    { 'z', 4, 12, "z" },    { 'Z', 4, 13, "Z" },
    { 'x', 4, 17, "x" },    { 'X', 4, 18, "X" },
    { 'c', 4, 22, "c" },    { 'C', 4, 23, "C" },
    { 'v', 4, 27, "v" },    { 'V', 4, 28, "V" },
    { 'b', 4, 32, "b" },    { 'B', 4, 33, "B" },
    { 'n', 4, 37, "n" },    { 'N', 4, 38, "N" },
    { 'm', 4, 42, "m" },    { 'M', 4, 43, "M" },
    { ',', 4, 47, "," },    { '<', 4, 48, "<" },
    { '.', 4, 52, "." },    { '>', 4, 53, ">" },
    { '/', 4, 57, "/" },    { '?', 4, 58, "?" },
    {  10, 4, 69, "LF" },
    { ' ', 5, 13, "                SPACE BAR                "},
    {'\0', 0,  0, ""  }
  },
  LK401_keytab [] = {
    { '`', 1,  3, "`" },    { '~', 1,  4, "~" },
    { '1', 1,  7, "1" },    { '!', 1,  8, "!" },
    { '2', 1, 12, "2" },    { '@', 1, 13, "@" },
    { '3', 1, 17, "3" },    { '#', 1, 18, "#" },
    { '4', 1, 22, "4" },    { '$', 1, 23, "$" },
    { '5', 1, 27, "5" },    { '%', 1, 28, "%" },
    { '6', 1, 32, "6" },    { '^', 1, 33, "^" },
    { '7', 1, 37, "7" },    { '&', 1, 38, "&" },
    { '8', 1, 42, "8" },    { '*', 1, 43, "*" },
    { '9', 1, 47, "9" },    { '(', 1, 48, "(" },
    { '0', 1, 52, "0" },    { ')', 1, 53, ")" },
    { '-', 1, 57, "-" },    { '_', 1, 58, "_" },
    { '=', 1, 62, "=" },    { '+', 1, 63, "+" },
    { 127, 1, 67, "DEL" },
    {   9, 2,  0, "TAB " },
    { 'q', 2,  9, "q" },    { 'Q', 2, 10, "Q" },
    { 'w', 2, 14, "w" },    { 'W', 2, 15, "W" },
    { 'e', 2, 19, "e" },    { 'E', 2, 20, "E" },
    { 'r', 2, 24, "r" },    { 'R', 2, 25, "R" },
    { 't', 2, 29, "t" },    { 'T', 2, 30, "T" },
    { 'y', 2, 34, "y" },    { 'Y', 2, 35, "Y" },
    { 'u', 2, 39, "u" },    { 'U', 2, 40, "U" },
    { 'i', 2, 44, "i" },    { 'I', 2, 45, "I" },
    { 'o', 2, 49, "o" },    { 'O', 2, 50, "O" },
    { 'p', 2, 54, "p" },    { 'P', 2, 55, "P" },
    { '[', 2, 59, "[" },    { '{', 2, 60, "{" },
    { ']', 2, 64, "]" },    { '}', 2, 65, "}" },
    { 13,  2, 69, "Return" },
    { 'a', 3, 11, "a" },    { 'A', 3, 12, "A" },
    { 's', 3, 16, "s" },    { 'S', 3, 17, "S" },
    { 'd', 3, 21, "d" },    { 'D', 3, 22, "D" },
    { 'f', 3, 26, "f" },    { 'F', 3, 27, "F" },
    { 'g', 3, 31, "g" },    { 'G', 3, 32, "G" },
    { 'h', 3, 36, "h" },    { 'H', 3, 37, "H" },
    { 'j', 3, 41, "j" },    { 'J', 3, 42, "J" },
    { 'k', 3, 46, "k" },    { 'K', 3, 47, "K" },
    { 'l', 3, 51, "l" },    { 'L', 3, 52, "L" },
    { ';', 3, 56, ";" },    { ':', 3, 57, ":" },
    {'\'', 3, 61, "'" },    { '"', 3, 62,"\"" },
    {'\\', 3, 66,"\\" },    { '|', 3, 67, "|" },
    { '<', 4,  9, "<" },    { '>', 4, 10, ">" },
    { 'z', 4, 13, "z" },    { 'Z', 4, 14, "Z" },
    { 'x', 4, 18, "x" },    { 'X', 4, 19, "X" },
    { 'c', 4, 23, "c" },    { 'C', 4, 24, "C" },
    { 'v', 4, 28, "v" },    { 'V', 4, 29, "V" },
    { 'b', 4, 33, "b" },    { 'B', 4, 34, "B" },
    { 'n', 4, 38, "n" },    { 'N', 4, 39, "N" },
    { 'm', 4, 43, "m" },    { 'M', 4, 44, "M" },
    { ',', 4, 48, "," },    { '<', 4, 49, "<" },
    { '.', 4, 53, "." },    { '>', 4, 54, ">" },
    { '/', 4, 58, "/" },    { '?', 4, 59, "?" },
    { ' ', 5, 14, "                SPACE BAR                "},
    {'\0', 0,  0, ""  }
  },
  *keytab;
/* *INDENT-ON* */

typedef struct {
  unsigned char prefix;
  const char *msg;
} CTLKEY;
/* *INDENT-OFF* */
static struct curkey {
    CTLKEY curkeymsg[3];
    int  curkeyrow;
    int  curkeycol;
    const char *curkeysymbol;
    const char *curkeyname;
} VT100_curkeytab [] = {

    /* A Reset,   A Set,     VT52  */

    {{{CSI,"A"}, {SS3,"A"}, {ESC,"A"}}, 0, 56, "UP",  "Up arrow"   },
    {{{CSI,"B"}, {SS3,"B"}, {ESC,"B"}}, 0, 61, "DN",  "Down arrow" },
    {{{CSI,"D"}, {SS3,"D"}, {ESC,"D"}}, 0, 66, "LT",  "Left arrow" },
    {{{CSI,"C"}, {SS3,"C"}, {ESC,"C"}}, 0, 71, "RT",  "Right arrow"},
    {{{0,  ""},  {0,  ""},  {0,  "" }}, 0,  0, "",    "" }
  },
  LK401_curkeytab [] = {

    /* A Reset,   A Set,     VT52  */

    {{{CSI,"A"}, {SS3,"A"}, {ESC,"A"}}, 8, 32, "Up",    "Up arrow"   },
    {{{CSI,"B"}, {SS3,"B"}, {ESC,"B"}}, 9, 31, "Down",  "Down arrow" },
    {{{CSI,"D"}, {SS3,"D"}, {ESC,"D"}}, 9, 24, "Left",  "Left arrow" },
    {{{CSI,"C"}, {SS3,"C"}, {ESC,"C"}}, 9, 38, "Right", "Right arrow"},
    {{{0,  ""},  {0,  ""},  {0,  "" }}, 0,  0, "",      "" }
  },
  *curkeytab;
static struct fnckey {
    CTLKEY fnkeymsg[2];
    int  fnkeyrow;
    int  fnkeycol;
    const char *fnkeysymbol;
    const char *fnkeyname;
} fnkeytab [] = {

    /* Normal,     VT100/VT52  */
    {{{CSI,"11~"}, {0,""}},  0,  1, "F1",   "F1 (xterm)"   },
    {{{CSI,"12~"}, {0,""}},  0,  4, "F2",   "F2 (xterm)"   },
    {{{CSI,"13~"}, {0,""}},  0,  7, "F3",   "F3 (xterm)"   },
    {{{CSI,"14~"}, {0,""}},  0, 10, "F4",   "F4 (xterm)"   },
    {{{CSI,"15~"}, {0,""}},  0, 13, "F5",   "F5 (xterm)"   },

    {{{CSI,"17~"}, {0,""}},  0, 18, "F6",   "F6"   },
    {{{CSI,"18~"}, {0,""}},  0, 21, "F7",   "F7"   },
    {{{CSI,"19~"}, {0,""}},  0, 24, "F8",   "F8"   },
    {{{CSI,"20~"}, {0,""}},  0, 27, "F9",   "F9"   },
    {{{CSI,"21~"}, {0,""}},  0, 30, "F10",  "F10"   },
    {{{CSI,"23~"}, {0,""}},  0, 36, "F11",  "F11"   },
    {{{CSI,"24~"}, {0,""}},  0, 40, "F12",  "F12"   },
    {{{CSI,"25~"}, {0,""}},  0, 44, "F13",  "F13"   },
    {{{CSI,"26~"}, {0,""}},  0, 48, "F14",  "F14"   },
    {{{CSI,"28~"}, {0,""}},  0, 54, "Help", "Help (F15)"   },
    {{{CSI,"29~"}, {0,""}},  0, 59, "Do",   "Do (F16)"   },
    {{{CSI,"31~"}, {0,""}},  0, 64, "F17",  "F17"   },
    {{{CSI,"32~"}, {0,""}},  0, 68, "F18",  "F18"   },
    {{{CSI,"33~"}, {0,""}},  0, 72, "F19",  "F19"   },
    {{{CSI,"34~"}, {0,""}},  0, 76, "F20",  "F20"   },
    {{{0,  ""},    {0,"" }}, 0,  0, "",     ""   }
  },
  edt_keypadtab[] = {
    {{{CSI,"1~"}, {0,""}}, 6, 24, "Find" ,  "Find"  },
    {{{CSI,"2~"}, {0,""}}, 6, 30, "Insert", "Insert Here"   },
    {{{CSI,"3~"}, {0,""}}, 6, 37, "Remove", "Remove"   },
    {{{CSI,"4~"}, {0,""}}, 7, 23, "Select", "Select"   },
    {{{CSI,"5~"}, {0,""}}, 7, 31, "Prev",   "Prev"   },
    {{{CSI,"6~"}, {0,""}}, 7, 38, "Next",   "Next"   },
    {{{0,  ""},   {0,""}}, 0,  0, "",       ""   }
  };
static struct fnkey {
    CTLKEY fnkeymsg[4];
    int  fnkeyrow;
    int  fnkeycol;
    const char *fnkeysymbol;
    const char *fnkeyname;
} num_keypadtab [] = {

  /* ANSI-num, ANSI-app,  VT52-nu,   VT52-ap,     r,  c,  symb   name        */

  {{{SS3,"P"}, {SS3,"P"}, {ESC,"P"}, {ESC,"P" }}, 6, 59, "PF1", "PF1"        },
  {{{SS3,"Q"}, {SS3,"Q"}, {ESC,"Q"}, {ESC,"Q" }}, 6, 63, "PF2", "PF2"        },
  {{{SS3,"R"}, {SS3,"R"}, {ESC,"R"}, {ESC,"R" }}, 6, 67, "PF3", "PF3"        },
  {{{SS3,"S"}, {SS3,"S"}, {ESC,"S"}, {ESC,"S" }}, 6, 71, "PF4", "PF4"        },
  {{{0,  "7"}, {SS3,"w"}, {0,  "7"}, {ESC,"?w"}}, 7, 59, " 7 ", "Numeric 7"  },
  {{{0,  "8"}, {SS3,"x"}, {0,  "8"}, {ESC,"?x"}}, 7, 63, " 8 ", "Numeric 8"  },
  {{{0,  "9"}, {SS3,"y"}, {0,  "9"}, {ESC,"?y"}}, 7, 67, " 9 ", "Numeric 9"  },
  {{{0,  "-"}, {SS3,"m"}, {0,  "-"}, {ESC,"?m"}}, 7, 71, " - ", "Minus"      },
  {{{0,  "4"}, {SS3,"t"}, {0,  "4"}, {ESC,"?t"}}, 8, 59, " 4 ", "Numeric 4"  },
  {{{0,  "5"}, {SS3,"u"}, {0,  "5"}, {ESC,"?u"}}, 8, 63, " 5 ", "Numeric 5"  },
  {{{0,  "6"}, {SS3,"v"}, {0,  "6"}, {ESC,"?v"}}, 8, 67, " 6 ", "Numeric 6"  },
  {{{0,  ","}, {SS3,"l"}, {0,  ","}, {ESC,"?l"}}, 8, 71, " , ", "Comma"      },
  {{{0,  "1"}, {SS3,"q"}, {0,  "1"}, {ESC,"?q"}}, 9, 59, " 1 ", "Numeric 1"  },
  {{{0,  "2"}, {SS3,"r"}, {0,  "2"}, {ESC,"?r"}}, 9, 63, " 2 ", "Numeric 2"  },
  {{{0,  "3"}, {SS3,"s"}, {0,  "3"}, {ESC,"?s"}}, 9, 67, " 3 ", "Numeric 3"  },
  {{{0,  "0"}, {SS3,"p"}, {0,  "0"}, {ESC,"?p"}},10, 59, "   0   ","Numeric 0"},
  {{{0,  "."}, {SS3,"n"}, {0,  "."}, {ESC,"?n"}},10, 67, " . ", "Point"      },
  {{{0,"\015"},{SS3,"M"}, {0,"\015"},{ESC,"?M"}},10, 71, "ENT", "ENTER"      },
  {{{0,  ""},  {0,  ""},  {0,  ""},  {0,  ""}},   0,  0, "",    ""           }
};
/* *INDENT-ON* */

struct natkey {
  char natc;
  int natrow;
  int natcol;
  const char *natsymbol;
};

#define TR_BEGIN(func) \
  if (LOG_ENABLED) \
    fprintf(log_fp, NOTE_STR #func "(%s)\n", flag ? "hilite" : "")
#define TR_ENDED(func) \
  if (LOG_ENABLED) \
    fprintf(log_fp, NOTE_STR "..." #func "(%s)\n", flag ? "hilite" : "")

static int same_CTLKEY(const char *response, const CTLKEY *code);

static int
find_cursor_key(const char *curkeystr, int ckeymode)
{
  int i;

  for (i = 0; curkeytab[i].curkeysymbol[0] != '\0'; i++) {
    if (same_CTLKEY(curkeystr, &curkeytab[i].curkeymsg[ckeymode])) {
      return i;
    }
  }
  return -1;
}

static int
find_editing_key(const char *keypadstr, int fkeymode)
{
  int i;

  for (i = 0; edt_keypadtab[i].fnkeysymbol[0] != '\0'; i++) {
    if (same_CTLKEY(keypadstr, &edt_keypadtab[i].fnkeymsg[fkeymode])) {
      return i;
    }
  }
  return -1;
}

static int
find_function_key(const char *keypadstr, int fkeymode)
{
  int i;

  for (i = 0; fnkeytab[i].fnkeysymbol[0] != '\0'; i++) {
    if (same_CTLKEY(keypadstr, &fnkeytab[i].fnkeymsg[fkeymode])) {
      return i;
    }
  }
  return -1;
}

static int
find_num_keypad_key(const char *keypadstr, int fkeymode)
{
  int i;

  for (i = 0; num_keypadtab[i].fnkeysymbol[0] != '\0'; i++) {
    if (same_CTLKEY(keypadstr, &num_keypadtab[i].fnkeymsg[fkeymode])) {
      return i;
    }
  }
  return -1;
}

static void
set_keyboard_layout(struct natkey *table)
{
  int i, j;

  for (j = 0; table[j].natc != '\0'; j++) {
    for (i = 0; keytab[i].c != '\0'; i++) {
      if (keytab[i].row == table[j].natrow &&
          keytab[i].col == table[j].natcol) {
        keytab[i].c = table[j].natc;
        keytab[i].symbol = table[j].natsymbol;
        break;
      }
    }
  }
}

static int
default_layout(MENU_ARGS)
{
  /* FIXME: nothing resets the default keytab to original state */
  return MENU_NOHOLD;
}

static int
same_CTLKEY(const char *response, const CTLKEY *code)
{
  switch (code->prefix) {
  case CSI:
    if ((response = skip_csi_2(response)) == NULL)
      return FALSE;
    break;
  case SS3:
    if ((response = skip_ss3_2(response)) == NULL)
      return FALSE;
    break;
  case ESC:
    if (*response++ != ESC)
      return FALSE;
    /* FALLTHRU */
  default:
    break;
  }
  return !strcmp(response, code->msg);
}

static int
set_D47_layout(MENU_ARGS)
{
  /* *INDENT-OFF* */
  static struct natkey table[] =
  {
    { '"', 1, 12, "\""},
    { '&', 1, 32, "&" },
    { '/', 1, 37, "/" },
    { '(', 1, 42, "(" },
    { ')', 1, 47, ")" },
    { '=', 1, 52, "=" },
    { '+', 1, 56, "+" },
    { '?', 1, 57, "?" },
    { '`', 1, 61, "`" },
    { '@', 1, 62, "@" },
    { '<', 1, 66, "<" },
    { '>', 1, 67, ">" },
    { '}', 2, 58, "}" },
    { ']', 2, 59, "]" },
    { '^', 2, 63, "^" },
    { '~', 2, 64, "~" },
    { '|', 3, 55, "|" },
    {'\\', 3, 56,"\\" },
    { '{', 3, 60, "{" },
    { '[', 3, 61, "[" },
    {'\'', 3, 71, "'" },
    { '*', 3, 72, "*" },
    { ',', 4, 47, "," },
    { ';', 4, 48, ";" },
    { '.', 4, 52, "." },
    { ':', 4, 53, ":" },
    { '-', 4, 57, "-" },
    { '_', 4, 58, "_" },
    {'\0', 0,  0, ""  }
  };
  /* *INDENT-ON* */

  set_keyboard_layout(table);
  return MENU_NOHOLD;
}

static int
set_E47_layout(MENU_ARGS)
{
  /* *INDENT-OFF* */
  static struct natkey table[] =
  {
    { '"', 1, 12, "\""},
    { '&', 1, 32, "&" },
    { '/', 1, 37, "/" },
    { '(', 1, 42, "(" },
    { ')', 1, 47, ")" },
    { '=', 1, 52, "=" },
    { '+', 1, 56, "+" },
    { '?', 1, 57, "?" },
    { '`', 1, 61, "`" },
    { '@', 1, 62, "@" },
    { '<', 1, 66, "<" },
    { '>', 1, 67, ">" },
    { '}', 2, 58, "}" },
    { ']', 2, 59, "]" },
    { '~', 2, 63, "~" },
    { '^', 2, 64, "^" },
    { '|', 3, 55, "|" },
    {'\\', 3, 56,"\\" },
    { '{', 3, 60, "{" },
    { '[', 3, 61, "[" },
    {'\'', 3, 71, "'" },
    { '*', 3, 72, "*" },
    { ',', 4, 47, "," },
    { ';', 4, 48, ";" },
    { '.', 4, 52, "." },
    { ':', 4, 53, ":" },
    { '-', 4, 57, "-" },
    { '_', 4, 58, "_" },
    {'\0', 0,  0, ""  }
  };
  /* *INDENT-ON* */

  set_keyboard_layout(table);
  return MENU_NOHOLD;
}

static void
show_character(int i, const char *scs_params, int hilite)
{
  int special = ((scs_params != NULL) && (strlen(keytab[i].symbol) == 1));

  vt_move(1 + 2 * keytab[i].row, 1 + keytab[i].col);
  if (hilite)
    vt_hilite(TRUE);
  if (special)
    esc(scs_params);
  tprintf("%s", keytab[i].symbol);
  if (special)
    scs(0, 'B');
  if (hilite)
    vt_hilite(FALSE);
}

static void
show_cursor_keys(int flag)
{
  int i;

  TR_BEGIN(show_cursor_keys);

  curkeytab = (terminal_id() < 200) ? VT100_curkeytab : LK401_curkeytab;

  for (i = 0; curkeytab[i].curkeysymbol[0] != '\0'; i++) {
    vt_move(1 + 2 * curkeytab[i].curkeyrow, 1 + curkeytab[i].curkeycol);
    if (flag)
      vt_hilite(TRUE);
    tprintf("%s", curkeytab[i].curkeysymbol);
    if (flag)
      vt_hilite(FALSE);
  }

  TR_ENDED(show_cursor_keys);
}

static void
show_editing_keypad(int flag)
{
  if (terminal_id() >= 200) {
    int i;

    TR_BEGIN(show_editing_keypad);

    for (i = 0; edt_keypadtab[i].fnkeysymbol[0] != '\0'; i++) {
      vt_move(1 + 2 * edt_keypadtab[i].fnkeyrow, 1 + edt_keypadtab[i].fnkeycol);
      if (flag)
        vt_hilite(TRUE);
      tprintf("%s", edt_keypadtab[i].fnkeysymbol);
      if (flag)
        vt_hilite(FALSE);
    }

    TR_ENDED(show_editing_keypad);
  }
}

static void
show_function_keys(int flag)
{
  if (terminal_id() >= 200) {
    int i;

    TR_BEGIN(show_function_keys);

    for (i = 0; fnkeytab[i].fnkeysymbol[0] != '\0'; i++) {
      vt_move(1 + 2 * fnkeytab[i].fnkeyrow, 1 + fnkeytab[i].fnkeycol);
      if (flag)
        vt_hilite(TRUE);
      tprintf("%s", fnkeytab[i].fnkeysymbol);
      if (flag)
        vt_hilite(FALSE);
    }

    TR_ENDED(show_function_keys);
  }
}

static void
show_keyboard(int flag GCC_UNUSED, const char *scs_params)
{
  int i;

  if (LOG_ENABLED)
    fprintf(log_fp, NOTE_STR "show_keyboard()\n");

  if (terminal_id() >= 200)   /* LK201 _looks_ the same as LK401 (to me) */
    keytab = LK401_keytab;
  else
    keytab = VT100_keytab;

  for (i = 0; keytab[i].c != '\0'; i++) {
    show_character(i, scs_params, TRUE);
  }

  if (LOG_ENABLED)
    fprintf(log_fp, NOTE_STR "...show_keyboard()\n");
}

static void
show_numeric_keypad(int flag)
{
  int i;

  TR_BEGIN(show_numeric_keypad);

  for (i = 0; num_keypadtab[i].fnkeysymbol[0] != '\0'; i++) {
    vt_move(1 + 2 * num_keypadtab[i].fnkeyrow, 1 + num_keypadtab[i].fnkeycol);
    if (flag)
      vt_hilite(TRUE);
    tprintf("%s", num_keypadtab[i].fnkeysymbol);
    if (flag)
      vt_hilite(FALSE);
  }

  TR_ENDED(show_numeric_keypad);
}

/******************************************************************************/

static int
tst_AnswerBack(MENU_ARGS)
{
  const char *abmstr;

  set_tty_crmod(TRUE);
  vt_clear(2);
  vt_move(5, 1);
  println("Finally, a check of the ANSWERBACK MESSAGE, which can be sent");
  println("by pressing CTRL-BREAK. The answerback message can be loaded");
  println("in SET-UP B by pressing SHIFT-A and typing e.g.");
  println("");
  println("         \" H e l l o , w o r l d Return \"");
  println("");
  println("(the double-quote characters included).  Do that, and then try");
  println("to send an answerback message with CTRL-BREAK.  If it works,");
  println("the answerback message should be displayed in reverse mode.");
  println("Finish with a single RETURN.");

  set_tty_crmod(FALSE);

  do {
    int row, col;

    vt_move(row = 17, col = 1);
    inflush();
    abmstr = get_reply();
    vt_move(row, col);
    vt_el(0);
    chrprint2(abmstr, row, col);
  } while (strcmp(abmstr, "\r"));

  restore_ttymodes();
  return MENU_NOHOLD;
}

static int
tst_AutoRepeat(MENU_ARGS)
{
  char arptstring[BUF_SIZE];

  vt_clear(2);
  vt_move(10, 1);
  println("Test of the AUTO REPEAT feature");

  println("");
  println("Hold down an alphanumeric key for a while, then push RETURN.");
  tprintf("%s", "Auto Repeat OFF: ");
  decarm(FALSE);  /* DECARM */
  inputline(arptstring);
  if (strlen(arptstring) == 0)
    println("No characters read!??");
  else if (strlen(arptstring) == 1)
    println("OK.");
  else
    println("Too many characters read.");
  println("");

  println("Hold down an alphanumeric key for a while, then push RETURN.");
  tprintf("%s", "Auto Repeat ON: ");
  decarm(TRUE);
  inputline(arptstring);
  if (strlen(arptstring) == 0)
    println("No characters read!??");
  else if (strlen(arptstring) == 1)
    println("Not enough characters read.");
  else
    println("OK.");
  println("");

  return MENU_HOLD;
}

static int
tst_ControlKeys(MENU_ARGS)
{
  int i, okflag;
  int kbdc;
  char temp[80];
  char *kbds = strcpy(temp, " ");
  /* *INDENT-OFF* */
  static struct {
      int  ccount;
      const char *csymbol;
  } ckeytab [] = {
      { 0, "NUL (CTRL-@ or CTRL-Space)" },
      { 0, "SOH (CTRL-A)" },
      { 0, "STX (CTRL-B)" },
      { 0, "ETX (CTRL-C)" },
      { 0, "EOT (CTRL-D)" },
      { 0, "ENQ (CTRL-E)" },
      { 0, "ACK (CTRL-F)" },
      { 0, "BEL (CTRL-G)" },
      { 0, "BS  (CTRL-H) (BACK SPACE)" },
      { 0, "HT  (CTRL-I) (TAB)" },
      { 0, "LF  (CTRL-J) (LINE FEED)" },
      { 0, "VT  (CTRL-K)" },
      { 0, "FF  (CTRL-L)" },
      { 0, "CR  (CTRL-M) (RETURN)" },
      { 0, "SO  (CTRL-N)" },
      { 0, "SI  (CTRL-O)" },
      { 0, "DLE (CTRL-P)" },
      { 0, "DC1 (CTRL-Q) (X-On)" },
      { 0, "DC2 (CTRL-R)" },
      { 0, "DC3 (CTRL-S) (X-Off)" },
      { 0, "DC4 (CTRL-T)" },
      { 0, "NAK (CTRL-U)" },
      { 0, "SYN (CTRL-V)" },
      { 0, "ETB (CTRL-W)" },
      { 0, "CAN (CTRL-X)" },
      { 0, "EM  (CTRL-Y)" },
      { 0, "SUB (CTRL-Z)" },
      { 0, "ESC (CTRL-[) (ESCAPE)" },
      { 0, "FS  (CTRL-\\ or CTRL-? or CTRL-_)" },
      { 0, "GS  (CTRL-])" },
      { 0, "RS  (CTRL-^ or CTRL-~ or CTRL-`)" },
      { 0, "US  (CTRL-_ or CTRL-?)" }
  };
  /* *INDENT-ON* */

  vt_clear(2);
  for (i = 0; i < 32; i++) {
    vt_move(1 + (i % 16), 1 + 40 * (i / 16));
    vt_hilite(TRUE);
    tprintf("%s", ckeytab[i].csymbol);
    vt_hilite(FALSE);
  }
  vt_move(19, 1);

  set_tty_crmod(TRUE);
  pause_replay();

  println("Push each CTRL-key TWICE. Note that you should be able to send *all*");
  println("CTRL-codes twice, including CTRL-S (X-Off) and CTRL-Q (X-On)!");
  println("Finish with DEL (also called DELETE or RUB OUT), or wait 1 minute.");

  set_tty_raw(TRUE);
  do {
    int row, col;

    vt_move(row = max_lines - 1, col = 1);
    kbdc = get_char();
    if (kbdc < 0)
      break;
    vt_move(row, col);
    vt_el(0);
    if (kbdc < 32) {
      tprintf("  %s", ckeytab[kbdc].csymbol);
      if (LOG_ENABLED)
        fprintf(log_fp, "Key: %s\n", ckeytab[kbdc].csymbol);
    } else {
      sprintf(kbds, "%c", kbdc);
      chrprint2(kbds, row, col);
      tprintf("%s", " -- not a CTRL key");
    }
    if (kbdc < 32) {
      ckeytab[kbdc].ccount++;
      if (ckeytab[kbdc].ccount == 2) {
        vt_move(1 + (kbdc % 16), 1 + 40 * (kbdc / 16));
        tprintf("%s", ckeytab[kbdc].csymbol);
      }
    }
  } while (kbdc != '\177');

  resume_replay();
  restore_ttymodes();

  vt_move(max_lines, 1);
  okflag = 1;
  for (i = 0; i < 32; i++)
    if (ckeytab[i].ccount < 2)
      okflag = 0;
  if (okflag)
    printxx("%s", "OK. ");
  else
    printxx("%s", "You have not been able to send all CTRL keys! ");
  return MENU_HOLD;
}

static int
tst_CursorKeys(MENU_ARGS)
{
  int i;
  int ckeymode;
  int row, col;
  const char *curkeystr;
  VTLEVEL save;

  static const char *curkeymodes[3] =
  {
    "ANSI / Cursor key mode RESET",
    "ANSI / Cursor key mode SET",
    "VT52 Mode"
  };

  vt_clear(2);
  save_level(&save);
  show_keyboard(0, (char *) 0);
  show_function_keys(0);
  show_editing_keypad(0);
  show_numeric_keypad(0);
  vt_move(max_lines - 2, 1);

  set_tty_crmod(FALSE);
  set_tty_echo(FALSE);

  for (ckeymode = 0; ckeymode <= 2; ckeymode++) {
    decckm(ckeymode);   /* DECCKM */

    show_cursor_keys(1);
    vt_move(21, 1);
    tprintf("<%s>%20s", curkeymodes[ckeymode], "");
    vt_move(max_lines - 2, 1);
    vt_el(0);
    vt_move(max_lines - 2, 1);
    printxx("%s", "Press each cursor key. Finish with TAB.");
    for (;;) {
      vt_move(max_lines - 1, 1);
      if (ckeymode == 2)
        set_level(0);   /* VT52 mode */
      curkeystr = instr();
      set_level(1);   /* ANSI mode */

      vt_move(row = max_lines - 1, col = 1);
      vt_el(0);
      vt_move(row, col);
      chrprint2(curkeystr, row, col);

      if (!strcmp(curkeystr, "\t"))
        break;
      if ((i = find_cursor_key(curkeystr, ckeymode)) >= 0) {
        vt_hilite(TRUE);
        show_result(" (%s key) ", curkeytab[i].curkeyname);
        vt_hilite(FALSE);
        vt_move(1 + 2 * curkeytab[i].curkeyrow, 1 + curkeytab[i].curkeycol);
        tprintf("%s", curkeytab[i].curkeysymbol);
      } else {
        vt_hilite(TRUE);
        show_result("%s", " (Unknown cursor key) ");
        vt_hilite(FALSE);
      }
    }
  }

  decckm(FALSE);
  restore_level(&save);
  vt_move(max_lines - 1, 1);
  vt_el(0);
  restore_ttymodes();
  return MENU_MERGE;
}

static int
tst_EditingKeypad(MENU_ARGS)
{
  int i;
  int fkeymode;
  int row, col;
  const char *fnkeystr;
  VTLEVEL save;

  static const char *fnkeymodes[] =
  {
    "Normal mode",
    "VT100/VT52 mode (none should be recognized)"
  };

  save_level(&save);
  show_keyboard(0, (char *) 0);
  show_cursor_keys(0);
  show_function_keys(0);
  show_numeric_keypad(0);
  vt_move(max_lines - 2, 1);

  if (terminal_id() < 200) {
    printxx("Sorry, a real VT%d terminal doesn't have an editing keypad\n",
            terminal_id());
    return MENU_HOLD;
  }

  set_tty_crmod(FALSE);
  set_tty_echo(FALSE);

  for (fkeymode = 0; fkeymode <= 1; fkeymode++) {
    show_editing_keypad(1);
    vt_move(21, 1);
    tprintf("<%s>%20s", fnkeymodes[fkeymode], "");
    vt_move(max_lines - 2, 1);
    vt_el(0);
    vt_move(max_lines - 2, 1);
    printxx("%s", "Press each function key. Finish with TAB.");

    for (;;) {
      vt_move(max_lines - 1, 1);
      if (fkeymode == 0)
        default_level();
      if (fkeymode != 0)
        set_level(1);   /* VT100 mode */

      fnkeystr = instr();

      vt_move(row = max_lines - 1, col = 1);
      vt_el(0);
      vt_move(row, col);
      chrprint2(fnkeystr, row, col);

      if (!strcmp(fnkeystr, "\t"))
        break;
      if ((i = find_editing_key(fnkeystr, fkeymode)) >= 0) {
        vt_hilite(TRUE);
        show_result(" (%s key) ", edt_keypadtab[i].fnkeyname);
        vt_hilite(FALSE);
        vt_move(1 + 2 * edt_keypadtab[i].fnkeyrow, 1 + edt_keypadtab[i].fnkeycol);
        tprintf("%s", edt_keypadtab[i].fnkeysymbol);
      } else {
        vt_hilite(TRUE);
        show_result("%s", " (Unknown function key) ");
        vt_hilite(FALSE);
      }
    }
  }

  vt_move(max_lines - 1, 1);
  vt_el(0);
  restore_level(&save);
  restore_ttymodes();
  return MENU_MERGE;
}

static int
tst_FunctionKeys(MENU_ARGS)
{
  int i;
  int fkeymode;
  int row, col;
  const char *fnkeystr;
  VTLEVEL save;

  static const char *fnkeymodes[] =
  {
    "Normal mode (F6-F20, except xterm also F1-F5)",
    "VT100/VT52 mode (F11-F13 only)"
  };

  save_level(&save);
  show_keyboard(0, (char *) 0);
  show_cursor_keys(0);
  show_editing_keypad(0);
  show_numeric_keypad(0);
  vt_move(max_lines - 2, 1);

  if (terminal_id() < 200) {
    printxx("Sorry, a real VT%d terminal doesn't have function keys\n",
            terminal_id());
    return MENU_HOLD;
  }

  set_tty_crmod(FALSE);
  set_tty_echo(FALSE);

  for (fkeymode = 0; fkeymode <= 1; fkeymode++) {
    show_function_keys(1);
    vt_move(21, 1);
    tprintf("<%s>%20s", fnkeymodes[fkeymode], "");
    vt_move(max_lines - 2, 1);
    vt_el(0);
    vt_move(max_lines - 2, 1);
    printxx("%s", "Press each function key. Finish with TAB.");

    for (;;) {
      vt_move(max_lines - 1, 1);
      if (fkeymode == 0)
        default_level();
      if (fkeymode != 0)
        set_level(1);   /* VT100 mode */

      fnkeystr = instr();

      vt_move(row = max_lines - 1, col = 1);
      vt_el(0);
      vt_move(row, col);
      chrprint2(fnkeystr, row, col);

      if (!strcmp(fnkeystr, "\t"))
        break;
      if ((i = find_function_key(fnkeystr, fkeymode)) >= 0) {
        vt_hilite(TRUE);
        show_result(" (%s key) ", fnkeytab[i].fnkeyname);
        vt_hilite(FALSE);
        vt_move(1 + 2 * fnkeytab[i].fnkeyrow, 1 + fnkeytab[i].fnkeycol);
        tprintf("%s", fnkeytab[i].fnkeysymbol);
      } else {
        vt_hilite(TRUE);
        show_result("%s", " (Unknown function key) ");
        vt_hilite(FALSE);
      }
    }
  }

  vt_move(max_lines - 1, 1);
  vt_el(0);
  restore_level(&save);
  restore_ttymodes();
  return MENU_MERGE;
}

static int
tst_NumericKeypad(MENU_ARGS)
{
  int i;
  int fkeymode;
  int row, col;
  const char *fnkeystr;
  VTLEVEL save;

  static const char *fnkeymodes[4] =
  {
    "ANSI Numeric mode",
    "ANSI Application mode",
    "VT52 Numeric mode",
    "VT52 Application mode"
  };

  vt_clear(2);
  save_level(&save);
  show_keyboard(0, (char *) 0);
  show_cursor_keys(0);
  show_function_keys(0);
  show_editing_keypad(0);
  vt_move(max_lines - 2, 1);

  set_tty_crmod(FALSE);
  set_tty_echo(FALSE);

  for (fkeymode = 0; fkeymode <= 3; fkeymode++) {
    show_numeric_keypad(1);
    vt_move(21, 1);
    tprintf("<%s>%20s", fnkeymodes[fkeymode], "");
    vt_move(max_lines - 2, 1);
    vt_el(0);
    vt_move(max_lines - 2, 1);
    printxx("%s", "Press each function key. Finish with TAB.");

    for (;;) {
      vt_move(max_lines - 1, 1);
      if (fkeymode >= 2)
        set_level(0);   /* VT52 mode */
      if (fkeymode % 2)
        deckpam();  /* Application mode */
      else
        deckpnm();  /* Numeric mode     */
      fnkeystr = instr();
      set_level(1);   /* ANSI mode */

      vt_move(row = max_lines - 1, col = 1);
      vt_el(0);
      vt_move(row, col);
      chrprint2(fnkeystr, row, col);

      if (!strcmp(fnkeystr, "\t"))
        break;
      if ((i = find_num_keypad_key(fnkeystr, fkeymode)) >= 0) {
        vt_hilite(TRUE);
        show_result(" (%s key) ", num_keypadtab[i].fnkeyname);
        vt_hilite(FALSE);
        vt_move(1 + 2 * num_keypadtab[i].fnkeyrow, 1 + num_keypadtab[i].fnkeycol);
        tprintf("%s", num_keypadtab[i].fnkeysymbol);
      } else {
        vt_hilite(TRUE);
        show_result("%s", " (Unknown function key) ");
        vt_hilite(FALSE);
      }
    }
  }

  deckpnm();
  vt_move(max_lines - 1, 1);
  vt_el(0);
  restore_level(&save);
  restore_ttymodes();
  return MENU_MERGE;
}

static int
tst_KeyboardLayout(MENU_ARGS)
{
  /* *INDENT-OFF* */
  static MENU keyboardmenu[] = {
      { "Standard American ASCII layout",                    default_layout },
      { "Swedish national layout D47",                       set_D47_layout },
      { "Swedish national layout E47",                       set_E47_layout },
        /* add new keyboard layouts here */
      { "",                                                  NULL }
    };
  /* *INDENT-ON* */

  if (terminal_id() < 200) {
    vt_clear(2);
    keytab = VT100_keytab;
    title(0);
    println("Choose keyboard layout:");
    (void) menu(keyboardmenu);
  }

  tst_keyboard_layout((char *) 0);

  return MENU_MERGE;
}

static int
tst_LED_Lights(MENU_ARGS)
{
  int i;
  const char *ledmsg[6], *ledseq[6];
  /* *INDENT-OFF* */
  ledmsg[0] = "L1 L2 L3 L4"; ledseq[0] = "1;2;3;4";
  ledmsg[1] = "   L2 L3 L4"; ledseq[1] = "1;0;4;3;2";
  ledmsg[2] = "   L2 L3";    ledseq[2] = "1;4;;2;3";
  ledmsg[3] = "L1 L2";       ledseq[3] = ";;2;1";
  ledmsg[4] = "L1";          ledseq[4] = "1";
  ledmsg[5] = "";            ledseq[5] = "";
  /* *INDENT-ON* */

#ifdef UNIX
  fflush(stdout);
#endif
  vt_clear(2);
  vt_move(10, 1);
  println("These LEDs (\"lamps\") on the keyboard should be on:");
  for (i = 0; i <= 5; i++) {
    vt_move(10, 52);
    vt_el(0);
    printxx("%s", ledmsg[i]);
    decll("0");
    decll(ledseq[i]);
    vt_move(12, 1);
    holdit();
  }
  decll("0");
  return MENU_NOHOLD;
}

/******************************************************************************/
int
tst_keyboard_layout(const char *scs_params)
{
  int i;
  int kbdc;
  char temp[80];
  char *kbds = strcpy(temp, " ");

  vt_clear(2);
  show_keyboard(1, scs_params);
  show_cursor_keys(0);
  show_function_keys(0);
  show_editing_keypad(0);
  show_numeric_keypad(0);
  vt_move(max_lines - 2, 1);

  set_tty_crmod(FALSE);
  set_tty_echo(FALSE);

  inflush();
  printxx("Press each key, both shifted and unshifted. Finish with RETURN:");

  do {          /* while (kbdc != 13) */
    int row, col;
    vt_move(row = max_lines - 1, col = 1);
    kbdc = get_char();
    vt_move(row, col);
    vt_el(0);
    if (scs_params != NULL && kbdc > ' ' && kbdc < '\177') {
      vt_hilite(TRUE);
      esc(scs_params);
      tprintf(" %c ", kbdc);
      scs(0, 'B');
      tprintf("= %d ", kbdc);
      scs(0, 'B');
      vt_hilite(FALSE);
    } else {
      sprintf(kbds, "%c", kbdc);
      chrprint2(kbds, row, col);
    }
    for (i = 0; keytab[i].c != '\0'; i++) {
      if (keytab[i].c == kbdc) {
        show_character(i, scs_params, FALSE);
        /* LK401 keyboard will have more than one hit for '<' and '>' */
      }
    }
  } while (kbdc != 13);

  vt_move(max_lines - 1, 1);
  vt_el(0);
  restore_ttymodes();
  return MENU_MERGE;
}

/******************************************************************************/
int
tst_keyboard(MENU_ARGS)
{
  /* *INDENT-OFF* */
  static MENU my_menu[] = {
      { "Exit",                                              NULL },
      { "LED Lights",                                        tst_LED_Lights },
      { "Auto Repeat",                                       tst_AutoRepeat },
      { "Keyboard Layout",                                   tst_KeyboardLayout },
      { "Cursor Keys",                                       tst_CursorKeys },
      { "Numeric Keypad",                                    tst_NumericKeypad },
      { "Editing Keypad",                                    tst_EditingKeypad },
      { "Function Keys",                                     tst_FunctionKeys },
      { "AnswerBack",                                        tst_AnswerBack },
      { "Control Keys",                                      tst_ControlKeys },
      { "", NULL }
    };
  /* *INDENT-ON* */

  do {
    vt_clear(2);
    __(title(0), printxx("Keyboard Tests"));
    __(title(2), println("Choose test type:"));
  } while (menu(my_menu));
  return MENU_NOHOLD;
}
