0

I've implemented a hook procedure that catches some Alt+letter key combinations and injects predefined strings. An example of a hook procedure is shown below. It detects the hotkeys LeftAlt+Q and LeftAlt+A and injects the string ABCD1234 in both cases, using the WinAPI function SendInput().

#include <windows.h>

BOOL APIENTRY DllMain(HMODULE hModule,
    DWORD  ul_reason_for_call,
    LPVOID lpReserved
)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

void DisplayString()
{
    INPUT in[24] = { 0 };
    int i;
    for (i = 0; i < ARRAYSIZE(in); i++) in[i].type = INPUT_KEYBOARD;
    i = 0;
    in[i++].ki.wVk = VK_CONTROL;    // CTRL
    in[i].ki.wVk = VK_CONTROL;      // CTRL Up
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i].ki.wVk = VK_LMENU;        // LeftAlt Up
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = VK_LSHIFT;     // LeftShift down
    in[i++].ki.wVk = 'A';
    in[i].ki.wVk = 'A';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = 'B';
    in[i].ki.wVk = 'B';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = 'C';
    in[i].ki.wVk = 'C';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = 'D';
    in[i].ki.wVk = 'D';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i].ki.wVk = VK_LSHIFT;    // LeftShift Up
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = '1';
    in[i].ki.wVk = '1';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = '2';
    in[i].ki.wVk = '2';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = '3';
    in[i].ki.wVk = '3';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = '4';
    in[i].ki.wVk = '4';
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    in[i++].ki.wVk = VK_CONTROL;    // CTRL
    in[i++].ki.wVk = VK_LMENU;      // LeftAlt Down
    in[i].ki.wVk = VK_CONTROL;      // CTRL Up
    in[i++].ki.dwFlags = KEYEVENTF_KEYUP;
    SendInput(ARRAYSIZE(in), in, sizeof(INPUT));
}

HHOOK hHook{ NULL };

#define     VK_HOT_1        0x51
#define     VK_HOT_2        0x41

extern "C" __declspec(dllexport)
LRESULT CALLBACK KeyboardHookProc(int code, WPARAM wParam, LPARAM lParam)
{
    if (code < 0) return CallNextHookEx(hHook, code, wParam, lParam);

    // If LeftAlt pressed
    if (wParam == WM_SYSKEYDOWN && (GetAsyncKeyState(VK_LMENU) & 0x80000000))
    {
        DWORD vkCode = ((KBDLLHOOKSTRUCT*)lParam)->vkCode;  // virtual-key code
        if (vkCode == VK_HOT_1 || vkCode == VK_HOT_2)
        {
            DisplayString();
            return 1;
        }
    }
    return CallNextHookEx(hHook, code, wParam, lParam);
}

The hook procedure is installed using the function SetWindowsHookExW().

The program works as expected in a broad range of applications. I've tested it with WordPad, Notepad++, Chrome, Edge, Command Prompt, PowerShell, and Visual Studio. However, the program exhibits unexplained behavior when the string is injected into Notepad. Only 2-4 characters come up, and the rest appear when additional keys are pressed on the keyboard, with a couple of characters following each key down.

This behavior is new. I used Notepad to test this program during development some weeks ago, and Notepad worked fine. I'm using this program on several PCs, and some work problem-free. All the PCs use Windows 11.

EDIT: AutoHotkey is an advanced Windows app for implementing user-defined hotkeys. The user configures the key combinations to use, and the action to perform when a hotkey is pressed. I've configured AutoHotkey for the same hotkeys and actions as in my application and tested it on a Windows 11 PC. AutoHotkey's behavior is the same as that of my application: injecting text works fine in all tested apps but Notepad.

Moreover, the behavior I observe now is different. The whole string comes out intermittently, two characters at a time, seemingly at the rate of cursor blinks.

