Search code examples
pythonwinapipython-3.5pyhookpythoncom

Python script monitors click events but blocks them, not allowing the click event to pass through


I've written a simple program which allows you to record click events and save them. You can then load the save again, and it will fire all the click events again as you saved them. For the folks wondering, I wrote this program a year ago for learning purposes. It worked perfectly then. I wanted to use it today again, but it doesn't run properly anymore.

I launch the program, and select option 1 which calls the selectAndSavePoints method. It waits till I press the key s to start, and then proceeds to monitor click events. The problem is, that it blocks them off. I can see that the position of the click event is written in the console, but when I as example click on my explorer icon in the task-bar to open it, it doesn't open because the click event is being hijacked instead of monitored and allowed to pass through to the Windows operating system.

I tried fixing this by copying my click method in the loadAndExecutePoints method and placing it inside the MonitorMouseClicks method above the declaration of the global coordsToClick variable. I'd then inside the onclick method call the click method to manually click it again. When I run this, it registers the first click, I can see it logged again, and then the program should manually click it for me, but the process then hangs. Pressing alt + f4, ctrl + break or taskkilling the program doesn't work then. I have to restart my computer for the command line window to be gone. I have no clue what is happening over here.

from keyboard import wait
from threading import Thread
from pythoncom import PumpMessages
from ctypes import windll
from pyHook import HookManager
from os import listdir
from win32api import mouse_event
from win32api import SetCursorPos
from win32con import MOUSEEVENTF_LEFTDOWN
from win32con import MOUSEEVENTF_LEFTUP
from time import sleep

coordsToClick = []

def sanitised_input(prompt, type_=None, min_=None, max_=None, range_=None, distinct_=False):
    if min_ is not None and max_ is not None and max_ < min_:
        raise ValueError("min_ must be less than or equal to max_.")
    while True:
        ui = input(prompt)
        if type_ is not None:
            try:
                ui = type_(ui)
            except ValueError:
                print("Input type must be {0}.".format(type_.__name__))
                continue
        if max_ is not None and ui > max_:
            print("Input must be less than or equal to {0}.".format(max_))
        elif min_ is not None and ui < min_:
            print("Input must be greater than or equal to {0}.".format(min_))
        elif range_ is not None and ui not in range_:
            if isinstance(range_, range):
                print("Input must be between {0.start} and {0.stop}.".format(range_))
            else:
                if len(range_) == 1:
                    print("Input must be {0}.".format(*range_))
                else:
                    print("Input must be {0}.".format(" or ".join((", ".join(map(str, range_[:-1])), str(range_[-1])))))
        elif distinct_ and not len(ui) == len(set(ui)):
            print("Input should only contain unique characters!")
        else:
            return ui

def selectAndSavePoints():
    print("Press the key 's' to start selecting points")
    print("Press the key 'p' to cancel last selected point, so previous action is undone. Does work multiple times")
    print("Once you're finished, press the key 'f' to save them")
    wait("s")
    def MonitorKeyPresses(_hookManager):
        global coordsToClick
        def onpress(event):
            if event.Key == "P":
                del coordsToClick[-1]
            return 0
        _hookManager.SubscribeKeyDown(onpress)
        _hookManager.HookKeyboard()
        PumpMessages()
    def MonitorMouseClicks(_hookManager):
        global coordsToClick
        def onclick(event):
            coordsToClick.append(event.Position)
            print(event.Position)
            return 0
        _hookManager.SubscribeMouseLeftDown(onclick)
        _hookManager.HookMouse()
        PumpMessages()
    hookManager = HookManager()
    threadClick = Thread(target = MonitorMouseClicks, args = (hookManager,))
    threadClick.start()
    threadPress = Thread(target = MonitorKeyPresses, args = (hookManager,))
    threadPress.start()
    wait('f')
    windll.user32.PostQuitMessage(0)
    hookManager.UnhookMouse()
    hookManager.UnhookKeyboard()
    filename = input("Enter the filename: ")
    file = open("../Saves/" + filename + ".txt", 'w')
    for coords in coordsToClick:
        file.write(str(coords[0]) + ":" + str(coords[1]) + "\n")
    file.close()


def loadAndExecutePoints():
    def click(x, y):
        SetCursorPos((x, y))
        mouse_event(MOUSEEVENTF_LEFTDOWN,x,y,0,0)
        mouse_event(MOUSEEVENTF_LEFTUP,x,y,0,0)
    files = listdir("../Saves")
    for i in range(len(files)):
        print("[" + str(i) + "]: " + files[i])
    filenumber = sanitised_input("Enter the file mumber: ", type_=int, range_=range(len(files)))
    filename = files[filenumber]
    print("Press 's' to start executing the clicks")
    wait('s')
    lines = [line.rstrip('\n') for line in open('../Saves/' + filename)]
    for line in lines:
        components = line.split(":")
        click(int(components[0]), int(components[1]))
        sleep(0.2)

def main():
    print("Select a option")
    print("[1]: Select and Save Points")
    print("[2]: Load and Execute Points")
    option = sanitised_input("", type_=int, range_=(1, 2))
    if(option == 1):
        selectAndSavePoints()
    else:
        loadAndExecutePoints()

while(True):
    main()

I hope to be able to find someone here who can help me figure out why this application has stopped working, what the exact problem is and how I can work around it or fix it. As you can see in the program, I use quite a lot imports. Most of them are default modules, but here are the instructions to get the modules which aren't default.

pip install keyboard
pip install pywin32

The pyHook module can be downloaded here https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyhook. I downloaded the pyHook‑1.5.1‑cp35‑cp35m‑win_amd64.whl file which you can then install by using this command

pip install pyHook‑1.5.1‑cp35‑cp35m‑win_amd64.whl

I am using python 3.5.4 by the way.


Solution

  • You need to set the return values of onKeyboardEvent(MonitorKeyPresses) and onMouseEvent(MonitorMouseClicks) to True, which is a normal call, and if it is False, the event will be intercepted.

    def MonitorKeyPresses(_hookManager):
        global coordsToClick
        def onpress(event):
            if event.Key == "P":
                del coordsToClick[-1]
            return True
    ...
    
    def MonitorMouseClicks(_hookManager):
        global coordsToClick
        def onclick(event):
            coordsToClick.append(event.Position)
            print(event.Position)
            return True
    

    (BTW, You don't need to restart the computer. The right button is not blocked. You can right-click the console and right-click close.)