Search code examples
visual-c++keyboardvirtual-keyboardsendinputscancodes

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


I would like to enumerate the list of all possible key combinations supported by the current keyboard layout (with virtual key codes, scan codes and their Unicode values). To map the remote user inputs to keys in order to simulate them.

I was expecting an API like UCKeyTranslate(ObjectiveC) for VC++ that can accept the virtual key codes and modifiers(ALT, SHIFT, CTRL) and provide me the scan codes, but couldn't find anything similar to that.

After a lot of research and spending 2 whole days, I had no other option to go with except for MapVirtualKeyEx.

I came with the following code but that has a lot of problems,

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

The MapVirtualKeyEx provides me only the list of basic scan codes and not the scan codes of the keys with the combinations of the modifiers (ALT, CTRL, SHIFT). Is there any way I can provide the combinations of the modifiers as input to the function so that I can generate the required key combinations?.

Any help would be appreciated. Thanks in advance.


Solution

  • Finally got a solution for this, reference 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] = '\0';
    
                        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);
    }