Low Level Global Keyboard Hook / Sink in C# .NET

Getting user input from the keyboard is easy in .NET, in either a WPF, or Windows Forms application. However, it’s not as easy as it might seem if you need to worry about the keyboard input focus being lost to another application or the desktop itself, or anything at all that isn’t your application for that matter!

Do you want to capture keyboard input in anything but a visible input field in your application? If you need to reliably capture whatever is being pressed on the keyboard no matter what has focus on the machine, you need to be listening for keyboard input at a “low-level”, that is, a lower level than your application, a lower level then the .NET framework itself in fact. That’s where p/invoke and low level keyboard hooking comes in…

P/Invoke and the [DLLImport] Attribute: Your low-level-windows-to-.NET liaison!

You will need to use some native, unmanaged Windows methods out of the Windows User32.DLL. That’s what the [DLLImport] attribute and p/invoke are for.

  • First: Know and understand the methods you will use, by visiting the appropriate article on MSDN.
  • Next: find the correct translation of the native User32 method to it’s C# and/or VB.NET signature.
  • For example: to access keyboard events at a low level, one of the native windows methods we will need is: SetWindowsHookEx, whose translation to .NET managed memory land via the C# language can be found at: setwindowshookex (user32).

Armed with your new low-level knowledge, add the methods you need to your application and put them to good and careful use! What I’ve created as an example, in the code below, is a class which encapsulates the low-level keyboard hooking. An instance of this type can broadcast an event containing the key pressed, every time anything is pressed on the keyboard, whether the input is done in the host application or not.

