A keyboard layout is a data file that consists of a header block and the keyboard layout data. A keyboard driver uses the keyboard layout to map scan codes to virtual key codes and vice versa. A keyboard layout file is a binary data file with the filename extension KBD.
When Windows calls the InquireEx function with the INQEX_NLCID flag, the driver must return the number of keyboard layouts that it supports. The driver fills a buffer with a list of the keyboard layouts when the INQEX_LPLCID flag is specified.
The format of a keyboard layout file is as follows.
// Header block typedef struct { BYTE KbdDesc[2]; // set to 'DS' DWORD DefaultLCID; // base language locale identifier WORD Version; // set to zero DWORD pKbdStart; // start of the layout data WORD kbdSize; // number of bytes in the data DWORD pDibData; // must be zero WORD dibSize; // must be zero DWORD pUnicode // must be zero DWORD nUnicode // must be zero } HeaderBlock;
The keyboard layout data begins at the location indicated by the pKbdStart field; typically, it follows immediately after the header. The KBDOFFSET values are offsets from the start of the header block, not the start of the file.
typedef struct { WORD Flags; WORD nStateKeys; WORD nStates; WORD nDeadKeys; WORD nLigKeys KBDOFFSET pkbdxxStates; // STATE_LIST KBDOFFSET pkbdxxToAscStates; // TOASC_STATES KBDOFFSET pkbdxxToAscStateTables; // STATETABLES KBDOFFSET pkbdxxToAscVkeyList; // VKEY_LISTS KBDOFFSET pkbdxxToAscVKeyListLens; // VKEY_LIST_LENS KBDOFFSET pkbdxxVKShiftStates; // VK_STATES KBDOFFSET pkbdxxScanToIdx; // SCANTOIDX KBDOFFSET pkbdxxVKeyToIdx; // VKEYTOIDX WORD ScanSize; // SCAN_SIZE KBDOFFSET pkbdxxVKeyToAnsi; // VKEYTOANSI KBDOFFSET pkbdxxDeadKeyTable; // DEAD_KEYS KBDOFFSET pkbdxxDeadTrans; // DEAD_KEYTRANS KBDOFFSET pkbdxxLigKeys; // LIG_KEYS KBDOFFSET KBDOFFSET pkbdxxCapsBits; // VKEY CAPSLOCK Table KBDOFFSET pkbdxxKanaNormal; // DBCS platforms (optional) } KbdDataHeader;
The entries in the Vkey and character table pairs must be in the same order. Also the scancode table (pkbdxxScanToIdx) is in the same order as the main Vkey table (pkbdxxVKeyToIdx).
The wFlags field can have one of these values:
Value | Meaning |
ALTGRUSED | The keyboard layout has ALTGR keys (normal, shifted or both). CAPS LOCK here uses the same CAPS LOCK bitmap as the normal and shifted (non-ALTGR) layout layers. |
CAPSNORMAL | The CAPS LOCK key assumes its normal function; that is, it converts alphabetic characters to upper or lower case. Only those keys which are enabled in the CAPS LOCK bitmap will be operated on. |
SHIFTLOCKUSED | The keyboard layout does not have a CAPS LOCK key, but does have a SHIFT LOCK key. In the SHIFTLOCKED state all keys are placed in the shifted state, unlike the CAPS LOCK key which would operate only on specific keys. |
DEADCOMBOS | Declares that the alternative deadkey list is being used, which allows specification of VKEY and shift states for each deadkey entry in the list. |
The CAPSNORMAL and SHIFTLOCKUSED flags are mutually exclusive.
The pKbdCapsBits field points to an array of 32 bytes (consisting of 256 bits) where each bit is indexed by the VKEY. If a VKEY's corresponding bit is not set in this CAPS LOCK bitmap, then the CAPS LOCK function will not operate on that VKEY. The CAPSNORMAL flag must be set to enable this bitmap. If the CAPSNORMAL flag is not set then a separate capitalization VKEY and ANSI table must be used. The standard US bitmap is shown below, where only the A through Z keys are capitalized.
pkbdxxCapsBits label byte ; CAPSNORMAL flag must be = 1 db 0,0,0,0,0,0,0,0 ; Controls & Numerals db 0FEh,0FFh,0FFh,07h,0,0,0,0 ; VK_A -> VK_Z & Numpad & Function Keys db 0,0,0,0,0,0,0,0 ; Function Keys & OEM Keys db 0,0,0,0,0,0,0,0 ; OEM Keys, OEM Controls
Note
A CAPS LOCK enabled VKEY is operated on by the CAPS LOCK function even in the ALTGR states, so that ALTGR keys will appear as ALTGR-SHIFT keys when operated on by the CAPS LOCK function.
The State list table indicates which basic states are enabled. Only the VK_CAPITAL state is optional (used only when CAPSNORMAL = 0, when the CAPS LOCK bitmap is not used).
pkbdxxStates label byte db VK_MENU, 080H ; 1 - Alt state db VK_SHIFT, 080H ; 2 - Shift state db VK_CONTROL, 080H ; 4 - Control state db VK_CAPITAL, 001H ; 8 - CapsLock state (optional) NSTATEKEYS equ ($ - pkbdxxStates) shr 1
The following two state tables are associated with the state list. The first table provides a list of the state indices. The second table provides the states themselves. State indices 8 through 15 are rarely used because the CAPS LOCK bitmap is usually adequate to provide character capitalization options.
db 0 ; Normal (must be outside the label) pkbdxxToAscStates label byte db 1 ; Alt db 2 ; Shift db 3 ; Alt+Shift db 4 ; Control db 6 ; Control+Shift db 5 ; AltGr db 7 ; AltGr+Shift db 8 ; CapsLock db 9 ; CapsLock + Alt db 10 ; CapsLock + Shift db 11 ; CapsLock + Alt + Shift db 12 ; CapsLock + Control db 14 ; CapsLock + Control + Shift db 13 ; CapsLock + AltGr db 15 ; CapsLock + AltGr + Shift kbdxx_NSTATES equ $-pkbdxxToAscStates (total states - 1) pkbdxxVKShiftStates label byte db 0 ; unshifted db 0 ; alt - (same as unshifted) db 1 ; shifted db 1 ; alt+shift - (same as shifted) db 2 ; Control db 3 ; Control + Shift db 6 ; AltGr - \ Alternative characters db 7 ; AltGr+Shift - / db 4 ; CapsLock - (selective keys shifted) db 4 ; CapsLock + Alt - (same as capslock) db 5 ; CapsLock + Shift - (same as unshifted) db 5 ; CapsLock + Alt + Shift - (same as AltGr) db 2 ; CapsLock + Control - (capslock not applicable) db 3 ; CapsLock + Control + Shift - (capslock not applicable) db 6 ; CapsLock + AltGr - (same as shifted AltGr) db 7 ; CapsLock + AltGr + Shift - (same as unshifted AltGr)
This allows a ligature codepoint to be unravelled into it's component parts by the keyboard driver prior to being sent to the application.
The format of each entry is shown below, where the first four bytes holds the base defining element feature, followed by a count and 16-bit words holding the subsequent characters. There is no limit to the extent of ligatures. The table must be terminated with two zero bytes.
"1st-codepoint|VKEY|SHIFTSTATE|0|WLength|2nd-codepoint|0|...." pkbdxxLigKeys label byte db 0E1H, VK_A, 0, 0, 1, 0C7H, 0 ; db 0E1H, VK_A, 2, 0, 1, 0C2H, 0 ; db 0F8H, VK_Q, 5, 0, 1, 0F3H, 0 ; db 0,0 ;table terminator
The ligature itself is identified by the VKEY, associated shiftstate, and the initial character forming the ligature.
Non-spacing diacritics are supported with two tables: the first table offers a list of diacritics in one of two forms. The second table offers the combination and resultant tables back-to-back.
pkbdxxDeadKeyTable | a list of diacritic keys. Where there is no ambiguity the first table form is used. Where multiple keys are used for the same diacritic then the VKEYs and their associated shift states must be shown in the second form of the deadkey table. The DEADCOMBOS bit must be set in the wFlags field to signal use of the second table form. form #1 pkbdxxDeadKeyTable label byte db GRAVE db UMLAUT NDEADKEYS equ ($ - pkbdxxDeadKeyTable) form #2 pkbdxxDeadKeyTable db CIRCUMFLEX, VKEY1, 005H, 000H ; db RING, VKEY2, 005H, 000H ; db RING, VKEY3, 002H, 000H ; NDEADKEYS equ (($ - pkbdxxDeadKeyTable) shr 2) |
The deadKey table with shift state, where each DWORD entry is as follows: "diacritic-char | Vkey | shiftState | 0" | |
Legal Shift states are: Normal=0, Shifted=2, AltGr=5, AltGrShift=7 | |
pkbdxxDeadTrans | A pair of tables back-to-back. The first table gives the legal diacritic and letter codepoint value combinations. The second table provides the resultant codepoint. The tables must have the same order, and pkbdxxDeadList1 and pkbdxxDeadList2 must be back-to-back. pkbdxxDeadTrans label byte ;combination list dw NDEADLIST pkbdxxDeadList1 label byte ;(legal diacritic/codepoint combinations) db acute, 'a' db acute, 'A' db acute, ' ' ;allows spacing acute db tilde, 'N' db tilde, 'N' db tilde, ' ' ;allows spacing tilde NDEADLIST equ (($-pkbdxxDeadTrans) shr 1)-1 pkbdxxDeadList2 label byte ;(resultant accented character codepoint) db 0E1h ;a-acute db 0C1h ;A-acute db acute ;allows standalone acute db 0e3h ;n-tilde db 0c3h ;N-tilde db tilde ;allows standalone tilde |
Each keyboard layout must be identified with a unique ID in the registry. The layout ID number is a DWORD (32 bit). The low word bits should be one of the predefined locales found in the NLSAPI specification. The high word should start out as 0000 and be incremented if the ID already exists in the registry.
As an example of how your unique keyboard layout ID could be determined, suppose you have developed a keyboard layout for the former Portuguese colony of Macao. This is basically a Portuguese language keyboard layout with a few changes to adapt Portuguese to the dialect used in the business community of Macao, with its unique Chinese and Indian influences. The lower word of the keyboard layout ID should be 0816 (from the NLSAPI specification). The high word will start out as 0000, but since 0816 already exists, it will become 0001. Thus the final keyboard ID will be 00010816.
You will now need to enumerate the registry at the following location and find the next available ID number. The ID numbers are three (3) digits.
HKEY_LOCAL_MACHINE, "system\currentcontrolset\control\keyboard layouts\<language identifier>\layout id
If you want your keyboard layout loaded each time the user boots their system, you will need to enumerate the registry at: HKEY_CURRENT_USER, "keyboard layout\preload\#" to find the next available # and then make the following entry:
HKEY_CURRENT_USER,"keyboard layout\preload\#",,<language identifier>
Using our above example:
HKEY_CURRENT_USER,"keyboard layout\preload\2",,00010816
If you want to immediately load your keyboard layout for the user you need to use the Win32 LoadKeyboardLayout function.
This cannot be done using an INF file, so you will have to develop an installation program that uses the Win32 functions to copy files and add a key to the registry.
The installation program needs to add to the Registry location HKLM,"system\currentcontrolset\control\keyboard layouts\<language identifier>" the string keys Layout File and Layout Text. Also, if the <language identifier> does not have 0000 in the high word, you will need the to add the layout ID key. The Layout File value should be the filename of the keyboard layout file and Layout Text should be a description of the keyboard layout that can be displayed in the Windows 95 user interface. Using the Portuguese (Macao) example, after your installation program runs the following values and keys should be added to the registry (assuming the name of your keyboard layout file is KBDPM.KBD:
HKLM,"system\currentcontrolset\control \keyboard layouts\00010816","layout file",,kbdpm.kbd HKLM,"system\currentcontrolset\control \keyboard layouts\00010816","layout text",,"Portuguese (Macao)" HKLM,"system\currentcontrolset\control \keyboard layouts\00010816","layout id",,013
You should provide documentation to the users of the new keyboard layout that the user can select any language they want, but should then select Properties to access the new keyboard layout.