DEV Community

Mirrai
Mirrai

Posted on

WINDOWS HOOKS ARE WEIRD

The other day I decided to learn keyboard hooks because I was working on a small project to read key inputs. After some brainstorming and losing my mind, I came up with this.

Hook program
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) {
    if (nCode == HC_ACTION && wParam == WM_KEYDOWN) {
        KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT*)lParam;

            unsigned char keyboard_state[256];
            wchar_t unicodeData[5] = {0};
            GetKeyboardState(keyboard_state);

            int unicodeKey = ToUnicodeEx(kb->vkCode, kb->scanCode, keyboard_state,
                                         unicodeData, sizeof(unicodeData) / sizeof(wchar_t),
                                         0, NULL);

            if (unicodeKey > 0) {
                printf("%ls", unicodeData);
            }

        if (kb->vkCode == VK_RETURN) {
            printf("\n");
        }
    }
    return CallNextHookEx(hook, nCode, wParam, lParam);
}

int main() {
    hook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardProc, NULL, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {

    }
}
Enter fullscreen mode Exit fullscreen mode

Not too shabby I’d say.

The Problem

While having a bit of fun, I found my caps locks or shift didn’t register on the printed key logs. I was baffled and did anything a self-respectable programmer would do. Go to Google. I came across this page
https://stackoverflow.com/questions/37142779/getkeyboardstate-cant-detect-while-i-am-pressing-the-key

A comment said
“GetKeyboardState() returns the synchronous state of the keyboard, the one it had when the OS last processed an input event for your process. Which ensures that stuff like modifier and dead keys have the correct state. That will not work when you don't pump a message loop yourself, the state won't get updated at all. Common issue with C++ programs, their runtime library doesn't know yet that teletypes and terminals are obsolete. You then have to use GetAsyncKeyState()”

The solution that Raised More Questions

I followed their advice and did:

short caps_held = GetKeyState(VK_CAPITAL) & 0x0001;
short shift_held = GetKeyState(VK_SHIFT) & 0x8000;

if (caps_held) {
    keyboard_state[VK_CAPITAL] |= 0x01;

} else {
    keyboard_state[VK_CAPITAL] &= ~0x01;
}

if (shift_held) {
    keyboard_state[VK_SHIFT] |= 0x80;

} else {
    keyboard_state[VK_SHIFT] &= ~0x80;
}
Enter fullscreen mode Exit fullscreen mode

Btw, I know I said I was supposed to use GetAsyncKeyState but being the rebel I am, I used GetKeyState because it worked so why not I thought. Keep this is mind for later.

And sure enough, the caps and shift keys were fixed but I had a new problem, ctrl. When I pressed ctrl + any key really, I got weird characters. With ctrl + c in particular I got ♥. This didn’t happen before so I set out to find why. I found that ctrl + c in keyboard_state would give me a value of 3 in unicodeData[0] which is a control character that gets represented as ♥. So what I did was

keyboard_state[VK_CONTROL] &= ~0x80;

Problem solved… or is it?

THE WEIRD DISCOVERY

Remember that GetKeyboardState() doesn’t work in an app that doesn’t update its message loop so what on earth is going on here? The answer? GetKeyState. For whatever reason, if you call GetKeyState inside of the callback function, it gets the previous state of the key. That is, the state the state your key was in before your function is called. In doing so it does something that causes GetKeyboardState to actually update. I found simply doing GetKeyState(0) removed the need for that caps and shift conditional I showed before. GetAsyncKeyState(0) doesn’t do this however. Is this intentional i wondered?

THE RESEARCH

Upon further reading I came across an article about GetAsyncKeyState and GetKeyState written in 2004 by Raymond Chen (The legend himself).
https://devblogs.microsoft.com/oldnewthing/20041130-00/?p=37173

