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 in the following, 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 in the following, 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