Search code examples
cwinapikeyboardhookdelay

Low level keyboard hook delay


I am currently programming a library in C that needs to keep track of key presses and releases. To do this optimally, I'm using a low level keyboard hook that calls a callback function where these key events are processed.

The problem I'm experiencing is that the key presses seem to be delayed. The slower my program loop executes, the longer it takes for the key events to arrive. My callback function stores all pressed keys in an array from which I can poll them later on. If the program would delay for a second, I'd expect all key events to be in the array when I check it afterwards. This is not the case, it seems that only one event is stored in this case, the rest comes later.

My callback function looks as follows, with the irrelevant part summarized:

static LRESULT CALLBACK llKeyProc(int nCode, WPARAM wParam, LPARAM lParam)
{
    if(nCode == HC_ACTION) {
        //The keys are processed and stored in an array to be polled by the user in the future
    }

    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

The hook is attached to the process (_window->llKeyHook is of the type HHOOK):

_window->llKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, llKeyProc, NULL, 0);

The key events are polled in my main loop. The framerate of this loop is currently limited by calling Sleep(ms) after each iteration. The amount of sleep time is determined by the execution speed of the main loop. When I fix this to a high value (like 300ms), only one key is caught every 300ms, even when I press many keys in between.


Solution

  • The slower my program loop executes, the longer it takes for the key events to arrive.

    That issue is covered by the documentation, which states:

    This hook is called in the context of the thread that installed it. The call is made by sending a message to the thread that installed the hook. Therefore, the thread that installed the hook must have a message loop.

    The keyboard input can come from the local keyboard driver or from calls to the keybd_event function. If the input comes from a call to keybd_event, the input was "injected". However, the WH_KEYBOARD_LL hook is not injected into another process. Instead, the context switches back to the process that installed the hook and it is called in its original context. Then the context switches back to the application that generated the event.

    The hook procedure should process a message in less time than the data entry specified in the LowLevelHooksTimeout value in the following registry key:

    HKEY_CURRENT_USER\Control Panel\Desktop

    The value is in milliseconds. If the hook procedure times out, the system passes the message to the next hook. However, on Windows 7 and later, the hook is silently removed without being called. There is no way for the application to know whether the hook is removed.

    Note Debug hooks cannot track this type of low level keyboard hooks. If the application must use low level hooks, it should run the hooks on a dedicated thread that passes the work off to a worker thread and then immediately returns.

    So your main app will receive keyboard notifications only as fast as the thread that installs the hook can poll its message queue for new messages from the hook. If you slow down that polling, your notifications get delayed.

    The documentation also states:

    In most cases where the application needs to use low level hooks, it should monitor raw input instead. This is because raw input can asynchronously monitor mouse and keyboard messages that are targeted for other threads more effectively than low level hooks can. For more information on raw input, see Raw Input.

    So you should consider using that instead.