An excerpt he wrote:
“ I’ve seen some confusion over the difference between the GetKeyState function and the GetAsyncKeyState function.
GetKeyState returns the virtual key state. In other words, GetKeyState reports the state of the keyboard based on the messages you have retrieved from your input queue. This is not the same as the physical keyboard state:
• If the user has typed ahead, GetKeyState doesn’t report those changes until you use the PeekMessage function or the GetMessage function to retrieve the message from your input queue.
• If the user has switched to another program, then the GetKeyState function will not see the input that the user typed into that other program, since that input was not sent to your input queue.
When should you use GetKeyState and when should you use GetAsyncKeyState?

However, I have some issues with this explanation. One, when he said it returns keys “based on the messages you have retrieved from your input queue” but i have to ask, what input queue exactly?

THe MSDN website wrote on this same function. “The key status returned from this function changes as a thread reads key messages from its message queue.”

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeystate

So, I believe making the assumption that the input and message queue are the same is fair, but this doesn’t make sense because GetKeyboardState gets values based on the message queue state and in my low level hook this message queue doesn't get pumped. How can this be? Because if the input and message queue are the same, then why didn’t GetKeyboardState work from the get go? Are these Queue different or the same? It’s unclear in the documentation.

This is speculative but I think that GetKeyboardState and GetKeyState reads from different queues or states and GetKeyState syncs whatever state GetKeyboardState reads from.

// Simple program
I decided to test GetKeyState myself with a simple function
int main() {
`while (true) {
     short ctrlPressed = GetKeyState('A');
   printf("%d\n", ctrlPressed & 0x8000);
}
}
Enter fullscreen mode Exit fullscreen mode

When I pressed “a” it showed 32768 even tho this program doesn’t seem to interact with a message queue at all. If we take Chen’s and MSDN's words directly, this implies that every windows app has a message queue which could be the case, but where is GetKeyState reading from?

Going back to GetKeyState msdn documentation “The key status returned from this function changes as a thread reads key messages from its message queue”

The key status changes when a thread reads key messages from its message queue but my simple program doesn't read from it's message queue neither does my hook program as far as I'm aware so something's not right.

Looking at the GetKeyboardState documentation in msdn.

https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getkeyboardstate

“An application can call this function to retrieve the current status of all the virtual keys. The status changes as a thread removes keyboard messages from its message queue. The status does not change as keyboard messages are posted to the thread's message queue, nor does it change as keyboard messages are posted to or retrieved from message queues of other threads.”

If we take MSDN's words at complete face value, then logically, this means that GetKeyState removes keyboard messages and processes a queue before getting the key which would explain why my simple program worked. This is a loaded assumption and don't believe this is the case.

Chen also stated that:
“If the user has switched to another program, then the GetKeyState function will not see the input that the user typed into that other program, since that input was not sent to your input queue. “
Considering the simple program captures all inputs this doesn’t add up too. I believe this is describing non-level hooks which are described to capture locally in a process. But, GetKeyState works in my simple program that doesn't use any hooks. So this is fascinating.

The only way I can explain this inconsistency is either Chen’s and MSDN's post is oversimplified, outdated or this undocumented functionality. Considering Chen's post was 20+ years ago, it could be all three but I honestly don’t know.

THE CONCLUSION

After this rabbit hole I fell into, I can say for sure that GetKeyState(0) refreshes whatever GetKeyboardState is reading from, although the “why” is uncertain. This worked on the the version of win 10 and 11 I have but i haven't tested on windows 7. I have no idea if this will ever change later so BEWARE.

Of course you could also try to create the state of key with the hook itself.

else if (nCode == HC_ACTION && wParam == WM_KEYUP) {
    KBDLLHOOKSTRUCT *kb = (KBDLLHOOKSTRUCT*)lParam;

    if (kb->vkCode == VK_LSHIFT || kb->vkCode == VK_RSHIFT) {
        shiftHeld = true;
        printf("%s\n", "Shift released");
    }
}
Enter fullscreen mode Exit fullscreen mode

This is valid too and is more fine tuned. I hope this article helps anyone who uses these hooks and of course, if I'm wrong about anything or you understand why happens please leave a comment or reach out to me I'm always available and I do want to understand what’s going on. Still next time.

Top comments (0)