From: cschmidt@ds5000.DAC.Northeastern.edu (Christopher Schmidt)
Newsgroups: comp.lang.pascal
Subject: Re: Ctrl-Break in TP 6.0 programs
Date: 14 Sep 92 04:57:50 GMT
Reply-To: cschmidt@ds5000 (Christopher Schmidt)
Organization: Leelanau Computing Inc

This message contains a Turbo Pascal unit that includes a solution to
the CTRL-BREAK problem.  The unit also includes basic routines for
reading the keyboard, and a list of constants for identifying the
control keys and special keys.

It is surprising to see in this newsgroup how many programmers use the
TP library function "readkey".  The comment at the top of the keyboard
unit included below contains my explanation of why programmers should
not use "readkey".

Here is a simple program that demonstrates the "key_read" and
"key_break" routines defined in the keyboard unit included below.
When you run this program, try pressing CTRL-BREAK a few times.  If
this program works on your machine as it does on mine, then you will
not see "^C" on the screen when the program exits.  I should note that
I use TP 5.5, and if you find that my CTRL-BREAK solution does not
work with TP 6.0, I would appreciate it if you would send me a note.

        uses keyunit;
        var
            keycode : word;
        begin
            key_break;
            writeln ('Press F10 to exit');
            repeat
                keycode := key_read;
                writeln (hi (keycode) : 4, lo (keycode) : 4);
            until keycode = KEY_F10;
        end.

------------------------------ cut here ------------------------------
{/////////////////////////////////////////////////////////////////////
KEYUNIT.PAS         Keyboard routines for Turbo Pascal

General purpose routines:

    key_clear       Flush the keyboard buffer
    key_next        Return the next keycode if there is one
    key_press       Return TRUE if the keyboard buffer is not empty
    key_read        Read and return the next keycode

Control-break handling routines:

    key_break       Install an ISR for CTRL-BREAK
    key_reset       Remove the CTRL-BREAK ISR installed by key_break

Author:

    Christopher Schmidt
    Waltham, Massachusetts, USA
    cschmidt@lynx.northeastern.edu

This unit is public domain.


KEYCODES

The "key_read" and "key_next" functions return a single, unsigned,
two-byte integer called a "keycode", representing the key the user
pressed.  Keys and keycodes are organized in two groups:

    o   ORDINARY KEYS are those that generate ASCII characters (or
        more precisely, characters in the IBM PC 8-bit character set).
        The low byte of the keycode contains the ASCII code and the
        high byte contains the scan code.  You can ignore the scan
        code except when you want to distinguish between, say, the
        number keys on the main keypad and the number keys on the
        numeric keypad, or between CTRL-M and ENTER.

    o   SPECIAL KEYS are those that do not represent any ASCII
        characters.  Special keys include the function keys, the ALT
        keys, the cursor keys, and a few others.  The low byte of the
        keycode is zero and the high byte is the scan code.

Here is an example showing how to use the returned keycode:

        keycode := key_read;
        case keycode of
        KEY_F1 : ... process function key ...
        KEY_F2 : ... process function key ...
        KEY_F3 : ... process function key ...
        else
            if lo (keycode) <> 0 then
                ... process "lo (keycode)" as an ASCII character ...
        end;

Here is why it is better to have the basic keyboard input routines
return a 2-byte keycode rather than a 1-byte char, and why the Turbo
Pascal "readkey" function was badly conceived and should not be used.
From the functional standpoint, there are two kinds of keys:

    o   TEXT KEYS are those that generate character codes that are
        usually stored in strings as textual data.  Most of the
        ordinary keys are text keys.

    o   COMMAND KEYS are those that instruct the program to perform
        some operation (other than storing a character).  All special
        keys are command keys.  Some ordinary keys are command keys,
        such as ESC, ENTER, BACKSPACE, and most of the control keys,
        depending on the application.

In practice, the use of text keys in program code is usually
restricted to a few, isolated, general-purpose functions that read
user input or that let the user modify text.  In contrast, most
program code that deals with keyboard input deals with command keys,
not text keys.  This is the essential fact.

The Turbo Pascal "readkey" function, which returns a 1-byte char and
must be called twice to read most command keys, is optimized for
dealing with text keys rather than command keys, which is exactly the
wrong way about.  Perhaps the "readkey" designers were influenced by
the greater number of text keys than command keys on the physical
keyboard, or by the hypothesis that users press text keys more often
on average than command keys.  These are irrelevent factors.


ROM BIOS EXTENDED KEYBOARD SERVICES

This unit does not use the ROM BIOS extended keyboard services
(services $10, $11, and $12), which simply do not work on a large
percentage of machines.  To my knowledge, the only advantages of using
the extended services are that you can read the F11 and F12 keys, and
you can distinguish beteen the "duplicate" ENTER keys, ALT keys, and
CTRL keys.  Any program that uses these features cannot run properly
on the older machines.


}
unit keyunit;

