Home > Mobile >  MapVirtualKey returns wrong chars in MAPVK_VK_TO_CHAR mode
MapVirtualKey returns wrong chars in MAPVK_VK_TO_CHAR mode

Time:06-07

I trying to use MapVirtualKey[A]/[W]/[ExA]/[ExW] API to map VK_* code to character by means of its MAPVK_VK_TO_CHAR (2) mode.

I have found that it always returns 'A'..'Z' chars for 'VK_A'..'VK_Z' no matter which keyboard layout I have active.

The docs are saying that:

The uCode parameter is a virtual-key code and is translated into an unshifted character value in the low order word of the return value. Dead keys (diacritics) are indicated by setting the top bit of the return value. If there is no translation, the function returns 0.

But I cannot get unshifted character value nor non-ASCII character from it.

For other buttons it works as described. And this behavior is even more annoying considering that, for example for US English keyboard layout it returns:

VK_Q (0x51) -> `Q` (U 0051 Latin Capital Letter Q)
VK_OEM_PERIOD (0xbe) -> `.` (U 002E Full Stop)

But for Russian keyboard layout it returns:

VK_Q (0x51) -> `Q` (U 0051 Latin Capital Letter Q)
                ^- here it should return `й` (U 0439 Cyrillic Small Letter Short I) according to docs
VK_OEM_PERIOD (0xbe) -> `ю` (U 044E Cyrillic Small Letter Yu)

How to use it properly?

CodePudding user response:

MapVirtualKey has a known broken behaviour.

The docs are lying you about MAPVK_VK_TO_CHAR or 2 mode.

According to experiments and leaked Windows XP source code (in \windows\core\ntuser\kernel\xlate.c file) it contains different behaviour for 'A'..'Z' VKs (those VKs are specifically not defined in Win32 API WinUser.h header and are equivalent to 'A'..'Z' ASCII chars):

    case 2:

        /*
         * Bogus Win3.1 functionality: despite SDK documenation, return uppercase for
         * VK_A through VK_Z
         */
        if ((wCode >= (WORD)'A') && (wCode <= (WORD)'Z')) {
            return wCode;
        }

Not sure why MS decided to pull this bug from Win 3.1 but current situation on my Windows 10 is like this.

Also some keyboard layouts can emit multiple WCHAR characters on a single key press (UTF-16 surrogate pairs or ligatures that can contain multiple Unicode code points). MapVirtualKey with MAPVK_VK_TO_CHAR is failing to return proper values for these keys too.

As a workaround I can recommend you to use ToUnicode[Ex] API that can do this mapping for you:

inline std::string ToUnicodeWrapper(uint16_t vkCode, uint16_t scanCode, bool isShift = false)
{
    const uint32_t flags = 1 << 2; // Do not change keyboard state of this thread

    static uint8_t state[256] = { 0 };
    state[VK_SHIFT] = isShift << 7; // Modifiers set the high-order bit when pressed

    wchar_t utf16Chars[10] = { 0 };
    // This call can produce multiple UTF-16 code points
    // in case of ligatures or non-BMP Unicode chars that have hi and low surrogate
    // See examples: https://kbdlayout.info/features/ligatures
    int charCount = ::ToUnicode(vkCode, scanCode, state, utf16Chars, 10, flags);

    // negative value is returned on dead key press
    if (charCount < 0)
        charCount = -charCount;

    // do not return blank space and control characters
    if ((charCount == 1) && (std::iswblank(utf16Chars[0]) || std::iswcntrl(utf16Chars[0])))
        charCount = 0;

    return utf8::narrow(utf16Chars, charCount);
}

Or even better: if you have Win32 message loop - just use TranslateMessage() (that calls ToUnicode() under the hood) and then process WM_CHAR message.

PS: Same applies to GetKeyNameText API since it calls the MapVirtualKey(vk, MAPVK_VK_TO_CHAR) under the hood for keys that do not have explicit name set in keyboard layout dll (usually only non-characters do have names).

  • Related