0

I want use a console application to click left with my mouse automatically, so i do this code:

int main()
{   
    Sleep(2000);
    
    SendMessage(0, WM_LBUTTONDOWN, 0, 0); 

return 0;
}

and this is not work, my mouse not click on anything, am on windows 10, any idea please

Trying to click where i put my mouse cursor

  • Is the target of the simulated mouse click your console application? Or is it a different application? – Andreas Wenzel Jun 25 '23 at 23:32
  • 1
    You may want to take a look at [`SendInput`](https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-sendinput). That function also allows you to specify relative mouse coordinates. In your case, you probably want both relative coordinates to be zero. – Andreas Wenzel Jun 25 '23 at 23:33

2 Answers2

4

Use SendInput instead of SendMessage. SendMessage is geared towards window handles with message loops. You're also providing a window handle of 0, which is targeting your console.

#include <Windows.h>
int main()
{
    INPUT simInput = { 0 };
    simInput.type = INPUT_MOUSE;
    simInput.mi.dwFlags = MOUSEEVENTF_LEFTDOWN;
    SendInput(1, &simInput, sizeof(INPUT));
    
    simInput.mi.dwFlags = MOUSEEVENTF_LEFTUP;
    SendInput(1, &simInput, sizeof(INPUT));

    return 0;
}

This will first simulate a down event on the left button. Then simulate an up event (mouse release) on the left button.

if you want to target specific coordinates you can tell the INPUT structure the coordinates.

    INPUT simInput = { 0 };
    simInput.type = INPUT_MOUSE;
    
    simInput.mi.dx = 0; //xposition
    simInput.mi.dy = 0; //yposition

    simInput.mi.dwFlags = MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_ABSOLUTE ;

    SendInput(1, &simInput, sizeof(INPUT));

You can retrieve window handles with FindWindow using either the classname or window name.

HWND hTarget = FindWindowA(NULL,"MyWindowName");
SendMessage(hTarget,WM_LBUTTONDOWN,0,0);
Andreas Wenzel
  • 22,760
  • 4
  • 24
  • 39
Irelia
  • 3,407
  • 2
  • 10
  • 31
  • [Is it a bug to pass a single-element array to SendInput?](https://stackoverflow.com/q/46744894/1889329) – IInspectable Jun 26 '23 at 07:12
  • *"You're also providing a window handle of 0, which is targeting your console."* - That's incorrect. The call just fails and the error code is `ERROR_INVALID_WINDOW_HANDLE`, unsurprisingly. *"You can retrieve window handles with `FindWindow` using either the classname or window name."* - Again, not correct. You can pass *both* just fine. But if you want to pass only one choosing the window name is the unreliable option. The window title a user sees, and the window name `FindWindow` operates on are commonly no the same thing. – IInspectable Jun 26 '23 at 14:33
  • None of that is terribly interesting, since [you can't simulate keyboard input with PostMessage](https://devblogs.microsoft.com/oldnewthing/20050530-11/?p=35513) anyway. I wonder why this answer even includes this non-option. And while that is bad already, it gets it wrong, too: The `lParam` specifies the position. But then, it also got the position wrong in the `SendInput` example. Reads like an answer ChatGPT could produce, when it's having a very bad day. – IInspectable Jun 26 '23 at 14:40
1

my mouse not click on anything

That is to be expected. The code is passing 0 as the recipient of SendMessageW, which isn't a valid window handle. With proper error handling this would have been easy to see:

int main() {
    ::SetLastError(0);
    auto const result = ::SendMessageW(0, WM_LBUTTONDOWN, 0, 0);
    if (result == 0) {
        auto const err = ::GetLastError();
        if (err != ERROR_SUCCESS) {
            printf("SendMessageW failed: %u\n", err);
       }
    }
}

When executed, this program prints the following:

SendMessageW failed: 1400

Error code 1400 has the symbolic constant ERROR_INVALID_WINDOW_HANDLE, and translates to "Invalid window handle." Precisely what one would expect, when passing an invalid window handle.

Now that you know what's wrong, it would be tempting to fix the immediate issue, and continue down that route. But that's not going to work. As we all (should) know (by now): You can't simulate keyboard input with PostMessage. The same principles apply to mouse input, and exchanging PostMessage with SendMessage merely makes sure that the calling thread stops making forward progress when the destination thread hangs. Replaying input is not the same as reprocessing it covers more issues you'll invariably run into when pursuing a solution that isn't.

What you'll want to do instead depends on the ultimate goal. If the question is to be taken at face value, assuming that it doesn't conceal any additional requirements, the solution is to synthesize input using the SendInput API:

int main() {
    ::Sleep(2000);

    // Inject "mouse click" using `SendInput`
    INPUT inp[] = { { .type = INPUT_MOUSE, .mi = { .dwFlags = MOUSEEVENTF_LEFTDOWN } },
                    { .type = INPUT_MOUSE, .mi = { .dwFlags = MOUSEEVENTF_LEFTUP } } };
    constexpr auto count = ARRAYSIZE(inp);
    auto const result = ::SendInput(count, inp, sizeof(INPUT));
    if (result == 0) {
        auto const err = ::GetLastError();
        printf("SendInput failed: %u\n", err);
    }
}

There are a few details here that aren't immediately obvious:

  • The INPUT structures are zero-initialized except for the fields explicitly named (this is using C++20 designated initializer syntax, just because lengthy sequences of 0, don't cut it for readability).
  • Since the MOUSEEVENTF_ABSOLUTE flag isn't set, the mouse input event is injected using the current mouse position.
  • A "mouse click" consists of two input events: Mouse down, followed by mouse up. SendInput is an improved version of mouse_event, that allows injecting multi-event input as an atomic operation, so they won't get interspersed with events from other input sources. Is it a bug to pass a single-element array to SendInput? covers the details.

As far as the system is concerned, injected input is treated no different from input generated by a physical input device. In case of a "mouse click at the current mouse cursor position" the consequence is, that the window under the current mouse position becomes the foreground window, and the previous foreground window loses foreground activation.

If that is not something you want, your only other option (ignoring custom automation interfaces) is to use UI Automation.

If the ultimate goal is to cheat a game into thinking that you totally rock, then that's a whole different challenge. I covered the complexities involved previously here.

IInspectable
  • 46,945
  • 8
  • 85
  • 181