VC++ :枚举虚拟键码列表以及所有可能的键组合的扫描码

VC++ : Enumerate the list of virtual key codes along with scan codes for all the possible key combinations

我想列举当前键盘布局支持的所有可能组合键的列表(包括虚拟键码、扫描码及其 Unicode 值)。将远程用户输入映射到键以模拟它们。

我期待 API 像 UCKeyTranslate(ObjectiveC) for VC++ 可以接受虚拟键代码和修饰符(ALT、SHIFT、CTRL)并提供给我扫描码,但找不到类似的东西。

经过大量研究并花了整整 2 天时间,除了 MapVirtualKeyEx 之外,我别无选择。

我得到了以下代码,但是有很多问题,

BOOL PopulateKeyMap()
{
    TCHAR Buff[120 * sizeof(TCHAR)] = { 0 };
    GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_ILANGUAGE, Buff, sizeof(Buff));
    HKL hKeyboardLayout = ::LoadKeyboardLayout(Buff, KLF_ACTIVATE);

    {
        lock_guard<recursive_mutex> lockHolder(cs_populate_key);

        if (hCurrentKeyboardLayout)
        {
            UnloadKeyboardLayout(hCurrentKeyboardLayout);
        }

        hCurrentKeyboardLayout = hKeyboardLayout;

        // Prepopulate keyCodeDictionary with common key combinations.
        for (int keyIndex = 0; keyIndex < KEY_CODES_DICT_SIZE; ++keyIndex)
        {
            {
                unsigned int Vk;

                tstring key_name = GetKeyName(keyIndex, Vk);

                if (key_name.compare(_T("")) != 0)
                {
                    SmartPtr<KeyCodeInfo> key = (new KeyCodeInfo);

                    if (key)
                    {
                        key->nIndex = keyIndex;
                        key->sVKCode = keyIndex;
                        key->nScanCode = Vk;

                        keyboard_map[key_name] = key;
                    }
                }// End if
            }
        }// End for

        bKeyMapInitialized = TRUE;
    }

    return TRUE;
}

tstring GetKeyName(unsigned int virtualKey, unsigned int &scanCode)
{
    if (!hCurrentKeyboardLayout)
    {
        PopulateKeyMap();
    }

    scanCode = MapVirtualKeyEx(virtualKey, MAPVK_VK_TO_VSC, hCurrentKeyboardLayout);

    // 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 |= KF_EXTENDED; // set extended bit
            break;
        }
    }

    TCHAR keyName[256];
    if (GetKeyNameText(scanCode << 16, keyName, sizeof(keyName)) != 0)
    {
        return keyName;
    }
    else
    {
        return _T("");
    }
}

MapVirtualKeyEx 仅向我提供了基本扫描码列表,而不是带有修饰符组合(ALT、CTRL、SHIFT)的按键扫描码。有什么方法可以提供修饰符的组合作为函数的输入,以便生成所需的组合键吗?

如有任何帮助,我们将不胜感激。提前致谢。

终于找到解决办法了,参考https://dxr.mozilla.org/mozilla-central/source/widget/windows/KeyboardLayout.cpp

void
FillKbdState(PBYTE aKbdState,
    const ShiftState aShiftState)
{
    if (aShiftState & STATE_SHIFT) {
        aKbdState[VK_SHIFT] |= 0x80;
    }
    else {
        aKbdState[VK_SHIFT] &= ~0x80;
        aKbdState[VK_LSHIFT] &= ~0x80;
        aKbdState[VK_RSHIFT] &= ~0x80;
    }

    if (aShiftState & STATE_CONTROL) {
        aKbdState[VK_CONTROL] |= 0x80;
    }
    else {
        aKbdState[VK_CONTROL] &= ~0x80;
        aKbdState[VK_LCONTROL] &= ~0x80;
        aKbdState[VK_RCONTROL] &= ~0x80;
    }

    if (aShiftState & STATE_ALT) {
        aKbdState[VK_MENU] |= 0x80;
    }
    else {
        aKbdState[VK_MENU] &= ~0x80;
        aKbdState[VK_LMENU] &= ~0x80;
        aKbdState[VK_RMENU] &= ~0x80;
    }

    if (aShiftState & STATE_CAPSLOCK) {
        aKbdState[VK_CAPITAL] |= 0x01;
    }
    else {
        aKbdState[VK_CAPITAL] &= ~0x01;
    }
}

