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

Не уверен, что это лучший вариант, но он делает именно то, что я хочу.

Другие вопросы по тегам