mathiasxx94 mathiasxx94 - 4 days ago 8
Python Question

Python: Counting while mouse Down, stop when mouse Up

I'm trying to make a python script that starts counting from 0 when the mouse button is pressed. My idea is to use pyHook to go into a function when left mouse button is pressed and exit the function when left mouse is released. I'm pretty new to python so sorry for bad explanations.
Some pseudocode:

import pyHook
import pythoncom

def termin():
return None
def counter(tell):
a=0
while True:
print a
a+=1
hm = pyHook.HookManager()
hm.SubscribeMouseLeftUp(termin)

hm = pyHook.HookManager()
hm.SubscribeMouseLeftDown(counter)
hm.HookMouse()
pythoncom.PumpMessages()
hm.UnhookMouse()


This code is my general idea, however I don't think it will work because SubscribeMouseLeftUp happens at a discrete time. What i'm looking for is maybe running the counter function and termin function in some kind of threading or multiprocessing module and use conditions in one function to terminate the other running function. But I'm not sure how to make this work.

Okay, so I tried this script after willpower's comment:

import pyHook,time,pythoncom

def counter(go):
for a in range(5):
time.sleep(1)
print a
return True

hm=pyHook.HookManager()
hm.SubscribeMouseLeftDown(counter)
hm.HookMouse()
pythoncom.PumpMessages()
hm.UnhookMouse()


The accepted answer from willpower2727 is the best solution I've seen so far. Before he posted his solution using threading I made the following code:

from multiprocessing import Process,Queue
import pyHook
import time
import pythoncom
import ctypes

def counter(tellerstate,q):
while True:
a=0
tellerstate=q.get()
if tellerstate==1:
while True:
a+=1
print a
tellerstate=q.get()
if tellerstate==0:
break
time.sleep(0.1)

def mousesignal(q):
def OnDown(go):
tellstate=1
q.put(tellstate)
return None

def OnUp(go):
tellstate=0
q.put(tellstate)
return None

def terminate(go):
if chr(go.Ascii)=='q' or chr(go.Ascii)=='Q':
ctypes.windll.user32.PostQuitMessage(0)
hm.UnhookKeyboard()
hm.UnhookMouse()
q.close()
q.join_thread()
process_counter.join()
process_mousesignal.join()
return None

hm=pyHook.HookManager()
hm.KeyDown = terminate
hm.MouseLeftDown = OnDown
hm.MouseLeftUp = OnUp
hm.HookMouse()
hm.HookKeyboard()
pythoncom.PumpMessages()

if __name__ == '__main__':
tellerstate=0
q=Queue()
process_counter = Process(target=counter,args=(tellerstate,q))
process_mousesignal = Process(target=mousesignal,args=(q,))
process_mousesignal.start()
process_counter.start()


My expected behaviour of this code is that the counter and mousesignal functions should run as separate processes. In the mousesignal process I'm putting either a 0 or 1 to a Queue based on mouse input. The counter function runs continuously and reads the Queue and uses if statements to enter and quit the loop in this function. This code doesn't work at all, but I can't understand why.

Answer

Ok I have an example of using threading to do something while the mouse button is held down. This example breaks/gets stuck if the user double-clicks the mouse very quickly. It uses a lock and a thread that only executes certain code when the lock is released (triggered by mouse down).

import time
import threading
import pyHook
import pythoncom

def DoThis(Cond):
    while True:
        with Cond: #calling "with" automatically calls acquire() on the lock
            print(time.time())
    print('stopping...')

global Cond
Cond = threading.Lock()#create a threading lock
Cond.acquire() #give the lock to the main thread

global t1
t1 = threading.Thread(target=DoThis,args=(Cond, )) #initialize the thread that does stuff while mouse button is down
t1.start() #start the thread, it won't do anything until mouse button is down

def OnDown(go):
    global Cond
    Cond.release() #allow the thread to acquire the lock
    print('Lock released')
    return True

def OnUp(go):
    global Cond
    Cond.acquire() #take the lock away from the thread
    print('Lock acquired')
    return True

hm = pyHook.HookManager()
hm.MouseLeftDown = OnDown
hm.MouseLeftUp = OnUp
hm.HookMouse()
pythoncom.PumpMessages()

The challenge with using threads is that they can only be started once so you couldn't call thread.start() everytime the mouse is pressed, it would only work the first time. By allowing the thread to stay alive but not do anything (except constantly check if it should) enables the possibility to only execute some code when the mouse is down. There are some more sophisticated ways to do this that might improve the processing load on the computer (maybe using a thread condition instead of a regular lock) but this is the general idea I had.

Comments