Exit loop with pynput input - python

The purpose of my code is to continuously type a letter very fast. It starts when a certain key is pressed (in this case f3) and stops when another key is pressed (f4). My code looks like this currently.
from pynput.keyboard import Controller, Listener
import time
keyboard = Controller()
confirm = False
def on_press(key):
if "f3" in str(key):
global confirm
confirm = True
while confirm:
keyboard.press('e')
keyboard.release('e')
time.sleep(0.10)
elif "Key." in str(key):
pass
def exit_loop(key):
if "f4" in str(key):
global confirm
confirm = False
elif "Key." in str(key):
pass
with Listener(on_press=on_press) as ListenerStart:
ListenerStart.join()
with Listener(on_press=exit_loop) as ListenerEnd:
ListenerEnd.join()
My problem is that, while starting the program with the f3 key works, I am unable to stop the program with f4. Also, the program is supposed to be paused, not exited from. Any help would be appreciated. Thanks.

If you have long-running code like while-loop then you have to run it in separated thread - because it blocks current code and it can't check if you press f4.
If you want to pause code then you should use some variable - ie. paused = True - to control if code inside while-loop should be executed or skiped.
And then you need only one Listener to check keys and check if paused is True or False
from pynput.keyboard import Controller, Listener
import time
import threading
def function():
keyboard = Controller()
while True:
if not paused:
keyboard.press('e')
keyboard.release('e')
time.sleep(0.1)
def on_press(key):
global paused
if paused:
if "f3" in str(key):
paused = False
else:
if "f4" in str(key):
paused = True
# global variables with default values at star
paused = True
# run long-running `function` in separated thread
thread = threading.Thread(target=function) # function's name without `()`
thread.start()
with Listener(on_press=on_press) as listener:
listener.join()
In corrent code you could use the same f3 to start and pause loop.
from pynput.keyboard import Controller, Listener
import time
import threading
def function():
keyboard = Controller()
while True:
if not paused:
keyboard.press('e')
keyboard.release('e')
time.sleep(0.1)
def on_press(key):
global paused
if "f3" in str(key):
paused = not paused
# global variables with default values at star
paused = True
# run long-running `function` in separated thread
thread = threading.Thread(target=function)
thread.start()
with Listener(on_press=on_press) as listener:
listener.join()
This code could be more complex - f3 could check if thread already exists and create thread when it doesn't exist.
from pynput.keyboard import Controller, Listener
import time
import threading
def function():
keyboard = Controller()
while True:
if not paused:
keyboard.press('e')
keyboard.release('e')
time.sleep(0.1)
def on_press(key):
global paused
global thread
if "f3" in str(key):
paused = not paused
if thread is None:
# run long-running `function` in separated thread
thread = threading.Thread(target=function)
thread.start()
# global variables with default values at star
paused = True
thread = None
with Listener(on_press=on_press) as listener:
listener.join()

Related

Hotkey to stop script - Python

Am trying to create a hotkey to stop my script, this is my code so far.
import time
import keyboard
running = True
def stop(event):
global running
running = False
print("stop")
# press ctrl+esc to stop the script
keyboard.add_hotkey("ctrl+esc", lambda: stop)
while running:
time.sleep(2)
print("Hello")
time.sleep(2)
add_hotkey expects a callback as the second argument, so you must pass it the stop function, on the other hand, when the callback is invoked, no event is passed.
A better solution than using a boolean variable is to use threading.Event since this is thread-safe since the callback is invoked in a secondary thread.
import threading
import time
import keyboard
event = threading.Event()
def stop():
event.set()
print("stop")
keyboard.add_hotkey("ctrl+esc", stop)
while not event.is_set():
time.sleep(2)
print("Hello")
time.sleep(2)

How to replace the "KeyboardInterrupt" command for another key?

I have the following code:
while True:
try:
#DoSomething
except KeyboardInterrupt:
break
But instead of using Crtl + C, I want to type another key to end the loop. How can I do this?
You can use the keyboard module:
import keyboard
while True:
if keyboard.is_pressed("some key"):
break
do_something()
This will keep doing something until some key is pressed. Then, it will break out of the endless loop.
To catch hotkeys, use the add_hotkey function:
import keyboard
def handle_keypress(key):
global running
running = False
print(key + " was pressed!")
running = True
keyboard.add_hotkey("ctrl+e", lambda: handle_keypress("Ctrl-E"))
while running:
do_something()
Or you can use pynput:
from pynput.keyboard import Listener
def on_press(key):
print('{0} pressed'.format(
key))
with Listener(
on_press=on_press) as listener:
listener.join()
Here's a simple example of using that keyboard module I mentioned in my second comment. It handles most of steps I mentioned in my first comment and works on several platforms. The loop will stop if and when the user presses the Ctrl + B key.
Note that Ctrl + C will still raise a KeyboardInterrupt.
import keyboard
from time import sleep
def callback(keyname):
global stopped
print(f'{keyname} was pressed!')
stopped = True
keyboard.add_hotkey('ctrl+b', lambda: callback('Ctrl-B'))
stopped = False
print('Doing something...')
while not stopped:
sleep(1) # Something
print('-fini-')

