1

I'm writing an autoclicker right now. I have a very difficult problem to solve at my level of knowledge. I have low level hooks to detect Mouse KeyDown & KeyUp.

private bool LeftButtonStatus;    
private void AutoClicker_Load(object sender, EventArgs e)
{      
    core.mouseHook.LeftButtonDown += new MouseHook.MouseHookCallback(mouseHook_LeftKeyDown);
    core.mouseHook.LeftButtonUp += new MouseHook.MouseHookCallback(mouseHook_LeftKeyUp);
}
private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma)
{
    LeftButtonStatus = true;
    StartClicking();
}
private void mouseHook_LeftKeyUp(KeyboardHook.VKeys key)
{
    LeftButtonStatus = false;
    StartClicking();    
}
private void StartClicking()
{
    if (LeftButtonStatus)
        LeftButtonTimer.Start();
    else
        LeftButtonTimer.Stop();      
}
private void LeftButtonTimer_Tick(object sender, EventArgs e)
{
    Core.LeftClick();
}

My click method in the Core class looks like this:

[DllImport("user32.dll")]
private static extern void mouse_event(int dwFlags, int dx, int dy, int dwData, int dwExtraInfo);
public static void LeftClick()
{
    mouse_event(((int)KeyStates.LeftDown) | ((int)KeyStates.LeftDown), 0, 0, 0, 0);
}

The problem is that when I call Core.LeftClick(); my hooks detect it and stops the Timer.

How to do I make sure Core.LeftClick(); is ignored by the mouseHook_LeftKeyUp and mouseHook_LeftKeyDown methods?

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84

2 Answers2

1

You could use a flag in your own code where you set ignore = true in LeftClick() and check if (ignore == true) in your hook methods. But the problem there is you open yourself up to a race condition where a user-generated click could be processed between the time that it's set to true and when your hook is run, which would lead to a user-generated click being ignored.

A better way would be to use the dwExtraInfo parameter of mouse_event. The documentation describes it as:

An additional value associated with the mouse event.

This seems like a great way to send some flag along in the event itself.

It is meant to be a pointer to a location in memory for some data, but I found an example of someone setting it to some arbitrary integer value (in their case 111) and it presumably worked. You could try that.

It would look something like this:

public static void LeftClick()
{
    mouse_event(((int)KeyStates.LeftDown) | ((int)KeyStates.LeftDown), 0, 0, 0, 111);
}
private void mouseHook_LeftKeyDown(MouseHook.MSLLHOOKSTRUCT ma)
{
    if (ma.dwExtraInfo == 111) return;
    LeftButtonStatus = true;
    StartClicking();
}

Your method signature for mouseHook_LeftKeyUp is different, but is that correct? If you can change that to use MSLLHOOKSTRUCT too, then you can do the same there, if you need to.

In your DllImport attribute, the type of dwExtraInfo should technically be IntPtr, but in this case it shouldn't matter.

Gabriel Luci
  • 38,328
  • 4
  • 55
  • 84
1

This answer consolidates and incorporates info and ideas from other SO posts here and here.


Your question is how to distinguish a physical mouse click from a virtual one. I see that in your code you're using a mouse hook and one way to "close the loop" is by examining the dwExtraInfo flag in the callback per Gabriel Luci's excellent suggestion. But what I set out to do is find a threadsafe approach that doesn't rely on a hook to detect auto clicks so I ended up discarding the mouse hook for my testing. And I tried several things, but what I found was most reliable in my experience is to essentially set a ~100 ms watchdog timer using a thread synchronization object (like the easy-to-use SemaphoreSlim). This semaphore can be tested by whatever target ultimately consumes the click to determine whether the WDT is has expired by calling Wait(0) on the semaphore and looking at the bool return value.

In the first of two tests, I checked the AutoClick button and let it run. As expected, the physical click shows up in black and the auto clicks show up in blue. The indicators all light up as expected.

simple


For the autoClick methods, I used SendInput since mouse_event is obsolete (see Hans Passant comment on this post.

public void autoClick(Control control)
{
    autoClick(new Point 
    { 
        X =  control.Location.X + control.Width / 2,
        Y =  control.Location.Y + control.Height / 2,
    });
}
public void autoClick(Point clientPoint)
{
    Cursor.Position = PointToScreen(clientPoint);

    var inputMouseDown = new INPUT { Type = Win32Consts.INPUT_MOUSE };
    inputMouseDown.Data.Mouse.Flags = (uint)MOUSEEVENTF.LEFTDOWN;
    // Go ahead and decorate with a flag as Gabriel Luci suggests.
    inputMouseDown.Data.Mouse.ExtraInfo = (IntPtr)MOUSEEXTRA.AutoClick;

    var inputMouseUp = new INPUT { Type = Win32Consts.INPUT_MOUSE };
    inputMouseUp.Data.Mouse.Flags = (uint)MOUSEEVENTF.LEFTUP;

    var inputs = new INPUT[]
    {
        inputMouseDown,
        inputMouseUp,
    };
    if (0 == SendInput((uint)inputs.Length, inputs, Marshal.SizeOf(typeof(INPUT))))
    {
        Debug.Assert(false, "Error: SendInput has failed.");
    }
}
[Flags]
enum MOUSEEXTRA : uint{ AutoClick = 0x40000000, }

The handler for the Auto Click 5 checkbox positions and clicks the mouse to light up 5 "indicator" check boxes.

    const int SETTLE = 100;
    SemaphoreSlim _sslimAutoClick = new SemaphoreSlim(1, 1);
    /// <summary>
    /// Responds to the button by auto-clicking 5 times.
    /// </summary>
    private async void onAutoClick(object sender, EventArgs e)
    {
        if (checkBoxAutoClick.Checked)
        {
            checkBoxAutoClick.Enabled = false;
            foreach (var indicator in indicators)
            {
                await _sslimAutoClick.WaitAsync();
                autoClick(indicator);
                // Don't await here. It's for the benefit of clients.
                Task
                    .Delay(SETTLE)
                    .GetAwaiter()
                    .OnCompleted(() =>_sslimAutoClick.Release());

                // Interval between auto clicks.
                await Task.Delay(TimeSpan.FromSeconds(2));
            }
            checkBoxAutoClick.Enabled = true;
            checkBoxAutoClick.Checked = false;
        }
    }

As a more rigorous test, I repeated this but while the 5x autoclick is running I did a few manual clicks to make sure they intersperse and print in black. Again, it worked as expected.

interspersed

If you'd like to browse the full code or experiment with it, the full sample code is on GitHub.

IVSoftware
  • 5,732
  • 2
  • 12
  • 23