/* scanchek.c */
/*
** By: Phil Benchoff, June 1986
** v2.0: 31 Aug 1987, Update Kermit to v2.29C, add Turbo-C support.
** v3.0: 31 Aug 1987 merge header and main body, telescope code, add
**    some revisons and Bios support for most C compilers. Joe R. Doupnik
** v3.1: 01 Sep 1987, add #ifndef before bioskey() function.
**
** Compiled with Computer Innovations C-86
** The C-86 IBMPC library will be needed at link time.
**
** This program displays various information about the keys pressed.
** It's original intent is to be used in testing the new IBM keyboards
** (you know the ones, with the F keys on the top).
**
** The key_getc() function in the C-86 IBMPC library returns the contents
** of the AX register after an interrupt 0x16, function 0. Most C compilers
** support the library int86() function to do an interrupt [jrd].
**
** The BIOS interrupt 0x16 is used for keyboard services. The particular
** service is determined by the service code in AH when the interrupt
** is invoked.
**
** AH = 0 returns the next available keyboard character.
**        AL = extended-ASCII character code
**        AH = keyboard scan code ( 0 if the alt-numeric input used )
**  or
**        AL = 0
**        AH = special character code ( F1-F10, Home, End, etc.)
** AH = 1 test for a character ( don't wait )
**        returned values same as above
** AH = 2 returns the current shift states
**        AL = shift states
**
** The MS-Kermit (2.29C) 'key ident' is also printed.  This value
** is used in the SET KEY command.  Note that Kermit uses the shift
** bits differently than the BIOS routines,  so multiple shifts
** (e.g. Ctrl-Alt-F1) can be used with the same key.
*/

#include <stdio.h>
#include <dos.h>

#define SCAN 256
#define SHIFT 512
#define CONTROL 1024
#define ALT 2048
#define BS 0x08
#define EOS '\0'
/*
** These two tables are useful for relating ascii and scan codes
** to the characters entered or keys pressed.
** There are several types of characters included.
** ASCII characters:
** - Characters 0x00 to 0x1F are control characters.
**   The array ascii_chars[i] contains the names of these
**   control characters. 'Space' is included for lack of
**   a better place to put it. The index i should be
**   the ascii code for the character. Note that the ascii
**   character NUL (0x00) cannot be entered on the IBM-PC keyboard.
** - The character codes 0x20 - 0xff are printable on the
**   IBM-PC and are not considered here (except 'space').
** Special characters:
**   For some characters, no ascii value is returned by BIOS interrupt
**   0x16. The scan codes of these characters identify them.
**   They include F keys, arrow keys, and alt keys.
**   The array special_chars[i] contains tha names of these keys.
**   The index i should be the scan code for the key.
**   The array is 132 elements long, but not all of the
**   scan codes in that range are special keys.
**
** Phil Benchoff, June 1986
*/

char *ascii_chars[] = {                 /* ANSI names of control codes */
   "NUL", "SOH", "STX", "ETX", "EOT", "ENQ",    /* ^@, ^A, ^B, ^C, ^D, ^E */
   "ACK", "BEL", "BS" , "HT" , "LF" , "VT",     /* ^F, ^G, ^H, ^I, ^J, ^K */
   "FF" , "CR" , "SO" , "SI" , "DLE", "DC1",    /* ^L, ^M, ^N, ^O, ^P, ^Q */
   "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",    /* ^R, ^S, ^T, ^U, ^V, ^W */
   "CAN", "EM" , "SUB", "ESC", "FS" , "GS",     /* ^X, ^Y, ^Z, ^[, ^\, ^] */
   "RS" , "US" , "Space" } ;                    /* ^^, ^_, space */