How to clear keyboard event buffer pynput.keyboard

I have a function that is called on_press. However, if the user constantly hits the key the keyboard event buffer queue gets really large and my function (which takes a few hundreds of ms) gets called even after the user has stopped pressing the key. How can I solve this issue?
from pynput import keyboard
def f1():
print("starting f1()..")
# f1 takes time to finish
def on_press(key):
print("pressed some key")
f1()
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
You could create a thread or process to achieve that,If you want to use thread, like this below:
from pynput import keyboard
import threading, time
def task():
time.sleep(3) # Simulate the time of cost
print("task finish")
def f1():
print("starting task..")
# f1 takes time to finish
# create a thread to execute the task.
threading.Thread(target=task).start()
def on_press(key):
print("pressed some key")
f1()
with keyboard.Listener(on_press=on_press) as listener:
listener.join()
For sure, you could reduce the amount of functions,just for easy to understand, so I define 3 functions.
An answer is given here: https://pythonadventures.wordpress.com/2019/11/16/flush-the-stdin/
The solution is to flush the buffer with:
import sys
import termios
termios.tcflush(sys.stdin, termios.TCIOFLUSH)
You can create a queue to store the incoming keyboard keys and only add to it when it is empty like so:
from pynput import keyboard
from queue import Queue
queue = Queue()
def on_press(key):
if queue.empty():
queue.put(key)
listener = keyboard.Listener(on_press=on_press)
listener.start()
while True:
key = queue.get()
f1()

pynput keyboard listener causes delays

I am making a program that toggles on and off by a certain key on the keyboard (using pynput). I placed the keyboard listener loop in the first thread, and the action loop in the second.
The problem is that after I start the code, it doesn't listen to the keyboard immediately, only after 9-10 seconds have passed. And sometimes it refuses to react to Esc button, and sometimes it works. How to fix the lag? Is the code ok?
from threading import Thread
from pynput import keyboard
import time
flag = False
kill = False
def on_press(key):
global flag
global kill
if key == keyboard.KeyCode.from_char('a'):
print('pressed A')
flag = not flag
if key == keyboard.Key.esc:
kill = True
return False
def thr2():
print('joining...')
with keyboard.Listener(on_press=on_press) as listen:
listen.join()
def thr1():
while True:
if kill:
break
if flag:
print('looping....')
time.sleep(0.4)
if __name__ == "__main__":
thread1 = Thread(target=thr1)
thread2 = Thread(target=thr2)
thread1.start()
thread2.start()
It looks like the actual delay is coming from the pynput keyboard.Listener context handler itself. I can't tell you whats happening under the hood but the delay is not coming from the way you are managing your threads.
# pynput library creating keyboard.Listener thread causes the delay
with keyboard.Listener(on_press=on_press) as listen:
print('listen thread created') # This does not happen until after the delay
listen.join()
You may want to rephrase the question so that it is specific to pynput keyboard.Listener
Here is a solution that works nicely with multiprocessing:
import sys
from pynput import keyboard
from time import sleep
from multiprocessing import Process, Event
from functools import partial
def thr2(kill_event, flag_event):
def on_press(kill_event, flag_event, key):
if key == keyboard.KeyCode.from_char('a'):
print('pressed A')
if flag_event.is_set():
flag_event.clear()
else:
flag_event.set()
if key == keyboard.Key.esc:
print('esc')
kill_event.set()
sys.exit(0)
with keyboard.Listener(on_press=partial(on_press, kill_event, flag_event)) as listen:
listen.join()
def thr1(kill_event, flag_event):
while True:
if kill_event.is_set():
print('kill')
sys.exit(0)
if flag_event.is_set():
print('looping....')
sleep(0.4)
if __name__ == "__main__":
kill_event = Event()
flag_event = Event()
thread1 = Process(target=thr1, args=(kill_event, flag_event))
thread2 = Process(target=thr2, args=(kill_event, flag_event))
thread1.start()
thread2.start()
thread1.join() # Join processes here to avoid main process exit
thread2.join()

Python 3 Autoclicker On/Off Hotkey

