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.

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!

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

  1. Hi Dylan,

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

    Thank you

  2. 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.

  3. 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.

  4. 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

  5. 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

  6. 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!

  7. 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!

  8. 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?

  9. 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 !

  10. 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!

  11. 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! :(

  12. 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.

  13. 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;
    }

  14. 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

  15. 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!

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

    What am I doing wrong?

  17. 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!

  18. 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!

  19. 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!

  20. 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 :)

  21. 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!

  22. 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

  23. 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?

  24. 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.

  25. 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?

  26. 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?)

  27. 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.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>