Search code examples
c++winapikeyboardhookkeylogger

Windows C++ GetKeyState caps lock detector does opposite


I've written a simple program that listens to the caps-lock key, and shows a message box saying wether the caps lock is currently on or off. So: user presses caps-lock, program determines what state the caps-lock is in now (on or off) and displays a message box. What actually happens is that when the caps-lock is turned on, the program displays the message box saying it is off and vice versa.

I've read the documentation of the functions, but still don't understand this unwanted (opposite) behavior and would like to know how (and if) this can be fixed.

Here's my code:


#include <Windows.h>

// Research/credits/references
// https://www.unknowncheats.me/forum/c-and-c-/83707-setwindowshookex-example.html
// http://www.rohitab.com/discuss/topic/38617-get-the-state-of-capslock/

HHOOK _hook;

// This struct contains the data received by the hook callback. As you see in the callback function
// it contains the thing you will need: vkCode = virtual key code.
KBDLLHOOKSTRUCT kbdStruct;

// This is the callback function. Consider it the event that is raised when, in this case, 
// a key is pressed.
LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
    if (nCode >= 0)
    {
        // the action is valid: HC_ACTION.
        if (wParam == WM_KEYDOWN)
        {
            // lParam is the pointer to the struct containing the data needed, so cast and assign it to kdbStruct.
            kbdStruct = *((KBDLLHOOKSTRUCT*)lParam);
            // a key (non-system) is pressed.
            if (kbdStruct.vkCode == VK_CAPITAL)
            {
                if ((GetKeyState(VK_CAPITAL) & 0x0001) != 0)
                    MessageBox(NULL, "Caps Lock ON!", "Caps Lock", MB_ICONINFORMATION);
                else
                    MessageBox(NULL, "Caps Lock OFF!", "Caps Lock", MB_ICONINFORMATION);
            }
        }
    }

    // call the next hook in the hook chain. This is nessecary or your hook chain will break and the hook stops
    return CallNextHookEx(_hook, nCode, wParam, lParam);
}

void SetHook()
{
    // Set the hook and set it to use the callback function above
    // WH_KEYBOARD_LL means it will set a low level keyboard hook. More information about it at MSDN.
    // The last 2 parameters are NULL, 0 because the callback function is in the same thread and window as the
    // function that sets and releases the hook. If you create a hack you will not need the callback function 
    // in another place than your own code file anyway. Read more about it at MSDN.
    if (!(_hook = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, NULL, 0)))
    {
        MessageBox(NULL, "Failed to install hook!", "Error", MB_ICONERROR);
    }
}

void ReleaseHook()
{
    UnhookWindowsHookEx(_hook);
}

int main()
{
    // Set the hook
    SetHook();

    // Don't mind this, it is a meaningless loop to keep a console application running.
    // I used this to test the keyboard hook functionality.
    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {

    }
    
    return 0;
}


Solution

  • GetKeyState returns the state before the current key press is processed and the keyboard state updated. For example, if CAPS LOCK is OFF and you press the CAPS LOCK key, the hook gets called and GetKeyState reports the current state as being OFF, then the keypress gets processed and CAPS LOCK is turned on.

    This is obliquely hinted in LowLevelKeyboardProc callback function:

    Note: When this callback function is called in response to a change in the state of a key, the callback function is called before the asynchronous state of the key is updated. Consequently, the asynchronous state of the key cannot be determined by calling GetAsyncKeyState from within the callback function.

    Since GetKeyState reflects a thread state prior to the physical one reported by GetAsyncKeyState, it is indirectly implied that the call to GetKeyState woud return the previous state.