MapVirtualKey(), GetKeyNameText(), and a story of how to get proper names for the arrow keys

— richardwb on Saturday, December 20, 2008 @ 14:50

So let's say you are currently storing and using virtual key codes to handle keyboard input for a game. You decide to let your users customize their keys, so you go ahead and write some key configuration code. This requires that you map virtual key codes to key names.

Luckily, Windows provides some functions that do exactly what you want! Essentially you use MapVirtualKey() to get a scancode for that key and then pass that scancode (left-shifted by 16) into GetKeyNameText().

Some minutes later you finish your code and you go to test it. You hit the A key and you see 'A'. The 5 key outputs '5'. Even the Backspace key outputs 'Backspace'. It works as expected.

Then you hit one of the arrow keys and your code spits out 'Num 8'. It's right yet it's wrong.

Michael Kaplan has a blog entry that describes a recent update to the MapVirtualKeyEx() call:

MAPVK_VK_TO_VSC_EX:

Windows Vista and later: The uCode parameter is a virtual-key code and is translated into a scan code. If it is a virtual-key code that does not distinguish between left- and right-hand keys, the left-hand scan code is returned. If the scan code is an extended scan code, the high byte of the uCode value can contain either 0xe0 or 0xe1 to specify the extended scan code. If there is no translation, the function returns 0.

Unfortunately this doesn't help much as Windows XP is still used by a majority of users. However, this does imply that the current MapVirtualKey[Ex]() call does not specify the extended scan code. So if we just set the extended bit for some characters we may end up with a function similar to this:

std::string GetKeyName(unsigned int virtualKey)
{
    unsigned int scanCode = MapVirtualKey(virtualKey, MAPVK_VK_TO_VSC);

    // because MapVirtualKey strips the extended bit for some keys
    switch (virtualKey)
    {
        case VK_LEFT: case VK_UP: case VK_RIGHT: case VK_DOWN: // arrow keys
        case VK_PRIOR: case VK_NEXT: // page up and page down
        case VK_END: case VK_HOME:
        case VK_INSERT: case VK_DELETE:
        case VK_DIVIDE: // numpad slash
        case VK_NUMLOCK:
        {
            scanCode |= 0x100; // set extended bit
            break;
        }
    }

    char keyName[50];
    if (GetKeyNameText(scanCode << 16, keyName, sizeof(keyName)) != 0)
    {
        return keyName;
    }
    else
    {
        return "[Error]";
    }
}
comments powered by Disqus