Search code examples
c++winapisendinput

Underscores do not get displayed with SendInput On C++


This is my first question I have posted on stackoverflow. I have been looking into SendInput for C++ in order to have my program 'type' into another program. I decided to start out by having it 'type' in a couple words with underscores into the terminal. I found no problem having it type upper and lowercase letters, as well as a period. But after getting to the underscore, typing in the number id 95 for the underscore letter, the underscore did not display, and acted completely like that letter was never pressed. Here is the code that I got off of cplusplus.com to that I based it off of, it is fully functional:

#include <iostream>
#include <windows.h>
using namespace std;

/* HWND = "Window Handle" */
HWND GameWindow = FindWindow(0, "Command Prompt");

/* This is a function to simplify usage of sending keys */
void GenerateKey(int vk, BOOL bExtended) {

    KEYBDINPUT  kb = {0};
    INPUT       Input = {0};

    /* Generate a "key down" */
    if (bExtended) { kb.dwFlags  = KEYEVENTF_EXTENDEDKEY; }
    kb.wVk  = vk;
    Input.type  = INPUT_KEYBOARD;
    Input.ki  = kb;
    SendInput(1, &Input, sizeof(Input));

    /* Generate a "key up" */
    ZeroMemory(&kb, sizeof(KEYBDINPUT));
    ZeroMemory(&Input, sizeof(INPUT));
    kb.dwFlags  =  KEYEVENTF_KEYUP;
    if (bExtended) { kb.dwFlags |= KEYEVENTF_EXTENDEDKEY; }
    kb.wVk = vk;
    Input.type = INPUT_KEYBOARD;
    Input.ki = kb;
    SendInput(1, &Input, sizeof(Input));

    return;
}