char *special_keys[] = {                /* common text for given scan code */
   "", "", "", "NUL", "", "", "", "", "", "", "", "", "", "", "", /* 0-14 */
   "Shift-Tab",                                                 /* 15 */
   "Alt-Q", "Alt-W", "Alt-E", "Alt-R", "Alt-T",                 /* 16-20 */
   "Alt-Y", "Alt-U", "Alt-I", "Alt-O", "Alt-P",                 /* 21-25 */
   "", "", "", "",                                              /* 27-29 */
   "Alt-A", "Alt-S", "Alt-D", "Alt-F", "Alt-G",                 /* 30-34 */
   "Alt-H", "Alt-J", "Alt-K", "Alt-L",                          /* 35-38 */
   "", "", "", "", "",                                          /* 40-43 */
   "Alt-Z", "Alt-X", "Alt-C", "Alt-V", "Alt-B",                 /* 44-48 */
   "Alt-N", "Alt-M",                                            /* 49-50 */
   "", "", "", "", "", "", "", "",                              /* 52-58 */
   "F1", "F2", "F3", "F4", "F5", "F6", "F7", "F8", "F9", "F10", /* 59-68 */
   "", "",                                                      /* 69-70 */
   "Home", "U-arrow", "PgUp", "", "L-arrow", "", "R-arrow",     /* 71-77 */
   "", "End", "D-arrow", "PgDn", "Ins", "Del",                  /* 79-83 */
   "Shift-F1", "Shift-F2", "Shift-F3", "Shift-F4", "Shift-F5",  /* 84-88 */
   "Shift-F6", "Shift-F7", "Shift-F8", "Shift-F9", "Shift-F10", /* 89-93 */
   "Ctrl-F1",  "Ctrl-F2",  "Ctrl-F3",  "Ctrl-F4",  "Ctrl-F5",   /* 94-98 */
   "Ctrl-F6",  "Ctrl-F7",  "Ctrl-F8",  "Ctrl-F9",  "Ctrl-F10",  /* 99-103 */
   "Alt-F1",   "Alt-F2",   "Alt-F3",   "Alt-F4",   "Alt-F5",    /* 104-108 */
   "Alt-F6",   "Alt-F7",   "Alt-F8",   "Alt-F9",   "Alt-F10",   /* 109-113 */
   "Ctrl-PrtSc", "Ctrl-L-arraw", "Ctrl-R-arrow",                /* 114-116 */
   "Ctrl-End",   "Ctrl-PgDn",    "Ctrl-Home",                   /* 117-119 */
   "Alt-1", "Alt-2", "Alt-3", "Alt-4", "Alt-5", "Alt-6",        /* 120-125 */
   "Alt-7", "Alt-8", "Alt-9", "Alt-0", "Alt--", "Alt-=",        /* 126-131 */
   "Ctrl-PgUp"                                                  /* 132 */

};


main()
{
   unsigned int count, ascii, scan;
   char *comment();
   unsigned int bios_key, bios_shifts, key_ident, kerm_key();
#ifdef C86
   int key_shft(), key_getc();
#endif

   explain();

   count = 1;

   while ( 1 ) {
      if (1 == count ) {
         header();
         count = 22;
      } else
         count--;

#ifdef C86
      bios_key = (unsigned_int)key_getc();
      bios_shifts = (unsigned int)key_shft();
#else
      bios_key = bioskey(0);
      bios_shifts = bioskey(2);
#endif

      ascii = bios_key & 0x00FF;     /* ASCII value is in low byte */
      scan  = bios_key >> 8;         /* Scan code is in high byte  */
      key_ident = kerm_key(bios_key,bios_shifts);

      /* Note: You can't enter a NUL (ascii 00) from the keyboard */

      if ( (ascii >= 33) && (ascii <= 255) )    /* Printable char. */
         printf("| %-13c ", ascii );

      else if ( (ascii >= 1) && (ascii <= 31) ) /* Control char.   */
         printf("| Ctrl-%c %-6s ", ascii + 64, ascii_chars[ascii]);

      else if (ascii == 32)                  /* Space       */
         printf("| Space         ");

      else if (ascii == 0) {                /* Special Key */
         if (scan <= 132)
            printf("| %-13s ", special_keys[scan]);
         else
            printf("| * unknown *   ");
      }

      else
         printf("| Out of range  ");

      printf("| %3d | 0x%02x | \\%-4d ", scan, ascii, key_ident);
      if (key_ident & ALT)
         printf("| A");
      else
         printf("| -");
      if (key_ident & CONTROL)
         printf("C");
      else
         printf("-");
      if (key_ident & SHIFT)
         printf("S ");
      else
         printf("- ");

      printf("| %-32s|\n", comment(scan,ascii) );

   }
   exit(0);
}

