【问题标题】:MapVirtualKey returns wrong chars in MAPVK_VK_TO_CHAR modeMapVirtualKey 在 MAPVK_VK_TO_CHAR 模式下返回错误的字符
【发布时间】:2022-07-09 00:26:35
【问题描述】:

我尝试使用MapVirtualKey[A]/[W]/[ExA]/[ExW] API 通过其MAPVK_VK_TO_CHAR (2) 模式将VK_* 代码映射到字符。

我发现无论我使用哪种键盘布局,它总是为 'VK_A'..'VK_Z' 返回 'A'..'Z' 字符。

The docs 在说:

uCode 参数是一个虚拟键码,被翻译成一个 返回值的低位字中未移位的字符值。 死键(变音符号)通过设置 返回值。如果没有翻译,函数返回0。

但我无法从中获取 unshifted character value 或非 ASCII 字符。

对于其他按钮,它按描述工作。考虑到这种行为更加烦人,例如对于美国英语键盘布局,它返回:

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

但对于俄语键盘布局,它会返回:

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)

如何正确使用?

【问题讨论】:

    标签: windows winapi input keyboard


    【解决方案1】:

    MapVirtualKey 有一个known broken behaviour

    The docsMAPVK_VK_TO_CHAR2 模式上撒谎。

    根据实验和leaked Windows XP source code(在\windows\core\ntuser\kernel\xlate.c 文件中),它包含'A'..'Z' VKs 的不同行为(那些VKs 没有在Win32 API WinUser.h 标头中明确定义,等效于' A'..'Z' ASCII 字符):

        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;
            }
    

    不知道为什么 MS 决定从 Win 3.1 中移除这个错误,但我的 Windows 10 上的当前情况是这样的。

    此外,some keyboard layouts 可以在单次按键时发出多个 WCHAR 字符(UTF-16 surrogate pairsligatures 可以包含多个 Unicode 代码点)。 MapVirtualKeyMAPVK_VK_TO_CHAR 也无法为这些键返回正确的值 - 在这种情况下它将返回 U+F002 代码点。

    作为一种解决方法,我建议您使用可以为您执行此映射的 ToUnicode[Ex] API:

    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);
    }
    

    甚至更好:如果您有 Win32 消息循环 - 只需使用 TranslateMessage()(在后台调用 ToUnicode())然后处理 WM_CHAR 消息。

    PS:同样适用于 GetKeyNameText API,因为它在后台调用 MapVirtualKey(vk, MAPVK_VK_TO_CHAR) 来获取在键盘布局 dll 中未设置明确名称的键(通常只有非字符才有名称)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多