Programming  /  C# December 15, 2010

Detecting mouse and keyboard input with .NET

A couple of weeks ago I built a little Windows application called Mouse Nag with WPF. Mouse Nag detects when you go from using the keyboard to using the mouse no matter what application you are currently running. In order to do so I had to do some low-level hooks from .NET into Windows that I’ve never done before. As I found this experience rather interesting I’ve put together a little sample application that illustrates how to get the last time of any input to the system as well as the last time for input from the keyboard and the mouse respectively. You are welcome to download the source code.

InputTrackingExample-screenshot

Getting the last input time

Finding out the last time any input was given to the system no matter the source (keyboard, mouse etc) is fairly easy thanks to a function called GetLastInputInfo. We can call GetLastInputInfo by creating a method with the extern keyword in a C# class. It will expect a struct called LASTINPUTINFO that we need to create as well.

[DllImport("User32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO plii);

[StructLayout(LayoutKind.Sequential)]
internal struct LASTINPUTINFO
{
    public uint cbSize;
    public uint dwTime;
}

When we call GetLastInputInfo we must call it with a LASTINPUTINFO whose cbSize variable is set to the size of the LASTINPUTINFO struct. After the call the struct we called the method with will have it’s dwTime field set to the tick count when the last input was received by the system.

In other words, we can get the date and time for the last input like this:

var lastInputInfo = new LASTINPUTINFO();
lastInputInfo.cbSize = (uint)Marshal.SizeOf(lastInputInfo);

GetLastInputInfo(ref lastInputInfo);

var lastInput = DateTime.Now.AddMilliseconds(
    -(Environment.TickCount - lastInputInfo.dwTime));

You'll find this functionality in the AllInputSources class in the sample code.

Getting the time for last input from the keyboard

The GetLastInputInfo method is great for checking when the system last received input from any source. Unfortunately there’s no such function for individual input sources, such as the keyboard. So in order to retrieve, or rather track, the time of the last input from the keyboard I had to use a hook.

To quote MSDN, “A hook is a point in the system message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure”.

To responsibly add and use a hook we need to do three things. First of all we need to add it, which is done using the SetWindowsHook function with a delegate that will get invoked when the event we’re listening for occurs. Once the delegate is invoked we need to call a function named CallNextHookEx, otherwise other hooks wont get called, possibly sabotaging other applications. Finally we also need to be able to remove our hook by calling the UnhookWindowsHookEx function.

I defined all three methods and the delegate in a helper class that looks like this:

public class WindowsHookHelper
{
    public delegate IntPtr HookDelegate(
        Int32 Code, IntPtr wParam, IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern IntPtr CallNextHookEx(
        IntPtr hHook, Int32 nCode, IntPtr wParam, IntPtr lParam);

    [DllImport("User32.dll")]
    public static extern IntPtr UnhookWindowsHookEx(IntPtr hHook);


    [DllImport("User32.dll")]
    public static extern IntPtr SetWindowsHookEx(
        Int32 idHook, HookDelegate lpfn, IntPtr hmod, 
        Int32 dwThreadId);
}

In order to create a hook for keyboard input we need to provide a delegate that matches the signature of WindowsHookHelper.HookDelegate. I did that in a separate class named KeyboardInput which you can find in the downloadable sample code.

I also defined a event in the KeyboardInput class that will fire whenever input is received from the keyboard.

public class KeyboardInput
{
    public event EventHandler<EventArgs> KeyBoardKeyPressed;

    private WindowsHookHelper.HookDelegate keyBoardDelegate;
    private IntPtr keyBoardHandle;
    private const Int32 WH_KEYBOARD_LL = 13;
    private bool disposed;

    public KeyboardInput()
    {
        keyBoardDelegate = KeyboardHookDelegate;
        keyBoardHandle = WindowsHookHelper.SetWindowsHookEx(
            WH_KEYBOARD_LL, keyBoardDelegate, IntPtr.Zero, 0);
    }

    private IntPtr KeyboardHookDelegate(
        Int32 Code, IntPtr wParam, IntPtr lParam)
    {
        if (Code < 0)
        {
            return WindowsHookHelper.CallNextHookEx(
                keyBoardHandle, Code, wParam, lParam);
        }

        if (KeyBoardKeyPressed != null)
            KeyBoardKeyPressed(this, new EventArgs());

        return WindowsHookHelper.CallNextHookEx(
            keyBoardHandle, Code, wParam, lParam);
    }
}

There’s a lot going on there! Let’s start with the constructor. In its first line it sets the keyBoardDelegate field to the KeyboardHookDelegate method. Then on the second line it calls the SetWindowsHookEx method in the helper class.

The first argument to SetWindowsHookEx is a constant with which we tell the function what type of hook we want to install. The available types is documented at MSDN along with the SetWindowsHookEx method. In this case we’re interested in low-level keyboard input so we use the WH_KEYBOARD_LL value, also known as 13.

The second argument is the delegate that will get called by the hook.

The third parameter is the handle to the DLL that the delegate is defined in. Since it’s defined in a DLL associated with the current process we don’t need to supply that so we just call it with zero.

The fourth and final parameter can be used to identify a specific thread that the hook should be associated with. Since we’re interested in input from all threads we call the method with zero for this parameter as well.

The SetWindowsHookEx function returns an integer which is the handle for the hook. If it’s zero the function will have failed and no hook will have been set. We store this value in a field named keyBoardHandle as we’re going to need it when we call the CallNextHookEx function as well as when we remove the hook.

The KeyboardHookDelegate method

With the constructors work done the KeyboardHookDelegate method will get invoked whenever the system receives input from the keyboard.

The first thing we need to do is to check if the Code parameter is less than zero. If it is we need to immediately call CallNextHookEx and return the value returned from it. I honestly have no idea why other than that MSDN says so :)

Anyhow, given that the Code parameter is greater than zero we check if there are any handlers registered for the KeyBoardKeyPressed event. If there are we fire it.

Finally we call the CallNextHookEx function and return the value returned from it.

Cleaning up

The above implementation in the KeyboardInput class will work. If we create a windows forms or WPF application, create an instance of KeyboardInput and add an event handler to its KeyBoardKeyPressed event it will get invoked whenever a keyboard key is pressed. However, wanting to be good citizens in the computer system we’re running on we should probably also remove the hook once it’s no longer needed. I’m not entirely sure that this is necessary, but I implemented the functionality, using the Dispose pattern, anyway as it also illustrates how you can uninstall a hook.

public class KeyboardInput : IDisposable
{
    //Already discussed methods and fields
    //omitted for brevity
    private bool disposed;

    public void Dispose()
    {
        Dispose(true);
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (keyBoardHandle != IntPtr.Zero)
            {
                WindowsHookHelper.UnhookWindowsHookEx(
                    keyBoardHandle);
            }

            disposed = true;
        }
    }

    ~KeyboardInput()
    {
        Dispose(false);
    }
}

Thanks to the code above, when an instance of the KeyboardInput class is disposed it will check if a valid hook handle exists. If it does it will uninstall that hook by calling the UnhookWindowsHookEx method with that handle.

Getting the time for last input from the mouse

The code needed to listen to input from the mouse is almost identical to the one needed for keyboard events. The only difference is that we call the SetWindowsHookEx function with the WH_MOUSE_LL constant, also known as 14, instead of the WH_KEYBOARD_LL constant. However, to make the sample code as readable as possible I implemented it in a separate class, named MouseInput, in the sample code.

References

The article Managing Low-Level Keyboard Hooks with the Windows API for VB .NET was a great help in learning about how to create Windows hooks for the keyboard and mouse. So was the MSDN article Using Hooks and the User Interaction sub-section of the Windows Application UI Development section at MSDN.

PS. For updates about new posts, sites I find useful and the occasional rant you can follow me on Twitter. You are also most welcome to subscribe to the RSS-feed.

Joel Abrahamsson

Joel Abrahamsson

I'm a passionate web developer and systems architect living in Stockholm, Sweden. I work as CTO for a large media site and enjoy developing with all technologies, especially .NET, Node.js, and ElasticSearch. Read more

Comments

comments powered by Disqus

More about C#