-2

! does not exist in the virtual key codes of Win32. Is there a way I could use keybd_event(), or anything else, to simulate a keyboard entry leading to !?

It's my first time making a bot to spam in a Discord casino (private).

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 3
    Well, there isn't a `!` key on a qwerty keyboard. To type `!`, you need two keys, `shift` + `1` – NathanOliver Dec 15 '21 at 20:46
  • See my [previous answer](https://stackoverflow.com/a/31307429/65863) for sending Unicode strings with `SendInput()` (`keybd_event()`'s successor) in C++. – Remy Lebeau Dec 16 '21 at 01:02
  • @RemyLebeau Only now did I notice that you have a [C++ answer](https://stackoverflow.com/questions/31305404/sending-two-or-more-chars-using-sendinput/31307429#31307429) for it too :-) Light Blur: Mind if we close this as a duplicate? – Ted Lyngmo Dec 16 '21 at 18:42

1 Answers1

0

Since keybd_event() has been superseded by SendInput(), I suggest that you use that instead.

With SendInput(), you send a number of INPUT structures. You can send mouse input, keyboard input, and hardware input. I'll show how to send keyboard input.

Keyboard input can be sent using scan codes - or Unicode characters. I'll use Unicode. Finding the Unicode character for something you don't know is usually as easy as: https://www.google.com/search?q=unicode+exclamation+mark and you'll get the answer, like U+0021 for !, which can be encoded as \u0021 in Unicode strings in C++.

I'll start by inheriting the INPUT structure to make it simpler to instantiate it:

#include <Windows.h>

#include <iostream>
#include <stdexcept>
#include <vector>

struct mINPUT : INPUT {
    mINPUT() : INPUT{} {}    // make sure it's clean if default constructed.

    // this constructor prepares the structure for different kinds of input:
    mINPUT(DWORD type) : INPUT{type} {
        switch (type) {
        case INPUT_MOUSE:
            // use mi.
            break;
        case INPUT_KEYBOARD:
            // use ki.
            ki.dwFlags = KEYEVENTF_UNICODE;  // we'll use unicode
            break;
        case INPUT_HARDWARE:
            // use hi.
            break;
        }
    }
};
// helper functions to create `mINPUT` structures from Unicode values:
mINPUT key_down(char16_t unicode_char) {
    mINPUT rv{INPUT_KEYBOARD};
    rv.ki.wScan = unicode_char;
    return rv;
}

mINPUT key_up(char16_t unicode_char) {
    mINPUT rv{INPUT_KEYBOARD};
    rv.ki.dwFlags |= KEYEVENTF_KEYUP;
    rv.ki.wScan = unicode_char;
    return rv;
}
// Helper functions to check UTF16 surrogate ranges
bool is_surrogate(char16_t code_unit) {
    return code_unit >= 0xD800 && code_unit <= 0xDFFF;
}

bool is_high_surrogate(char16_t code_unit) {
    return code_unit >= 0xD800 && code_unit <= 0xDBFF;
}

bool is_low_surrogate(char16_t code_unit) {
    return code_unit >= 0xDC00 && code_unit <= 0xDFFF;
}
// A helper structure to prepare a sequence of events
struct Inputs {
    UINT cInputs() const { return static_cast<UINT>(inputs.size()); }
    LPINPUT pInputs() { return inputs.data(); }
    int cbSize() const { return static_cast<int>(sizeof(INPUT)); }

    // A helper function to add down+up events for a string:
    void add_string(const char16_t* str) {
        while (*str) {
            char16_t ch = *str++;

            if (is_surrogate(ch)) {
                char16_t first = ch;
                char16_t second = *str++;
                if (!is_high_surrogate(first) || !is_low_surrogate(second))
                    throw std::runtime_error("Broken UTF16 surrogate pair");
                inputs.push_back(key_down(first));
                inputs.push_back(key_down(second));
                inputs.push_back(key_up(first));
                inputs.push_back(key_up(second));
            } else {
                inputs.push_back(key_down(ch));
                inputs.push_back(key_up(ch));
            }
        }
    }
 
    UINT Send() {            // Send the stored events
        return SendInput(cInputs(), pInputs(), cbSize());
    }

    std::vector<mINPUT> inputs;
};
int main() {
    std::cout << "Switch to Notepad or some other app taking input" << std::endl;
    Sleep(5000);  // in 5 seconds, you should see the input

    Inputs x; // Create an event container

    // Add events for a full string including exclamation marks in two
    // different formats:
    x.add_string(u"Hello world!!! or \u0021\u0021\u0021 ");
    x.add_string(u"This is something with surrogate pairs:  ");

    // Send the events:
    UINT rv = x.Send();
    
    std::cout << "Sent " << rv << " events\n";
}

If everything goes according to plan, it will send 134 events and you should see the exclamation mark and other characters appear in any app you have active if it's capable of receiving keyboard input and displaying the result, like Notepad or Visual Studio - so be careful where you place the cursor.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 1
    Note, your code is not handling UTF-16 surrogates correctly. See my [previous answer](https://stackoverflow.com/a/50420607/65863) about that issue. – Remy Lebeau Dec 16 '21 at 01:06
  • @RemyLebeau Thanks, that looks nice! Strange that MS haven't documented this. I'll read your answer more closely after work. :) – Ted Lyngmo Dec 16 '21 at 06:35
  • @RemyLebeau Thanks again for bringing this to my attention. I updated it to take care of the surrogates too. – Ted Lyngmo Dec 16 '21 at 18:26
  • 1
    After detecting the 1st surrogate, `if (*str == u'\0')` doesn't detect an invalid 2nd surrogate correctly. I would suggest something more like this instead: `if (first >= 0xDC00 || *str < 0xDC00 || *str > 0xDFFF) throw ...;` – Remy Lebeau Dec 16 '21 at 20:35
  • 1
    @RemyLebeau Ah, yes, thanks. I added a proper surrogate check. – Ted Lyngmo Dec 16 '21 at 21:01
  • @RemyLebeau Brilliant edit too :-) Cheers! – Ted Lyngmo Dec 16 '21 at 21:16