Customizing the Keypad Keymap by John Orr and Carolyn Scheppner The article ``Loading Keymaps'' in this issue of Amiga Mail discusses how to load a keymap file from disk and add it to the system's list of keymaps. The article also mentions that, although it does have some legitimate uses, a console-based application rarely needs to concern itself with system-global keymaps. In most cases, it is both easier and more system friendly for the application to duplicate its console's keymap and make some modifications to the copy of the keymap. Since such an application has a private copy of the keymap, the application does not have to interfere with the user's global keymap settings. Since this application is using a copy of the user's default keymap, the technique works with many different national keyboards. The KeyMap To understand how to alter a KeyMap, you have to understand the layout of a KeyMap. This is covered in the ``Keymap Library'' chapter of the Amiga ROM Kernel Reference Manual: Libraries, although it is a bit hard to follow. Hopefully, this section describes the layout of an Amiga Keymap a little better. Each key on the Amiga keyboard has its own 7-bit raw key code. It is a raw key because there is no character value assigned to it yet, so it isn't ``cooked'' yet. The Amiga doesn't bother assigning a character value to a specific key on a keyboard because the letter printed on a key can vary greatly between keyboards intended for use with different languages. The Amiga uses a KeyMap to map raw key codes from the keyboard to an ANSI character. The Amiga's input.device handles generating raw key input events. The input.device reads the state of the keyboard using the keyboard.device. There are two ways to use the keyboard to trigger the input.device into generating a raw key input event. When the user presses a key, the input.device creates an IECLASS_RAWKEY InputEvent structure (see <devices/inputevent.h>) and stores the raw key code as a byte in the ie_Code field. This is known as a ``key down'' event. The input.device also creates a similar input event when the user releases a key, but it sets the high bit of the raw code byte to indicate the user let go of the key (sometimes called a ``key up'' event). If any qualifier keys (Control, Shift, or Alt) were pressed when the raw key was pressed (or released), the input.device sets some qualifier bits in the InputEvent's ie_Qualifier field. The input.device then adds the InputEvent to the input stream. As the input events travel down the input stream, a variety of input handlers (like Intuition's input handler) have the opportunity to examine the input events, possibly adding and removing input events. If the IECLASS_RAWKEY InputEvent eventually arrives at the console.device's input handler, the console.device can use a KeyMap to convert (or ``cook'') the raw key event into one of several things: An ANSI character A String (32 characters or less) A Dead Key (or Double Dead Key) The following is the KeyMap structure (from <devices/keymap.h>): struct KeyMap { UBYTE *km_LoKeyMapTypes; ULONG *km_LoKeyMap; UBYTE *km_LoCapsable; UBYTE *km_LoRepeatable; UBYTE *km_HiKeyMapTypes; ULONG *km_HiKeyMap; UBYTE *km_HiCapsable; UBYTE *km_HiRepeatable; }; The KeyMap maps the range from raw key 0x00 through 0x7F. As mentioned earlier, the high bit of the raw code is reserved for the up/down state of the key. Each field in the KeyMap structure is a vector that points to a table. The KeyMap splits the range of raw keys into a low set, 0x00 through 0x39, and a high set, 0x40 though 0x7F. The vectors with names that start with ``km_Lo'' refer to the lower range (0x00 through 0x39). Likewise, the vectors with names that start with ``km_Hi'' refer to the higher range (0x40 through 0x7F). Each table has an entry for each raw key in its range. The km_LoKeyMapTypes and km_HiKeyMapTypes fields each point to an array of bytes. Each array has an entry for each raw key in its range (0x00-0x39 for LoKeyMapTypes and 0x40-0x7F for HiKeyMaps). These arrays, known as the Key Type tables, describe a key's type and its relevant qualifiers. Each entry is a bitfield composed of the following flags (from <devices/keymap.h>): KCF_SHIFT /* The mapping is different for shifted mapping vs. non-shifted */ KCF_ALT /* The mapping is different for ALTed keys vs. non-ALTed */ KCF_CONTROL /* The mapping is different for CTRLed keys vs. non-CTRLed */ KCF_DEAD /* This key is a ``deadable'' key */ KCF_STRING /* This key maps to a string */ KCF_NOP /* This key maps to nothing */ The km_LoKeyMap and km_HiKeyMap fields each point to an array containing a long word entry for each raw key in its range. These arrays are known as the Key Map tables. The purpose of the long word depends on the corresponding entry from the key type tables: For string keys (the KCF_STRING bit from the key type table is set), the entry is a 32-bit pointer to string data (more on this later). For deadable keys (the KCF_DEAD bit from the key type table is set), the entry is a 32-bit pointer to dead key data. For KCF_NOP keys, the entry is undefined. For all other keys (for lack of a better term, we'll call them ``normal'' keys), the entry consists of four bytes. Each of these bytes is an eight bit ANSI code. The raw key ``maps'' to one of these four bytes. The qualifier keys that were pressed when the user hit the raw key determine which of the four ANSI codes to use. Unfortunately, since each normal raw key can only map to one of four bytes, there is not enough space to handle all possible qualifier combinations. Since there are three qualifier keys (Shift, Alt, and Control), each raw key would require eight bytes to have an entry for all possible qualifier key combinations. As this scheme can't support all possible qualifier combinations, it only supports some combinations. The easiest case to handle is where the user hits the raw key without any qualifiers. The raw key without qualifiers always maps to the ANSI value in the low order byte of its entry in the Key Map table. The meaning of the upper three bytes of the key map table entry changes according to the raw key's key type value (from the key type table). The following chart shows how the key type value changes the meaning of all four bytes. If a qualifier does not appear in the ``Key Type'' column, the keymapping software ignores that qualfier for the key. For example, if the key type is KCF_SHIFT|KCF_ALT and the user is holding the shift and alt keys while pressing a normal raw key, the raw key maps to the ANSI code in the high order byte (bits 31-24). If the key map entry is the long word 0xD0F04464, the raw key maps to the ANSI value 0xD0. Another good example to consider is where the key type is KC_VANILLA. The vanilla qualifier does not mean plain! KC_VANILLA is a combination (a logical OR) of the three qualifier key types. When the key type is KC_VANILLA and the user is holding the Control key, the keymapping software maps the the raw key to the low order byte, but it clears bits five and six of the value it ouputs. The keymapping software ignores the state of the shift and Alt keys. Using the key map entry 0xD0F04464, the raw key maps to the low byte (0x64) but with its fifth and sixth bit cleared: %0110 0100 This is 0x64 in binary %1001 1111 bitwise AND to clear bits 5 and 6 ----------- %0000 0100 = 0x04 The next fields from the KeyMap structure are km_LoCapsable and km_HiCapsable, which point to the Capsable tables. These tables contain a single bit for each raw key in their range. Their purpose is to tell the keymapping software what to do when the Caps Lock button is on (the LED in the Caps Lock button is lit). If a raw key's Capsable bit is set, the keymapping software treats the raw key as a shifted raw key while the Caps Lock LED is on. Like the Capsable tables, the Repeatable tables (km_LoRepeatable and km_HiRepeatable) contain a single bit for each raw key in their range. If a raw key's Capsable bit is set, the keymapping software should allow the key to repeat if the user holds it down long enough. The Input Preferences editor sets the time intervals that the input.device waits before repeating the key and between key repeats. The input.device takes care of adding these events to the input stream. When the keymapping software sees the series of repeated keys, it may elect to throw away almost all of them if the key is not marked as repeatable in the keymap. String Output Keys A keymap can also map a raw key to a string. A raw key maps to a string if the key's entry in the key type table has the KCF_STRING bit set. For string keys, the corresponding long word entry in the Lo/HiKeyMap is a 32-bit pointer to the string data. The string data consists of a table of 16-bit entries. Unlike the ``normal'' keys, string keys can account for every possible qualifier key combination. Since there must be an entry in the string key's table for each qualfier combination, the number of table entries depends on how many qualifier bits are set in the key's key type table entry. If x is the number of qualifier bits set in the key type table then the number of table entries is 2x. If all three qualifier bits are set, there are eight entries. They appear in the table in the following order: Table 2 - String Key Data Table 000 No Qualifiers 001 Shift 010 Alt 011 Alt+Shift 100 Control 101 Control+Shift 110 Control+Alt 111 Control+Alt+Shift To adjust the table above for fewer qualifiers, remove the rows that contain the missing qualifier. For example, if the KCF_SHIFT and KCF_CONTROL bits are set, the KCF_ALT qualifier is missing, so remove all the lines with an ``Alt'' in them: Table 3 - A Shiftable Control Key 00 No Qualifiers 01 Shift 10 Control 11 Control+Shift Each entry in the string data table is organized into two bytes, the first byte tells the keymapping software the length of the string that the raw key maps to. The second byte is a positive offset from the beginning of the table to the string itself. Since this offset is only eight bits wide, every string must start within the 255 bytes after the beginning of the table. For a raw key that uses a full string data table, the keymap needs the first 16 bytes, leaving enough room for about 34 bytes per string. Normally, the key's strings should not exceed 32 bytes as larger strings can overflow the console.device's input buffer, so 32 bytes is a good upper limit. Unlike a C-style string, the raw key's string does not need a zero terminator at the end (although it's not a bad idea to have a zero there if there is enough space). Simple KeyMap Example The following is a pseudo-assembler fragment that shows the key type and the map table entries for the first three keys of the high keymap (raw keys 0x40, 0x41, and 0x42): newHiKeyTypes: DC.B KCF_ALT,KC_NOQUAL, ;key 0x40 (spacebar), key 0x41 (backspace) DC.B KCF_STRING+KCF_SHIFT, ;key 0x42 is the tab key on the US keyboard ... ;(other keys) newHiKeyMap: DC.B 0,0,$A0,$20 ;key 0x40: nil, nil, the Alted-space key maps to $A0 which ; is the ANSI Non-Breakable Space (NBSP), the plain space ; key maps to $20 which is the space character. DC.B 0,0,0,$08 ;key 0x41: Back Space key only DC.L newkey42 ;key 0x42: definition for string to output for Tab key ... ;(other keys) newkey42: DC.B new42ue - new42us ;length of the unshifted string (new42us) DC.B new42us - newkey42 ;number of bytes from start of ; string descriptor to start of this string DC.B new42se - new42ss ;length of the shifted string (new42ss) DC.B new42ss - newkey42 ;number of bytes from start of ; string descriptor to start of this string new42us: DC.B '[TAB]' new42ue: new42ss: DC.B '[SHIFTED-TAB]' new42se: The keymap fragment above shows two kinds of keys, a ``normal'' key and a string key. Looking at the key types table which starts at the newHiKeyTypes label, key 0x40 is neither a dead key, a No Op key, or a string key, so key 0x40 is a ``normal'' key. It has one modifier, KCF_ALT. Looking at Table 1, the Key Map table entry for key 0x40 should only have two defined bytes. The third byte is the raw key with the Alt modifier and the fourth byte is the raw key without modifiers. Looking at key 0x40's entry in the key map table (the first four bytes of newHiKeyMap), if the user hits raw key 0x40 with the Alt key down, this keymap maps the key to ANSI character 0xA0, which is the ANSI Non-Breakable Space character (NBSP). Since only the KCF_ALT qualifier bit is set, this keymap ignores the other qualifier keys. Key 0x41 is similar to key 0x40 as it is a ``normal'' key as well. One way in which key 0x41 differs from key 0x40 is that it has no qualifiers. According to Table 1, if the user presses raw key 0x41 with or without qualifiers, this keymap maps to the fourth byte in key 0x41's entry in the key map table, so raw key 0x41 maps to the ANSI character 0x08, which is the backspace character. The last key, 0x42, has two bits set in its entry from the key types table, KCF_STRING and KCF_SHIFT, so it is a shiftable string key. For a string key, the entry from the key map table is a 32-bit pointer to string key data instead of the four ANSI codes a ``normal' key uses. Key 0x42's key map entry points to newkey42. As key 0x42 is shiftable, the string data should only have two entries in it. To adjust Table 2 for the shift qualifier, remove the rows that contain Alt and Control. The table that starts at newkey42 should look like this: 00 No Qualifiers 01 Shift Looking at the keymap example, the ``No Qualifiers'' entry in the string table shows that the length of the ``No Qualifiers'' string is new42ue-new42us. The second byte in the string table entry is the offset from newkey42 to the ``No Qualifiers'' string, which points to the string ``[TAB]''. A Word About Dead-Class Keys The Amiga keymap scheme also supports dead-class keys, which allow one key press to modify the next key press. However, robustly altering a keymap by adding dead-class keys requires an impractical amount of overhead and is not particularly useful for the purposes described here. Dead-class keys are not covered in this article. For more information, see the ``Keymap Library'' chapter of the Amiga ROM Kernel Reference Manual: Libraries. Cloning and Altering the KeyMap The AppKeyMap code at the end of this article contains a function which duplicates a KeyMap structure plus all the KeyMap's associated tables. Cloning a keymap is a fairly straightforward memory copy, although the AppKeyMap code adds a little twist to make it easier for other functions to reference the entries in the table. The way <devices/keymap.h> defines the KeyMap structure, the keymap tables are split into a low set (``km_Lo...'') and a high set (``km_Hi...''). This makes looking up entries in the tables a nuisance because you have to treat ``Lo'' raw key and ``Hi'' raw keys separately. When the CreateAppKeyMap() function creates the tables, it organizes the tables so the ``Hi'' table directly follows the ``Lo'', joining the two tables into one. This allows other functions in the AppKeyMap code to use a raw key value as an index into the table. Altering the keymap is also rather straightforward, particularly because the CreateAppKeyMap() function made the table entries much easier to reference. The only remotely complicated part is handling the Capsable and Repeatable tables. These are both bit tables (there a single bit entry for each raw key), so handling them in C is a little cumbersome. About the Code Example The appmap_demo.c example is a shell-only program. It opens the console.device and uses its CD_ASKKEYMAP command to obtain a copy of the shell's current keymap. Using the AppKeyMap functions, the example duplicates and alters the keymap copy. Appmap_demo.c then uses the console's CD_SETKEYMAP command to install the keymap copy as the shell's current keymap. The user can type at the shell's window trying out the altered keymap. When finished, the example restores the shell's original keymap using the console's CD_SETKEYMAP command and exits. An application can also use this technique when attaching a console.device to an Intuition window. AppKeyMap.doc AppKeyMap.c AppKeyMap.h AppMap_demo.c