pythonwinapiwin32gui

Why does SendMessage not work for some applications?


Background:

I was trying to program an auto clicker to click in the background to an application (Roblox, not trying to do anything malicious). I was able to get the window and perform commands like closing it. However, when trying to send clicks to the window it returns 0. (I'm using SendMessage so I don't activate the window.)

Minimum reproducible example:

import win32gui
import win32con
import win32api
hwnd = win32gui.FindWindow(None, "Roblox")

while True:
    lParam = win32api.MAKELONG(100, 100)
    temp = win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, None, lParam)
    win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, None, lParam)
    print(temp)

Things I tried:

  1. I tried changing the window to see if it was the wrong window, or if it didn't see the window

  2. I tried sending the message normally:

    lParam = win32api.MAKELONG(100, 100)  # Get the coordinates and change to long
    temp = win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, None, lParam)  # Send message to handle
    win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, None, lParam)  # Release key from sent message to handle
    
  3. I tried it with other windows, and it worked, but not for Roblox

  4. I tried with other commands and it works, but clicks don't. This works: (So I know it's the right window)

    temp = win32gui.SendMessage(hwnd, win32con.WM_CLOSE, 0, 0)  # Close window with SendMessage
    

Solution

  • You cannot do that.

    Let's start by rephrasing the problem statement to more easily follow along, why that is the case:

    "How do I convince a program that has chosen to ignore mouse input messages—by decision or coincidence—to acknowledge mouse input messages?"

    As it turns out, that part is actually solved. As the documentation for WM_LBUTTONDOWN notes:

    If an application processes this message, it should return zero.

    And zero you get, so there's no reason to question the fact that the message has been handled to the extent deemed necessary by the application. This probably falls down the "coincidence" branch, where the application just isn't interested in mouse messages any more than passing them on to DefWindowProc, the kitchen sink for all messages that aren't relevant enough to even ignore.

    Key insight here is: A program that needs to process and respond to mouse input can decide to ignore mouse input messages1. (And clients that are based on mouse message handling can easily identify fake input messages, too, and respond by, y'know, not responding altogether.)

    So, in essence, sending (or posting) fake mouse messages isn't going to work. Reliably. Ever.

    Which leaves you with essentially 3 alternatives:

    The first two options are listed for completeness only. They are commonly available for applications that actively support being automated. Games generally don't, and protecting against those avenues is easy, and cheap: An application doesn't have to do anything.

    SendInput won't work, either. As far as the system is concerned, injected input is processed the same way as any other input (this blog post offers a helpful illustration). Specifically, when a mouse click is injected over a window, that window comes to the foreground. So that fails the requirement to have the application "in the background".

    Even if that wasn't the case, injected input is easily and reliably identifiable. A low-level mouse hook is all that's required to get an MSLLHOOKSTRUCT, whose flags field gives this information readily away. With a low-level hook's ability to prevent input from being passed on to the system, a return 1; is all that's needed to filter out those input events.


    And that covers all supported ways to automate a foreign application. It's a dead end so dead that it's not worth beating.


    Now, if automating an application that runs in the background using fake input summarizes the requirements, then your only option is to run the application in a virtualized environment (which ensures that a click stays confined within the virtual environment and won't bring the application to the front). Keep in mind that all restrictions covered above still apply, and you cannot use any of the methods above. You would have to implement and install a custom mouse driver that generates input that's indiscernible from genuine hardware-sourced input events.

    But even then, applications have ways of discovering that they are running in a virtualized environment, and refuse to operate when they do.


    The bottom line is: Cheating is hard. Really hard. And your problem doesn't have an easy solution.


    1 Mouse input messages are generated by the system as a convenience. They represent a useful (and lossy) abstraction over hardware input events. The full fidelity of those hardware input events is generally not required by "standard" applications.
    Games, on the other hand, will usually use lower-level input processing infrastructure, such as Raw Input, and not even look at any of the higher-level processing artifacts.