Multi language on-screen keyboard using Windows keyboard dlls

 

Multi language - Hello World preview

Introduction

The multi language onscreen keyboard is a result of "lazyness" to not invent the wheel again.It reads all the keyboard layouts that are available in the windows-directory and layout is presented with a Apple / Android / Windows 8 look-a-like keyboard. Deadkeys are yellow and presented on a seperate row, all available keys are shown.

The keyboard has been tested on Windows XP / 2003 server / Vista / 7 / 2008 server / 8, all operating systems seem to work as expected.

Background

My inital start was to create own keyboard maps manually. Going through each language, using the Microsoft On-screen Keyboard. After I started on my third keyboard map, I got fed up and started searching for solutions to read the existing layouts.

In my quest I discovered a bug while loading keyboards on x86/x64 systems, the solution can be found here. The same technique is used in this application. The solution is two different kbd.h versions, one for x86 and one for x64. I wanted to know what caused this problem, so I contacted the Microsoft-keyboard-guru Michael Kaplan but his answer was negative, since this is "undocumented". His thumb down made me even more persistent to actually complete this project, here we are :)
Another smart guy "kwhat" made another solution that shows the x86/x64 bit-shifts more clear, his code is also usable with GNU Compiler Collection (GCC).

(Note: Please share if you know any good APIs for getting the keyboard layout. I’m looking for approache/API/function that extract the scan code + character relationship; comment your solution below!)

Scan codes

Scan codes are the actually physical reference for a key on the keyboard, a hex-number.
See the picture to get an overview, an English layout uses the 0x10 as "q"-key, the 0x11 is "w", the 0x1E is "a", and so on…

The main work done by this class is to sort out all the characters and link them to correct scan code.
There are three different scan codes registers as described in wiki, Microsoft uses set 1; IBM PC XT.

Using the code

There are a split between the loading of a keyboard layout and the presentation of it.

Loading keyboard dlls

There are one class that handles the loading of DLL layouts; CKLL (Keyboard Layout Loader).
The function LoadDLL loads the keyboard layout, and afterwards the function GetCharFromSC is used to retrive a VK_CHAR based on scancode and keyboard-state. The variables returned are:

struct VK_CHAR
{
    WCHAR wChar;        //Actual char
    OSKState nState;    //Which state it are in
    BOOL bDead;         //Dead key
};

The keyboard states are the variation of common-modifiers:
enum OSK_KEYBOARD_STATES
{
    Normal,         //000
    Shift,          //001
    Alt,            //010
    ShiftAlt,       //011
    Ctrl,           //100
    ShiftCtrl,      //101
    CtrlAlt,        //110
    ShiftCtrlAlt,   //111
};

Each keyboard layout dll has a file description that make a reasonable description of the layout. This info can be retrived by calling GetKeyboardName(). (Only in MFC-version)

Presentation

The presentation is done by a CStatic derivate called CKeyboardStatic.
It has a subclass called CKeyButton that contain each character with the scan code, character and state.
Which key goes where are linked to the array OSKScanCodes, in this version the standard qwerty is used. A change of this table will affect presentation directly.

const char OSKScanCodes[4][13] = {
    {0x29, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D},
    {0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x00},
    {0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x2B, 0x00},
    {0x56, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x00, 0x00}
};

The different presentations was made just for fun :) The picture below shows the variations of presentations, function of keyboard is equal in all three:

By using GDI+ it is possible to create good looking graphics, using AA and the ARGB-coloring, that is the technique used here. The Apple style require much painting to replicate, but it sure looks the best of all three (in my eyes…)

CKeyboardStatic has these public functions, read comment behind for description.

void Clear(); //Resets the static from all keys
BOOL SetKey(...); //Adds a character linked to a scan code + deadkey info
void SetKeyboardState(...); //Sets the state the keyboard are in
OSKState GetKeyboardState(...); //Returns the state the keyboard are in
void SetKeyboardPresentation(...); //Sets the presentation of the static; Apple, Android or Windows 8
Using the CKLL class you can set the scan code + character, using the SetKey() to link to the "physical" key, based on the OSKScanCodes-array. The SetKeyboardPresentation() changes the characters, based on the OSK_KEYBOARD_STATES-array value. The SetKeyboardLayout() changes the presentation of keyboard.

MFC or not to MFC

The CKLL class comes in two flavors, one MFC version and one MFC-free.

The MFC-free CKLL uses a vector based array to to keep track of scan codes and characters instead of CPtrArray.
The CKeyboardStatic is a pure MFC and is not straightforward to convert to other types.

Project "ConsoleKeyboard" is a pure win32 console application and the string handling between CKLL has changed to wstring (for this mod.) It was made to show off how little code needed to present an keyboard. See picture below for results:

You have to change the console font to a unicode supported one, select Lucida Console or Consolas.
Rightclick on header and select "Properties" to change it.

The source for getting this presentation is simple (code has been stripped for errorhandling for easy reading):

int _tmain(int argc, _TCHAR* argv[])
{
    //Set unicode mode
    _setmode(_fileno(stdout), _O_U16TEXT);
 
    //Load the keyboard-dll
    CKLL m_kll;
    m_kll.LoadDLL(argv[1]);
 
    //Set our state to "Normal"
    OSKState nState = OSK::Normal;
 
    wprintf(L"\n");
 
    //Loop through each row
    for(int nRow = 0; nRow < ( sizeof(OSK::OSKScanCodes) / sizeof(OSK::OSKScanCodes[0]) ) ; nRow++)
    {
        wprintf(L"-----------------------------------------------------\n");
 
        //Loop through each scan code in that row
        for(int nScanCode = 0; nScanCode < (sizeof(OSK::OSKScanCodes[0]) / sizeof(OSK::OSKScanCodes[0][0])); nScanCode++)
        {
            //The non 0x00 are actually keys
            if(OSK::OSKScanCodes[nRow][nScanCode] != 0x00)
            {
                CKLL::VK_CHAR aChar;
                if(m_kll.GetCharFromSC(OSK::OSKScanCodes[nRow][nScanCode], nState, aChar))
                    wprintf(L"| %c ", aChar.wChar);
            }
        }
        wprintf(L"|\n");
    }
    wprintf(L"-----------------------------------------------------\n");
    return 0;
}
By using CKLL class you could easly make an multilanguage onscreen keyboard for a OpenGL, QT, or any other implementation in Windows.

Issues

Most of the layouts has not been confirm, and frankly I don’t "understand" all of them. Please take time to check your day-by-day keyboard layout and see if the keys are placed in your normal position.
The keyboard does not present modifiers and functions keys as shift, control, enter, space, tab etc… I did not use any time to include this so concider the presentation as a proof-of-concept.
In some layouts a square is presented as char, that is normally not a unicode-character, that isn’t handled. It will might be fixed, in a future version.
Microsoft keyboard DLLs can have multiple layouts in them, this class does only handle the first item we find.

History

v1.00 – First public release

 Posted by at 00:30:02

Leave a Reply