{$A+,B-,D-,E-,F-,G-,I-,L-,N-,R-,S-,V-,X-}

unit KeyBrdP;

interface

function ExtKbrd : boolean;

implementation

uses dos;

var
	OldInt16, OldInt60, ExitSave : pointer;

procedure Int16(_Flags,_CS,_IP,_AX,_BX,_CX,_DX,_SI,_DI,_DS,_ES,_BP: Word);
interrupt;
var
	Regs : Registers;
begin
	with Regs do
	begin
		ax := _ax;
		if ah <= 2 then Inc(ah, $10);
		Intr($60, Regs);
		_Flags := Flags;
		if (al = $E0) and (ah <> 0) then al := 0;
		_ax := ax;
	end;
end;

function ExtKbrd : boolean;
var
	Regs : Registers;
begin
	with Regs do
	begin
		AX := $1200;
		Intr($16, Regs);
		ExtKbrd := (AX <> $1200);
	end;
end;

procedure UnitExitProc; far;
begin
	ExitProc := ExitSave;
	SetIntVec($16, OldInt16);
	SetIntVec($60, OldInt60);
end;

begin
	if ExtKbrd then
	begin
		GetIntVec($16, OldInt16);
		GetIntVec($60, OldInt60);
		SetIntVec($60, OldInt16);
		SetIntVec($16, @Int16);
		ExitSave := ExitProc;
		ExitProc := @UnitExitProc;
	end;
end.

As you can see, we can get by without using assembly by using the keyword
interrupt, which makes writing interrupt procedures in BP/TP easy. (If you
are not familiar with this keyword and the required procedure header, consult
your Borland manuals).
Because of the requirements of the Intr procedure, it is necessary to store
the old INT $16 in a user defined interrupt. Number $60 is usually available
for this purpose. Some of the enhanced cursor key's (basically, the gray keys)
give an ASCII value $E0, so this is changed to $0 to give results consistent
with the equivalent white keys, but not before checking that the user didn't
enter Alt-224. The unit also inserts its own exit routine in the chain of
exit procedures, which automatically resets the changed interrupt vectors
when your program exits. To USE this unit and make ReadKey and KeyPressed
recognize enhanced keystokes, simply add the unit name to the USES clause
in your main program. Here's a simple test program you can use:

program TestKeyb;

uses crt, dos, keybrdP;

var
	ch : char;
begin
	ClrScr;
	repeat
		ch := ReadKey;
		if (ch = #0) then
		begin
			ch := ReadKey;
			writeln('0 ',ord(ch));
		end else writeln(ord(ch));
	until ch = #27;                                  { pressing Esc key exits }
end.

Of course my unit is not without its drawbacks. Evidently, if you need to
support enhanced keyboards on machines *without* an enhanced keyboard BIOS,
(PC-XT's) then this unit won't solve your problem. In that case, you will
need to write a replacement INT $9 handler. Turbo Vision programs install a
replacement INT $9 handler to allow certain special key combinations to be
recognized. If you have BP7 and want to know how this is done, take a look
at SYSINT.ASM in the the supplied Runtime Library Source Code. Frankly, I
can't see much need for this, since XT's are obsolete anyway, and many of
those machines still in use don't have an enhanced keyboard. A second
objection to my unit could be that it requires a user defined interrupt.
The need for this can be obviated if you are willing to use the inline
assembler (BASM), which is better anyway to use in an interrupt handler
because it allows for more compact code. Here's a functionally equivalent
version of my unit using BASM :

{$A+,B-,D-,E-,F-,G-,I-,L-,N-,R-,S-,V-,X-}

unit KeyBrdA;

interface

function ExtKbrd : boolean;

implementation

uses dos;

var
	OldInt16, ExitSave : pointer;

procedure Int16; assembler;
asm
	push bp
	mov bp, sp
	push ds
	push ax
	mov ax, SEG @Data            { need DS to point to data segment in order }
	mov ds, ax                   { to access Pascal variables                }
	pop ax
	pushf                        { must push flags on stack ourselves since  }
	cmp ah, 2                    { INT expects flags to be pushed on stack,  }
	ja      @OldInt              { whereas CALL only pushes return address   }
	add ah, 10h
@OldInt:
	call OldInt16
	pushf                        { update flags which were originally pushed }
	pop word ptr [bp+6]          { on stack at entry of this handler         }
	cmp al, 0E0h
	jne @Exit
	cmp ah, 0
	je @Exit
	xor al, al
@Exit:
	pop ds
	pop bp
	iret
end;

function ExtKbrd : boolean; assembler;
asm
	mov ax, 1200h
	int 16h
	cmp ax, 1200h
	mov ax, 0
	je  @Exit
	inc ax
@Exit:
end;

procedure UnitExitProc; far;
begin
	ExitProc := ExitSave;
	SetIntVec($16, OldInt16);
end;

begin
	if ExtKbrd then
	begin
		GetIntVec($16, OldInt16);
		SetIntVec($16, @Int16);
		ExitSave := ExitProc;
		ExitProc := @UnitExitProc;
	end;
end.
