C# Global Keyboard Listeners – implementation of key hooks

In a previous post I briefly outlined the pros and cons of using global event listeners. Although the summary was to use them with caution, the entire use of listeners only came about due to a quick bit of personal programming I did yesterday, in which global listeners were a necessity (due to the program being used when not in focus). As such, this is a quick tutorial on how to implement a global keyboard listener (also known as implementing global key hooks) in C#.

Inclusions

For this project, you will only be needing to use the interoperability services from the system runtime library and the forms library (for accessing keys). The include for this is:

using System.Runtime.InteropServices;
using System.Windows.Forms;

A lot of people will give you a list of includes without really telling you which bit is for what. Well in this instance, the first include is to ensure we can use the DllImport function later on. The second include gives us access to the Keys type (which stores which keys are available to us) and some other functionality regarding messages, etc. Obviously, if you’re building this in WPF rather than Windows Forms, then you just need to do System.Windows.Forms.Keys etc. to make use of both libraries. Avoid using (as in including them at the top) both if you can, as it can cause conflicts when both contain definitions for certain code. In a similar fashion to C++ where we use std:: to define a variable is coming from the std library, it might be handy to do the same here in C#. Of course, you could use both and then just define your specifics later on, but that’s up to you.

Variables

For this demo, we are only using one variable for one key. If you’re listening for more than one key, you will need more than one variable.

const int mActionHotKeyID = 1;

This is to register the ID of the hot key later. It is set to const because we never want it to change during the lifetime of the program (especially once it has been registered).

Extras

As mentioned above, we need to use the DllImport function. This needs to be global to our code (i.e. declared outside of methods, etc.).

[DllImport("user32.dll")]
public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int key);
[DllImport("user32.dll")]
public static extern bool UnregisterHotKey(IntPtr hwnd, int id);

These are the functions we need to register and unregister our key listener ID (declared in variables). The arguments of each method are:

  • hWnd – this is the handle of the window which is taking responsibility for the listener (in most cases it’ll be this.Handle sent as the argument)
  • id – this is the ID variable we created above. When the key is chosen, the ‘id’ of that key from then one will be registered to the const id we used above.
  • fsModifiers- this is the number for any modifiers (additional keys) that need to be pressed with your target key. We’ll discuss this further later.
  • vlc – This is the integer representation of the key you want to listen out for. Selecting this is discussed later on.

Registering keys

This function call is typically done during set up or initialisation of your program.
RegisterHotKey(this.Handle, mActionHotKeyID, 0, (int)Keys.Escape);

As seen above, this.Handle refers to the window handle of the program responsible for this listener (hWnd). mActionHotKeyID is our variable used to register the key hook to. The 0 means there are no modifiers, so the escape key can be pressed on its own for this listener to respond. Keys.Escape refers to the System.Windows.Forms implementation of keys, where you can then put any key in. If you’re using Visual Studio, Intellisense will offer a drop down of available options, but it’s essentially any key you want (e.g. Keys.F12 (for the F12 key)).
Notice that it then needs to be cast as an integer with (int) before the Keys.Escape bit. Missing this bit out might cause errors in compilation!

Key Modifiers

Key modifiers are used to combine key presses together. For example, the common save command of Ctrl+S requires two key presses. If your program is meant to do the same, you need to provide the modifiers to the registering function. The modifiers are:

  • Alt = 1
  • Ctrl = 2
  • Shift = 4
  • Win (Windows key for opening the start menu) = 8

You can combine the key modifiers as well, simply by adding together the modifier code. For example, if you want the user to have to press Ctrl+Shift+Escape, the modifier would be Ctrl(2) + Shift(4) = (6). Where we have then used 0 in the registration above, we would replace this with 6. This would then require the user to press Ctrl+Shift when using the key they need. Obviously, you can combine all the modifiers (8+4+2+1 = 15) and require the user to press the Alt+Ctrl+Shift+Win keys on top of your chosen key. Of course, this will reduce usability but the option is there.

Handling the key stroke

When the user presses keys on the keyboard, you need to handle them. Of course, you need to perform a check to ensure the key pressed matches the key you’re wishing to listen for. The method for this is:

protected override void WndProc(ref Message m) {
    if(m.Msg == 0x0312 && m.WParam.ToInt32() == mActionHotKeyID) {
        //Do something here, the key pressed matches our listener
    }
    base.WndProc(ref m);
}

Obviously if you have multiple keys, you will need to do multiple if statements (or a switch statement, which ever becomes cleaner/easier to implement).

All together now

This post has provided (hopefully) an explanation into how you can add and use global key listeners in C#. However, for those of who you want to learn by playing (and don’t want to stitch the pieces together), here as summary, is all the above code put together for you to copy and paste into your editing program and compile/play with.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

using System.Runtime.InteropServices;

namespace mySpace
{
    public partial class Form1 : Form
    {

        const int mActionHotKeyID = 1;

        // DLL libraries used to manage hotkeys
        [DllImport("user32.dll")]
        public static extern bool RegisterHotKey(IntPtr hWnd, int id, int fsModifiers, int vlc);
        [DllImport("user32.dll")]
        public static extern bool UnregisterHotKey(IntPtr hWnd, int id);

        public Form1()
        {
            InitializeComponent();
            //Modifier keys codes: Alt = 1, Ctrl = 2, Shift = 4, Win = 8
            RegisterHotKey(this.Handle, mActionHotKeyID, 0, (int)Keys.Escape);
        }

        protected override void WndProc(ref Message m)
        {
            if (m.Msg == 0x0312 && m.WParam.ToInt32() == mActionHotKeyID)
            {
                //Do something here, the key pressed matches our listener
            }
            base.WndProc(ref m);
        }
    };
}

Leave a Reply

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