Windows: Как запросить состояние клавиш-модификаторов в низкоуровневой клавиатуре?
Для инструмента конфигурации USB-клавиатуры мне нужно перехватить весь ввод с клавиатуры и определить, какие клавиши-модификаторы и обычные клавиши нажимаются одновременно. Поэтому я использую хук низкого уровня Windows (WH_KEYBOARD_LL), который работает нормально, за исключением того, что я не могу определить, нажата ли клавиша WIN (VK_LWIN / VK_RWIN) (control / shift и alt работает).
Я сделал небольшой инструмент командной строки, чтобы показать проблему:
#include <Windows.h>
#include <iostream>
using namespace std;
HHOOK hKeyboardHook;
LRESULT CALLBACK LowLevelKeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{
if (nCode < 0 || nCode != HC_ACTION )
return CallNextHookEx( hKeyboardHook, nCode, wParam, lParam);
KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
if(wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
// working
if(GetAsyncKeyState(VK_CONTROL) & 0x8000)
cout << "CONTROL" << endl;
if(GetAsyncKeyState(VK_SHIFT) & 0x8000)
cout << "SHIFT" << endl;
if(GetAsyncKeyState(VK_MENU) & 0x8000) // ALT
cout << "ALT" << endl;
// VK_xWIN not working at all
if((GetAsyncKeyState(VK_LWIN) & 0x8000) || (GetAsyncKeyState(VK_RWIN) & 0x8000))
cout << "WIN" << endl;
// working for ALTGR/right-handed ALT
if((GetAsyncKeyState(VK_LCONTROL) & 0x8000) || (GetAsyncKeyState(VK_RCONTROL) & 0x8000))
cout << "LRCONTROL" << endl;
// not working at all
if((GetAsyncKeyState(VK_LSHIFT) & 0x8000) || (GetAsyncKeyState(VK_RSHIFT) & 0x8000))
cout << "LRSHIFT" << endl;
if((GetAsyncKeyState(VK_LMENU) & 0x8000) || (GetAsyncKeyState(VK_RMENU) & 0x8000))
cout << "LRMENU" << endl;
}
//return CallNextHookEx( hKeyboardHook, nCode, wParam, lParam);
return 1;
}
int main(int argc, char* argv[])
{
hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(NULL), 0 );
MSG message;
while (GetMessage(&message,NULL,0,0)) {
TranslateMessage( &message );
DispatchMessage( &message );
}
UnhookWindowsHookEx(hKeyboardHook);
return 0;
}
Если я возвращаю "1" из LowLevelKeyboardProc, каждое нажатие клавиши "проглатывается" (за исключением CTRL+ALT+DEL и WIN+L). Если я вызываю следующий хук в конце функции обратного вызова, поведение меняется (и ключи, очевидно, больше не глотаются). Затем, если клавиша WIN нажата вместе с другой клавишей, я получаю информацию о том, что клавиша WIN нажата.
Что мне нужно сделать, чтобы перехватить весь ввод с клавиатуры и обнаружить нажатие клавиши WIN (с помощью GetAsyncKeyState)? Или есть другой способ получить все (вкл. WIN) нажатые клавиши-модификаторы?
4 ответа
Я не понял, почему я не могу получить состояние ключа Windows изнутри хука, но я реализовал простой обходной путь, который я не хочу скрывать от вас.
Функция обратного вызова вызывается, если я нажимаю / отпускаю клавишу Windows. Поэтому я просто сохраняю состояние ключа и использую эту информацию при следующих нажатиях клавиш.
Таким образом, измененная функция обратного вызова выглядит так:
bool leftWinKeyPressed = false;
bool rightWinKeyPressed = false;
LRESULT CALLBACK LowLevelKeyboardProc( int nCode, WPARAM wParam, LPARAM lParam )
{
if (nCode < 0 || nCode != HC_ACTION )
return CallNextHookEx( hKeyboardHook, nCode, wParam, lParam);
KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT*)lParam;
// save state of win keys manually... (needs to be tested some more)
if(p->vkCode == VK_LWIN)
leftWinKeyPressed = (wParam == WM_KEYDOWN)?true:false;
else if(p->vkCode == VK_RWIN)
rightWinKeyPressed = (wParam == WM_KEYDOWN)?true:false;
if(wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
{
// not beautifull but working...
if(leftWinKeyPressed || rightWinKeyPressed)
cout << "WIN" << endl;
// for example
if(leftWinKeyPressed && p->vkCode == 68 )
cout << "LEFT WINDOWS + D";
}
return 1;
}
Чтобы очистить его, я, вероятно, сделаю это для всех клавиш-модификаторов и использую битовое поле для хранения состояния клавиш-модификаторов. Так что я не зависим от GetAsyncKeyState
и это странное поведение. Но если кто-то узнает, почему он так себя ведет, пожалуйста, дайте мне знать.
Документация для LowLevelKeyboardProc
говорит следующее о возвращаемом значении:
Если процедура подключения обработала сообщение, она может вернуть ненулевое значение, чтобы система не могла передать сообщение остальной части цепи подключения или процедуре целевого окна.
Так, return 1
говорит: "Я обрабатываю информацию, и с этими ключами больше ничего нельзя сделать".
Я не уверен, как вы решаете проблему с обнаружением клавиши WIN самостоятельно - я подозреваю, что система просто не пересылает сообщение с клавиатуры до тех пор, пока не будет нажата вторая клавиша.
Я могу опоздать на эту вечеринку, но CTRL+ALT+DELETE и другие последовательности являются защищенными последовательностями клавиш, которые нельзя перехватить или перезаписать. Был какой-то способ сделать это еще в Windows 7, если я прав, но, насколько мне известно, сегодня его нет, или это очень сложно, или считается уязвимостью.
Вопрос старый, поэтому собираюсь сделать обновление с 2022 года.
У меня была такая же проблема, и поскольку мы не можем использовать функцию APIGetAsyncKeyState
внутриLowLevelKeyboardProc
единственный способ, который я нашел, - это написать собственный код:
void SetModifiers(YOUR_STRUCT_TYPE* sStruct)
{
// Get a code
auto const code = sStruct->vkCode;
// Check if key is a modifier key
bool const is_control = code == VK_CONTROL || code == VK_LCONTROL || code == VK_RCONTROL;
bool const is_alt = code == VK_MENU || code == VK_LMENU || code == VK_RMENU;
bool const is_shift = code == VK_SHIFT || code == VK_LSHIFT || code == VK_RSHIFT;
bool const is_caps_lock = code == VK_CAPITAL;
// If the key is a modifier, then set it to `true/false` if the key is `DOWN/UP`, otherwise - ignore and keep prev state(if the key is not a modifier)
// I use my custom struct here, but you can change it for global vars or something else
sStruct->control = is_control ? !sStruct->keyUp : sStruct->control;
sStruct->alt = is_alt ? !sStruct->keyUp : sStruct->alt;
sStruct->shift = is_shift ? !sStruct->keyUp : sStruct->shift;
sStruct->capsLock = is_caps_lock ? !sStruct->keyUp : sStruct->capsLock;
}
Не уверен, что это лучший вариант, но он делает именно то, что я хочу.