marko devcic

Software Engineer
  • github:
    deva666
  • email:
    madevcic {at} gmail.com
.NET Keyboard Hook

.NET Keyboard Hook

Posted on 10/25/2014

There's a ton of code samples on the web with possible low level keyboard hook implementations, but I haven't found one that takes into account users keyboard layout.

All of these samples showed how you can get a keycode that user pressed, but for one project I did, I needed to scan keyboard input exactly as it appeared on the users screen. So if the users switches the keyboard layout to let's say German, then I want to get the actual German keys he pressed. If he switches to English back, again I'd like the actual English characters that are appearing on the screen.
Here's a full code for a class that does that.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;


namespace KeyboardHook
{
    publicdelegate IntPtr KeyboardHookCallback(int code, IntPtr wParam, IntPtr lParam);

    publicsealedclass KeyBoardHook : IDisposable
    {
        privateconstint WH_KEYBOARD_LL = 13;
        privateconstint WM_KEYDOWN = 0x0100;
        privateconstint WM_KEYUP = 0x0101; 
        privateconstint WM_SYSKEYDOWN = 0x0104;
        privateconstint WM_SYSKEYUP = 0x0105;
        privateconstint VK_SHIFT = 0x10;
        privateconstint VK_CONTROL = 0x11;
        privateconstint VK_MENU = 0x12;
        privateconstint VK_CAPITAL = 0x14;

        publicevent EventHandler<KeyboardHookArgs> KeyDown;

        KeyboardHookCallback _hookCallBack;
        IntPtr _hookID = IntPtr.Zero;

        privatebool _isDisposed;

        public KeyBoardHook()
        {
            _hookCallBack = new KeyboardHookCallback(HookCallback);
            using (Process process = Process.GetCurrentProcess())
            {
                using (ProcessModule module = process.MainModule)
                {
                    _hookID = WinAPI.SetWindowsHookEx(WH_KEYBOARD_LL, _hookCallBack, WinAPI.GetModuleHandle(module.ModuleName), 0);                   
                }
            }
        }

        private IntPtr HookCallback(int code, IntPtr wParam, IntPtr lParam)
        {
            if ( code < 0)
                return WinAPI.CallNextHookEx(_hookID, code, wParam, lParam);

            WinAPI.KBDLLHOOKSTRUCT keybStruct = (WinAPI.KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(WinAPI.KBDLLHOOKSTRUCT));

            if ((int)wParam == WM_KEYDOWN || (int)wParam == WM_SYSKEYDOWN)
            {
                bool isDownShift = ((WinAPI.GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
                bool isDownCapslock = (WinAPI.GetKeyState(VK_CAPITAL) != 0 ? true : false);
                StringBuilder builder = new StringBuilder(10);
                byte[] lpKeyState = newbyte[256];
                if (WinAPI.GetKeyboardState(lpKeyState))
                {
                    if (WinAPI.ToUnicodeEx((uint)keybStruct.vkCode
                                            , (uint)keybStruct.scanCode
                                            , lpKeyState
                                            , builder
                                            , builder.Capacity
                                            , 0
                                            , WinAPI.GetKeyboardLayout(0)) != -1)
                    {
                        KeyDown.InvokeSafely<KeyboardHookArgs>(this, new KeyboardHookArgs(keybStruct.vkCode, builder.ToString()));
                    }
                }
            }
            return WinAPI.CallNextHookEx(_hookID, code, wParam, lParam);
        }

        ~KeyBoardHook()
        {
            Dispose(false);            
        }

        privatevoid Dispose(bool disposing)
        {            
            if (_isDisposed) return;
            WinAPI.UnhookWindowsHookEx(_hookID);
            _isDisposed = true;
        }

        publicvoid Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
    }

}

In the hook call back method, after we obtain the keycode that was pressed, we need to call a few more Win32 functions to get the keyboard layout and keyboard state.
Here are all of the Win32 functions that we need to import.

publicclass WinAPI
{
        [StructLayout(LayoutKind.Sequential)]
        publicstruct KBDLLHOOKSTRUCT
        {
            publicint vkCode;
            publicint scanCode;
            publicint flags;
            int time;
            IntPtr dwExtraInfo;
        }
        
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        publicstaticextern IntPtr SetWindowsHookEx(int idHook, KeyboardHookCallback lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
        internalstaticextern IntPtr GetModuleHandle(string lpModuleName);
        
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        internalstaticextern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        
        [DllImport("user32.dll")]
        [return: MarshalAs(UnmanagedType.Bool)]
        internalstaticexternbool GetKeyboardState(byte[] lpKeyState);
        
        [DllImport("user32.dll")]
        internalstaticextern IntPtr GetKeyboardLayout(uint idThread);
        
        [DllImport("user32.dll")]
        internalstaticexternint ToUnicodeEx(uint wVirtKey, uint wScanCode, byte[]
           lpKeyState, [Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder pwszBuff,
           int cchBuff, uint wFlags, IntPtr dwhkl);
 } 

And this is what the EventArgs class looks like

publicclass KeyboardHookArgs : EventArgs
{       
        int _keyCode;
        string _keyName;        
        string _string;

        publicint KeyCode { get { return _keyCode; } }
        publicstring KeyName { get { return _keyName; } }        
        publicstring String { get { return _string; } }

        public KeyboardHookArgs(int keyCode)
        {
            _keyCode = keyCode;
            _keyName = System.Windows.Input.KeyInterop.KeyFromVirtualKey(keyCode).ToString();
        }

        public KeyboardHookArgs(int keyCode, string str)
            : this(keyCode)
        {
            _string = str;
        }
 }

Happy coding!