Search code examples
winapikeyhook

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


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?


Solution

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