I'm new to Python and I figured I'd make a simple autoclicker as a cool starter project.
I want a user to be able to specify a click interval and then turn the automatic clicking on and off with a hotkey.
I am aware of Ctrl-C, and you'll see that in my current code, but I want the program to work so that the hotkey doesn't have to be activated in the python window.
import pyautogui, sys
print("Press Ctrl + C to quit.")
interval = float(input("Please give me an interval for between clicks in seconds: "))
try:
while True:
pyautogui.click()
except KeyboardInterrupt:
print("\n")
Do I need to make a tkinter message box to make the switch or can I use a hotkey?
Thanks for the help.
Update
import multiprocessing
import time
import pyHook, pyautogui, pythoncom
import queue
click_interval = float(input("Please give an interval between clicks in seconds: "))
class AutoClicker(multiprocessing.Process):
def __init__(self, queue, interval):
multiprocessing.Process.__init__(self)
self.queue = queue
self.click_interval = click_interval
def run(self):
while True:
try:
task = self.queue.get(block=False)
if task == "Start":
print("Clicking...")
pyautogui.click(interval == click_interval)
except task == "Exit":
print("Exiting")
self.queue.task_done()
break
return
def OnKeyboardEvent(event):
key = event.Key
if key == "F3":
print("Starting auto clicker")
# Start consumers
queue.put("Start")
queue.join
elif key == "F4":
print("Stopping auto clicker")
# Add exit message to queue
queue.put("Exit")
# Wait for all of the tasks to finish
queue.join()
# return True to pass the event to other handlers
return True
if __name__ == '__main__':
# Establish communication queues
queue = multiprocessing.JoinableQueue()
# create a hook manager
hm = pyHook.HookManager()
# watch for all mouse events
hm.KeyDown = OnKeyboardEvent
# set the hook
hm.HookKeyboard()
# wait forever
pythoncom.PumpMessages()
AutoClicker.run(self)
First of all if you want to monitor global input outside the Python window you will need pyHook or something similar. It will allow you to monitor keyboard events. I chose to act upon the F3 and F4 key-presses, which are used for starting and stopping the autoclicker.
To accomplish what you've asked, the best way I'm aware of is to create a process which will do the clicking, and to communicate with it through the use of a Queue. When the F4 key is pressed, it will add an "Exit" string to the queue. The autoclicker will recognize this and then return.
Before the F4 key has been pressed, the queue will remain empty and the queue.Empty exception will continually occur. This will execute a single mouse click.
import multiprocessing
import time
import pyHook, pyautogui, pythoncom
import queue
class AutoClicker(multiprocessing.Process):
def __init__(self, queue, interval):
multiprocessing.Process.__init__(self)
self.queue = queue
self.click_interval = interval
def run(self):
while True:
try:
task = self.queue.get(block=False)
if task == "Exit":
print("Exiting")
self.queue.task_done()
break
except queue.Empty:
time.sleep(self.click_interval)
print("Clicking...")
pyautogui.click()
return
def OnKeyboardEvent(event):
key = event.Key
if key == "F3":
print("Starting auto clicker")
# Start consumers
clicker = AutoClicker(queue, 0.1)
clicker.start()
elif key == "F4":
print("Stopping auto clicker")
# Add exit message to queue
queue.put("Exit")
# Wait for all of the tasks to finish
queue.join()
# return True to pass the event to other handlers
return True
if __name__ == '__main__':
# Establish communication queues
queue = multiprocessing.JoinableQueue()
# create a hook manager
hm = pyHook.HookManager()
# watch for all mouse events
hm.KeyDown = OnKeyboardEvent
# set the hook
hm.HookKeyboard()
# wait forever
pythoncom.PumpMessages()
Keep in mind this implementation is far from perfect, but hopefully it's helpful as a starting point.
Why can't I use a simple while loop or if/else statement?
A while loop is blocking, which means when the loop is running it blocks all other code from executing.
So once the clicker loop starts it will be stuck in that loop indefinitely. You can't check for F4 key presses while this is happening because the loop will block any other code from executing (ie. the code that checks for key-presses). Since we want the auto-clicker clicks and the key-press checks to occur simultaneously, they need to be separate processes (so they don't block each other).
The main process which checks for key presses needs to be able to communicate somehow with the auto-clicker process. So when the F4 key is pressed we want the clicking process to exit. Using a queue is one way to communicate (there are others). The auto-clicker can continuously check the queue from inside the while loop. When we want to stop the clicking we can add an "Exit" string to the queue from the main process. The next time the clicker process reads the queue it will see this and break.

Categories

Resources