inline int32_t GetKeyIndex(uint8_t aVirtualKey)
{
    // Currently these 68 (NS_NUM_OF_KEYS) virtual keys are assumed
    // to produce visible representation:
    // 0x20 - VK_SPACE          ' '
    // 0x30..0x39               '0'..'9'
    // 0x41..0x5A               'A'..'Z'
    // 0x60..0x69               '0'..'9' on numpad
    // 0x6A - VK_MULTIPLY       '*' on numpad
    // 0x6B - VK_ADD            '+' on numpad
    // 0x6D - VK_SUBTRACT       '-' on numpad
    // 0x6E - VK_DECIMAL        '.' on numpad
    // 0x6F - VK_DIVIDE         '/' on numpad
    // 0x6E - VK_DECIMAL        '.'
    // 0xBA - VK_OEM_1          ';:' for US
    // 0xBB - VK_OEM_PLUS       '+' any country
    // 0xBC - VK_OEM_COMMA      ',' any country
    // 0xBD - VK_OEM_MINUS      '-' any country
    // 0xBE - VK_OEM_PERIOD     '.' any country
    // 0xBF - VK_OEM_2          '/?' for US
    // 0xC0 - VK_OEM_3          '`~' for US
    // 0xC1 - VK_ABNT_C1        '/?' for Brazilian
    // 0xC2 - VK_ABNT_C2        separator key on numpad (Brazilian or JIS for Mac)
    // 0xDB - VK_OEM_4          '[{' for US
    // 0xDC - VK_OEM_5          '\|' for US
    // 0xDD - VK_OEM_6          ']}' for US
    // 0xDE - VK_OEM_7          ''"' for US
    // 0xDF - VK_OEM_8
    // 0xE1 - no name
    // 0xE2 - VK_OEM_102        '\_' for JIS
    // 0xE3 - no name
    // 0xE4 - no name

    static const int8_t xlat[256] =
    {
        // 0   1   2   3   4   5   6   7   8   9   A   B   C   D   E   F
        //-----------------------------------------------------------------------
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 00
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 10
        0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 20
        1,  2,  3,  4,  5,  6,  7,  8,  9, 10, -1, -1, -1, -1, -1, -1,   // 30
        -1, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,   // 40
        26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, -1, -1, -1, -1, -1,   // 50
        37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, -1, 49, 50, 51,   // 60
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 70
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 80
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // 90
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // A0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 52, 53, 54, 55, 56, 57,   // B0
        58, 59, 60, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // C0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 61, 62, 63, 64, 65,   // D0
        -1, 66, 67, 68, 69, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,   // E0
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1    // F0
    };

    return xlat[aVirtualKey];
}

void PopulateKeyMap(HKL aLayout)
{
    BYTE kbdState[256];
    memset(kbdState, 0, sizeof(kbdState));

    BYTE originalKbdState[256];
    // Bitfield with all shift states that have at least one dead-key.
    uint16_t shiftStatesWithDeadKeys = 0;
    // Bitfield with all shift states that produce any possible dead-key base
    // characters.
    uint16_t shiftStatesWithBaseChars = 0;

    ::GetKeyboardState(originalKbdState);

    int index = 0;

    // For each shift state gather all printable characters that are produced
    // for normal case when no any dead-key is active.

    for (ShiftState shiftState = 0; shiftState < 16; shiftState++) 
    {
        FillKbdState(kbdState, shiftState);

        for (uint32_t virtualKey = 0; virtualKey < 256; virtualKey++) 
        {
            int32_t vki = GetKeyIndex(virtualKey);

            if (vki < 0) 
            {
                continue;
            }

            wchar_t uniChars[5];
            int32_t ret =
                ::ToUnicodeEx(virtualKey, 0, kbdState, (LPWSTR)uniChars,
                    ArrayLength(uniChars), 0, hCurrentKeyboardLayout);

            // neither a dead-key nor there is no translation
            if (ret > 0)
            {
                if (ret == 1) 
                {
                    // dead-key can pair only with exactly one base character.
                    shiftStatesWithBaseChars |= (1 << shiftState);
                }


                {
                    index++;

                    uniChars[ret] = '[=10=]';

                    CString key_name(uniChars);

                    unsigned int scanCode = MapVirtualKeyEx(virtualKey, MAPVK_VK_TO_VSC, hCurrentKeyboardLayout);

                    // 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 |= KF_EXTENDED; // set extended bit
                        break;
                    }
                    }

                    if (false == key_name.IsEmpty())
                    {
                        SmartPtr<KeyCodeInfo> key = (new KeyCodeInfo)->template DetachObject<KeyCodeInfo>();

                        if (key)
                        {
                            key->nIndex = index;
                            key->sVKCode = virtualKey;
                            key->nScanCode = scanCode;

                            keyboard_map[tstring(key_name.GetBuffer())] = key;
                        }
                    }// End if
                }
            }
        }
    }

    ::SetKeyboardState(originalKbdState);
}