PAGE 64,132 TITLE GOLD key from NUMLOCK NAME GOLD ; ; This program maps the NUM LOCK key (scan code 45H) to another key ; (default is F1, scan code 3BH) for use with Kermit emulating a DEC VTxxx ; terminal. ; ; Version 2.4 - June 1992 ; Fixed spurious recognition of PAUSE key as GOLD (caused by ; presence of 45H in six byte sequence for PAUSE) ; Version 2.3 - June 1992 ; Added facility to have modifier (CTRL, SHIFT, ALT) ; for chosen key ; Version 2.2 - February 1992 ; Added safety checks to installation code ; Version 2.1 - June 1991 ; Changed default multiplex ID to 09FH (was 0DCH) to avoid ; problems with some versions of the KEYB program. ; Version 2.0 - March 1991 ; SHIFT and NUM LOCK acts as normal NUM LOCK ; ALT and NUM LOCK inverts current GOLD status ; Fix for problem with some clone BIOSes ; Fix non-detection of BIOS intercept support ; Use INT 09H if interrupt intercept not available ; ; Bob Eager ; rde@ukc.ac.uk USENET (preferred over ibmpcug) ; rde@ibmpcug.co.uk USENET ; 100016,2770 CompuServe ; +44 227 367270 Telephone ; ; You may distribute this program freely as long as all of the files that ; make up the package (see the documentation file) are kept with it (including ; this source file) and you don't try to make money from it either by selling ; it directly or incorporating it into a product you sell. ; ; Values for exit status: ; ; 0 - success ; 1 - could not install program ; 2 - no BIOS support present ; 3 - unsupported DOS version (< 3.0) ; 4 - invalid parameter ; ; Constants ; --------- ; CR = 0DH ; Carriage return LF = 0AH ; Linefeed TAB = 09H ; Tab ; ID = 0DCH ; Multiplex ID ; NUMSCAN = 045H ; Num Lock scan code P1SCAN = 0E1H ; Pause scan code byte 1 P2SCAN = 01DH ; Pause scan code byte 2 ; CSEG SEGMENT BYTE PUBLIC 'CODE' ; $BEGIN EQU $ ; ORG 0100H ; ASSUME CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG ; SUBTTL Data areas PAGE+ ; ; The following jump to the initialisation code also provides three bytes ; of storage, which are used later by the resident part of the code. ; BEGIN: JMP INIT ; jump to initialisation code ; ; Redefine storage for the above jump ; ORG BEGIN MBIT EQU THIS BYTE ; Make/Break bit ORG BEGIN+1 ONOFF EQU THIS BYTE ; On/Off flag (0=off, initially on) ORG BEGIN+2 SHFLAG EQU THIS BYTE ; Shift bits changed flag (0=No, 1=Yes) ; ; Back to normal storage allocation ; ORG BEGIN+3 ; ; The following four values may be changed if required. ; ; MPID is the multiplex ID, and will only need alteration if some ; other TSR is using the same value. Choose another at random ; until it works! ; NEWKEY is the make code value for the key to replace NUM LOCK. ; Examples: ; 3B - F1 ; 1E - A ; See a key code table (e.g. in IBM Technical Reference Manual) ; for the rest. ; MODE is used to force a particular operation mode; it is particularly ; useful when a BIOS says that it supports the keyboard intercept ; function, but doesn't. ; MODBIT is a way of specifying that a particular modifier key (CTRL, ; ALT, or SHIFT) should be forced down when the replacement ; keystroke is transmitted. Set as follows: ; 08H - ALT ; 04H - CTRL ; 02H - left SHIFT ; 01H - right SHIFT ; These bits may be combined (e.g. 0CH for CTRL+ALT). Either ; SHIFT key will generally work. ; MPID DB 09FH ; 102H Multiplex ID ** DO NOT MOVE ** NEWKEY DB 03BH ; 103H Replacement key ** DO NOT MOVE ** ;NEWKEY DB 01EH ; 103H Replacement key ** DO NOT MOVE ** MODE DB 0 ; 104H Mode selector ** DO NOT MOVE ** ; 00H = auto ; 01H = use INT 15H ; 02H = use INT 09H MODBIT DB 00H ; 105H Replacement key modifier bit ;MODBIT DB 08H ; 105H Replacement key modifier bit ; *** DO NOT MOVE *** SAVESH DB 0 ; Saved copy of shift flags STATE DB 0 ; State for PAUSE recognition ; ; 0 = normal scan ; ; 1 = after E1 ; ; 2 = after E1 1D ; INTOFF DW ? ; Old interrupt vector offset INTSEG DW ? ; Old interrupt vector segment I2FOFF DW ? ; Old INT 2FH offset I2FSEG DW ? ; Old INT 2FH segment ; SUBTTL INT 2FH (multiplex) handler PAGE+ ; ; This INT 2FH handler is hooked into the MS-DOS multiplex interrupt. ; ; Input parameters: ; ; AH = handler ID (MPID for this program) ; Calls with unrecognised handler IDs are passed on ; AL = function code ; 00 - get installation status ; 01 - get GOLD status ; 02 - set GOLD status ; ; Output parameters: ; ; AL = result ; Get installation status: ; FFH - Already installed ; Get GOLD status: ; 00H - OFF ; 01H - ON ; Set GOLD status: ; 00H - OK ; I2FHAN PROC FAR ; ASSUME CS:CSEG,DS:NOTHING,ES:NOTHING,SS:NOTHING ; CMP AH,MPID ; for this program? JE I2FH10 ; j if so JMP DWORD PTR I2FOFF ; else use old handler ; I2FH10: OR AL,AL ; AL=0, get installation status? JNE I2FH20 ; j if not MOV AL,0FFH ; indicate already installed IRET ; and return ; I2FH20: DEC AL ; AL=1, get GOLD status? JNE I2FH30 ; j if not MOV AL,ONOFF ; get value IRET ; and return ; I2FH30: DEC AL ; AL=2, set GOLD status? JNE I2FH40 ; j if not MOV ONOFF,DL ; set new value, drop through ; I2FH40: IRET ; and return ; I2FHAN ENDP ; SUBTTL INT 15H (system services) handler PAGE+ ; ; This INT 15H handler is hooked into the BIOS system services interrupt. ; It is used only if the keyboard interrupt intercept capability is available ; in the BIOS. ; ; Input parameters: ; AH - function. Only 4FH (keyboard intercept) is handled; ; other values cause the action to be passed to the ; previous handler. ; AL - scan code for key just pressed ; ; Output parameters: ; AL - input scan code, or mapped version of it. ; CY - set to indicate that keystroke is to be processed. ; All other register contents are preserved. ; ASSUME CS:CSEG,DS:NOTHING,ES:NOTHING,SS:NOTHING ; I15HAN PROC FAR ; CMP AH,4FH ; keyboard intercept function? JE I15H10 ; j if so JMP DWORD PTR INTOFF ; else call old handler ; I15H10: PUSH AX ; save register PUSH AX ; save again XOR AL,AL ; set zero XCHG AL,SHFLAG ; get and clear flags bit OR AL,AL ; set condition flags JZ I15H15 ; j if not currently changed PUSH DS ; save data segment MOV AX,40H ; BIOS data segment MOV DS,AX ; address it MOV AL,CSEG:SAVESH ; get saved keyboard flags MOV DS:[17H],AL ; restore them POP DS ; recover data segment I15H15: POP AX ; recover register CMP STATE,0 ; normal state? JE I15H17 ; j if so CMP STATE,1 ; had just one PAUSE byte? JE I15H16 ; j if so MOV STATE,0 ; else in state 2 - reset state... JMP I15H60 ; ...and skip checks ; I15H16: CMP AL,P2SCAN ; got second byte now? JE I15H18 ; j if so, => state 2 DEC STATE ; else reset state JMP SHORT I15H19 ; and perform normal checks ; I15H17: CMP AL,P1SCAN ; possible PAUSE sequence? JNE I15H19 ; j if not, else => state 1 ; I15H18: INC STATE ; update state JMP SHORT I15H60 ; skip checks ; I15H19: MOV AH,AL ; copy scan code AND AH,80H ; isolate make/break bit MOV MBIT,AH ; save it AND AL,7FH ; mask out make/break bit CMP AL,NUMSCAN ; NUM LOCK? JNE I15H60 ; j if not - just return ; ; NUM LOCK has been pressed. Check for SHIFT (retain normal action). ; PUSH DS ; save segment MOV AX,40H ; BIOS data segment MOV DS,AX ; address BIOS data segment MOV AL,DS:[17H] ; get keyboard flags POP DS ; recover segment AND AL,0FH ; mask out lock status MOV AH,AL ; take copy for later comparison CMP ONOFF,0 ; is GOLD on? JE I15H20 ; j if not - do nothing here AND AL,03H ; mask out ALT and CTRL status JE I15H20 ; j if not shifted CMP AH,AL ; see if just SHIFT JE I15H60 ; if so, treat as normal NUM LOCK call ; ; Check for ALT (invert GOLD status). This needs to work whether GOLD is ; ON or OFF. ; I15H20: MOV AL,AH ; recover shift status bits AND AL,08H ; mask out CTRL and SHIFT status JE I15H40 ; j if not ALT - pass through CMP AH,AL ; see if just ALT JNE I15H40 ; j if not - pass through TEST MBIT,80H ; make operation? JNE I15H30 ; j if not - ignore XOR ONOFF,1 ; flip GOLD ON/OFF flag ; I15H30: CLC ; absorb keystroke POP AX ; recover register RETF 2 ; return, preserving flags ; I15H40: CMP ONOFF,0 ; is GOLD on? JE I15H60 ; j if not - do nothing ; I15H50: PUSH DS ; save data segment MOV AX,40H ; BIOS data segment MOV DS,AX ; address BIOS data segment MOV AH,DS:[17H] ; get keyboard flags MOV AL,CSEG:MODBIT ; get extra modifier OR AL,AH ; combine with existing ones MOV DS:[17H],AL ; update keyboard flags POP DS ; recover segment MOV SAVESH,AH ; save old flags INC SHFLAG ; mark changed POP AX ; recover register MOV AL,NEWKEY ; set replacement scan code OR AL,MBIT ; include make/break bit, drop through STC ; make sure keystroke processed RETF 2 ; return, preserving flags ; I15H60: STC ; make sure keystroke processed POP AX ; recover register RETF 2 ; return, preserving flags ; I15HAN ENDP ; I15LEN = $ - I15HAN ; length of INT 15H handler ; SUBTTL INT 09H (keyboard interrupt) handler PAGE+ ; ; This INT 09H handler is hooked into the keyboard interrupt vector. ; It is used only if the keyboard interrupt intercept facility is not ; available in the BIOS, and is moved in memory so that the space occupied ; by the INT 15H handler is not wasted. ; ; Input parameters: ; No explicit inputs. Implicit input from the keyboard ; hardware. ; ; Output parameters: ; No explicit outputs. Control is passed to the normal keyboard ; interrupt handler unless the keystroke is to be modified or ; absorbed. Modified keystrokes are placed into the BIOS input ; buffer. ; All registers are preserved. ; I09HAN PROC FAR ; PUSH AX ; save register IN AL,60H ; get next character from hardware PUSH AX ; save character XOR AL,AL ; set zero XCHG AL,SHFLAG ; get and clear flags bit OR AL,AL ; set condition flags JZ I09H05 ; j if not currently changed PUSH DS ; save data segment MOV AX,40H ; BIOS data segment MOV DS,AX ; address it MOV AL,CSEG:SAVESH ; get saved keyboard flags MOV DS:[17H],AL ; restore them POP DS ; recover data segment I09H05: POP AX ; recover character CMP STATE,0 ; normal state? JE I09H07 ; j if so CMP STATE,1 ; had just one PAUSE byte? JE I09H06 ; j if so MOV STATE,0 ; else in state 2 - reset state... JMP I09H80 ; ...and skip checks ; I09H06: CMP AL,P2SCAN ; got second byte now? JE I09H08 ; j if so, => state 2 DEC STATE ; else reset state JMP SHORT I09H09 ; and perform normal checks ; I09H07: CMP AL,P1SCAN ; possible PAUSE sequence? JNE I09H09 ; j if not, else => state 1 ; I09H08: INC STATE ; update state JMP I09H80 ; skip checks ; I09H09: MOV AH,AL ; copy scan code AND AH,80H ; isolate make/break bit MOV MBIT,AH ; save it AND AL,7FH ; mask out make/break bit CMP AL,NUMSCAN ; NUM LOCK? JE I09H10 ; j if so JMP I09H80 ; else just pass on to original handler ; ; NUM LOCK has been pressed. Check for SHIFT (retain normal action). ; I09H10: PUSH DS ; save segment MOV AX,40H ; BIOS data segment MOV DS,AX ; address BIOS data segment MOV AL,DS:[17H] ; get keyboard flags POP DS ; recover segment AND AL,0FH ; mask out lock status MOV AH,AL ; take copy for later comparison CMP ONOFF,0 ; is GOLD on? JE I09H20 ; j if not - do nothing here AND AL,03H ; mask out ALT and CTRL status JE I09H20 ; j if not shifted CMP AH,AL ; see if just SHIFT JNE I09H20 ; j if not JMP I09H80 ; else treat as normal NUM LOCK call ; ; Check for ALT (invert GOLD status). This needs to work whether GOLD is ; ON or OFF. ; I09H20: MOV AL,AH ; recover shift status bits AND AL,08H ; mask out CTRL and SHIFT status JE I09H30 ; j if not ALT - pass through CMP AH,AL ; see if just ALT JNE I09H30 ; j if not - pass through TEST MBIT,80H ; make operation? JNE I09H70 ; j if not - ignore XOR ONOFF,1 ; flip GOLD ON/OFF flag JMP SHORT I09H70 ; absorb keystroke and exit ; I09H30: CMP ONOFF,0 ; is GOLD on? JE I09H80 ; j if not - pass through ; I09H40: TEST MBIT,80H ; break code? JNZ I09H70 ; j if so - ignore PUSH DS ; save segment PUSH SI ; save register PUSH BX ; save register MOV AX,40H ; BIOS data segment MOV DS,AX ; address it MOV BX,DS:[1CH] ; get offset of next slot in buffer MOV SI,BX ; save for later ADD BX,2 ; advance pointer CMP BX,DS:[82H] ; time to wrap? JNZ I09H50 ; j if not MOV BX,DS:[80H] ; else do it ; I09H50: CMP BX,DS:[1AH] ; buffer is full? JZ I09H60 ; j if so - discard character MOV AH,NEWKEY ; set replacement scan code XOR AL,AL ; extended code MOV WORD PTR [SI],AX ; set into buffer MOV DS:[1CH],BX ; save updated buffer pointer MOV AH,DS:[17H] ; get keyboard flags MOV AL,CSEG:MODBIT ; get extra modifier OR AL,AH ; combine with existing ones MOV DS:[17H],AL ; update keyboard flags MOV CSEG:SAVESH,AH ; save old flags INC SHFLAG ; mark changed ; I09H60: POP BX ; recover register POP SI ; recover register POP DS ; recover segment ; ; Clear the keyboard port, acknowledging the character. ; I09H70: IN AL,61H ; get control port MOV AH,AL ; copy for later reset OR AL,80H ; set bit to acknowledge JMP SHORT $+2 ; wait for settle OUT 61H,AL ; do the acknowledge JMP SHORT $+2 ; wait for settle MOV AL,AH ; get original setting OUT 61H,AL ; put it back JMP SHORT $+2 ; wait for settle MOV AL,20H ; End-Of-Interrupt OUT 20H,AL ; send to interrupt controller POP AX ; recover register IRET ; return without calling original ; I09H80: POP AX ; recover register JMP DWORD PTR INTOFF ; jump to original interrupt handler ; I09HAN ENDP ; I09LEN = $ - I09HAN ; length of INT 09H handler ; SUBTTL Initialisation code PAGE+ ; ; This is the program initialisation code. It is not present in the resident ; copy of the program. ; ASSUME CS:CSEG,DS:CSEG,ES:CSEG,SS:CSEG ; INIT: MOV ONOFF,1 ; set initial value MOV SHFLAG,0 ; set initial value MOV STATE,0 ; set initial value ; ; Select the operating mode ; CMP MODE,2 ; force INT 09H to be used? JE INIT50 ; j if so PUSH ES ; save ES MOV AH,0C0H ; see if keyboard intercept supported INT 15H ; get system configuration parameters JNC INIT10 ; j if configuration call supported POP ES ; recover ES JMP SHORT INIT20 ; try for INT 09H mode ; INIT10: TEST BYTE PTR ES:[BX+5],10H ; see if intercept flag set POP ES ; recover ES JNE INIT30 ; j if intercept supported - use it ; INIT20: CMP MODE,1 ; force INT 15H mode? JNE INIT40 ; j if not - use INT 09H mode LEA DX,MES0 ; 'Sorry - this machine does not...' MOV AH,9 ; output message INT 21H ; do it MOV AX,4C02H ; exit with error status INT 21H ; INIT30: MOV MODE,1 ; select INT 15H mode JMP SHORT INIT50 ; check DOS version now ; INIT40: MOV MODE,2 ; select INT 09H mode ; ; The INT 2FH code will work only on DOS 3.0 and above. See if it is OK ; to use it. ; INIT50: MOV AX,3000H ; get DOS version INT 21H ; returns minor in AH, major in AL CMP AL,3 ; see if 3 or greater JGE INIT60 ; j if so - OK LEA DX,MES1 ; 'Sorry - DOS 3.0 or above is...' MOV AH,9 ; output message INT 21H ; do it MOV AX,4C03H ; exit with error status INT 21H ; ; See if already installed ; INIT60: MOV AH,MPID ; multiplex ID XOR AL,AL ; function 00H XOR BX,BX ; zero for safety XOR CX,CX ; zero for safety XOR DX,DX ; zero for safety INT 2FH ; call multiplex PUSH CS ; restore DS in case corrupted... POP DS ; ...by other program PUSH CS ; restore ES... POP ES ; ...for same reason OR AL,AL ; see if OK to install (AL unchanged) JZ INIT70 ; j if so CMP AL,0FFH ; already installed? JE INIT100 ; j if so - skip installation LEA DX,MES2 ; 'Cannot install program' MOV AH,9 ; output message INT 21H ; do it MOV AX,4C01H ; exit with error status INT 21H ; ; Program is not installed; install it now. ; INIT70: PUSH ES ; save segment MOV AX,352FH ; get old INT 2FH handler INT 21H ; returns value in ES:BX MOV I2FOFF,BX ; save offset MOV I2FSEG,ES ; save segment LEA DX,I2FHAN ; point to new INT 2FH handler MOV AX,252FH ; set INT 2FH vector INT 21H ; from DS:DX ; ; Perform installation conditional on the selected mode ; CMP MODE,1 ; use INT 15H mode? JNE INIT80 ; j if not - set up for INT 09H ; ; Use the INT 15H code, which uses the BIOS keyboard interrupt intercept ; MOV AX,3515H ; get old INT 15H handler INT 21H ; returns value in ES:BX MOV INTOFF,BX ; save offset MOV INTSEG,ES ; save segment LEA DX,I15HAN ; point to new INT 15H handler MOV AX,2515H ; set INT 15H vector INT 21H ; from DS:DX POP ES ; recover segment MOV WORD PTR INTLEN,I15LEN ; save interrupt routine length JMP SHORT INIT90 ; rejoin common code ; ; Use the INT 09H code, which uses the keyboard hardware interrupt ; INIT80: MOV AX,3509H ; get old INT 09H handler INT 21H ; returns value in ES:BX MOV INTOFF,BX ; save offset MOV INTSEG,ES ; save segment LEA DX,I15HAN ; point to new INT 09H handler ; (where it WILL be) MOV AX,2509H ; set INT 09H vector INT 21H ; from DS:DX POP ES ; recover segment MOV CX,I09LEN ; get interrupt routine length MOV INTLEN,CX ; save it CLD ; autoincrement MOV SI,OFFSET I09HAN ; get address of interrupt routine MOV DI,OFFSET I15HAN ; where to move it REP MOVSB ; do so ; ; Complete installation ; INIT90: INC BYTE PTR RESFLAG ; remember to stay resident ; ; The program is now installed. Handle parameters. ; INIT100:CLD ; autoincrement MOV SI,81H ; offset of command tail MOV DI,81H ; put characters back in same place ; INIT110:LODSB ; get next command character CMP AL,CR ; end of command? JE INIT130 ; j if so CMP AL,'a' ; check if lower case alphabetic JL INIT120 ; j if not CMP AL,'z' ; check range JG INIT120 ; j if not in range SUB AL,'a'-'A' ; convert to upper case ; INIT120:STOSB ; return possibly modified character JMP INIT110 ; keep scanning ; INIT130:MOV SI,81H ; back to start of command tail ; INIT140:LODSB ; get next character CMP AL,' ' ; space? JE INIT140 ; j if so - ignore CMP AL,TAB ; tab? JE INIT140 ; j if so - ignore CMP AL,CR ; end of parameters? JE INIT150 ; j if so DEC SI ; point back to first non-space MOV BX,SI ; save pointer MOV CX,2 ; check for ON LEA DI,ON ; 'ON' REP CMPSB ; matched? JE INIT190 ; j if so MOV SI,BX ; recover pointer MOV CX,3 ; check for OFF LEA DI,OFF ; 'OFF' REP CMPSB ; matched? JE INIT180 ; j if so LEA DX,MES4 ; 'Parameter must be ON or OFF' MOV AH,9 ; output message INT 21H ; do it MOV AX,4C01H ; indicate error INT 21H ; and exit ; ; No parameter given - just report status unless initial installation ; INIT150:LEA DX,MES3 ; 'GOLD is ' MOV AH,9 ; output message INT 21H ; do it MOV AH,MPID ; multiplex ID MOV AL,1 ; request status INT 2FH ; returns AL=0 for OFF, AL=1 for ON OR AL,AL ; test value JNZ INIT160 ; j if ON LEA DX,OFF ; 'OFF' JMP SHORT INIT170 ; join common code ; INIT160:LEA DX,ON ; 'ON' ; INIT170:MOV AH,9 ; output message INT 21H ; do it JMP SHORT INIT220 ; use common code ; INIT180:XOR DL,DL ; clear flag JMP SHORT INIT200 ; jump to setting code ; INIT190:MOV DL,1 ; set flag ; ; AL now contains the required GOLD setting flag. Check that the rest of ; the command line is blank, then if all is OK set the flag appropriately. ; INIT200:LODSB ; get next character CMP AL,' ' ; space? JE INIT200 ; j if so - ignore CMP AL,TAB ; tab? JE INIT200 ; j if so - ignore CMP AL,CR ; end of parameters? JE INIT210 ; j if so LEA DX,MES5 ; 'Invalid parameter' MOV AH,9 ; output message INT 21H ; do it MOV AX,4C04H ; indicate error INT 21H ; and exit ; ; The command line is OK. Set the GOLD flag. ; INIT210:MOV AH,MPID ; multiplex ID MOV AL,2 ; set flag from DL INT 2FH ; set new flag value in resident copy ; ; If this is not the first load of GOLD, just exit. ; INIT220:TEST BYTE PTR RESFLAG,1 ; make resident? JNE INIT230 ; j if so MOV AX,4C00H ; else just exit with success INT 21H ; ; This is the first load of GOLD; terminate and stay resident ; INIT230:MOV ES,ES:[2CH] ; get environment segment MOV AH,49H ; free memory for it INT 21H ; do it MOV DX,OFFSET I15HAN ; size of common part ADD DX,INTLEN ; add size of interrupt routine ADD DX,15 ; round to next paragraph MOV CL,4 ; amount to shift SHR DX,CL ; convert to paragraphs MOV AX,3100H ; exit with success status INT 21H ; and stay resident ; INTLEN DW ? ; Size of selected interrupt handler RESFLAG DB 0 ; Set to 1 if to stay resident ; ON DB 'ON',CR,LF,'$' OFF DB 'OFF',CR,LF,'$' MES0 DB 'Sorry - this machine does not support the GOLD utility',CR,LF DB 'if the interrupt intercept mode is selected' DB CR,LF,'$' MES1 DB 'Sorry - DOS 3.0 or above is required for the GOLD utility' DB CR,LF,'$' MES2 DB 'Sorry - cannot install the GOLD utility',CR,LF,'$' MES3 DB 'GOLD is $' MES4 DB 'Parameter must be ON or OFF',CR,LF,'$' MES5 DB 'Invalid parameter',CR,LF,'$' ; INFO DB '====GOLD version 2.4====' ; CSEG ENDS ; END BEGIN