GetKeyboardState one key delay

1.5k views Asked by At

I'm working on ToUnicodeEx function and it requires keyboard state as the input parameter. So, I used the GetKeyboardState function to do that. But I noticed when I'm typing key combinations with modifier keys like SHIFT+A there is one character delay. Here is the example.

aaa (holding SHIFT now) aAAAAAAA (release SHIFT) Aaaa

While I was debugging this I noticed GetKeyboardState is causing this delay. How can I handle or prevent this delay?

Here is my whole keyboard hook proc.

void proc(KBDLLHOOKSTRUCT kbdStruct) {


    fdebug = fopen("debug.txt", "a");
    foutput= fopen("output.txt", "a");
    WCHAR pwszBuff[9];
    WCHAR key[9];
    char str[8];
    BOOL isDead = FALSE;
    BYTE lpKeyState[256];
    HWND currentHwnd = GetForegroundWindow();
    LPDWORD currentProcessID = 0;
    DWORD currentWindowThreadID = GetWindowThreadProcessId(currentHwnd, currentProcessID);
    DWORD thisProgramThreadId = GetCurrentThreadId();
    hkl = GetKeyboardLayout(thisProgramThreadId);
    if (AttachThreadInput(thisProgramThreadId, currentWindowThreadID, TRUE))
    {
        GetKeyboardState(lpKeyState);

        AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);
    }
    else
    {
        GetKeyboardState(lpKeyState);
    }

    int ret = ToUnicodeEx(kbdStruct.vkCode, kbdStruct.scanCode, lpKeyState, pwszBuff, 8, 0, hkl);
    fprintf(fdebug, "vkCode: %d\n", (int)kbdStruct.vkCode);
    fprintf(fdebug, "ret: %d\n", (int)ret);
    fprintf(fdebug, "lastIsDead: %d\n", (int)lastIsDead);
    fprintf(fdebug, "lastIsMod: %d\n", (int)lastIsMod);
    fprintf(fdebug, "lastVKCode: %d\n", (int)lastVKCode);
    if (ret == -1) {
        isDead = TRUE;
        ClearKeyboardBuffer(kbdStruct.vkCode, kbdStruct.scanCode, hkl);
    }
    else if (ret == 0) {

    }
    else {
        memcpy(&key, &pwszBuff, sizeof(pwszBuff));
        WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
        fprintf(fdebug, "str: %s\n", str);
    }

    if (lastVKCode != 0 && lastIsDead == TRUE) {
        ToUnicodeEx(lastVKCode, lastScanCode, lastKeyState, pwszBuff, 4, 0, hkl);
        memcpy(&key, &pwszBuff, sizeof(pwszBuff));
        WideCharToMultiByte(CP_UTF8, 0, key, -1, str, sizeof(str), NULL, NULL);
        fprintf(fdebug, "str: %s\n", str);
        lastVKCode = 0;

    }
    fprintf(fdebug, "%s", "---------------------------------------------------\n");

    fprintf(foutput, "LSHIFT: %d\n", (int)lpKeyState[160]);
    fprintf(foutput, "RSHIFT: %d\n", (int)lpKeyState[161]);
    fprintf(foutput, "%s", "---------------------------------------------------\n\n");

    lastVKCode = kbdStruct.vkCode;
    lastScanCode = kbdStruct.scanCode;
    lastIsDead = isDead;
    fclose(fdebug);
    fclose(foutput);
}

Here is updated version of hookcallback thanks for Ton Plooij. But still, I have the same problem.

LRESULT __stdcall HookCallback(int nCode, WPARAM wParam, LPARAM lParam)
{
    LRESULT ret = CallNextHookEx(_hook, nCode, wParam, lParam);
    if (nCode >= 0)
    {
        if (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
        {
            hookStruct = *((KBDLLHOOKSTRUCT*)lParam);
            proc(hookStruct);

        }
    }
    return ret;
}
4

There are 4 answers

1
Hans Passant On BEST ANSWER
AttachThreadInput(thisProgramThreadId, currentWindowThreadID, FALSE);

This does not do what you hope it does. Sometimes. It is a valiant and necessary effort to get the proper values when you call GetKeyboardState(). Took me a while to find the failure mode, it wasn't obvious at all and I could not get the code to fail the same way. It works just fine when a GUI process is in the foreground, try it with Notepad or VS for example.

But not when it is a console mode process.

Explaining that is a bit convoluted, it is actually GetWindowThreadProcessId() that returns misleading information. It tries too hard to keep up the illusion that it is the console process that owns the console window. It doesn't, it is actually the associated conhost.exe process that owns it. It was csrss.exe on old Windows versions, conhost.exe was added in Windows 7 to solve the drag+drop issues caused by UIPI (aka UAC).

But without any decent way to discover the specific conhost.exe process that owns the window, let alone the thread id. The subject of this Q+A. Pretty doubtful it is going to help you.

Only decent advice is the well-known unpleasant one: if you need to reliably translate keystrokes then you need to use a WH_KEYBOARD hook instead of WH_KEYBOARD_LL. So GetKeyboardState() is always accurate, the hook callback runs in-process.

3
John Scart On

You can try GetAsyncKeyState(). This function in my keyboard hook module work perfectly.

https://msdn.microsoft.com/en-us/library/windows/desktop/ms646293(v=vs.85).aspx

It seems that this function can catch hardware interrupt when you press keys.

And your GetKeyboardState has something to do with the Windows Registry. It seems to have something to do with belows:

“HKEY_USERS\DEFAULT\ControlPanel\Keyboard”,Modify “KeyboardDelay” value to 0(default 1) And change “KeyboardSpeed” value to 48(dafault 31).

7
Ton Plooij On

A LowLevelKeyboardProc receives WM_KEYUP and WM_KEYDOWN messages in its wParam. You could simply keep track of pressed modifier keys yourself, in this case to detect shift down and up. Store the key state information in a static variable and use this to test if shift is pressed while processing other keys instead of using GetKeyState.

2
kenarsuleyman On

According to Hans Passant's answer I searched for how can I get correct value from GetWindowThreadProcessId() and succeeded with instead of getting hWnd and windowThreadID every time in hook callback, I get these values to global variable just after program start and used variables in hook callback.