Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to hook Win + Tab using LowLevelKeyboardHook

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)
like image 464
Christian Rondeau Avatar asked Nov 28 '14 02:11

Christian Rondeau


2 Answers

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 
}
like image 199
OhMyGeo Avatar answered Sep 29 '22 21:09

OhMyGeo


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://stackoverflow.com/a/317550/55721

like image 30
dss539 Avatar answered Sep 29 '22 21:09

dss539