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!