Craig Steele
Craig Steele

Reputation: 61

Numpad key events result in stuck keys from GetKeyboardState

I've got a C++ (MFC) app that needs to check the keystate on a timer. If the user is holding down a key, we delay processing of some code.

Here's the check for the keydown:

if (!GetKeyboardState(keyState)) 
{
    s_executeDeferredResult = e_executeDeferredButtonCheckFailed;
    return;
}   
s_executeDeferredStuckKeys.clear();
for (int index=0; index<arrsize(keyState); ++index)
{
    if (keyState[index] & 0x80)
    {
        s_executeDeferredStuckKeys.insert(index);
    }
}
if (!s_executeDeferredStuckKeys.empty())
{
    s_executeDeferredResult = e_executeDeferredButtonsActive;
    return;
}

But, there are some key combos that get stuck:

  1. Turn on NUMLOCK
  2. Press SHIFT
  3. Press NumPad8
  4. Release SHIFT
  5. Release NumPad8
    (this is one example, there are others, including a doozy with CTRL-ALT-DEL)

GetKeyboardState will now report that VK_UP is pressed.

The events that happen are (corresponding to the actions above).

  1. <None>
  2. WM_KEYDOWN, VK_SHIFT
  3. WM_KEYUP, VK_SHIFT
    WM_KEYDOWN, VK_UP
    WM_KEYDOWN, VK_SHIFT
  4. WM_KEYUP, VK_SHIFT
  5. WM_KEYUP, VK_NUMPAD8

So, Windows doesn't recognize that the Up key came up, and now GetKeyboardState is broken.

Is there any good way to check the real state of the key? GetAsyncKeyState and GetKeyState both report that the key is down as well.

Upvotes: 4

Views: 1379

Answers (1)

Craig Steele
Craig Steele

Reputation: 61

Solved it.

I hooked into the keyboard events in InitInstance and am tracking the ups and downs by scan code (a map with the scan code as key and the virtual keys as the value).

m_keyboardHook = SetWindowsHookEx(WH_KEYBOARD, &KeyboardHook, NULL, GetCurrentThreadId());

static LRESULT CALLBACK KeyboardHook(
    __in int nCode,
    __in WPARAM wParam,
    __in LPARAM lParam
    )
{
    // According to the docs, we're not allowed to do any "further processing" if nCode is < 0.
    // According to the docs, we should process messages if we get code HC_ACTION. 
    // http://msdn.microsoft.com/en-us/library/windows/desktop/ms644984(v=vs.85).aspx
    // It doesn't specify what to do if another code comes in, so we will just ignore any other codes.
    if (nCode == HC_ACTION)
    {
        uint8 scanCode = (uint8)((lParam & 0x00FF0000) >> 16);
        uint8 virtKey = (uint8)wParam;
        if (lParam & 0x80000000) // key up
            KeyState::SetKeyUp(scanCode);
        else
            KeyState::SetKeyDown(scanCode, virtKey);
    }

    // We must call the next hook in the chain, according to http://msdn.microsoft.com/en-us/library/windows/desktop/ms644975%28v=vs.85%29.aspx
    // First param is ignored, according to http://msdn.microsoft.com/en-us/library/windows/desktop/ms644974%28v=vs.85%29.aspx )
    return CallNextHookEx(0, nCode, wParam, lParam);
}

So, my defer checking becomes:

// Similarly, don't process deferred events if there are keys or mouse buttons which are currently down.
s_executeDeferredStuckKeys.clear();
if (KeyState::AnyKeysDown(s_excludeKeys, arrsize(s_excludeKeys)))
{
    s_executeDeferredResult = e_executeDeferredButtonsActive;
    KeyState::GetDownKeys(s_executeDeferredStuckKeys);
    return;
}

Upvotes: 2

Related Questions