1

I want to use the SendInput() function to automate pressing some keys.

My code:

#include "stdafx.h"
#include <iostream>
#include <string>
#include <Windows.h>

using std::cin;
using std::cout;
using std::string;

void presskeys(string s){
    INPUT ip;
    ip.ki.wScan = 0;
    ip.ki.time = 0;
    ip.ki.dwExtraInfo = 0;
    HKL kbl = GetKeyboardLayout(0);
    for (unsigned int i = 0; i < s.length(); ++i) {
        ip.type = INPUT_KEYBOARD;
        char c = s[i];
        int vk = VkKeyScanEx((WCHAR)c, kbl);
        ip.ki.wVk = vk;
        SendInput(1, &ip, sizeof(ip));
        ip.ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(1, &ip, sizeof(ip));
    }
}
int main()
{
    cout << "Enter a string!\n";
    string str;
    cin >> str;
    Sleep(5000);
    presskeys(str);
    cout << "Done!\n";
    return 0;
}

I boot up the program and enter a string, only for nothing to happen. Where did it go wrong?

TheRniz
  • 53
  • 2
  • 10
  • 1
    Check the return code from sendinput and call getlasterror. – David Thomas Jul 27 '16 at 20:34
  • What application is supposed to receive this input? Research UIPI. – David Thomas Jul 27 '16 at 20:38
  • @RemyLebeau "This function fails when it is blocked by UIPI. Note that neither GetLastError nor the return value will indicate the failure was caused by UIPI blocking." [per msdn description of the sendinput function](https://msdn.microsoft.com/en-us/library/windows/desktop/ms646310(v=vs.85).aspx) – David Thomas Jul 28 '16 at 00:58
  • @DavidThomas: you are right, I did not see that caveat before. – Remy Lebeau Jul 28 '16 at 01:02
  • Since `SendInput()` injects into the hardware input queue, I don't understand how UIPI can block it, though. Maybe @RaymondChen could post a blog explaining that in more detail. – Remy Lebeau Jul 28 '16 at 01:33

2 Answers2

3

Like Barmak said, you are mixing ANSI and UNICODE together in an improper manner, and you are not managing the input flags correctly.

When sending string characters, it is better to send UTF-16 codeunits via the KEYEVENTF_UNICODE flag instead of using scan codes or virtual keys.

You should also call SendInput() one time with multiple INPUT structs for the entire string, so that all of the characters get sent together as a single unit and cannot be interrupted by, or intermixed with, other input events that are generated by the user or other apps/threads at the same time.

See my answer to Sending Two or more chars using SendInput.

For example:

#include "stdafx.h"
#include <Windows.h>

#include <iostream>
#include <string>
#include <vector>

void presskeys(const std::wstring &str)
{
    int len = str.length();
    if (len == 0) return;

    std::vector<INPUT> in(len*2);
    ZeroMemory(&in[0], in.size()*sizeof(INPUT));

    int i = 0, idx = 0;
    while (i < len)
    {
        WORD ch = (WORD) str[i++];

        if ((ch < 0xD800) || (ch > 0xDFFF))
        {
            in[idx].type = INPUT_KEYBOARD;
            in[idx].ki.wScan = ch;
            in[idx].ki.dwFlags = KEYEVENTF_UNICODE;
            ++idx;

            in[idx] = in[idx-1];
            in[idx].ki.dwFlags |= KEYEVENTF_KEYUP;
            ++idx;
        }
        else
        {
            in[idx].type = INPUT_KEYBOARD;
            in[idx].ki.wScan = ch;
            in[idx].ki.dwFlags = KEYEVENTF_UNICODE;
            ++idx;

            in[idx].type = INPUT_KEYBOARD;
            in[idx].ki.wScan = (WORD) str[i++];
            in[idx].ki.dwFlags = KEYEVENTF_UNICODE;
            ++idx;

            in[idx] = in[idx-2];
            in[idx].ki.dwFlags |= KEYEVENTF_KEYUP;
            ++idx;

            in[idx] = in[idx-2];
            in[idx].ki.dwFlags |= KEYEVENTF_KEYUP;
            ++idx;
        }
    }

    SendInput(in.size(), &in[0], sizeof(INPUT));
}

int main()
{
    std::wcout << L"Enter a string and press enter:\n";
    std::wstring str;
    if (std::getline(std::wcin, str))
    {
        presskeys(str);
        std::wcout << L"Done!\n";
    }
    return 0;
}
Community
  • 1
  • 1
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
1

You are working with ANSI string so use VkKeyScanExA, or switch to std::wstring. But don't mix ANSI with UNICODE, and don't use casting to hide the compiler warnings.

You have to reset ip.ki.dwFlags to zero to indicate key down. Then set it to KEYEVENTF_KEYUP.

You also have to check the key state for caplock and shift, etc. Using SendInput in that way is pointless, the first parameter is there to allow sending multiple inputs at once. You might as well use keybd_event

void presskeys(string s) 
{
    INPUT ip;
    ip.type = INPUT_KEYBOARD;
    ip.ki.wScan = 0;
    ip.ki.time = 0;
    ip.ki.dwExtraInfo = 0;
    HKL kbl = GetKeyboardLayout(0);
    for (unsigned int i = 0; i < s.length(); ++i) 
    {
        char c = s[i];
        ip.ki.wVk = VkKeyScanExA(c, kbl); //<== don't mix ANSI with UNICODE
        ip.ki.dwFlags = 0; //<= Add this to indicate key-down
        SendInput(1, &ip, sizeof(ip));
        ip.ki.dwFlags = KEYEVENTF_KEYUP;
        SendInput(1, &ip, sizeof(ip));
    }
}
Barmak Shemirani
  • 30,904
  • 6
  • 40
  • 77