Keyboard Layouts

Introduction: Keyboard Layouts

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.

Creating a Keyboard Layout File

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.

CAPS LOCK BITMAP

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.

STATE TABLES

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)
LIGATURE TABLES

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.

DEADKEY TABLES

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


Selecting a Keyboard Layout ID

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

Keyboard Layout Installation Procedure

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

New Keyboard Layout User Documentation

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.