interface

const
    { code for general-purpose undefined keycode }

    KEY_NIL         = $FFFF;

    { codes for ordinary keys (low byte non-zero) }

    KEY_BACKSP      = $0E08;
    KEY_TAB         = $0F09;
    KEY_ENTER       = $1C0D;
    KEY_ESC         = $011B;
    KEY_SPACE       = $3920;

    KEY_CTRL_BACKSP = $0E7F;
    KEY_CTRL_ENTER  = $1C0A;

    KEY_CTRL_A      = $1E01;
    KEY_CTRL_B      = $3002;
    KEY_CTRL_C      = $2E03;
    KEY_CTRL_D      = $2004;
    KEY_CTRL_E      = $1205;
    KEY_CTRL_F      = $2106;
    KEY_CTRL_G      = $2207;
    KEY_CTRL_H      = $2308;
    KEY_CTRL_I      = $1709;
    KEY_CTRL_J      = $240A;
    KEY_CTRL_K      = $250B;
    KEY_CTRL_L      = $260C;
    KEY_CTRL_M      = $320D;
    KEY_CTRL_N      = $310E;
    KEY_CTRL_O      = $180F;
    KEY_CTRL_P      = $1910;
    KEY_CTRL_Q      = $1011;
    KEY_CTRL_R      = $1312;
    KEY_CTRL_S      = $1F13;
    KEY_CTRL_T      = $1414;
    KEY_CTRL_U      = $1615;
    KEY_CTRL_V      = $2F16;
    KEY_CTRL_W      = $1117;
    KEY_CTRL_X      = $2D18;
    KEY_CTRL_Y      = $1519;
    KEY_CTRL_Z      = $2C1A;
    KEY_CTRL_LBRACK = $1A1B;
    KEY_CTRL_BSLASH = $2B1C;
    KEY_CTRL_RBRACK = $1B1D;
    KEY_CTRL_CARET  = $071E;
    KEY_CTRL_UNDER  = $0C1F;

    { codes for numeric keypad keys }

    KEY_NPAD_ASTER  = $372A;
    KEY_NPAD_MINUS  = $4A2D;
    KEY_NPAD_PLUS   = $4E2B;
    KEY_NPAD_PERIOD = $532E;
    KEY_NPAD_0      = $5230;
    KEY_NPAD_1      = $4F31;
    KEY_NPAD_2      = $5032;
    KEY_NPAD_3      = $5133;
    KEY_NPAD_4      = $4B34;
    KEY_NPAD_5      = $4C35;
    KEY_NPAD_6      = $4D36;
    KEY_NPAD_7      = $4737;
    KEY_NPAD_8      = $4838;
    KEY_NPAD_9      = $4939;

    { codes for special keys (low byte zero) }

    KEY_UP          = $4800;
    KEY_LEFT        = $4B00;
    KEY_RIGHT       = $4D00;
    KEY_DOWN        = $5000;
    KEY_HOME        = $4700;
    KEY_END         = $4F00;
    KEY_PGUP        = $4900;
    KEY_PGDN        = $5100;
    KEY_INSERT      = $5200;
    KEY_DELETE      = $5300;

    KEY_CTRL_LEFT   = $7300;
    KEY_CTRL_RIGHT  = $7400;
    KEY_CTRL_HOME   = $7700;
    KEY_CTRL_END    = $7500;
    KEY_CTRL_PGUP   = $8400;    { cannot appear in case label before TP 5.5 }
    KEY_CTRL_PGDN   = $7600;

    KEY_F1          = $3B00;
    KEY_F2          = $3C00;
    KEY_F3          = $3D00;
    KEY_F4          = $3E00;
    KEY_F5          = $3F00;
    KEY_F6          = $4000;
    KEY_F7          = $4100;
    KEY_F8          = $4200;
    KEY_F9          = $4300;
    KEY_F10         = $4400;

    KEY_SHFT_F1     = $5400;
    KEY_SHFT_F2     = $5500;
    KEY_SHFT_F3     = $5600;
    KEY_SHFT_F4     = $5700;
    KEY_SHFT_F5     = $5800;
    KEY_SHFT_F6     = $5900;
    KEY_SHFT_F7     = $5A00;
    KEY_SHFT_F8     = $5B00;
    KEY_SHFT_F9     = $5C00;
    KEY_SHFT_F10    = $5D00;

    KEY_CTRL_F1     = $5E00;
    KEY_CTRL_F2     = $5F00;
    KEY_CTRL_F3     = $6000;
    KEY_CTRL_F4     = $6100;
    KEY_CTRL_F5     = $6200;
    KEY_CTRL_F6     = $6300;
    KEY_CTRL_F7     = $6400;
    KEY_CTRL_F8     = $6500;
    KEY_CTRL_F9     = $6600;
    KEY_CTRL_F10    = $6700;

    KEY_ALT_F1      = $6800;
    KEY_ALT_F2      = $6900;
    KEY_ALT_F3      = $6A00;
    KEY_ALT_F4      = $6B00;
    KEY_ALT_F5      = $6C00;
    KEY_ALT_F6      = $6D00;
    KEY_ALT_F7      = $6E00;
    KEY_ALT_F8      = $6F00;
    KEY_ALT_F9      = $7000;
    KEY_ALT_F10     = $7100;

    KEY_ALT_1       = $7800;
    KEY_ALT_2       = $7900;
    KEY_ALT_3       = $7A00;
    KEY_ALT_4       = $7B00;
    KEY_ALT_5       = $7C00;
    KEY_ALT_6       = $7D00;
    KEY_ALT_7       = $7E00;
    KEY_ALT_8       = $7F00;
    KEY_ALT_9       = $8000;    { cannot appear in case label before TP 5.5 }
    KEY_ALT_0       = $8100;    { cannot appear in case label before TP 5.5 }
    KEY_ALT_MINUS   = $8200;    { cannot appear in case label before TP 5.5 }
    KEY_ALT_EQUAL   = $8300;    { cannot appear in case label before TP 5.5 }

    KEY_ALT_Q       = $1000;
    KEY_ALT_W       = $1100;
    KEY_ALT_E       = $1200;
    KEY_ALT_R       = $1300;
    KEY_ALT_T       = $1400;
    KEY_ALT_Y       = $1500;
    KEY_ALT_U       = $1600;
    KEY_ALT_I       = $1700;
    KEY_ALT_O       = $1800;
    KEY_ALT_P       = $1900;

    KEY_ALT_A       = $1E00;
    KEY_ALT_S       = $1F00;
    KEY_ALT_D       = $2000;
    KEY_ALT_F       = $2100;
    KEY_ALT_G       = $2200;
    KEY_ALT_H       = $2300;
    KEY_ALT_J       = $2400;
    KEY_ALT_K       = $2500;
    KEY_ALT_L       = $2600;

    KEY_ALT_Z       = $2C00;
    KEY_ALT_X       = $2D00;
    KEY_ALT_C       = $2E00;
    KEY_ALT_V       = $2F00;
    KEY_ALT_B       = $3000;
    KEY_ALT_N       = $3100;
    KEY_ALT_M       = $3200;

    KEY_SHFT_TAB    = $0F00;
    KEY_CTRL_BREAK  = $0000;
    KEY_CTRL_PRTSC  = $7200;

    {   Here is a workaround for keycodes in case statement labels
        prior to TP 5.5.  Note that keycodes are normally unsigned
        integers.

        o   In TP 4.0 and 5.0, an unsigned integer constant greater
            than 32767 (most significant bit set) may not appear in
            case statement labels, which are signed two-byte integers
            and must be in the range -32768 to 32767.

        o   Starting with TP 5.5, negative values in case statement
            labels are out of range when the case statement expression
            is unsigned.

        The following five constants are for case statement labels
        prior to TP 5.5.
    }

    KEY_CTRL_PGUP_  = -31744;
    KEY_ALT_9_      = -32768;
    KEY_ALT_0_      = -32512;
    KEY_ALT_MINUS_  = -32256;
    KEY_ALT_EQUAL_  = -32000;