/* 
Copyright (c) 2019 dylansweb.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows.Input;
 
namespace DesktopWPFAppLowLevelKeyboardHook
{
    public class LowLevelKeyboardListener
    {
        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;
        private const int WM_SYSKEYDOWN = 0x0104;
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);
 
        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
 
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);
 
        public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);
 
        public event EventHandler<KeyPressedArgs> OnKeyPressed;
 
        private LowLevelKeyboardProc _proc;
        private IntPtr _hookID = IntPtr.Zero;
 
        public LowLevelKeyboardListener()
        {
            _proc = HookCallback;
        }
 
        public void HookKeyboard()
        {
            _hookID = SetHook(_proc);
        }
 
        public void UnHookKeyboard()
        {
            UnhookWindowsHookEx(_hookID);
        }
 
        private IntPtr SetHook(LowLevelKeyboardProc proc)
        {
            using (Process curProcess = Process.GetCurrentProcess())
            using (ProcessModule curModule = curProcess.MainModule)
            {
                return SetWindowsHookEx(WH_KEYBOARD_LL, proc, GetModuleHandle(curModule.ModuleName), 0);
            }
        }
 
        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
            {
                int vkCode = Marshal.ReadInt32(lParam);
 
                if (OnKeyPressed != null) { OnKeyPressed(this, new KeyPressedArgs(KeyInterop.KeyFromVirtualKey(vkCode))); }
            }
 
            return CallNextHookEx(_hookID, nCode, wParam, lParam);
        }
    }
 
    public class KeyPressedArgs : EventArgs
    {
        public Key KeyPressed { get; private set; }
 
        public KeyPressedArgs(Key key)
        {
            KeyPressed = key;
        }
    }
}

And here’s the MainWindow.cs of a WPF desktop application that simply demonstrates using our awesome keyboard hook by blindly writing out to a text box, every key-press you do on the keyboard, regardless of “where” you are actually typing:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
 
namespace DesktopWPFAppLowLevelKeyboardHook
{
    public partial class MainWindow : Window
    {
        private LowLevelKeyboardListener _listener;
 
        public MainWindow()
        {
            InitializeComponent();
        }
 
        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            _listener = new LowLevelKeyboardListener();
            _listener.OnKeyPressed += _listener_OnKeyPressed;
 
            _listener.HookKeyboard();
        }
 
        void _listener_OnKeyPressed(object sender, KeyPressedArgs e)
        {
            this.textBox_DisplayKeyboardInput.Text += e.KeyPressed.ToString();
        }
 
        private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e)
        {
            _listener.UnHookKeyboard();
        }
    }
}

A quick test of the reliability of this method for keyboard input capture is to do the following: Open notepad with the app open as well, and start typing in notepad, or even the address bar of your web browser…no matter where or what you type, the keys will be written to the text-box because, it is being spoon-fed by your shiny new low-level keyboard listener 🙂 – Happy Coding!

57 thoughts on “Low Level Global Keyboard Hook / Sink in C# .NET

  1. He there,

    i tried this in a console application and it works fine.

    I also tried to create a windows service but there is no keypress detected.
    Hase someone a solution for a windows service?

  2. Hi Dylan… your article has helped me a lot.
    But one thing is still missing:
    I have a keyboard and two NFC readers (which behave like a keyboard). How can I find out which device the inputs are coming from?
    Thanks a lot, Thomas

  3. Pingback: Reduslim prezzo in farmacia

  4. Hai, thanks for the code. I have a question, After I got the key, I want to change that key to another key and send to other windows (like remapping keyboard). How to do that?

    I’ve been working for 1 weeks to solve this. This is my latest code
    private void _listener_OnKeyPressed(object sender, KeyPressedArgs e)
    {
    string KeyPressed = e.KeyPressed.ToString();
    if (KeyPressed == “A”)
    {
    KeyPressed = “B”;
    }
    sendkey(KeyPressed);
    }
    The key which I pressed also printed . How to delete the exact key? if you have any clues, I’ll be grateful for it

  5. This code is what i looking for, i want to make a virtual keyboard from it, but keys that i press, remains pressed. how to implement in this code WM_SYSKEYUP to release keys, to stay pressed only if i hold it?

  6. Hi Dylan,
    Article is very useful.Can we use similar way for tapping windows explorer events like properties,cut,copy,paste

  7. Sorry I did not see this sooner. If you are still interested in a solution, ping back once more and I will take a look if you do.

  8. Hey Jonathan – sorry, only just saw this comment. I will take a look to see what you might want to do in that case and ping back here if you’re still interested in an answer.

  9. Hi Dylan,

    How do I activate the hook only when I hit a number and deactivate the hook as soon as my specified action is complete?

  10. hey dude it’s so cool and I love it and it work for me but I have some problem with it how can I find out what language user is typing in your class any language you type it’s return English to you…
    how can I solve that problem ?
    when some body change keyboard language and type …
    any Unicode or something like that ???

  11. Please disregard what BestRenato wrote, the lack of advertisements on your website is a refreshing change.

  12. Hello!
    Great code and description, I was try it and it’s work OK.
    Is it possible to catch multiple key press?

    For examle, when I press “SPACE” and “A” at real keyboard I see in programm only “SPACE” or “A”, not both keys.

    One keys detect correctly.

    Thank you for good Job at blog.
    P.S. Sorry: first message was cut my key names, maybe it did site..

  13. nice solution, it’s basically working for me.
    One question:
    How could I ignore the key that was pressed so that the key is not further processed?
    For example I am running Word and want to use F1 as hotkey to display my custom helpfile – but I dont want to trigger the default help-menu…
    (So something like “e.Handled=true”)

  14. Can I kill a particular keypress before it’s picked up by notepad / any other app? I need to kill the escape key – it’s being picked up by MS Access even when it doesn’t have the focus. -_-

  15. Your class is what I was looking for, Is there a license on using the keyboard hook class ?

  16. I have noticed you don’t monetize your blog, don’t waste your traffic, you can earn additional cash every month.
    You can use the best adsense alternative for any type of website (they approve all websites), for more info simply search in gooogle: boorfe’s tips monetize your website

  17. Pingback: Using global keyboard hook (WH_KEYBOARD_LL) in WPF / C# - QuestionFocus

  18. Hi Dylan,

    I need to use combination, for example: Shift M and application will do something. Please help me on that

    Thank you

  19. Great article. Unfortunately the code doesn’t work, even after copy and paste it. Just nothing happens after pressing any key on the keyboard and I guess something should. It seems that the method _listener_OnKeyPressed is not being invoked somehow. You probably haven’t told something in your video or written something in your post.

  20. This is one the simple and and working post on Global Keyboard hook out there in internet. It worked in my WPF application but to refresh hook after each key press I added following line in the WPF app – IDK if it is write or wrong but gets the job done.

    void _listener_OnKeyPressed(object sender, KeyPressedArgs e)
    {

    _listener.UnHookKeyboard();
    _listener.HookKeyboard();
    }

    The reason to refresh hook is to prevent app hanging after few clicks on the button.

  21. I discovered your Low Level Global Keyboard Hook / Sink in C# .NET | Dylan’s Web page and noticed you could have a lot more traffic. I have found that the key to running a website is making sure the visitors you are getting are interested in your subject matter. We can send you targeted traffic and we let you try it for free. Get over 1,000 targeted visitors per day to your website. Start your free trial: http://r.rokapack.com/19

  22. Hi Dylan,

    Great Article! Thanks it works perfectly! What I am trying to do is suppress/handle the keystroke after its been hooked.

    eg, Textbox shows R key is pressed but the letter R is not typed in notepad.

    Any ideas how I would adjust to accommodate this? Many thanks in advance.

    Cheers…Lucy

  23. Hi Sara.
    The hooks are listening to keyboard events no matter where they come from, that will not change. I think to achieve what you are looking for, you’d have to setup something that lets you know what window on the desktop has focus at the time the event arrives. That way, knowing what window has keyboard focus should allow you to relatively safely assume the incoming keyboard event is coming from user input occurring within that window/app. There are ways to know what has focus on the desktop, google it and see what others have done to ‘know’ what window has focus on a desktop, or what you may be able to come up with yourself. So I guess my best answer to your question is you’ll need to use both the keyboard hook as well as something that tells you which window is currently active. The using the two together, you may get what you are looking for. Good luck!

  24. Hi Dylan,
    Thanks for the video and article. They are helpful!
    Actually I wondering how can I hook the events (for the purpose of event monitoring for a single or some threads).
    please guid me if it is possible in c#.

    Thanks in advance!

  25. Hello Dylan. Thanks for the tutorial I really appreciate it. So what I used the code for was adapting it into a counter to count how many times enter was pressed but I also needed it in the background. So what I’m trying to do now I create a toggle button to turn on the hook. I tried using;
    private void toggleButton_Checked(object sender, RoutedEventArgs e)
    {
    LowLevelKeyboardListener.Enabled = false;
    textBox.Text = “Hook is Enabled”;
    }

    private void toggleButton_Unchecked(object sender, RoutedEventArgs e)
    {
    LowLevelKeyboardListener.Enabled = true;
    textBox.Text = “Hook is Disabled”;
    }
    This didn’t work unfortunately. Can you please point me in the right direction?

  26. Hello,
    I think i found the reason it doesn’t work in a console or a service application.
    The provided code uses some externlized dll method, like SetWincodwsHookEx(), for example.
    This method doesn’t work, because “this would require crossing a desktop boundary, which is forbidden”. It is the consequence of the Windows architecture.
    The specification of OF_ALLOWOTHERACCOUNTHOOK does not allow cross-desktop hook setting…
    It is not dead, but that needs a little more in order to work in a console or in a service application.
    Bye !

  27. Oh, that’s very weird! It works in Windows Forms project, but doesn’t in the Console project. Any ideas how to fix this? I’d be very glad!

  28. This doesn’t work for some reason 🙁
    Does this code work while marked as “static”?
    I have a console application, I put this code in, did everything as the tutorial said and it doesn’t register the key presses.
    I’ve been try a lot of other methods of doing such low level keyboard listeners, but they all fail to work for some reason. Please help! 🙁

  29. Hello and many thanks for this great contents !
    I used it in a form application without restriction about the specialization, it’s very cool.
    Finally I tried to call this listener from a Windows Service, and unfortunately I didn’t succeed. I don’t really understand, because I call the constructor by the same way in the OnStart() method which is similar to a WindowLoaded method for event management in a form or a wpf application.

    Maybe do you have any idea ? Have you try to use it from a sevice ? I am sorry to ask your time, but if you have a trick for me, I take it with pleasure.

    Thanks again.

  30. Dylan, first just wanna say great article!
    I have followed the steps and got really stuck! My application is about grabbing the keyboard keys when pressed and remapping them to other Unicode characters before using SendKeys.send(). It also detect if “Caps Lock” is on and send letters in lowercase.

    When a key such as Q is pressed, the program should map it to “Ɣ”; in lowercase as well.

    Currently, if I check for caps_lock, the program disable the “Caps lock” and pressing produce no result. Here is my code. Can you please help me.
    Appreciate it advance.
    private bool kcaplock = false;
    string retValue =” “;
    private string GetCommandAssocKey(Keys keys)
    { if (keys == Keys.CapsLock)
    kcaplock = true;
    else
    {
    kcaplock = false;
    }

    if(kcaplock)
    {
    switch(keys)
    {
    case Keys.Q:
    retValue= “Ɣ”;
    break;
    case Keys.F:
    retValue = “Ö”;
    break;
    default:
    retValue = ” “;
    break;
    }
    }
    else
    {
    kcaplock = false;
    switch (keys)
    {
    case Keys.Q:
    retValue = “ɣ”;
    break;
    case Keys.F:
    retValue = “ö”;
    break;
    default:
    retValue = ” “;
    break;
    }
    }
    return retValue;
    }

  31. hello sir,
    Thank you very much for this great tutorial. I have question how i can get the small letter of the pressed key all i get is the capital letter only
    Thanks a lot

  32. Hi Joe. It sounds like you pasted the code into a C# Console Project, rather than a C# WPF Desktop project. In the video included in the article, between 0:27 and 0:32, you will see me choosing the WPF desktop application project template in Visual Studio. That’s the one you want to paste the block of code into. Give it another try with that in mind and see where you get 🙂 Good luck, Happy Coding!

  33. I keep getting this error “Program does not contain a static ‘Main’ method suitable for an entry point (CS5001)”

    What am I doing wrong?

  34. Hello Nikki.
    Please see the comment below that I posted in response to Sahil’s question, since it also kind of answers your question.
    You should listen for the LowLevelKeyboardListener.OnKeyPressed event, and filter what keys you want to listen for there, rather than modify the code in the LowLevelKeyboardListener class. Use an if/else or switch statement on the ‘Key’ code being provided in the event argument.
    This way, you can easily listen for the specific keys you are interested in.
    I hope that is helpful to you.
    Good luck.
    Happy Coding!

  35. Hello Sethmo, sure thing.

    I will demonstrate using an easy modification to the code I posted, have a look:

    In the article, I used the LowLevelKeyboardListener.OnKeyPressed event listener as follows:

    void _listener_OnKeyPressed(object sender, KeyPressedArgs e)
    {
    this.textBox_DisplayKeyboardInput.Text += e.KeyPressed.ToString();
    }

    …so, since this is where we are listening for the key presses, here is where we’ll filter out what we want to listen for.
    You want to listen for the enter key, which can be either Key.Return or Key.Enter.
    If we wrap our logic which uses the key press sent to us in the event argument, then we can choose which ones we do something with, as follows:

    if (e.KeyPressed == Key.Enter || e.KeyPressed == Key.Return)
    {
    this.textBox_DisplayKeyboardInput.Text += e.KeyPressed.ToString();
    }

    Now only the enter key will be used.
    Happy Coding!

  36. Hello Sahil.
    I’m afraid I’m not sure what you mean by hooks for your anti-virus.
    As for the low level global keyboard hook I exemplified in this article, I am fairly certain you do not need to concern yourself with any Windows File Filter Drivers to put it to use.
    Happy Coding!

  37. Hi Dylan,

    This is a great article for beginners like me.

    I would appreciate your help if could lead me to something where I can make hooks for my anti-virus.

    Also, I came across Windows File Filter Drivers. Do I need to use that?

    Thanks in advance 🙂

  38. I am looking to capture only when the enter key is pressed. I followed your directions and was able to get your program to work as advertised, but how would I go about only capturing the enter key? I am looking through the LowLevelKeyboardListerner class to try to understand the method, but can’t quite get it.

    Any help is appreciated. Thanks!

  39. Hello Dylan,

    I have a query. I want to hook something when a certain key is pressed. For example if “a” is pressed then i want to send the Alt+Code. So for basic test — What i did — When a is pressed i want to send c in return. But when i follow this above procedure and enter below code.
    private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
    {
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN || wParam == (IntPtr)WM_SYSKEYDOWN)
    {
    int vkCode = Marshal.ReadInt32(lParam);

    switch (vkCode.ToString())
    {
    case “65”:
    {
    keybd_event(0x43, 0, 0, 0);
    break;
    }
    }
    }

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

    In return i get “CAC”.

    Can you please help me for this issue?
    What i want is — if “A” is pressed then in return a user should get “C”.

    Thanks,
    Nikki

  40. Code example works fine except when I’m running a game in foreground. Hook stops working and does not register any key pressed.
    Do you have any thought on why?

  41. Hi Andy, I have posted a screen-cast which demonstrates using the code from the article to produce the working example:

    https://youtu.be/Lt3H5swUl8Q

    The ‘KeyInterop’ comes from the System.Windows.Input namespace, which is contained within the ‘WindowsBase’ DLL, which must be referenced in your project. WindowsBase is referenced by default in any WPF desktop application project. It is not required for the use of the Low Level Keyboard Hook type though, KeyInterop is just a convenience. If you remove its use and just display the keycode, I think you will see what I mean. Check out the video though, it does show the example being built right from the article.

  42. Hi, i am having the same problem as andy.
    Error 1 The name ‘KeyInterop’ does not exist in the current context
    Error 3 The type or namespace name ‘Key’ could not be found (are you missing a using directive or an assembly reference?)

    I basically copied your whole class and its not working. Maybe because in not in WPF?

  43. I compiled this code but there are errors at first code. Is there any idea about this error???

    Error 1 The name ‘KeyInterop’ does not exist in the current context
    Error 3 The type or namespace name ‘Key’ could not be found (are you missing a using directive or an assembly reference?)

  44. Dylan – this solution you have is exactly what I am looking for. I need to capture all of the input from a USB bar code scanner while another program is running in the forefront. I copied the code you wrote above into VS13, compiled and ran it. It appears that the onKeyPress Listener does not ever fire – I type and it does not grab the typed text.

    What am I missing? Thanks in advance for your help.

Leave a Reply

Your email address will not be published.