Search code examples
c#winformshookwindow-messages

Catch WinKey+D key sequence in Winforms


I'm trying to make my application to be always presented on desktop level. It means that my app need to ignore key sequences like LWin+D or RWin+D . I tried to make it work this way:

protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
{
    if (prefixSeen)
    {
        if (keyData == Keys.D)
        {
            MessageBox.Show("Got it!");
        }
        prefixSeen = false;
        return true;
    }
    if (keyData == Keys.LWin)
    {
        prefixSeen = true;
        return true;
    }
    return base.ProcessCmdKey(ref msg, keyData);
}

But it catches only the the RWin/LWin buttons, without the D button.

I've also tried to create my own message filter, but I've got lost in it. All these messages and Bitwise:

    public class KeystrokMessageFilter : System.Windows.Forms.IMessageFilter
    {
        public KeystrokMessageFilter() { }

        public bool PreFilterMessage(ref Message m)
        {
            if ((m.Msg == 256 /*0x0100*/))
            {
                switch (((int)m.WParam) | ((int)Control.ModifierKeys))
                {
                    case (int)(Keys.Control | Keys.Alt | Keys.K):
                        MessageBox.Show("You pressed ctrl + alt + k");
                        break;
                    case (int)(Keys.Control | Keys.C): MessageBox.Show("ctrl+c");
                        break;
                    case (int)(Keys.Control | Keys.V): MessageBox.Show("ctrl+v");
                        break;
                    case (int)Keys.Up: MessageBox.Show("You pressed up");
                        break;
                }
            }
            return false;
        }
}

Application.AddMessageFilter(keyStrokeMessageFilter);

So, how do I make my application to catch/ignore R/LWin+D?


Solution

  • This code registers a low level keyboard hook and listens for D keypress while the windows key is held. If this is detected, the hook simply ignores the keypress. Otherwise, it forwards the keypress along.

    using System;
    using System.Diagnostics;
    using System.Runtime.InteropServices;
    using System.Windows.Forms;
    
    namespace KbHook
    {
        public static class Program
        {
            const int HC_ACTION = 0;
            const int WH_KEYBOARD_LL = 13;
            const int WM_KEYDOWN = 0x0100;
            static IntPtr HookHandle = IntPtr.Zero;
            static Form1 Form1;
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            static extern IntPtr SetWindowsHookEx(int idHook, KbHook lpfn, IntPtr hMod, uint dwThreadId);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            static extern bool UnhookWindowsHookEx(IntPtr hhk);
    
            [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            static extern IntPtr GetModuleHandle(string lpModuleName);
    
            [STAThread]
            static void Main()
            {
                try
                {
                    using (var proc = Process.GetCurrentProcess())
                        using (var curModule = proc.MainModule)
                        {
                            var moduleHandle = GetModuleHandle(curModule.ModuleName);
                            HookHandle = SetWindowsHookEx(WH_KEYBOARD_LL, IgnoreWin_D, moduleHandle, 0);
                        }
    
                    Form1 = new Form1();
                    Application.Run(Form1);
                }
                finally
                {
                    UnhookWindowsHookEx(HookHandle);
                }
            }
    
            [DllImport("user32.dll")]
            static extern short GetAsyncKeyState(Keys vKey);
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    
            static IntPtr IgnoreWin_D(int nCode, IntPtr wParam, IntPtr lParam)
            {
                if (nCode == HC_ACTION
                    && IsWin_D(wParam, lParam))
                    return (IntPtr) 1; //just ignore the key press
    
                return CallNextHookEx(HookHandle, nCode, wParam, lParam);
            }
    
            static bool IsWin_D(IntPtr wParam, IntPtr lParam)
            {
                if ((int) wParam != WM_KEYDOWN)
                    return false;
    
                var keyInfo = (KbHookParam) Marshal.PtrToStructure(lParam, typeof (KbHookParam));
                if (keyInfo.VkCode != (int) Keys.D) return false;
                return GetAsyncKeyState(Keys.LWin) < 0
                       || GetAsyncKeyState(Keys.RWin) < 0;
            }
    
            delegate IntPtr KbHook(int nCode, IntPtr wParam, [In] IntPtr lParam);
    
            [StructLayout(LayoutKind.Sequential)]
            struct KbHookParam
            {
                public readonly int VkCode;
                public readonly int ScanCode;
                public readonly int Flags;
                public readonly int Time;
                public readonly IntPtr Extra;
            }
        }
    }
    

    See also How to hook Win + Tab using LowLevelKeyboardHook