home *** CD-ROM | disk | FTP | other *** search
- Path: sparky!uunet!news.centerline.com!noc.near.net!ds5000!cschmidt
- From: cschmidt@ds5000.DAC.Northeastern.edu (Christopher Schmidt)
- Newsgroups: comp.lang.pascal
- Subject: Re: Ctrl-Break in TP 6.0 programs
- Message-ID: <7403@ds5000.DAC.Northeastern.edu>
- Date: 14 Sep 92 04:57:50 GMT
- Reply-To: cschmidt@ds5000 (Christopher Schmidt)
- Organization: Leelanau Computing Inc
- Lines: 495
-
- 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.
-