Cambuchi
Cambuchi

Reputation: 112

pyautogui mouseDown() not working for automated mouse movement

In chapter 20 of Automate the Boring Things, there is an example that uses pyautogui to draw a spiral. The code uses .drag() to draw each segment, but on each corner the mouse releases the cursor and starts a new line. This creates a break in each corner for whatever brush type you are using.

enter image description here

To address this aesthetic issue I tried to modify the code to use .mouseDown() and .move() instead. However this ended up drawing nothing except a line going from the top left corner to the bottom left corner, as if each iteration of the while loop was repeating a click.

By testing:

while True:
    pyautogui.mouseDown()

In mspaint, the .mousedown() function is indeed performing correctly when I move the mouse myself.

Trying:

while True:
    pyautogui.mouseDown()
    pyautogui.move(300, 0, duration=0.5)

Also correctly draws a line, but does so in 300 pixel chunks. I.e. every time the loop iterated the line segment would load in on mspaint.

Repeating this experiment in a square:

while True:
    pyautogui.mouseDown()
    pyautogui.move(300, 0, duration=0.5)
    pyautogui.move(0, 300, duration=0.5)
    pyautogui.move(-300, 0, duration=0.5)
    pyautogui.move(0, -300, duration=0.5)

Ended up drawing nothing.

What is going on here? Why is the .mouseDown() behavior so inconsistent? How do I get my code to draw a nice unbroken spiral?

Full original code for reference here:

#! python3
# spiralDraw.py - draws a spiral in mspaint

import pyautogui
import time

time.sleep(5)   # give time to go to paint app
distance = 300
change = 20
pyautogui.mouseDown()

while distance > 0:
    pyautogui.move(distance, 0, duration=0.5)   
    distance = distance - change
    pyautogui.move(0, distance, duration=0.5)   
    pyautogui.move(-distance, 0, duration=0.5)  
    distance = distance - change
    pyautogui.move(0, -distance, duration=0.5)
    
if distance == 0:
    pyautogui.mouseUp()

Upvotes: 0

Views: 4013

Answers (2)

Cambuchi
Cambuchi

Reputation: 112

While I am unsure why this is the case, the desired result can be achieved by dividing the mouse hold and the mouse movements into separate threads and running them simultaneously.

Here is the output of the non-multi threaded code with the crayon brush, notice the blocky corners implemented by drag as each line is a separate mouse press:

enter image description here

And here is the result from code that separates the tasks into two threads and runs them simultaneously, as you can see the corners are smooth, and it is all one long line as when I press ctrl+z the entire drawing disappears:

enter image description here

Here is the code that simply implements multithreading:

import pyautogui
import time
import threading

def mouseHoldDown():
    global distance
    while distance > 0:
        pyautogui.mouseDown()
    if distance == 0:
        time.sleep(1)
        pyautogui.mouseUp()
    
def movingSpiralMouse():
    global distance
    change = 20
    while distance > 0:
        pyautogui.move(distance, 0, duration=0.25)
        distance = distance - change
        pyautogui.move(0, distance, duration=0.25)
        pyautogui.move(-distance, 0, duration=0.25)
        distance = distance - change
        pyautogui.move(0, -distance, duration=0.25)

distance = 300
time.sleep(5)

mouseObj = threading.Thread(target=mouseHoldDown)
mouseObj.start()

moveObj = threading.Thread(target=movingSpiralMouse)
moveObj.start()

I would still greatly appreciate an explanation as to why this needs to be separated into multiple threads to achieve the desired results.

Upvotes: 0

Lucas Belfanti
Lucas Belfanti

Reputation: 283

I found that you have to use drag to draw a line, because the move function only moves the pointer without doing the click down (documentation).

However you can still use the mouseDown function instead of the click function, because it "does the same thing as a left-button mouse click" (documentation).

The drag function (at least in the last version of the package) need a parameter called button which could be "left", "right" or "middle" representing each one of the three clicks of the mouse (documentation).

Based on the previous information, the code is:

import pyautogui
import time

time.sleep(5)  # give time to go to paint app
distance = 300
change = 20
pyautogui.mouseDown()

while distance > 0:
    pyautogui.drag(distance, 0, duration=0.5, button='left')
    distance = distance - change
    pyautogui.drag(0, distance, duration=0.5, button='left')
    pyautogui.drag(-distance, 0, duration=0.5, button='left')
    distance = distance - change
    pyautogui.drag(0, -distance, duration=0.5, button='left')

if distance < 0:
    pyautogui.mouseUp()

Upvotes: 0

Related Questions