How can I convert a vk constant to text?

Viewed 257

I'm trying to translate vk constants (possibly joined together) to their names (e.g., 81 (VK_CONTROL|VK_SHIFT|VK_A) = “Control+Shift+A.” I tried using the GetKeyNameText function (both with vk and scancode constants), but it does not seem to work (the string is blank, it returns 0, and GetLastError returns 0 as well).

3 Answers

As far as I know, there is no API function that directly converts the combined virtual keys into text. For non-character keys (VK_CONTROL, VK_SHIFT, etc.), you need to manually concatenate strings.

For character keys, MapVirtualKey works fine for me.

I tested the code you provided:

UINT t = MapVirtualKey(VK_A, MAPVK_VK_TO_CHAR);

I can get its return value:

enter image description here

Filter out the control and shift with

char keybuff[64] = {0};

if (keys & VK_CONTROL) {
  strcat(keybuff, "Control+");
  keys &= ~VK_CONTROL; // turn bits OFF
}
if (keys & VK_SHIFT) {
  strcat(keybuff, "Shift+");
  keys &= ~VK_SHIFT;
}

And use MapVirtualKey to map the VK code to a character

https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-mapvirtualkeya

So in the following case key becomes 65 which is the ascii value for capital 'A'. Cast it to a char and you have the letter. This works for all codes below ascii 127.

UINT key = MapVirtualKey(VK_A, MAPVK_VK_TO_CHAR);
char c = (char)key; // 'A'

I advise you not to use GetKeyNameText API at all since this an outdated shit that is broken for non-US layouts. Create and use some lookup table youself instead. :)

If you still decide to use it then proper call of GetKeyNameText is tricky:

// Get keyboard layout specific localized key name
std::string GetKeyNameTextWrapper(uint16_t scanCode)
{
    wchar_t name[128];
    int charCount = 0;

    // GetKeyNameText is not working for these keys
    // due to use of broken MAPVK_VK_TO_CHAR under the hood
    // See https://stackoverflow.com/a/72464584/1795050
    const uint16_t vkCode = LOWORD(::MapVirtualKeyW(scanCode, MAPVK_VSC_TO_VK_EX));
    if ((vkCode >= (uint16_t)'A') && (vkCode <= (uint16_t)'Z'))
    {
        const uint32_t flags = 1 << 2; // Do not change keyboard state of this thread
        static uint8_t state[256] = { 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
        charCount = ::ToUnicode(vkCode, scanCode, state, name, 128, flags);

        // negative value is returned on dead key press
        if (charCount < 0)
            charCount = -charCount;
    }
    else
    {
        const LONG lParam = MAKELONG(0, ((scanCode & 0xff00) != 0 ? KF_EXTENDED : 0) | (scanCode & 0xff));
        charCount = ::GetKeyNameTextW(lParam, name, 128);
    }

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

Also note that GetKeyNameText will return different string according to string data that is sewn into keyboard layout dll. And that layout dlls may have bugs.

Related