/*
** Kermit-MS determines the 'key ident' from the value returned in ax
** (ah=scan code, al=ascii value) from BIOS call 0x16 function 0,  the
** status of various shift keys, and a table of special keys (aliaskey).
** The aliaskey table handles cases where more than one key may generate
** the same ascii value (i.e. the numeric keypad).  The entries in table
** aliaskey are words with the high byte holding a scan code and the low
** byte holding an ascii value.  The general method is as follows:
**
**    BIOS int 0x16 function 0           returns key's code in register ax.
**    if (ax is in aliaskey list)
**       al = 0                    clear normal ascii to simulate special key
**    ascii = al                         do this in either case
**    if ( ascii == 0 ) {                now, if the key is a special one ...
**    scancode = ah                      work with scan code instead
**       key_ident = scancode + 256      set SCAN code flag bit
**       if ( LeftShift || RightShift || (NumKeypadWhiteKey && NumLock) )
**          key_ident |= 512             set smart SHIFT key flag bit
**       if ( Ctrl )
**          key_ident |= 1024            set CONTROL key flag bit
**       If ( Alt )
**          key_ident |= 2048            set ALT key flag bit
**    } else
**       key_ident = ascii
*/
unsigned int kerm_key(bios_key,bios_shifts)
unsigned int bios_key, bios_shifts;
{
   unsigned  key_id;
                                /* table of Bios keycodes needing inspection*/
   switch (bios_key)
   {
      case ( 14 * SCAN ) + BS:  /* high byte = scan code, low byte = ascii */
      case ( 55 * SCAN ) + '*':
      case ( 74 * SCAN ) + '-':
      case ( 78 * SCAN ) + '+':
      case ( 71 * SCAN ) + '7':
      case ( 72 * SCAN ) + '8':
      case ( 73 * SCAN ) + '9':
      case ( 75 * SCAN ) + '4':
      case ( 76 * SCAN ) + '5':
      case ( 77 * SCAN ) + '6':
      case ( 79 * SCAN ) + '1':
      case ( 80 * SCAN ) + '2':
      case ( 81 * SCAN ) + '3':
      case ( 82 * SCAN ) + '0':
      case ( 83 * SCAN ) + '.':
                key_id = bios_key & 0xff00;     /* clear ascii low byte */
                break;
      default:  key_id = bios_key;              /* key is not in the list */
                break;
                };

   if ( (key_id & 0x00ff) == 0 ) {
                                /* No ASCII value, get a kermit scan code. */
      key_id = ( key_id >> 8 ) | SCAN;          /* set scancode flag */
      if (bios_shifts & 3)                      /* left or right shift?*/
         key_id |= SHIFT;                       /* yes, set shift flag */
      else if ((bios_shifts & 0x20)             /* NumLock set? */
             && ( key_id >= ( 71 + SCAN ) )     /* on numeric keypad ? */
             && ( key_id <= ( 83 + SCAN ) )
             && ( key_id != ( 74 + SCAN ) )     /* not the grey - key? */
             && ( key_id != ( 78 + SCAN ) ) )   /* not the grey + key? */
         key_id |= SHIFT;                       /* all true, set shift */
      if (bios_shifts & 0x04)                   /* Ctrl-key pressed? */
         key_id |= CONTROL;
      if (bios_shifts & 0x08)                   /* Alt-key pressed? */
         key_id |= ALT;
   } else
      /* We have an ASCII value,  return that */
      key_id &= 0xff;

   return key_id;
}

char *comment(scan,ascii)
int  scan, ascii;
{
    static char line[40];

    line[0] = '\0';                     /* start with an empty line */

    if ( (ascii !=0) && (scan >= 71) )
       strcpy(line,"Numeric Keypad");
    else if ( (ascii == 0) &&  (scan != 0) )
       strcpy(line,"Special Key");
    else if ( (ascii != 0) && (scan == 0) )
       strcpy(line,"Alt-numeric method.");
    return(line);
}

header() {
   char *dash39 = "---------------------------------------",
        *left   = "|Key            |Scan |ASCII |Kermit |S",
        *right  = "hift| Notes                           |";

   printf("%s%s\n%s%s\n%s%s\n",dash39,dash39,left,right,dash39,dash39);

}

explain()
{
/* Tell what the program does, and how to get out */
   printf("ScanChek 3.1\n\n");
   printf("This program displays the scan code, ASCII code, and\n");
   printf("the Kermit 'key ident' used in the Kermit-MS SET KEY command.\n");
   printf("Keycodes are obtained from the IBM-PC keyboard BIOS interrupt");
   printf(" 16H, function 0.\n");
   printf("Do not type ahead of the display.\n\n");
   printf("Key    - The key pressed. Determined from ascii value or\n");
   printf("         BIOS scan code.\n");
   printf("Scan   - The BIOS scan code (ah).\n");
   printf("ASCII  - The ASCII value (al).\n");
   printf("Kermit - The Kermit-MS key ident.\n");
   printf("Shift  - Shift key pressed, valid only if \"Kermit\" field");
   printf(" is > 255\n         (A=Alt, C=Ctrl, S=Shift)\n");
   printf("\nPress Control-BREAK to exit.\n\n");
}

#ifndef C86
bioskey(function)                       /* For most C compilers [jrd] */
int function;                           /* do Interrupt 16H via int86() */
{
   static union REGS reg;                       /* for Bios int86 calls */
   reg.h.ah = function;
   int86(0x16, &reg, &reg);
   return (reg.x.ax);
}
#endif