Kalle Svensson
  • 353
  • 1
  • 10
  • Windows 10, or the version of Windows that came after the *"there will not **ever** be a version of Windows beyond Windows 10"* statement? – IInspectable May 04 '23 at 20:32
  • That is an unusual way to fill the `INPUT[]` array. Also, I'm not sure what you are trying to do with the Ctrl and Alt keys. But, it is much more reliable to send textual characters using the `KEYEVENTF_UNICODE` flag, see [this example](https://stackoverflow.com/a/31307429/65863). – Remy Lebeau May 04 '23 at 21:29
  • @Remy Lebeau: I fill `INPUT[]` this way to avoid renumbering elements when I add/remove keys in the middle. `LeftAlt Up` is necessary because the string is injected when `LeftAlt` is pressed down. `Ctrl Down/Up` is used to emulate what the keyboard driver does in this situation. Note, however, that this code works everywhere but in `Notepad`, and it used to work in `Notepad` as well. – Kalle Svensson May 04 '23 at 22:04
  • @IInspectable: Hello again! It's Windows 11. I've added it to the problem statement. – Kalle Svensson May 04 '23 at 22:12
  • There are several issues with the code. Since (apparently) you're installing a low-level keyboard hook, you can get rid of the DLL code. The hook procedure isn't required to live in a DLL. Next, you should consider using [aggregate initialization](https://en.cppreference.com/w/cpp/language/aggregate_initialization) syntax. Properly aligned, this makes it far easier to visually grasp the intended effect. When that is done it should be easy to see that the `SendInput()` call leaves several keys pressed. Lastly, the hook procedure doesn't filter out injected input, which may trigger it again. – IInspectable May 05 '23 at 10:38
  • @IInspectable: I've removed the DLL. `INPUT in[24]` is an array of unions of structs. The union member `KEYBDINPUT`, which I use, is not first. Consequently, you would have to use designated initializers. The complexity of this is high; I suspect you cannot even do it in Visual Studio C++. The only key left pressed is `LeftAlt`, which is deliberate and correct. The hook procedure filters (ignores) everything but `LeftAlt+Q` and `LeftAlt+A`. Fortunately, these keys are never injected. However, you don't really address the Notepad problem. Observe also the **EDIT** in the question. – Kalle Svensson May 05 '23 at 21:50
  • Visual Studio supports designated initializers, so long as you compile with `/std:c++20` or `/std:c++latest`. While I cannot reproduce the issue, the code is all I can go by, so getting that into a more comprehensible shape was the intent behind the comment. From what I hear, Notepad is now a Windows App SDK/WinUI 3 application in Windows 11, and Lord knows what its dispatcher does internally. WinUI is the grandchild of a UI framework initially meant for touch-driven devices, so keyboard support is an afterthought. – IInspectable May 06 '23 at 05:39
  • @IInspectable: Switching to `/std:c++20` was the first thing I did, but an attempt to use designated initializers for `INPUT in[24]` failed. On Google, I read that the designated initialization of arrays is not supported in C++ (dated July 2020). If you can produce a compilable example, why don't you show the first two rows of `in[24]`? – Kalle Svensson May 06 '23 at 10:19
  • Something like [this](https://godbolt.org/z/qsMe75ajf) will do. – IInspectable May 07 '23 at 11:53
  • @IInspectable: Yes, it goes through the compiler, it's easy to read, and you can add or remove key actions without renumbering anything. A catch is that the compiler must zero the unitialized struct members; otherwise, it's too much writing. I'll use it next time. Thank you. But, honestly, my code is also easy to read if I put all the details for one key action on a single code line. And it's relatively compact. – Kalle Svensson May 07 '23 at 15:12
  • *"A catch is that the compiler must zero the unitialized struct members"* - I believe that's happening. *"my code is also easy to read"* - It is, but it's equally hard to discern the *outcome* of running several state machines, superimposed on top of each other. If you wish to maximize the chances of getting a comprehensive answer, you should consider minimizing the mental load required to comprehend the problem. – IInspectable May 07 '23 at 15:33

0 Answers0