Skip to content

jnm2/LowLevelHooking

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Low Level Hooking for .NET

NuGet Gitter

This library is intended to serve as a reference implementation for Windows low level keyboard hooks. (And mouse hooks, if there is interest.) Very often I see implementations floating around containing noob mistakes, and let's be honest- should you actually have to roll your own implementation and spend time in the same pitfalls, just to do something this ordinary? I have, several times over the years, and I want to contribute back what I believe is the most optimal implementation. If you spot something that could be done more optimally, please don't hesitate to comment!

Get up and running

  1. Add a LowLevelHooking NuGet package reference to your project.

  2. Windows low level hooks communicate with your process by sending a message to your thread message pump. That means you'll only be able to receive low level notifications while running Application.Run (already in place if you're using Windows Forms) or Dispatcher.PushFrame (already in place if you're using WPF). SharpDX has RenderLoop.Run. Any UI framework will be running something similar.

    If you're not running UI at all, for example if you're a console app, you'll have to implement your own message loop. This is rare, but I do have a .NET Core console app sample planned, so stay tuned.)

    With this in mind, and because you want to create as few hooks as possible (read: one), it makes the most logical sense to tie the lifetime of the hook to the lifetime of the message loop rather than to a window:

    public static class Program
    {
        public static GlobalKeyboardHook GlobalKeyboardHook { get; private set; }
        
        [STAThread]
        public static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            
            using (GlobalKeyboardHook = new GlobalKeyboardHook())
            {
                Application.Run(new Form1());
            }
        }
    }

    (See sample Program.cs)

  3. Once that's taken care of, you can subscribe and unsubscribe as needed:

    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
            Program.GlobalKeyboardHook.KeyDownOrUp += GlobalKeyboardHook_KeyDownOrUp;
            Disposed += MainView_Disposed;
        }
    
        private void MainView_Disposed(object sender, EventArgs e)
        {
            // This isn't strictly necessary since Form1 does not have a shorter lifetime
            // than the whole application, but just in case something should change...
            // Unsubscribing allows the garbage collector to free everything associated with Form1
            // and of course, stops doing unnecessary work on each keypress system-wide.
            Program.GlobalKeyboardHook.KeyDownOrUp -= GlobalKeyboardHook_KeyDownOrUp;
        }
    
        private void GlobalKeyboardHook_KeyDownOrUp(object sender, GlobalKeyboardHookEventArgs e)
        {
            Debug.WriteLine($"{e.KeyCode} {(e.IsUp ? "up" : "down")}");
        }
    }

    (See sample MainView.cs)

    And that's it!

Feedback

If you have questions, critiques or contributions, I can't wait to hear from you via Gitter or GitHub issues!