How to hook Win + Tab using LowLevelKeyboardHook
Asked Answered
H

2

10

In a few words: blocking Win up after Win + Tab makes Windows think Win is still down, so then pressing S with the Win key up for example will open the search charm rather than just type "s"... until the user presses Win again. Not blocking it means the Windows Start menu will show up. I'm in a conundrum!


I have no trouble hooking into shortcuts using Alt + Tab using LowLevelKeyboardHook, or Win + Some Ubounded Key using RegisterHotKey. The problem happens only with the Win key using LowLevelKeyboardHook.

In the example below, I'm taking over the Win up event when the Win + Tab combination is detected. This results in making every following keystrokes behave as if the Win key was still down.

        private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode != HC_ACTION)
                return CallNextHookEx(_hookID, nCode, wParam, lParam);

            var keyInfo = (Kbdllhookstruct)Marshal.PtrToStructure(lParam, typeof(Kbdllhookstruct));

            if (keyInfo.VkCode == VK_LWIN)
            {
                if (wParam == (IntPtr)WM_KEYDOWN) {
                    _isWinDown = true;
                } else {
                    _isWinDown = false;

                    if (_isWinTabDetected) {
                        _isWinTabDetected = false;
                        return (IntPtr)1;
                    }
                }
            }
            else if (keyInfo.VkCode == VK_TAB && _isWinDown) {
                _isWinTabDetected = true;

                if (wParam == (IntPtr)WM_KEYDOWN) {
                    return (IntPtr)1;
                } else {
                    _isWinTabDetected = true;
                    Console.WriteLine("WIN + TAB Pressed");
                    return (IntPtr)1;
                }
            }

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

You can find the complete code here (note that it should replace your Program.cs in an empty WinForms project to run): https://gist.github.com/christianrondeau/bdd03a3dc32a7a718d62 - press Win + Tab and the Form title should update each time the shortcut is pressed.

Note that the intent of hooking into this specific combination is to provide an Alt + Tab alternative without replacing Alt + Tab itself. An answer providing the ability to launching custom code using Win + Tab will also be accepted.

Here are my ideas, for which I could not find documentation. All would potentially answer my question successfully.

  • Tell Windows to "cancel" the Win up without actually triggering it
  • Prevent Windows from launching the Start menu once
  • Hook directly in the Windows' Win + event rather than manually hooking into the keystrokes (This would be by far my first choice if that exists)
Hyperborean answered 28/11, 2014 at 2:35 Comment(0)
P
2

This appears to do exactly what you want (omit RWin if you wish).

Please be considerate and unregister this KB hook when your app loses focus!

    [DllImport("user32.dll")]
    static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode == HC_ACTION)
        {
            var keyInfo = (Kbdllhookstruct) Marshal.PtrToStructure(lParam, typeof (Kbdllhookstruct));
            if ((int) wParam == WM_KEYDOWN
                && keyInfo.VkCode == VK_TAB
                && (GetAsyncKeyState(Keys.LWin) < 0 || GetAsyncKeyState(Keys.RWin) < 0))
            {
                _mainForm.Text = "Win + Tab was pressed " + (++_winTabPressCounter) + " times";
                return (IntPtr) 1;
            }
        }

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

I tried several things before discovering this technique. This post was the most helpful https://mcmap.net/q/161096/-re-assign-override-hotkey-win-l-to-lock-windows

Pinkerton answered 24/7, 2015 at 22:30 Comment(3)
So in short: hook the Win key down, instead of the Win key up, and don't prevent other keystrokes :) I removed if (wParam == (IntPtr)WM_KEYDOWN) { return (IntPtr)1; } and the other return (IntPtr)1; in the if (keyInfo.VkCode == VK_LWIN) block, and that worked too, though I think your code is simpler and cleaner. I've been stuck for 9 months on this, so my heartfelt thanks for taking the time to look into this! (And the solution was simple, obviously...)Hyperborean
Hmm, I did not try what you suggest. I considered it, but assumed that trapping winkey down would interfere with other winkey functions, like winkey+R. Does your approach allow that?Pinkerton
Yes it does. In other words, I "record" the Win Down instead of checking the Win Down state when the Tab Down happens. This simply made the code harder to read for the same behavior, so... yours is better!Hyperborean
V
4

System need to know you release the Windows key. I check the difference between my own hook who doesn't have this problem and the only diffence between your and mine is this line :

if (_isWinTabDetected) {
    _isWinTabDetected = false;
     return (IntPtr)1; //THIS LINE 
}
Vanden answered 3/7, 2015 at 13:27 Comment(1)
This solution won't work, because when Windows receives the <kbd>Win</kbd> up without a shortcut, it launches the Windows start menu. The only reason you don't see that in the example is because I used a MessageBox, and it seems the MessageBox will cancel the Windows menu. I'll change the example to write something in the Form1 instead...Hyperborean
P
2

This appears to do exactly what you want (omit RWin if you wish).

Please be considerate and unregister this KB hook when your app loses focus!

    [DllImport("user32.dll")]
    static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);

    private static IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
        if (nCode == HC_ACTION)
        {
            var keyInfo = (Kbdllhookstruct) Marshal.PtrToStructure(lParam, typeof (Kbdllhookstruct));
            if ((int) wParam == WM_KEYDOWN
                && keyInfo.VkCode == VK_TAB
                && (GetAsyncKeyState(Keys.LWin) < 0 || GetAsyncKeyState(Keys.RWin) < 0))
            {
                _mainForm.Text = "Win + Tab was pressed " + (++_winTabPressCounter) + " times";
                return (IntPtr) 1;
            }
        }

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

I tried several things before discovering this technique. This post was the most helpful https://mcmap.net/q/161096/-re-assign-override-hotkey-win-l-to-lock-windows

Pinkerton answered 24/7, 2015 at 22:30 Comment(3)
So in short: hook the Win key down, instead of the Win key up, and don't prevent other keystrokes :) I removed if (wParam == (IntPtr)WM_KEYDOWN) { return (IntPtr)1; } and the other return (IntPtr)1; in the if (keyInfo.VkCode == VK_LWIN) block, and that worked too, though I think your code is simpler and cleaner. I've been stuck for 9 months on this, so my heartfelt thanks for taking the time to look into this! (And the solution was simple, obviously...)Hyperborean
Hmm, I did not try what you suggest. I considered it, but assumed that trapping winkey down would interfere with other winkey functions, like winkey+R. Does your approach allow that?Pinkerton
Yes it does. In other words, I "record" the Win Down instead of checking the Win Down state when the Tab Down happens. This simply made the code harder to read for the same behavior, so... yours is better!Hyperborean

© 2022 - 2024 — McMap. All rights reserved.