procedure key_clear;
function key_next : word;
function key_press : boolean;
function key_read : word;

procedure key_break;
{$f+}
procedure key_reset;
{$f-}

implementation

{////////////////////////////////////////////////////////////////////}
uses dos;

const
    KEY_INTKEY  = $16;          { BIOS keyboard services interrupt }
    KEY_INTOFF  = $FA;          { disable interrupts }
    KEY_INTON   = $FB;          { enable interrupts }
    key_iret    : word = $CF;   { an IRET instruction }

var
    key_saveint : pointer;      { save interrupt 1B }
    key_savepro : pointer;      { save turbo pascal exit procedure address }

{/////////////////////////////////////////////////////////////////////
key_clear       -- Flush the keyboard buffer

This routine removes all remaining unread keycodes from the keyboard
buffer.  It is not an error if the keyboard buffer is already empty.

}
procedure key_clear;

var
    head        : integer absolute $0040:$001A;
    tail        : integer absolute $0040:$001C;

begin
    { disable interrupts while modifying the BIOS keyboard buffer }
    inline (KEY_INTOFF);
    tail := head;
    inline (KEY_INTON);
end;

{/////////////////////////////////////////////////////////////////////
key_next        -- Return the next keycode if there is one

There are two cases:

    o   If the BIOS keyboard buffer is not empty, the function returns
        the oldest unread keycode currently stored in the BIOS
        keyboard buffer.

    o   If the BIOS keyboard buffer is empty, the function returns
        KEY_NIL.

}
function key_next : word;

var
    regs        : registers;

begin
    regs.flags := 0;
    regs.ax := $0100;
    intr (KEY_INTKEY, regs);
    key_next := KEY_NIL;
    if (regs.flags and $40) = 0 then
        key_next := regs.ax;
end;

{/////////////////////////////////////////////////////////////////////
key_press       -- Return TRUE if the keyboard buffer is not empty

This routine returns TRUE if the keyboard buffer is not empty,
otherwise FALSE.

}
function key_press : boolean;

var
    regs        : registers;

begin
    regs.flags := 0;
    regs.ax := $0100;
    intr (KEY_INTKEY, regs);
    key_press := (regs.flags and $40) = 0;
end;

{/////////////////////////////////////////////////////////////////////
key_read        -- Read and return the next keycode

This routine reads and returns the next unread keycode.  If the BIOS
keyboard is empty, the routine waits for the user to press a key.

}
function key_read : word;

var
    regs        : registers;

begin
    regs.ax := 0;
    intr (KEY_INTKEY, regs);
    key_read := regs.ax;
end;

{/////////////////////////////////////////////////////////////////////
key_break       -- Install an ISR for CTRL-BREAK

This routine installs an ISR (interrupt service routine) for the
CTRL-BREAK interrupt so that when the user presses CTRL-BREAK the
program does not halt and it does not echo ^C.  The routine also
installs a Turbo Pascal exit routine to reset the previous CTRL-BREAK
ISR address.

The ISR that this routine installs is simply a pointer to an IRET
instruction.  When the user presses CTRL-BREAK, BIOS still clears the
keyboard and inserts into the keyboard four bytes of zero.  The
program can check for CTRL-BREAK like this:

        if key_next = 0 then
            ... CTRL-BREAK was pressed ...

}
procedure key_break;

begin
    getintvec ($1B, key_saveint);
    key_savepro := exitproc;

    inline (KEY_INTOFF);
    setintvec ($1B, @key_iret);
    exitproc := @key_reset;
    inline (KEY_INTON);
end;

{/////////////////////////////////////////////////////////////////////
key_reset       -- Remove the CTRL-BREAK ISR installed by key_break

This routine removes the ISR installed by the key_break routine.  This
routine must not be called before calling key_break.  It is usually
not necessary to call this routine explicitly, because key_break
installs this routine as an exit routine.  However, this routine
should be called before spawning a subprocess, as in this example:

        key_reset;
        errcode := execute_program (progname, progargs);
        key_break;

}
{$f+}
procedure key_reset;
{$f-}

begin
    inline (KEY_INTOFF);
    setintvec ($1B, key_saveint);
    exitproc := key_savepro;
    inline (KEY_INTON);
end;

end.