Blog

Sunday 02 January, 2011 at 12:32 am / 6 comments

Keyboard Hooking With C#

When I first started coding QuickShift, I stumbled upon a blog post which described a Hotkey class the author had written based on the RegisterHotkey and UnregisterHotkey Windows API functions. I found it because someone had posted a link on this StackOverflow question which, as of this writing, is still the number two Google hit for “C# hotkey”. I really liked the way it was implemented and ended up using it with minimal modification as the parent class for my own hotkey implementation in QuickShift. But recently, as I considered how to rework QuickShift to make it more useful in Windows 7, I decided I need to move beyond hotkeys. So even though there are roughly one BILLION .NET-based keyboard hook implementations out there—in one form or another—I decided I wanted to write my own. It’s pretty obvious that I was heavily influenced by the Hotkey class referenced above. I can only hope that someone finds my KeyboardHook class as useful as I found that Hotkey class. But my debt to the Hotkey guy isn’t the only one I need to pay. Stephen Toub’s blog post conveniently laid out how to use all the necessary building blocks for a keyboard hook. And, even though I discovered this code project post by Emma Burrows after I was nearly finished with my own hook, I did borrow a couple of her ideas. Her class is actually very similar to what I produced. After I found it I almost regretted spending the time writing my own, but there are certain design choices I made that illustrate a slightly different agenda, making it worthwhile after all.

For one thing, I decided not to allow multiple hooks to have the same key combinations assigned to them. It didn’t seem logical. Instead, I specifically designed the KeyboardHook to handle multicast events. So if for whatever reason you wanted to assign more than one method to a hook’s Pressed event, that’s totally doable. Plus, all hook events execute asynchronously. That wasn’t something I intended to do from the outset, but I found that if the hook callback method didn’t return right away, Windows seemed to reassert its responsibility for the keystrokes. I discovered that during testing when I put a message box in the callback. It was supposed to display the box and then block further processing of the key strokes. I got the message box, but still got the keystroke output as well. (I was typing in notepad.) When I replaced the message box call with a Console.WriteLine()it worked correctly, so my assumption is that Windows found the delay induced by a modal dialog box unacceptable. Calling the events asynchronously resolved that issue. [Edit: My assumption was correct. There is a default hook timeout somewhere in the neighborhood of 300ms.]

My KeyboardHook uses a modified version of the Windows.Forms.Keys enumeration called EnhancedKeys. Since the original enumeration doesn’t include a modifier for the Windows Logo key, I added it. Incidentally, I just noticed that WPF has its own keys enumeration that does include the Windows Logo modifier key. I guess I can add that to my growing list of reasons to learn WPF. One last thing worth stating: if you hate out parameters, too bad! =P

Anyway, here’s the kitchen sink exposé of my hook class. You can download the code here. Feel free to use it, abuse it, distribute it, alter it, remove my out parameter, whatever. Post your thoughts in the comments below.

void SomeMethod()
{
	KeyboardHook hook = new KeyboardHook("Override minimize all");
 
	string errorMessage;
	if (!hook.TrySetKeys(EnhancedKeys.WinLogo | EnhancedKeys.M, out errorMessage))
	{
		// If another hook already owns these keys, you will get an error here.
		MessageBox.Show(errorMessage);
		return;
	}
 
	// Instead of minimizing all windows, WinLogo + M will now display
	// this messagebox.
	hook.Pressed += (s, e) =>
		{
			MessageBox.Show("Pressed!");
		};
 
        // Activate the hook.
	hook.Engage();
 
	// Sometime later...
 
	// By default, the hook will only allow its Pressed event(s) to execute. To
	// allow additional processing, set this property to true. In this example, setting
	// AllowPassThrough to true will (1) display a message box and (2) minimize all windows.
	hook.AllowPassThrough = true;
 
	// By default, the hook's Pressed event(s) will only execute once per key press. To
	// continuously execute these events while the key is held down, set this to true.
	hook.AutoRepeat = true;
 
	// Disengage to temporarily disconnect the hook from the system. To re-activate,
	// call Engage() again.
	hook.Disengage();
 
	// Kills off the hook. Don't forget to call Dispose or the hook's keys will remain
	// unavailable until after its finalizer has executed. Dispose() calls Disengage() so
	// it is not necessary to call both.
	hook.Dispose();
 
	// Most of the rest is just informational
	Console.WriteLine("Is alt key used: " + hook.Alt);
	Console.WriteLine("Is shift key used: " + hook.Shift);
	Console.WriteLine("Is win logo key used: " + hook.Windows);
	Console.WriteLine("Is control key used: " + hook.Control);
	Console.WriteLine("Current keys: " + hook.Keys);
	Console.WriteLine("Current unmodified key: " + hook.UnmodifiedKey);
	Console.WriteLine("Is hook active: " + hook.IsEngaged);
	Console.WriteLine("Hook has no keys assigned: " + hook.IsEmpty);
}

Related Posts

Extracting Icons From Exe & Dll Files


Leave a Reply

Your email address will not be published. Required fields are marked *

