Key Mapper
Volume Number: 2
Issue Number: 10
Column Tag: Forth Forum
Keyboard Re-Mapper Utility
By Jörg Langowski, MacTutor Editorial Board
Keyboard configuration and reconfiguration
This time I'd like to address a problem that is rather neglected in Inside
Macintosh, but not by MacTutor, and that is the configuration of the Mac keyboard. [As
background, you may wish to refer to the August issue for an article by Joel West and
David Smith on the Keyboard Sleuth.] The section on international resources mentions
the different existing keyboards, their layouts and the keycodes; but it is nowhere said
explicitly how the keycode that is sent out when a key is pressed is mapped to an ASCII
character and where the tables are kept that are used for that assignment.
There is one program, the Localizer, that is generally used to change the
keyboard configuration from one layout to another. This works well for the standard
keyboards that come for the different countries in which the Mac is sold. It would be
very convenient, though, to be able to change the keyboard mapping according to one's
taste. That way one could layout a Dvorak keyboard or make simple changes just to have
some different symbols available, without changing the whole keyboard arrangement.
As an example, I prefer to have a decimal point instead of a comma on my (German)
Mac+ numeric keypad, without changing the rest of the layout so that all the symbols
stay where they are. There is no 'canned' configuration in the localizer that provides
for this change. So, here's a nice chance to write a small application in Forth that does
something useful and at the same time show some instructive details about how the
keyboard is configured.
The INIT 0 and INIT 1 resources
Doing so requires some NOSYing around in the routines that translate keycodes
into ASCII characters. These routines are built at system initialization time through
the INIT resources ID=0 and ID=1. [One can find this out easily by looking at the
Localizer, which contains quite a few INIT resources that all look very much the same
except for some tables that contain the actual keyboard mapping]. Disassembly reveals
how a keycode is translated into an ASCII character by the INIT routines. See Listing 1.
The modifier bits (Shift, Option, Caps Lock) are taken as an index into a small offset
table, from which the beginning of the actual translation table is obtained. The keycode
then serves as an index into that table. Two different translation tables exist for the old
Mac and the Mac+ keyboards. The keyboard type is read from the system global
KybdType at $21E, which is continually being updated by the background routine that
checks for the presence of a keyboard. A hex $B here means we have a Mac+ keyboard
plugged in, other numbers so far refer to the old keyboard, and keypad, optionally. [A
zero means, there is no keyboard... but in that case, we'll never get to this routine
anyway...]
A very simple way to change the translation table to our preferences is to
emulate the table lookup process in Forth, starting with the keycode. Mach1 (also the
new version Mach2) contains the word ?TERMINAL which returns the character code,
keycode and modifier flags if a key has been pressed.
Fig. 1 Our Keyboard Remapper in Mach2
Listing 2 defines a couple of words that will return the absolute address of the
translation table entry from the keycode and modifiers. keycode and modifiers return
the respective fields. keyaddress, given a modifier / keycode input on the stack,
determines which INIT resource to use (INIT 0 for the main keyboard and INIT 1 for the
numeric keypad), leaves a handle to that resource on the stack and the table entry's
address on top of it. This address can then be used by the remapping routine to put a new
entry into the table. The handle will be used to call _ChangedResource to mark the INIT
resource as changed so that it is rewritten when _UpdateResource is called at the end.
get.key1.data and get.key2.data are words that are used by keyaddress to
determine the beginning of the translation tables for the main and numeric keyboards.
They are somewhat kludgy as they make use of the internal structure of the INIT 0 and
INIT1 resource codes, reading the table addresses out of the LEA ,A0 instructions
at the beginning of the code. When a system is released where those instructions occur
in different positions, my program will get completely lost. In that case, it would
either have to be modified to accommodate the new addresses or the kludge would have to
be generalized, using a routine that looks for a LEA ,A0 instruction. All this is
embarassingly bad programming style, but it was the simplest way that I could think
up to find the table addresses.
The INIT resources are read once at the beginning of the main word remap and
stored in two variables hand0 and hand1. remap itself is an endless loop which
comprises the user dialog for remapping of the keys. When the user hits the go-away
box, the loop is exited through a vector which is installed in the task's goaway-hook;
the resource file is updated (file id=0 indicates the System file), and control is
returned to the Finder.