2

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!

  • 1
    95 is `VK_SLEEP`, a virtual key code for Sleep key. Why again do you expect it to produce an underscore? Recall that `SendInput` sends key presses, not characters. Which characters those key presses produce depends on the receiving thread's keyboard layout. Pressing a key immediately to the right of Tab doesn't always produce `Q`. – Igor Tandetnik Sep 23 '18 at 20:45
  • 2
    Underscore is not a key. It is the character that you get when holding down `Shift` and pressing the hyphen key (assuming american keyboard layout). – Sid S Sep 23 '18 at 20:46
  • Check out `Spy++` to see what messages are actually sent to a window when you use your keyboard. It comes with Visual Studio. – Sid S Sep 23 '18 at 20:53

1 Answers1

5

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;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks! I really appreciate it. I hope you don't mind that I posted a second answer to my own question just fixing the bugs in your code. It looks like my code I was basing my stuff off of was flawed :( – Gavin Downard Sep 24 '18 at 04:03
  • @GavinDownard while it is acceptable for you to post your own answer to your own question, it would have been more polite to simply point out the bugs in my answer so I could fix them (which I have now done) and you could then accept it. – Remy Lebeau Sep 24 '18 at 07:02