*

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=""> <strike> <strong> <pre lang="" line="" escaped="" cssfile="">

  • Raven says:

    Hello, I’m kinda new to developing gui applications.
    (Though not from programming languages)

    And I’m trying to make a small hot keys application.

    I want to use your class (and of course, give due credits) but I’m having a problem on how to implement this:

    if the user pressed LShift + Q
    then myLabel.content = “Content Here” (in WPF).

    How could I do that?

    Thanks and more success to your applications! :)

    • HeadMinion says:

      Hi,

      Are you just asking how to handle a key press event using this hook class?

      KeyboardHook hook = new KeyboardHook();
      hook.TrySetKeys(EnhancedKeys.Shift | EnhancedKeys.Q);
      hook.Pressed += (s,e) => myLabel.content = “Content Here”;
      hook.Engage();

      Let me know if I misunderstood your question.

      -Nick

  • Nathan says:

    Hi,

    I’ve tried including your code in a VSTO add-in application. I’m getting this error:

    CallbackOnCollectedDelegate was detected
    Message: A callback was made on a garbage collected delegate of type ‘!.NativeMethods+LowLevelKeyboardProc::Invoke’. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

    Can you help me with this? I tried your code on a simple Windows form and it works.

    • HeadMinion says:

      Hey Nathan, I wouldn’t be able to give you an exact answer without looking at how you integrated my code into your project, but the error message you’re getting seems pretty clear: The callback delegate is getting garbage collected when it shouldn’t be. You need to change your code so that it’s kept alive.

  • Nathan says:

    Hi HeadMinion,

    You are right. I debugged my application and discovered it was being garbage collected. I was able to fix it already.

    I have a different question this time, is disengaging & re-engaging the hooks on a different thread going to cause a different? I noticed that disengaging them on a thread works but after putting the thread to sleep and re-engaging them, the hooks don’t work. Checking the value of IsEngage returns true but the code itself is not being called.

    Thanks a lot! This article is really helpful.

    • HeadMinion says:

      Well, it depends. :) It “shouldn’t” matter if another thread accesses the hook, but I did not design that hook class with thread safety in mind. As always, multithreading requires an extra degree of caution; perhaps something funky is happening with your design which is rendering the hook unusable. Again, it’s hard for me to say without looking at your code.

  • 6 comments


    1. Raven Comment:November 3, 2011 at 9:55 am

      Hello, I’m kinda new to developing gui applications.
      (Though not from programming languages)

      And I’m trying to make a small hot keys application.

      I want to use your class (and of course, give due credits) but I’m having a problem on how to implement this:

      if the user pressed LShift + Q
      then myLabel.content = “Content Here” (in WPF).

      How could I do that?

      Thanks and more success to your applications! :)


    2. HeadMinion Comment:November 3, 2011 at 9:29 pm

      Hi,

      Are you just asking how to handle a key press event using this hook class?

      KeyboardHook hook = new KeyboardHook();
      hook.TrySetKeys(EnhancedKeys.Shift | EnhancedKeys.Q);
      hook.Pressed += (s,e) => myLabel.content = “Content Here”;
      hook.Engage();

      Let me know if I misunderstood your question.

      -Nick


    3. Nathan Comment:April 17, 2012 at 4:56 am

      Hi,

      I’ve tried including your code in a VSTO add-in application. I’m getting this error:

      CallbackOnCollectedDelegate was detected
      Message: A callback was made on a garbage collected delegate of type ‘!.NativeMethods+LowLevelKeyboardProc::Invoke’. This may cause application crashes, corruption and data loss. When passing delegates to unmanaged code, they must be kept alive by the managed application until it is guaranteed that they will never be called.

      Can you help me with this? I tried your code on a simple Windows form and it works.


    4. HeadMinion Comment:April 22, 2012 at 7:44 pm

      Hey Nathan, I wouldn’t be able to give you an exact answer without looking at how you integrated my code into your project, but the error message you’re getting seems pretty clear: The callback delegate is getting garbage collected when it shouldn’t be. You need to change your code so that it’s kept alive.


    5. Nathan Comment:May 2, 2012 at 3:24 am

      Hi HeadMinion,

      You are right. I debugged my application and discovered it was being garbage collected. I was able to fix it already.

      I have a different question this time, is disengaging & re-engaging the hooks on a different thread going to cause a different? I noticed that disengaging them on a thread works but after putting the thread to sleep and re-engaging them, the hooks don’t work. Checking the value of IsEngage returns true but the code itself is not being called.

      Thanks a lot! This article is really helpful.


    6. HeadMinion Comment:May 6, 2012 at 8:43 pm

      Well, it depends. :) It “shouldn’t” matter if another thread accesses the hook, but I did not design that hook class with thread safety in mind. As always, multithreading requires an extra degree of caution; perhaps something funky is happening with your design which is rendering the hook unusable. Again, it’s hard for me to say without looking at your code.




    Leave a Reply

    Your email address will not be published. Please fill the required fields...

    You may use: <a href="" title=""></a> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>.