pythonLearner
pythonLearner

Reputation: 11

Taking Single Click Event and Propagating it to Multiple Areas - python, pynput, pyautogui

Having trouble turning 1 mouse click into multiple mouse clicks. Basically what I want to do is to control multiple windows at once. I want to click on one master window and have the clicks propagate to the subsequent windows. In this snippet there are 4 windows and I track them via determining the offset between it and the master window.

I'm using python3 with pynput for the mouse listener and pyautogui for mouse control.

What I'm having a problem with is setting up the mouse listener such that it listens to my actual clicks but ignores the programmatic clicks. Right now, I think it's getting stuck in an infinite loop where my initial click triggers the on_click event, propagates the clicks, each triggering an additional on_click event, propagates the clicks, etc. When I run the below code it starts fine, and then when I first click it just heavily lags my mouse for a minute before return back to normal with no mouse listener active anymore. My guess is that a failsafe kicks in to return it to normal.

Things I have tried:

My snippet is below:

from pynput import mouse, keyboard
import pyautogui

pyautogui.PAUSE = 0.01
mouseListener = None
killSwitch = False

# this is just a keyboard listener for a kill switch
def on_release(key):
    if key == keyboard.Key.f1:
        global killSwitch
        print('@@@ Kill switch activated @@@')
        killSwitch = True

# on mouse release I want to propogate a click to 4 other areas
def on_click(x, y, button, pressed):
    print('{0} at {1}'.format('Pressed' if pressed else 'Released', (x, y)))
    if not pressed:
        propogateActions(x, y, button)

# propogates clicks
def propogateActions(x, y, button):
    print('propogating actions to {0} windows'.format(len(offsets)+1))
    for offset in offsets:
        pyautogui.moveTo(x+offset.x, y+offset.y)
        print('mouse moved')
        if button == mouse.Button.left:
            print('left clicking at ({0}, {1})'.format(x+offset.x, y+offset.y))
            pyautogui.click()
    pyautogui.moveTo(x, y)

# point class for ease of use
class Point():
    def __init__(self, x, y):
        self.x = x
        self.y = y
    def __repr__(self):
        return 'Point(x={0}, y={1})'.format(self.x, self.y)

# main method
def doTheThing():
    print('started')
    while not killSwitch:
        pass

# initializations and starting listeners
# offsets tracks how far the subsequent clicks are from the initial click point
offsets = [Point(50, 0), Point(50, 50), Point(0, 50)]
keyboardListener = keyboard.Listener(on_release=on_release)
mouseListener = mouse.Listener(on_click=on_click)
keyboardListener.start()
mouseListener.start()
doTheThing()

My Question:

This is the small section of code that's relevant to the issue at hand. offsets has an initialization that sets it more appropriately and there's other bells and whistles, but this is the section relevant to the problem. I appreciate your help.

Upvotes: 0

Views: 1338

Answers (1)

pythonLearner
pythonLearner

Reputation: 11

Found the answer! Had to go a layer deeper.

Pynput has a method of suppressing events that exposes the win32 data behind the click event. Ran a test of one of my clicks vs a pyautogui.click() and lo-and-behold there is a difference. The data.flags was set to value 0 on a user click event and set to value 1 on a programmatic click.

That's good enough for me to filter on. This is the pertinent filter:

def win32_event_filter(msg, data):
    if data.flags:
        print('suppressing event')
        return False

added that to my above code and changed the

mouseListener = mouse.Listener(on_click=on_click)

to

mouseListener = mouse.Listener(on_click=on_click, win32_event_filter=win32_event_filter)

and it works!

My real clicks prevail, programmatic clicks are propagated, and I am not stuck in an infinite loop. Hope this helps if others run into this issue.

Upvotes: 1

Related Questions