Windows Low-Level keyboard hook, allow (Win + H) not other Win + hotkeys

190 Views Asked by At

We have a kiosk app where we would like to allow use of the Win + H hotkey to bring up Voice Typing, but still filter out other Win + ... hotkeys and the Win key by itself.

The hook is set with SetWindowsHookEx(WH_KEYBOARD_LL, callback, hInstance, 0);

Log from pressing Win + H:

// w = bool, GetAsyncKeyState for left and right windows keys
// h = bool, ...for "h" key
// key = KBDLLHOOKSTRUCY* pkh->vkCode,  72 = h, 91 = left Windows key
// flags = KBDLLHOOKSTRUCT* pkh->flags
// pressed Windows, then H, then released
w=0  h=0  key= 91  flags = 1
w=-1  h=0  key= 91  flags = 1
... repeats ....
w=-1  h=0  key= 91  flags = 1
w=-1  h=0  key= 72  flags = 0 
w=-1  h=-1  key= 72  flags = 80
w=-1  h=0  key= 91  flags = 81

What I'm finding while trying different permutations of code in the callback is, either I get no keypress, or I get a plain h (so Voice Typing is not launched), or all other Win + ... hotkeys get through not just Win + H.

This treats the press as a plain h, so apparently at least some of the Win key events before the h events must be allowed past the filter:

if (nCode == HC_ACTION) 
{
    BOOL bWinKeyDown = (GetAsyncKeyState(VK_LWIN) >> 15) | (GetAsyncKeyState(VK_RWIN) >> 15);
    if (pkh->vkCode == 'H')
    {
        // w=-1  h=0  key= 72  flags = 0 
        // w=-1  h=-1  key= 72  flags = 80
        return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);
    }
    else if ((pkh->vkCode == VK_LWIN || pkh->vkCode == VK_RWIN))
    {
        // also just "h" if we allow through the w=-1  h=0  key= 91  flags = 1 events
        // if ((bWinKeyDown && (pkh->flags == 1))   )
        //    return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);
        //  else
        return -1;
    }
}
return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);

This lets all Win + ... hotkeys through, it seems like the first event acts as a "dead key" that gets applied to any next letter key:

if (nCode == HC_ACTION)
{
    BOOL bWinKeyDown = (GetAsyncKeyState(VK_LWIN) >> 15) | (GetAsyncKeyState(VK_RWIN) >> 15);
    if (pkh->vkCode == 'H')
    {
        // w=-1  h=0  key= 72  flags = 0 
        // w=-1  h=-1  key= 72  flags = 80
        return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);
    }
    else if ((pkh->vkCode == VK_LWIN || pkh->vkCode == VK_RWIN))
    {
        if ((!bWinKeyDown && (pkh->flags == 1))) // // w=0  h=0  key= 91  flags = 1 - first event in log
            return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);
        else
            return -1;
    }

Is there any way to trap all Win + ... hotkey except for Win + H?

2

There are 2 best solutions below

6
On BEST ANSWER

According to my test, win+h produces four messages, Index 1 to 4. enter image description here You can trap them at the second message. The following code which traps all Win+... hotkey except for win+h works for me.

LRESULT CALLBACK Proc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION)
    {
        KBDLLHOOKSTRUCT* s= (KBDLLHOOKSTRUCT*)lParam;
        std::cout << " Index=" << nwinhooksdll++ << "wParam="<< wParam <<" s->vkCode=" << s->vkCode << " s->scanCode=" << s->scanCode << " s->flags=" << s->flags << " s->dwExtraInfo=" << s->dwExtraInfo << "\n";
        if (GetAsyncKeyState(VK_LWIN))
        {
            if (s->vkCode == 'H')
            {

           }
            else
            {
                return 1;
            }
        }
    }

    return CallNextHookEx(_hook, nCode, wParam, lParam);
}

Update:

