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

  1. Jens
    Sunday, February 28, 2010 @ 16:25

    I am not able to recognise any change when i try to use MAPVK_VK_TO_VSC_EX instead of MAPVK_VK_TO_VSC.

    And GetKeyNameText returns "PAUSE" for VK_NUMLOCK, "Num" for VK_DIVIDE and "Num" for VK_MULTIPLY.

    Do you have any hints how to solve this with bit corrections or do i have to manipulate strings at the end.

  2. richardwb
    Tuesday, March 2, 2010 @ 20:40

    Odd. You're absolutely right. Even using MapVirtualKeyEx() doesn't make MAPVK_VK_TO_VSC_EX behave the way the docs seem to imply. Thanks for bringing it up!

    On a side note, is there anything preventing you from setting the extended bit like in the code above and proceeding that way? It should work out pretty well.

  3. foobar
    Sunday, October 30, 2011 @ 09:23

    What beats me is why one would want to use MapVirtualKey in the first place. The documentation to GetKeyNameText states that the function expects "the second parameter of the keyboard message (such as WM_KEYDOWN)".

    If one does not try to be smarter than necessary (translating and shifting keycodes, and playing with bits) but instead just gives the function what it expects, this works just fine under Windows XP, for all keys.

  4. richardwb
    Wednesday, December 28, 2011 @ 17:02

    It's been awhile since I looked at this, but I think I did this roundabout way of getting key names because I saved virtual keys in the configuration rather than scan codes, hence requiring the translation from virtual keys back to scan codes for GetKeyNameText to work.

    But yes, the better approach would be to either save strings or scan codes instead of virtual keys and avoid the translation.

Add a comment
(?)

BBCode