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()
Related
I recently got to know about the python module signal. With that, we can capture a SIGINT and do what we want after capturing it. I used it as below. In that case I am just using SIGINT to print that program is going to be stopped and stop the program.
import signal
import os
import time
def signalHandler(signalnumb, frame):
print("Signal Number:", signalnumb, " Frame: ", frame)
print('Exiting the program...')
os._exit(0)
signal.signal(signal.SIGINT, signalHandler)
c=0
# Loop infinite times using the while(1) which is always true
while 1:
print(c)
#sleep for 1 seconds using the sleep() function in time
time.sleep(1)
c=c+1
Now I want to give any signal from keyboard(for example pressing 'q') and as soon as signal was recieved, the python program should be stopped. Has anyone got some experience on how to do that? Any other method rather than using signal module (for example using multithreading) is accepted.
Edit1-
Later I tried to use pynput module as suggested in one of a similar kind of question. For sure I have done a mistake. It doesn't work as I expected. It means with a key press, I couldn't stop the for loop from running.
from pynput import keyboard
import time
def on_press(key):
for i in range(100):
print(i)
time.sleep(1)
if key == keyboard.Key.esc:
return False # stop listener
try:
k = key.char # single-char keys
except:
k = key.name # other keys
if k in ['1', '2', 'left', 'right']: # keys of interest
# self.keys.append(k) # store it in global-like variable
print('Key pressed: ' + k)
return False # stop listener; remove this if want more keys
listener = keyboard.Listener(on_press=on_press)
listener.start() # start to listen on a separate thread
listener.join() # remove if main thread is polling self.keyspython
Can someone point out how to do it using pynput in correct way?
This was my original implementation:
a = input('Press a key to exit')
if a:
exit(0)
However, it seems that you need a piece of code that will allow for any key to be clicked and immediately exit out of the program, without hitting enter afterwards. This may be a better way to do that:
import readchar
print("Press Any Key To Exit")
k = readchar.readchar()
Hope this helps!
After carefully understanding about the threads and pynput module, I managed to stop a for loop (or any program which runs as a separate thread) using a key press callback.
from pynput import keyboard
import os
import threading
import time
loop_running = False
def on_press(key):
print(dir(key))
global loop_running
#if key == keyboard.Key.delete and not loop_running:
if ('char' in dir(key)) and (key.char == 's') and (not loop_running):
t=threading.Thread(target=start_loop)
t.start()
#elif key==keyboard.Key.tab: #if this is used, the attributes related to 'key' will be changed. you can see them since I have used a print(dir(key))
elif key.char == 'q':
loop_running=False
def start_loop():
global loop_running
loop_running = True
for i in range(100):
if not loop_running:
os._exit(0)
print(i)
time.sleep(1)
with keyboard.Listener(on_press=on_press) as listner:
listner.join()
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()
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()
I have a Function which keep listening.. I want to stop the Listener after a particular time
import time
from pynput.keyboard import Listener
with Listner(on_press=onPress) as l:
l.join
this is an Endless Loop... i want to Stop the listener after a particular time
You could use timer:
from threading import Timer
from pynput.keyboard import Listener
def on_press(key):
print(key)
with Listener(on_press=on_press) as l:
Timer(5, l.stop).start()
l.join()
print('5 seconds passed')
import time
from pynput.keyboard import Listener
from threading import Thread
def on_press(key):
print(f"Key pressed: {key}")
with Listener(on_press=on_press) as ls:
def time_out(period_sec: int):
time.sleep(period_sec) # Listen to keyboard for period_sec seconds
ls.stop()
Thread(target=time_out, args=(5.0,)).start()
ls.join()
After some research on how to properly ask a thread to stop, I am stuck into an unexpected behavior.
I am working on a personal project. My aim is to run a program on a RaspberryPi dedicated to domotics.
My code is structured as below:
a first thread is dedicated to scheduling : everyday at the same hour, I send a signal on GPIO output
a second thread is dedicated to monitoring keyboard for manual events
whenever a specific key is pressed, I want to start a new thread that is dedicated to another routine just like my first thread
Here is how I proceed:
import schedule
from pynput import keyboard
import threading
first_thread = threading.Thread(target=heating, name="heating")
second_thread = threading.Thread(target=keyboard, name="keyboard")
first_thread.start()
second_thread.start()
stop_event = threading.Event()
My heating routine is defined by:
def heating():
def job():
GPIO.output(4,GPIO.HIGH)
return
schedule.every().day.at("01:00").do(job)
while True:
schedule.run_pending()
time.sleep(0.5)
My keyboard monitor is defined as follow:
def keyboard():
def on_press(key):
if key == keyboard.Key.f4:
shutter_thread = threading.Thread(name="shutter", target=shutter, args=(stop_event,))
shutter_thread.start()
if key == keyboard.Key.f5:
stop_event.set()
with keyboard.Listener(on_press=on_press,on_release=on_release) as listener:
listener.join()
My shutter thread target is similar to the heating one:
def shutter(stop_event):
def open():
GPIO.output(6,GPIO.HIGH)
return
t = threading.currentThread()
schedule.every().day.at("22:00").do(open)
while not stop_event.is_set():
schedule.run_pending()
time.sleep(0.5)
Problem is everytime I press the key to start my shutter thread, the shutter routine is called but:
the job within my shutter routine is executed twice
the job within the first thread is also now executed twice every time it is on schedule !
once I press the key to ask the shutter thread to stop, the heating (first) thread come back to its original (and correct) behaviour, but the shutter thread does not stop
I have no idea why starting this new thread yields such modification in the behaviour of the other thread. And why my stopping event is not working ?
What am I doing wrong ?
Since you are using the schedule framework for managing tasks a clean solution would be to use the same framework's API for canceling jobs (instead of using threading.Event). That way tasks management remains within schedule and user interaction is handled by threading.
def keyboard():
tasks = []
def on_press(key):
if key == keyboard.Key.f4:
# Shutter task.
tasks.append(
schedule.every().day.at("22:00").do(lambda: GPIO.output(6,GPIO.HIGH))
)
if key == keyboard.Key.f5:
schedule.cancel_job(tasks.pop(-1))
with keyboard.Listener(on_press=on_press,on_release=on_release) as listener:
listener.join()
# Heating task.
schedule.every().day.at("01:00").do(lambda: GPIO.output(4,GPIO.HIGH))
# Start keyboard listener.
ui = threading.Thread(target=keyboard)
ui.start()
while True:
schedule.run_pending()
time.sleep(0.5)
Even if a_guest solution is a clean one, I can share a second solution for those who can face a similar situation.
A working solution is to define a specific scheduler in the different threads instead of using the default one.
Illustration:
def heating():
def job():
GPIO.output(4,GPIO.HIGH)
return
heat_sched = schedule.Scheduler()
heat_sched.every().day.at("01:00").do(job)
while True:
heat_sched.run_pending()
time.sleep(1)
def shutter(stop_event):
def open():
GPIO.output(6,GPIO.HIGH)
return
shutter_sched = schedule.Scheduler()
shutter_sched.every().day.at("22:00").do(open)
while True:
if not stop_event.is_set():
shutter_sched.run_pending()
time.sleep(0.5)
else:
shutter_sched.clear()
return