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);
}
我想列举当前键盘布局支持的所有可能组合键的列表(包括虚拟键码、扫描码及其 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);
}