KEYBOARD.DCP File Format

Written by Martin Lafaix

What's That?
Keyboard layouts are stored in KEYBOARD.DCP. These layouts are used when converting a raw scancode (that is, the value sent by the keyboard controller) to its corresponding value (that is, a symbol, like "A", or a virtual key code, like F1 or CapsLock).

Knowing this file format would allowed us to create customized layouts, or new layouts for specific keyboards, or whatever. Wouldn't it be nice?

Another advantage would be the possibility for us to write our own keyboard handler; that is, something which converts a scancode to a key value. Naturally, we can already do that, but we have to define the keyboard layout, which is, er, boring? Reinventing the wheel is not always funny!

Why?

Well, to please our beloved editor.

Contents

This article describes the OS/2 2.x KEYBOARD.DCP file format. It contains three main parts: the first one being the description properly-speaking, the second one explaining how to translate a raw scancode to an OS/2 key code and the third one describing a keyboard layout manipulation tool.

Credit

The first part of this paper is mainly based upon Ned Konz's SWAPDCP tool, available from your favorite ftp site.

How is KEYBOARD.DCP Organized?
The first four bytes of KEYBOARD.DCP contain the index table offset (0-based), ito.

The first two bytes of the index table contain the index entry count, iec.

Following this index entry count are iec Index Entries. Each index entry is as follow: typedef struct { WORD    word1; BYTE   Country[2];	    /* i.e. "US"  */ BYTE   SubCountryID[4];  /* i.e. "153" */ WORD   word2; WORD   XTableID;	    /* i.e. 0x1b5 (437) */ WORD   KbdType; ULONG  HeaderLocation;   /* of beginning of table (header) } IndexEntry; ''Figure 1. The Index Entry structure.''

So, to find a specific keyboard layout, we have to (1) read the first four bytes to find the index table and (2) locate the specified index entry (Country, SubCountryID, XTableID and keyboard type). If such an entry exists, its HeaderLocation field contains the Keyboard Layout Table Entry offset.

The Keyboard Layout Table Entry
Each keyboard layout table entry contains a header, followed by key and accent definitions. The header is as follows: /* code page header */ typedef struct XHeader { WORD    XTableID;	  /* code page number */

/* note: 32-bit wide field */ struct {   /* which shift key or key combo affects Char3 of each KeyDef */ BITFIELD   ShiftAlt     :1; /* use shift-alt instead of ctrl-alt */ BITFIELD   AltGrafL     :1; /* use left alt key as alt- graphics */ BITFIELD   AltGrafR     :1; /* use right alt key as alt- graphics */ /* other modifiers */ BITFIELD   ShiftLock    :1; /* treat caps lock as shift lock */ BITFIELD   DefaultTable :1; /* default table for the language */

BITFIELD   ShiftToggle  :1; /* TRUE:. toggle, FALSE:. latch shiftlock */

BITFIELD   AccentPass   :1; /* TRUE:. pass on accent keys and beep, FALSE:. just beep */ BITFIELD   CapsShift    :1; /* caps-shift uses Char5 */ BITFIELD   MachDep      :1; /* machine-dependent table */ /* Bidirectional modifiers */ BITFIELD   RTL	     :1; /* Right-To-Left orientation */ BITFIELD   LangSel      :1; /* TRUE:. National language layout FALSE:. English language layout */ /* default layout indicator */ BITFIELD   DefaultLayout:1; /* default layout for the country */ } XTableFlags1;

WORD   KbdType;		/* keyboard type */ WORD   KbdSubType;		/* keyboard sub-type */ WORD   XtableLen;		/* length of table */ WORD   EntryCount;		/* number of KeyDef entries */ WORD   EntryWidth;		/* width in bytes of KeyDef entries */ BYTE   Country[2];		/* country ID, i.e. "US" */ WORD   TableTypeID;		/* Table type, 0001=OS/2 */ BYTE   SubCountryID[4];	/* sub-country ID, ASCII, i.e. "153 " */ WORD   Reserved[8]; } XHeader; ''Figure 2. The Table header structure.''

This table header is followed by EntryCount KeyDef entries. Each KeyDef entry is as follows: /* Key definition, one per scan code in table */ typedef struct { WORD    XlateOp;

BYTE   Char1; BYTE   Char2; BYTE   Char3; BYTE   Char4; BYTE   Char5; } KeyDef; ''Figure 3. The KeyDef structure.''

The specific meaning of the charx fields depends on the XlateOP value, as explained in the key type subsection. The default value of the EntryWidth field (in the header) is 7, but, if this value is bigger, then, there are additional charx fields in the KeyDef structure. (Namely, you have EntryWidth - sizeof(XlateOP) charx fields, with sizeof(XlateOP) being 2.)

These EntryCount KeyDef entries are then followed by the Accent Table, which contains the seven Accent Table Entries (one per possible accent - if there's more than seven accents, the seventh entry contains the additional entries).

Each Accent Table Entry is as follows: /* Accent Table Entry, one per accent, up to seven accent */ typedef struct { CHAR charOrg;			/* The key's ASCII value, i.e. "a" */ CHAR charRes;			/* The resulting ASCII value, i.e. "…" */ } TRANS;

typedef struct { BYTE AccentGlyph;		/* What to show while waiting for a key */ BYTE byte1; BYTE byte2; BYTE byte3; BYTE byte4; BYTE byte5; TRANS aTrans[20];		/* The allowed substitutions */ } AccentTableEntry; ''Figure 4. The AccentTableEntry structure.''

The seventh entry has a slightly different format. If there's more than 6 accents, its first byte contains the length of the seventh Accent Table Entry. This entry is then followed by a byte whose contents is the length of the eighth entry, and so on:



''Figure 5. The seventh Accent Table Entry structure.''

There's no "end of entry" indicator.Use the XTableLen field to check it: AccentTableEntryLen = XTableLen - sizeof(XHeader) - EntryCount * EntryWidth.

The first six entries take 6*sizeof(AccentTableEntry) = 276 bytes. The remaining Accent Table entries fit in AccentTableEntryLen-276 bytes. When the sum of the size of the additional entries (that is, l7 + l8 + ...) reaches this value, it's done.

If there's less than seven accents, the first byte of the seventh entry is 0x00.

So, the accent-key+ASCII-key translation process is quite easy: when an accent key is pressed, just remember the accent code, and optionally display the corresponding glyph (AccentGlyph). Then, wait for another key to be pressed. If this key accepts the remembered accent (that is, the corresponding bit in the 7 high bits of XlateOP is set), locate the corresponding charRes in the aTrans array of the Accent Table Entry (yes, you'll have to browse this array until you find the right charOrg!). If the pressed key does not accept the remembered accent (or if you can't find the corresponding charOrg in aTrans), just beep. You're done!

Layout Flags
Various flags describe the layout's behavior.

The "Key type" value specifies the meaning of the charx fields in the KeyDef entries.
 * Key Type

Note: In the following table, "xxx'ed'" means holding down xxx while pressing the key. And, if an entry contains "???", well, its meaning is not completely known...

ScanCode to Key Value Conversion
In this section, we'll describe the keyboard scancode to ASCII char conversion scheme. Doing such a conversion is required when you want to write your own keyboard handler, or when, for any other reason, you have to deal directly with scancodes.

Portion of code will be given in REXX. Please refer to SHOWDCP.CMD for missing functions.

And, it's just a scheme, it's not a complete and fully-functional scancode to key value converter.

Determining the Required Keyboard Layout
The first thing to do is to load the correct keyboard layout. We first have to find the current Country/CodePage value by using the DosQueryCp/DosQueryCtryInfo functions (Refer to Control Program Guide and Reference for more information on those two functions).

We then have to find the current keyboard type - that is, an old (89 keys) one or a "new" one (101/102 keys). If you know how to do this, please, let me know!

The last step is to find the user's desired keyboard layout. The easiest way to do this is probably to scan the CONFIG.SYS, or to provide a command parameter. (We need both country abbrev and subcountry code.)

We could then load the corresponding keyboard layout. /* Loading the keyboard layout ito = readl call charin infile,ito iec = readw do iec call getindex if (country = rcn) & (rss = subcntr) & (rcp = cp) & (rty = type) then leave end /* do */
 * rcp is current codepage
 * rcn is current country abbrev (US, FR, ...)
 * rss is current subcountry (153, 189, 120)
 * rty is current keyboard type
 * rty is current keyboard type

if (country \= rcn) | (rss \= subcntr) | (rcp \= cp) | (rty \= type) then do  say "Keyboard layout not found!" exit end

call getentry offset Having read the layout header, we then have to read the corresponding KeyDefs: do i = 1 to EntryCount call getkeydef /* here, we have to store is somewhere... */  ... end

And then come the last initialization step:

Determining the Accent Key Conversion Table
free = tablelen - 40 - entrycount * entrywidth j = 1 empty = 1 do while free > 0 call getaccententry j  /* We here have to store it somewhere ... */  ...   j = j + 1 free = free - len end /* do */ We are now ready...

Converting a Scancode to a Key Value
The first thing to do is to maintain some Boolean values containing various special keys status (CapsLock, NumLock, ScrollLock and Alt/Shift/Ctrl). We have to remember the last accent key pressed, too.

We then have to handle each key type. /* scan is the current key scancode */ type = key.scan.keytype select /* One of the many "toggle" key */ when type = 'CAPSLOCK' then CapsLock = \CapsLock ...  when type = 'ALPHAKEY' then do	if \PendingAccent then select when CtrlPressed then code = key.scan.char1 - 96 when AltPressed then code = '00'x||scan when ShiftPressed & \CapsLock then code = key.scan.char2 when ShiftPressed then code = key.scan.char1 when CapsLock then code = key.scan.char2 otherwise code = key.scan.char1 end /* select */ else select when \allowedAccent | CtrlPressed | AltPressed then call BEEP when ShiftPressed & \CapsLock then code = addAccent(key.scan.char2) when ShiftPressed then code = addAccent(key.scan.char1) when CapsLock then code = addAccent(key.scan.char2) otherwise code = addAccent(key.scan.char1) end end ... otherwise /* Not a known type! */  say 'Type 'type' unknown!' end

The complete implementation is, err, left as an exercise.

Using Keyboard Layouts
A very important note:be sure to have a safe copy of your original KEYBOARD.DCP before any experimentation! You've been warned.

In SHOWDCP.ZIP is included a REXX script which allows you to explores/modify keyboard layouts. Its usage is as follow: showdcp usage

Usage: showdcp [param] file [country [subcountry [cp [type]]]] [file2]

-h		 - Access Help ; -v[n]	 - View matching layouts. n is the detail level ; -x		 - Extract matching layouts ; -a		 - Add layout to file ; -ds,t,c	 - Define a key ; -sk1,k2	 - Swap key k1 and key k2.

country	 = country code (US, FR, ...) or * subcountry = subcountry code (189, 120, ...) or * cp		 = code page (437, 850, ...) or * type	 = keyboard type (0 = 89 keys, 1 = 101/102 keys) or * ''Figure 6. The showdcp command usage''

Example 1
If you want to create a restricted KEYBOARD.DCP file which contains all US layouts, but nothing else, enter the following commands: showdcp -x c:\os2\keyboard.dcp US * * * dummy showdcp -a mylayout.dcp dummy And then, replace the DEVINFO=KBD... line in your CONFIG.SYS with: DEVINFO=KBD,US,D:\TMP\MYLAYOUT.DCP

Example 2
If you want to find all layouts which use the 863 (Canadian French) codepage, enter: showdcp -v c:\os2\keyboard.dcp * * 863 You'll get something like: Operating System/2 Keyboard.dcp file viewer Version 1.05.000 Jan 25 1995 (C) Copyright Martin Lafaix 1994, 1995 All rights reserved.



Example 3
If you want to change the definition of the "A" key in the standard French layout so that the key caps are reversed, enter: copy c:\os2\keyboard.dcp mykbd.dcp showdcp -d16,1E05,4161000000 MYKBD.DCP FR 189 * 1 If you want to try the newly defined layout, and assuming your boot drive is "C:", enter: copy c:\os2\keyboard.dcp keyboard.org copy mybkd.dcp c:\os2\keyboard.dcp keyb fr189 Then, experiment with it (with the French layout, the "A" key is on the US "Q" key). And, after that, restore your initial configuration: keyb us copy keyboard.org c:\os2\keyboard.dcp

The "-d" parameter revisited
The "-d" parameter is immediately followed by the key scancode. It's a decimal number. It's then followed by a comma. The key type comes next. It's a 16bits hexadecimal value. Its 9 low bits contains the key type properly speaking, while the 7 high bits contain the allowed accents. The key type is followed by another comma, which is followed by the key definition. It's an hexadecimal string. The first two hexadecimal digits corresponds to the char1 field, and so on. In the previous example, we are assigning 0x41 to the char1 field ("A"), 0x61 ("a") to char2, and 0x00 to all remaining fields (char3, char4 and char5). If the key definition string does not defines all fields, the value of the non-specified fields is not modified. In the previous example, we could have used "4161" instead of "4161000000".

Be really careful when using the "-d" parameter.

Summary
Please tell me what you think!

I hope you find this article useful and informative. If you like what I have done, please let me know; if not, please tell me why. I will use your comments to make upcoming papers better.

Thank you!