LRESULT CALLBACK CallWndProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION) {
        KBDLLHOOKSTRUCT* s = (KBDLLHOOKSTRUCT*)lParam;
        std::cout << " Index=" << nwinhooksdll++ << "wParam=" << wParam << " s->vkCode=" << s->vkCode << " s->scanCode=" << s->scanCode << " s->flags=" << s->flags << " s->dwExtraInfo=" << s->dwExtraInfo << "\n";
        static BOOL H = FALSE;
        static BOOL Other = FALSE;
        static BOOL Send = FALSE;
        if (Send) {
            Send = FALSE;
            INPUT inputs = {};
            inputs.type = INPUT_KEYBOARD;
            inputs.ki.wVk = VK_LWIN;
            inputs.ki.dwFlags = KEYEVENTF_KEYUP;
            UINT uSent = SendInput(1, &inputs, sizeof(INPUT));
        }
        if (GetAsyncKeyState(VK_LWIN) >> 15) {
            if (s->vkCode == 'H') {
                H = TRUE;
            }
            else if (s->vkCode == 91) {
                if (H || Other)
                {
                    H = FALSE;
                    Other = FALSE;
                    Send = TRUE;
                    std::cout << "return 1\n";
                    return 1;
                }else{

                }
            }else{ 
                Other = TRUE;
                if (wParam == 256) { 
                    std::cout << "return 1\n";
                    return 1; 
                } 
            }
        }
    }
    return CallNextHookEx(_hook, nCode, wParam, lParam);
}

Update 2:

LRESULT CALLBACK Proc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION)
    {
        if (wParam > 0)
        {
            std::cout << "sent by the current thread wParam=" << wParam;
        }
        KBDLLHOOKSTRUCT* s= (KBDLLHOOKSTRUCT*)lParam;

        std::cout << " Index=" << nwinhooksdll++ << "wParam="<< wParam <<" s->vkCode=" << s->vkCode << " s->scanCode=" << s->scanCode << " s->flags=" << s->flags << " s->dwExtraInfo=" << s->dwExtraInfo << "\n";

        static BOOL H = FALSE;
        static BOOL Other = FALSE;
        static BOOL Send = FALSE;
        //TODO
        if (Send)
        {
            Send = FALSE;
            if (s->vkCode != 91 && s->scanCode != 0 && wParam == 256)
            {
                if (!H && !Other)
                {
                    INPUT inputs = {};
                    inputs.type = INPUT_KEYBOARD;
                    inputs.ki.wVk = s->vkCode;
                    inputs.ki.dwFlags = KEYEVENTF_KEYUP;
                    UINT uSent = SendInput(1, &inputs, sizeof(INPUT));
                }

                INPUT inputs = {};
                inputs.type = INPUT_KEYBOARD;
                inputs.ki.wVk = VK_LWIN;
                inputs.ki.dwFlags = KEYEVENTF_KEYUP;
                UINT uSent = SendInput(1, &inputs, sizeof(INPUT));
            }
        }

        H = FALSE;
        Other = FALSE;

        if (GetAsyncKeyState(VK_LWIN)>>15 && s->scanCode != 0)
        {
            if (s->vkCode == 'H')
            {
                H = TRUE;
            }
            else if (s->vkCode == 91)
            {
                    if (wParam == 257)
                    {
                        if (s->scanCode !=0)
                        {
                            Send = TRUE;
                            std::cout << "return 1\n";
                            return 1;
                        }
                    }
            }
            else
            {
                Other = TRUE;
                if (wParam == 256)
                {
                    std::cout << "return 1\n";
                    return 1;
                }
            }
        }
    }

    return CallNextHookEx(_hook, nCode, wParam, lParam);
}
4
On

You can just add a test for WM_KEYDOWN and add test when Win+ key is down and H is pressed down.

Returning non-zero value is not recommended, and the code below ends up eating a message, which is no good. But this is only eating the message for key down state. So everything should be okay once that key is released.

LRESULT CALLBACK kbd_proc(int nCode, WPARAM wp, LPARAM lp)
{
    if (nCode == HC_ACTION)
    {
        KBDLLHOOKSTRUCT* pkh = (KBDLLHOOKSTRUCT*)lp;
        BOOL bWinKeyDown = (GetAsyncKeyState(VK_LWIN) >> 15) | 
                    (GetAsyncKeyState(VK_RWIN) >> 15);
        if (pkh->vkCode != 'H' && wp == WM_KEYDOWN && bWinKeyDown)
        {
            OutputDebugString(L"not Win+H...\n");
            return 1;
        }
    }
    return CallNextHookEx(g_hHookKbdLL, nCode, wp, lp);
}