Windows Global Mouse Hook

Windows provides a way to include an application handler into a system hook chain; this can be used to implement, for example:

  • Global Actions or Hot-Keys handlers at application or system level
  • Cross-processes interactions for applications that perform Desktop Integration

If the second point sounds a bit obscure, well… that’s exactly the case we needed to improve. Our software implements an integration system that operates not only at protocol level (IPC) but also at graphical level through cross-process UI reparenting. Yes, yes, we know… that could be a dangerous thing, but we love to juggle chainsaws.

Anyway, the problem we were trying to solve was about a feature that needed we to install a low-level global mouse hook handler. Adding a handler in a System Hook Chain is something that has always caused urticaria symptoms in me but the specific situation could not be solved in any other way.

SetWindowsHookEx is the syscall you have to use to add a hook into a system chain:

HHOOK SetWindowsHookExA(
  [in] int       idHook,
  [in] HOOKPROC  lpfn,
  [in] HINSTANCE hmod,
  [in] DWORD     dwThreadId
);

the first parameter, idHook, is the type of hook we want to install – for example:

  • 4 – WH_CALLWNDPROC; “Installs a hook procedure that monitors messages before the system sends them to the destination window procedure“. If this rings “spy++ in your head, probably that’s not completely wrong.
  • 2 – WH_KEYBOARD; “Installs a hook procedure that monitors keystroke messages
  • 7 – WH_MOUSE; “Installs a hook procedure that monitors mouse messages.
  • 14 – WH_MOUSE_LL; “Installs a hook procedure that monitors low-level mouse input events.

After a bit of testing the WH_MOUSE_LL mode resulted to be the one good for us; last two parameters (hmod and dwThreadId) have been set to 0 since we needed events coming from all the threads.

We are not going here to have a deep dive in all the nuances of this kind of activity, for example, don’t forget to call the next handler in the hook chain through the CallNextHookEx, something that the documentation says “is optional but highly recommended” – I’d say it’s optional because it doesn’t put the whole box on fire but better call it anyway. I’m just reporting here the problem we’ve had and that can be found around if you search for “Windows global mouse hook lagging“.

Sometimes, in particular at startup or when it is handling incoming data, our application can be a bit CPU-intensive, meaning that there could be UI operations that can interfere with the instant responsiveness of the main loop (I guess this is an acceptable turn of phrase to describe it).

These situations are bad, vary bad if you installed a Global Mouse Hook from your main loop. In the sample used to replicate le problem the CPU load is simulated through the update of a 20×20 matrix of Label widgets – this, together with the Global Mouse Hook installed, will result in a horrible mouse pointer lagging:

In the GIF above the effect is also visible in the {X, Y} updates in the “Mouse info” window.

The reason of this mouse pointer lag is due to the fact that we have inserted a custom hook into the mouse events chain that is also affected by how the application behaves at runtime: if the application has moments of high CPU usage that delay a bit the handling of the messages from the pump, the local mouse handler will produce the above effect.

An alternative to the Windows Hooks is the Raw Input handling but this seems a bit too low level for our purpose.

There’s a simple alternative to this: moving the global mouse hook handler to another thread in which we’ve installed another message pump; in .NET this can be easily done using an ApplicationContext-derived class:

internal class CustomContext : ApplicationContext
{
    private Thread _mainThread;
    private WinHookMgr _llmh;
    private MouseInfo _info;

    public CustomContext()
    {
        _mainThread = new Thread(new ThreadStart(MainLoop));
    }

    private void MainLoop()
    {
        Initialize();
        Application.Run();
    }

    private void Initialize()
    {
        _info = new MouseInfo();
        _llmh = new WinHookMgr();
        _llmh.InstallGlobalMouseHookLL();
        [...]
    }
}

this will run an extra thread connected to a message pump on which we’ll try the same global hook as before:

Since the new message pump is independent from the graphical invalidates that are clogging the Main Thread, the mouse pointer is back to its usual fluidity.

The full code can be found on Github.

Leave a Reply

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