Path: utzoo!utgpu!attcan!uunet!husc6!ukma!rutgers!aramis.rutgers.edu!athos.rutgers.edu!hedrick From: hedrick@athos.rutgers.edu (Charles Hedrick) Newsgroups: comp.sys.ibm.pc Subject: Re: REQUEST: AT keyboard [control key]/[caps lock] remapping Message-ID: Date: 28 Jul 88 09:22:39 GMT References: <2341@rtech.rtech.com> <4330100@hpindda.HP.COM> Organization: Rutgers Univ., New Brunswick, N.J. Lines: 215 I use the following program to make my AT keyboard work reasonably as a terminal. It turns caps lock into a control key. It puts caps lock where it belongs: on the back of the system unit. It also exchanges ESC and ~. You should find it easy to modify to do whatever other changes you want. It is for the Mark Williams assembler, which uses Unix syntax. I haven't yet figured out how to make the overblown Microsoft monster do what I want. I think some of the code is based on other PD stuff, which I should acknowledge but have lost the name of. / This program turns the caps lock key into a control key. It also / swaps the ` and ESC keys. It works / by patching the interrupt handler for interrupt 9 (keyboard) to / call hard_kybd_int in this program, and interrupt 16 (read char) / to call soft_kybd_int in this program. These routines call the real / interrupt routine, and hack on the results. / Presumably this program can be used at the same / time as other keyboard enhancers such as ProKey, but I wouldn't / want to bet very much on it. The cap lock hack seems to work all / the time. The ` ESC reversal doesn't work with micro-Gnu, though / it works with other Emacs. Presumably it fails only with software / that does hardware-level I/O. Fortunatley micro-Gnu has its own / way to redefine keys. (Probably other software that works at this / level does also.) / This program is written using "as" from Mark Williams C. This is / in effect a Unix assembler, rather than the usual Microsoft masm. / I apologize for this, but that happens to be the assembler I have... / The following labels refer to offsets in the interrupt area. / They must be used with a segment register that has 0 in it. kb_int=0x9*4 rd_int=0x16*4 / The following labels refer to offsets in the BIOS magic data area / starting at 0x400. They must be used with a segment register that / has 0x40 in it. equip_flag=0x10 kybd_flag=0x17 addr_6845=0x63 / The rest of the program is conventional. Note that we put our / local data into program segment. This is because we are using / ds to refer to the special areas defined above. .prvi / This assembler doesn't seem to have an END statement. The programs / always start at the beginning. So go to the place where we really / want to start. Note that the first part of the program is going / to be left around permanently to deal with interrupts. start: jmp initialize / Data needed for the permanent part. old_kyf: .byte 0x00 / This routine is our handler for interrupt 9. / This does the caps-lock to control mapping. hard_kybd_int: sti pushf / The following kludge is because this darned assembler can't assemble / call far except for a direct lable. / 9a is the op code for the inline form of call far. / I don't much like self-modifying code, but this was the safest form / to use under the circumstances. .byte 0x9a rom_key_int: .word 0,0 push ax / get some free registers push ds mov ax,$0x40 / we need 40 in ds to address the funny area mov ds,ax / There are two magic bytes involved here. Bit 40 in kybd_flag is the / flag used to control caps lock action. Bit 40 in kybd_flag+1 indicates / that the caps lock key is actually depressed. This can differ, because / some programs set caps lock for themselves by hacking on the bit in / kybdf_flag. Unfortunately, there is only one bit for the control keys. / So if somebody puts down both shift lock and control, we aren't going to / be able to keep track of exactly what is going on. movb al,kybd_flag+1 / 40 is caps lock down movb ah,al / save new val in ah, since xor will change it xorb al,cs:old_kyf / changes since last time testb al,$0x40 / has caps lock changed? jz nochange / no movb cs:old_kyf,ah / change - save new value movb al,kybd_flag / get status bits andb al,$0xbb / clear shift lock and control testb ah,$0x40 / but if caps lock went on jz wentoff orb al,$0x04 / then turn on control wentoff: movb kybd_flag,al / put in new flags nochange: pop ds pop ax iret / This routine is our handler for interrupt 16. / This does any other character mappings that may be desired. Note / however that software that works directly with interrupt 9, as / micro-Gnu seems to, will not see these mappings. Fortunately, / micro-Gnu has its own way to redefine keys, and I suspect other / software that works at that level will as well. soft_kybd_int: sti / The user program passes a request code. We only trap 0, which is / read char and 1, which is lookahead. orb ah,ah jz dochange cmpb ah,$1 je dochangex / This is a jump far to the original handler, for all codes that we / don't want to handle. .byte 0xea rom_rd_int2: .word 0,0 / Here for codes 0, read request dochange: pushf / Call the original read routine .byte 0x9a rom_rd_int: .word 0,0 / Now exchange ESC and ` mapit: cmpb al,$0140 je backqt cmpb al,$033 je esc doret: iret / The test above checks the character code that is about to be / returned. We also check ah, which contains the low-level hardware / code for the key that was pressed. This is done so that we map / the ESC key but not ^[ (which is also ESC). Note that we / supply new values for both al and ah, just in case any software / happens to be looking at the key rather than the character. backqt: cmpb ah,$0x29 jne doret movb al,$033 movb ah,$0x1 iret esc: cmpb ah,$0x1 jne doret movb al,$0140 movb ah,$0x29 iret / Here for code 1, lookahead dochangex: push bp mov bp,sp / Call original read routine pushf .byte 0x9a rom_rd_int3: .word 0,0 / This code returns the zero flag to say whether there is a char or not jz iszer / Have to pass the flag back to the caller. The caller's flags are / on the stack. push ax mov ax,6(bp) and ax,$0xffbf mov 6(bp),ax pop ax pop bp jmp mapit / Nothing there - pass zero flag up to the caller iszer: push ax mov ax,6(bp) or ax,$0x0040 mov 6(bp),ax pop ax pop bp iret / The following is the main program. It saves the old value of / the int 9 and int 16 handlers, and puts ours in instead. initialize: / hardware kb int movb ah,$53 / get interrupt vector movb al,$0x9 / for hardware int int 33 / call DOS mov cs:rom_key_int,bx / save old address:seg mov cs:rom_key_int+2,es mov dx,$hard_kybd_int / to our handler mov ax,cs / in our segment mov ds,ax movb ah,$37 / set interrupt vector movb al,$0x9 / for hardware int int 33 / software read ch int movb ah,$53 / get interrupt vector movb al,$0x16 / for hardware int int 33 / call DOS mov cs:rom_rd_int,bx / save old address:seg mov cs:rom_rd_int2,bx / need it 3 places mov cs:rom_rd_int3,bx mov cs:rom_rd_int+2,es mov cs:rom_rd_int2+2,es mov cs:rom_rd_int3+2,es mov dx,$soft_kybd_int / to our handler mov ax,cs / in our segment mov ds,ax movb ah,$37 / set interrupt vector movb al,$0x16 / for hardware int int 33 / Now we exit, telling the system to leave the code needed to do the / interrupt handling around. mov dx,$initialize int 0x27