[comp.sys.ibm.pc] REQUEST: AT keyboard [control key]/[caps lock] remapping

mintz@hpindda.HP.COM (Ken Mintz) (07/27/88)

> Does anyone know of a Terminate and stay resident (TSR) program, or
> device driver which could [swap the Control and Caps Lock keys]?

  I agree, and it's one of the first things I plan to do if/when I get my
  AT compatible.  (It's been on order for a l-o-n-g time.)

  If anyone has s/w to do this, I'd appreciate a copy of the source.  Email
  to the address below.

  But I would suggest a slightly different (re)mapping.  Put Caps Lock up where
  Esc is ... or get rid of it altogether.  Put Control where Caps Lock is.  And
  put Esc where Control is. 

  Pity to lose the right-hand Control key.  But this is the best compromise I
  can come up with.  Any other recommendations?

Ken Mintz
UUCP: hplabs!hpda!mintz
ARPA: mintz%hpda@hplabs.hp.com

seeger@beach.cis.ufl.edu (Charles Seeger) (07/28/88)

I just got a Honeywell keyboard that remaps the cntrl and caps-lock
via a dip-switch.  Pretty nice for ~$75.00.  They are *brand* new and
may not be far along in distribution, yet.  It's one of their
"Silent-Tactile" line.

hedrick@athos.rutgers.edu (Charles Hedrick) (07/28/88)

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