int main() {

    /*
       SetForegroundWindow will give the window focus for the
       keyboard/mouse! In other words, you don't have to have
       the game opened upfront in order to emulate key/mouse
       presses, it's very helpful if it's a game that runs
       in fullscreen mode, like StarCraft: Brood War does
    */

    SetForegroundWindow(GameWindow);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('A', FALSE);
    GenerateKey('M', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('C', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('R', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('T', FALSE);
    GenerateKey('H', FALSE);
    GenerateKey('A', FALSE);
    GenerateKey('N', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('Y', FALSE);
    GenerateKey('O', FALSE);
    GenerateKey('U', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('W', FALSE);
    GenerateKey('I', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey('L', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('E', FALSE);
    GenerateKey('V', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('R', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey('B', FALSE);
    GenerateKey('E', FALSE);
    GenerateKey('n', FALSE);
    GenerateKey(' ', FALSE);

    GenerateKey(0x3A, FALSE); /* period key */
    GenerateKey(0x0D, FALSE); /* enter key */

    return 0;
}

And this is the code that I made that ran incorrectly:

#include <iostream>
#include <fstream>
#include <windows.h>

using namespace std;

/* HWND = "Window Handle" */
HWND GameWindow = FindWindow(0, "Command Prompt");

/* This is a function to simplify usage of sending keys */
void GenerateKey(int vk, BOOL bExtended) {

    KEYBDINPUT  kb = {0};
    INPUT       Input = {0};

    /* Generate a "key down" */
    if (bExtended) { kb.dwFlags  = KEYEVENTF_EXTENDEDKEY; }
    kb.wVk  = vk;
    Input.type  = INPUT_KEYBOARD;
    Input.ki  = kb;
    SendInput(1, &Input, sizeof(Input));

    /* Generate a "key up" */
    ZeroMemory(&kb, sizeof(KEYBDINPUT));
    ZeroMemory(&Input, sizeof(INPUT));
    kb.dwFlags  =  KEYEVENTF_KEYUP;
    if (bExtended) { kb.dwFlags |= KEYEVENTF_EXTENDEDKEY; }
    kb.wVk = vk;
    Input.type = INPUT_KEYBOARD;
    Input.ki = kb;
    SendInput(1, &Input, sizeof(Input));

    return;
}

int main() {

    /*
       SetForegroundWindow will give the window focus for the
       keyboard/mouse! In other words, you don't have to have
       the game opened upfront in order to emulate key/mouse
       presses, it's very helpful if it's a game that runs
       in fullscreen mode, like StarCraft: Brood War does
    */

    SetForegroundWindow(GameWindow);

    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('N', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey('N', FALSE);
    GenerateKey('J', FALSE);
    GenerateKey('A', FALSE);
    GenerateKey(0xBE, FALSE);   // GenerateKey(0x3A, FALSE); did not work
    GenerateKey(' ', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('H', FALSE);
    GenerateKey(VK_CAPITAL, TRUE);
    GenerateKey('I', FALSE);
    GenerateKey('1', FALSE);
    GenerateKey('2', FALSE);
    GenerateKey('3', FALSE);
    GenerateKey(95 , FALSE);   // GenerateKey('_', FALSE); did not work either
    GenerateKey('4', FALSE);
    GenerateKey('5', FALSE);
    GenerateKey('6', FALSE);
    return 0;
}

This outputs Ninja. Hi123456 instead of Ninja. Hi123_456.

Other things worthy of note:

1). For the period ('.') being 'typed' out the working id was 0xBE instead of 0x3A.

2). This was compiled on Windows 10 using Mingw.

I hope this was thorough enough, thank you in advance!


Solution

  • Virtual Key Code 0x3A is not a period character. In fact, per Microsoft's documentation, 0x3A is NOT EVEN DEFINED at all. For a period character, you must use VK_OEM_PERIOD instead:

    VK_OEM_PERIOD
    0xBE

    For any country/region, the '.' key

    That being said, calling SendInput() with cInputs=1 is usually a logic bug. Certainly ALWAYS a bug when you are sending multiple input events back-to-back, as your example code is doing. The whole reason SendInput() exists at all is to replace keybd_event() (and mouse_event()), which can only send one input event at a time. When simulating multiple events, you don't want other events getting injected in between your events, and vice versa. SendInput() is atomic with other input mechanisms, but when sending multiple events, that atomicity is only guaranteed when you send all of your events at one time.

    You should put your INPUTs into an array and call SendInput() ONCE, with cInputs set to the total number of INPUTs you are sending.

    Also, when sending key input for text characters, use need to use VkKeyScan/Ex() to get the correct virtual key code and shift state, though it is a lot easier to use the KEYEVENTF_UNICODE flag instead so you can send actual Unicode characters instead of virtual key codes.

    Try something more like this instead:

    #include <iostream>
    #include <vector>
    #include <string>
    #include <windows.h>
    
    /* HWND = "Window Handle" */
    HWND GameWindow = FindWindow(NULL, TEXT("Command Prompt"));
    
    void GenerateKeyDown(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
    {
        INPUT in = {};
    
        in.type = INPUT_KEYBOARD;
        in.ki.wVk = vk;
        if (bExtended) in.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    
        inputQueue.push_back(in);
    }
    
    void GenerateKeyUp(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
    {
        INPUT in = {};
    
        in.type = INPUT_KEYBOARD;
        in.ki.wVk = vk;
        if (bExtended) in.ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
        in.ki.dwFlags |= KEYEVENTF_KEYUP;
    
        inputQueue.push_back(in);
    }
    
    void GenerateKey(std::vector<INPUT> &inputQueue, int vk, bool bExtended = false)
    {
        INPUT in[2] = {};
    
        in[0].type = INPUT_KEYBOARD;
        in[0].ki.wVk = vk;
        if (bExtended) in[0].ki.dwFlags = KEYEVENTF_EXTENDEDKEY;
    
        in[1] = in[0];
        in[1].ki.dwFlags |= KEYEVENTF_KEYUP;
    
        inputQueue.insert(inputQueue.end(), in, in+1);
    }
    
    void GenerateString(std::vector<INPUT> &inputQueue, const std::wstring &str)
    {
        int len = str.length();
        if (len == 0) return;
    
        inputQueue.reserve(inputQueue.size()+(len*2));
    
        INPUT in = {};
        in.type = INPUT_KEYBOARD;
        in.ki.dwFlags = KEYEVENTF_UNICODE;
    
        int i = 0;
        while (i < len)
        {
            WORD ch = (WORD) str[i++];
    
            if ((ch < 0xD800) || (ch > 0xDFFF))
            {
                in.ki.wScan = ch;
                in.ki.dwFlags &= ~KEYEVENTF_KEYUP;
                inputQueue.push_back(in);
    
                in.ki.dwFlags |= KEYEVENTF_KEYUP;
                inputQueue.push_back(in);
            }
            else
            {
                WORD ch2 = (WORD) str[i++];
    
                in.ki.wScan = ch;
                in.ki.dwFlags &= ~KEYEVENTF_KEYUP;
                inputQueue.push_back(in);
    
                in.ki.wScan = ch2;
                inputQueue.push_back(in);
    
                in.ki.wScan = ch;
                in.ki.dwFlags |= KEYEVENTF_KEYUP;
                inputQueue.push_back(in);
    
                in.ki.wScan = ch2;
                inputQueue.push_back(in);
            }
        }
     }
    
    int main()
    {
        /*
           SetForegroundWindow will give the window focus for the
           keyboard/mouse! In other words, you don't have to have
           the game opened upfront in order to emulate key/mouse
           presses, it's very helpful if it's a game that runs
           in fullscreen mode, like StarCraft: Brood War does
        */
    
        SetForegroundWindow(GameWindow);
    
        std::vector<INPUT> inputQueue;
    
        /*
        GenerateString(inputQueue, L"I Am cooler than you will ever ben .");
        GenerateKey(inputQueue, VK_RETURN);
        */
    
        GenerateString(inputQueue, L"NInja. HI123_456");
    
        /* alternatively:
    
        GenerateString(inputQueue, L"NInja");
        GenerateKey(inputQueue, VK_OEM_PERIOD);
        GenerateString(inputQueue, L" HI123");
    
        // see why using KEYEVENTF_UNICODE is easier?
        SHORT ret = VkKeyScanW(L'_');
        BYTE vk = LOBYTE(ret);
        BYTE shift = HIBYTE(ret);
        if (vk != -1)
        {
            SHORT state = GetKeyState(VK_SHIFT);
            bool bIsDown = (state & 0x800);
    
            if (shift & 1)
            {
                if (!bIsDown)
                    GenerateKeyDown(inputQueue, VK_SHIFT);
            }
            else
            {
                if (bIsDown)
                    GenerateKeyUp(inputQueue, VK_SHIFT);
            }
    
            GenerateKey(inputQueue, vk);
    
            if (shift & 1) 
            {
                if (!bIsDown)
                    GenerateKeyUp(inputQueue, VK_SHIFT);
            }
            else
            {
                if (bIsDown)
                    GenerateKeyDown(inputQueue, VK_SHIFT);
            }
        }
    
        GenerateString(inputQueue, L"456");
        */
    
        SendInput(inputQueue.size(), &inputQueue[0], sizeof(INPUT));
    
        return 0;
    }