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).