Pynput ignore key held - python

I have a program that prints time elapsed when a user first presses and holds down a key and prints again when the key is released. If after 5 seconds, the user presses and holds the up arrow on their keyboard for 3 seconds before releasing, the program should print "0:05 response 1 ON" and then "0:08 response 1 OFF". The problem I'm having is that holding down the key registers as multiple key presses, resulting in the time being printed multiple times per second. Any ideas how to treat a key being pressed and held as a single key press?
import time
from pynput import keyboard
from pynput.keyboard import Key, Listener
f = open("quick_data.txt", "a")
f.write(time.ctime() + "\n")
def show(key):
if key == keyboard.Key.enter:
global start
start = time.perf_counter()
if key == keyboard.Key.delete:
return False
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 ON" + "\n")
def on_release(key):
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 OFF" + "\n")
with keyboard.Listener(
on_press=show,
on_release=on_release) as listener:
listener.join()

The library does not natively provide key press holding support, but you can easily utilize a timer to emulate said functionality.
Just initiate a timer at the start to register the time when the key associated with "RESPONSE 1" was pressed. If the new key input's time differs from the last keypress's time by more than the threshold, then it's (probably) a new keypress, and hence you write to the file "response 1 ON". Then you reset the time of the last detected keypress of RESPONSE 1 to ensure the keypress algorithm is in sync. When you release the key, on_release triggers and completes the pair. Of course, you can adjust and augment the threshold to suit your specific needs.
from pynput import keyboard
from pynput.keyboard import Key, Listener
f = open("quick_data.txt", "a")
f.write(time.ctime() + "\n")
pushdown_up = time.perf_counter() # initialize variable to store the time when the key associated with RESPONSE 1 was pressed
def show(key):
threshold = 0.10 # threshold for input timing to differentiate between a key being held and a new key
if key == keyboard.Key.enter:
global start
start = time.perf_counter()
print(start)
if key == keyboard.Key.delete:
return False
if key == keyboard.Key.up:
elapsed = time.perf_counter()
global pushdown_up # time of last detected keypress
if elapsed - pushdown_up < threshold:
# if another INPUT 1 press is detected again within a short threshold, then it is probably part of the current input press
pass
else: # else, then it is a start of a new input.
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 ON" + "\n")
print(str(y) + " response 1 ON" + "\n")
pushdown_up = elapsed # set the last registered keypress's time\
def on_release(key):
if key == keyboard.Key.up:
elapsed = time.perf_counter()
x = time.gmtime(elapsed - start)
y = time.strftime('%M:%S', x)
f.write(str(y) + " response 1 OFF" + "\n")
print(str(y) + " response 1 OFF" + "\n")
with keyboard.Listener(
on_press=show,
on_release=on_release) as listener:
listener.join()

Related

How to prevent users from pressing key too early in a reaction time test

I try to make a reaction time test in python.
The code works fine but users can press enter key too early which results in their reaction time being 0.0.
Code
import time
import random
print('When you see GO! press enter.')
q1 = input('Type y to start:')
if q1 == 'y' or q1 == ' y' or q1 == 'Y' or q1 == ' Y':
pass
else:
print('---------')
print('***END***')
print('---------')
quit()
print('Get Ready!')
time.sleep(random.randint(3,5))
print('---------')
print(' G0! ')
print('---------')
a = time.perf_counter()
input()
b = time.perf_counter()
timespent = b-a
print('Your reaction time is ', round(timespent, 3))
I am very new to python.
How to prevent or ignore premature key presses?
Issue supposed
I can't figure out the issue. Seems like if enter pressed it is recorded in the STDIN buffer. When input() is invoked later, it will be returned there immediately - without waiting on a new key pressed. See Python STDIN User Input Issue.
Solution
But you can use any other key input library as explained in
How to detect key presses?.
Example with pynput
Install pynput and modify your code as shown below:
import time
import random
# Use pynput to listen for key press and release events
from pynput.keyboard import Key, Listener
def on_press(key):
print('{0} pressed'.format(key))
# could also react on ENTER key pressed here
def on_release(key):
print('{0} release'.format(key))
if key == Key.enter: # react on ENTER key released
# Stop listener
return False
# What you had so far
print('When you see GO! press enter.')
q1 = input('Type y to start:')
if q1 == 'y' or q1 == ' y' or q1 == 'Y' or q1 == ' Y':
pass
else:
print('---------')
print('***END***')
print('---------')
quit()
print('Get Ready!')
time.sleep(random.randint(3,5))
print('---------')
print(' G0! ')
print('---------')
a = time.perf_counter()
# instead of input() collect events until released
with Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
b = time.perf_counter()
timespent = b-a
print(f"start: {a}, end: {b}, duration: {timespent}") # debug print
print('Your reaction time is ', round(timespent, 3), 'seconds') # added unit seconds

Keyboard input (via pynput) and threads in python

I'm trying to make a kind of text-based game in Python 3. For the game I will need to listen for keyboard input, in particular measuring how long a key is held down, while printing things to the screen. I'm trying to start by making a working minimal example.
First, the following code, using pynput, appears to successfully measures the length time for which the user holds down a key:
from pynput import keyboard
import time
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
def on_press(key):
global keys_currently_pressed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
def on_release(key):
global keys_currently_pressed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
listener.join()
Now what I'd like to do is, only while a key is pressed down by the user, print a text-based "animation" to the screen. In the following example my "animation" is simply printing "*" every half second.
So far I've tried to have the "animation" handled by a second thread but I am a total novice when it comes to multithreading. The following code will start the animation at the correct time but won't stop it.
from pynput import keyboard
import sys
import time
import threading
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
def my_animation():
# A simple "animation" that prints a new "*" every half second
limit = 60 # just in case, don't do more than this many iterations
j = 0
while j<limit:
j += 1
sys.stdout.write("*")
time.sleep(0.5)
anim = threading.Thread(target=my_animation)
def on_press(key):
global keys_currently_pressed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
anim.start()
def on_release(key):
global keys_currently_pressed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener: listener.join()
Here's an approach (following #furas's comment) where the animation is coded after the with statement, however I cannot get this to work for me:
from pynput import keyboard
import time
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
# animation flag
anim_allowed = False
def on_press(key):
global keys_currently_pressed
global anim_allowed
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed:
keys_currently_pressed[key] = time.time()
anim_allowed = True
def on_release(key):
global keys_currently_pressed
global anim_allowed
if key in keys_currently_pressed:
animate = False
duration = time.time() - keys_currently_pressed[key]
print("The key",key," was pressed for",str(duration)[0:5],"seconds")
del keys_currently_pressed[key]
anim_allowed = False
if key == keyboard.Key.esc:
# Stop the listener
return False
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
while anim_allowed:
sys.stdout.write("*")
time.sleep(0.5)
listener.join()
Ultimately I want to be able to do this with more complex animations. For example
def mysquare(delay):
print("#"*10)
time.sleep(delay)
for i in range(8):
print("#" + " "*8 + "#")
time.sleep(delay)
print("#"*10)
What's the right way to approach this? Many thanks!
Listener already uses thread so there is no need to run animation in separated thread. You can run it in current tread in
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
#... your code ...
listener.join()
or without with ... as ...
listener = keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True)
listener.start()
#... your code ...
#listener.wait()
listener.join()
You can run there even long runing code - ie. endless while loop which will check if variable animate is True and write new *.
I had to add sys.stdout.flush() on my Linux to see * on screen.
My version:
It runs animation all time when you press any button but there is also code with variable counter to limit animation to 6 moves. If you press new key when it runs animation then it reset this counter and animation will longer.
This loop has to run all time to check if there is new animation - you can't finish this loop when animation is finished
from pynput import keyboard
import sys
import time
# --- functions ---
def on_press(key):
global keys_currently_pressed
global animate
#global counter
# Record the key and the time it was pressed only if we don't already have it
if key not in keys_currently_pressed and key != keyboard.Key.esc:
keys_currently_pressed[key] = time.time()
animate = True
#counter = 0 # reset counter on new key
def on_release(key):
global keys_currently_pressed
global animate
if key in keys_currently_pressed:
duration = time.time() - keys_currently_pressed[key]
print("The key", key, "was pressed for", str(duration)[0:5], "seconds")
del keys_currently_pressed[key]
if not keys_currently_pressed:
animate = False
if key == keyboard.Key.esc:
# Stop the listener
return False
# --- main ---
print("Press and hold any key to measure duration of keypress. Esc ends program")
# A dictionary of keys pressed down right now and the time each was pressed down at
keys_currently_pressed = {}
animate = False # default value at start (to use in `while` loop)
#limit = 6 # limit animation to 6 moves
#counter = 0 # count animation moves
with keyboard.Listener(on_press = on_press, on_release=on_release, suppress=True) as listener:
while listener.is_alive(): # infinite loop which runs all time
if animate:
#sys.stdout.write("\b *") # animation with removing previous `*`
sys.stdout.write("*") # normal animation
sys.stdout.flush() # send buffer on screen
#counter += 1
#if counter >= limit:
# counter = 0
# animate = False
time.sleep(0.5)
listener.join()

Why are these lines of code causing keyboard lag and is there an alternative?

So I am learning python and I am trying to create a code that detects the time between pressing "space" and "a" for something in Minecraft. The problem is that this program lags my keyboard/causes delay in keyboard presses.
I've narrowed the issue down to this:
while True:
if keyboard.is_pressed ('p'):
strafe45()
If I replace this with something like this: It does not cause keyboard lag.
run = 1
while run == 1:
strafe45()
I think it is because the first is constantly checking if I am typing 'p' or not, which is the cause of the lag, but how else can I write something like that? I cannot use while run == 1: because eventually it gives me an error since I am holding down 'a' and the variable 'start' does not have an assigned value any more.
Here is the full code if needed:
import keyboard
import time
import math
def strafe45():
while True:
if keyboard.is_pressed ('space'):
print ("starting timer")
start = time.perf_counter()
time.sleep(0.05)
if keyboard.is_pressed ('a'):
end = time.perf_counter()
print ("ending timer")
tickTime = end - start
tick = 0.05
if tickTime > tick:
print ("Did not make strafe. Too slow by " + str(tickTime - tick) + "\n" +
"Time passed (r): " + str(round(tickTime/tick, 2)) + "\n" +
"Time passed (a): " + str(tickTime/tick))
break
if tickTime < tick:
print ("Did make strafe by " + str(tick - tickTime) + "\n" +
"Time passed (r): " + str(round(tickTime/tick, 2)) + "\n" +
"Time passed (a): " + str(tickTime/tick))
break
run = 1
while run == 1:
strafe45()
"""while True:
if keyboard.is_pressed ('p'):
strafe45()"""
while True:
if keyboard.is_pressed ('p'):
strafe45()
When the p key is pressed, strafe45 gets called, and some sleep calls happen as a result.
As long as the p key is not being pressed, there is a tight while loop that keeps checking for when the key gets pressed.
You should have a single while loop, outside the key-handling function, and ensure that a time.sleep call occurs every time through this loop - by putting it in the loop explicitly. If you call out to functions to handle the keys (a good idea as the code becomes more complicated), they should not have their own loop - they should just make the appropriate changes to the program state according to what was pressed.
For example:
begin = None
def begin_timing():
global begin
begin = time.perf_counter()
def end_timing():
global begin
if begin is not None: # otherwise, we weren't timing yet.
end = time.perf_counter()
print('elapsed time:', end - begin)
begin = None # so that we can begin timing again.
while True:
# There needs to be a delay each time through the loop,
# but it needs to be considerably shorter than the time interval you're
# trying to measure.
time.sleep(0.01)
if keyboard.is_pressed('b'): # begin
begin_timing()
elif keyboard.is_pressed('e'): # end
end_timing()
Rather than constantly checking each loop, add a hook and check only when a key is pressed. keyboard.on_press(callback) adds a listener to every keyboard and key that envokes the given callback. This should alleviate your latency issues. Check out the Keyboard API Page for full documentation
def check_key(x): #x should be an keyboard.KeyboardEvent
print x.name, x.scan_code, x.time
if x.name == "":
something
elif x.name == "":
something else
keyboard.on_press(check_key)

How do I exit a while loop from a keypress without using KeyboardInterrupt? [python]

I am making a timer that beeps every x seconds but the timer restarts during a certain keypress.
The first part of the code gets the timer to start.
Then it goes into a while loop for the timer. I want to interrupt the loop without pressing keyboard interrupt but rather another key.
Any help?Here is the code below
import time, winsound, keyboard
x = 0
while x == 0:
if keyboard.is_pressed(','):
x = x+1
while True:
try:
while x==1:
for i in range(29):
time.sleep(1)
print(i)
if i == 28:
winsound.Beep(300,250)
except KeyboardInterrupt:
continue
Here is the example I promised you.
I did not need to import any mods for this program, but I believe the msvcrt mod that I use to control keyboard input is Windows specific. Be that as it may; even though we use different methods to control keyboard input, I hope you will see how your stopwatch can be controlled by key presses while a main program repeatedly loops and handles keyboard input.
import time # Contains the time.time() method.
import winsound # Handle sounds.
import msvcrt # Has Terminal Window Input methods
# ===========================================
# -------------------------------------------
# -- Kbfunc() --
# Returns ascii values for those keys that
# have values, or zero if not.
def kbfunc():
return ord(msvcrt.getch()) if msvcrt.kbhit() else 0
# -------------------------------------------
# -- Get_Duration() --
# Gets the time duration for the stopwatch.
def get_duration():
value = input("\n How long do you want the timer to run? ")
try:
value = float(value)
except:
print("\n\t** Fatal Error: **\n Float or Integer Value Expected.\n")
exit()
return value
# ===========================================
# Body
# ===========================================
# To keep the program as simple as possible, we will only use
# standard ascii characters. All special keys and non-printable
# keys will be ignored. The one exception will be the
# carriage return key, chr(13).
# Because we are not trapping special keys such as the
# function keys, many will produce output that looks like
# standard ascii characters. (The [F1] key, for example,
# shares a base character code with the simicolon.)
valid_keys = [] # Declare our valid key list.
for char in range(32,127): # Create a list of acceptable characters that
valid_keys.append(char) # the user can type on the keyboard.
valid_keys.append(13) # Include the carriage return.
# ------------------------------------------
duration = 0
duration = get_duration()
print("="*60)
print(" Stopwatch will beep every",duration,"seconds.")
print(" Press [!] to turn Stopwatch OFF.")
print(" Press [,] to turn Stopwatch ON.")
print(" Press [#] to quit program.")
print("="*60)
print("\n Type Something:")
print("\n >> ",end="",flush = True)
run_cycle = True # Run the program until user says quit.
stopwatch = True # Turn the stopwatch ON.
T0 = time.time() # Get the time the stopwatch started running.
while run_cycle == True:
# ------
if stopwatch == True and time.time()-T0 > duration: # When the duration
winsound.Beep(700,300) # is over, sound the beep and then
T0 = time.time() # reset the timer.
# -----
key = kbfunc()
if key == 0:continue # If no key was pressed, go back to start of loop.
if key in valid_keys: # If the user's key press is in our list..
if key == ord(","): # A comma means to restart the timer.
duration = get_duration() # Comment line to use old duration.
print(" >> ",end="",flush = True) # Comment line to use old duration.
t0 = time.time()
stopwatch = True
continue # Remove if you want the ',' char to be printed.
elif key == ord("!"): # An exclamation mark means to stop the timer.")
stopwatch = False
continue # Remove if you want the "!" to print.
elif key == ord("#"): # An At sign means to quit the program.
print("\n\n Program Ended at User's Request.\n ",end="")
run_cycle = False # This will cause our main loop to exit.
continue # Loop back to beginning so that the at sign
# is not printed after user said to quit.
elif key == 13: # The carriage return means to drop down to a new line.
print("\n >> ",end="",flush = True)
continue
print(chr(key),end="",flush = True) # Print the (valid) character.
# Keys that are not in our list are simply ignored.

math quiz with a time limit (simultaneous functions) - advanced python

So I would like to run two programs, a timer and a math question. But always the input seems to be stopping the timer funtion or not even run at all. Is there any ways for it to get around that?
I'll keep the example simple.
import time
start_time = time.time()
timer=0
correct = answer
answer = input("9 + 9 = ")
#technically a math question here
#so here until i enter the input prevents computer reading the code
while True:
timer = time.time() - start_time
if timer > 3:
#3 seconds is the limit
print('Wrong!')
quit()
So recap i would like the player to answer the question in less than 3 seconds.
after the 3 seconds the game will print wrong and exit
if the player answer within three seconds the timer would be 'terminated' or stopped before it triggers 'wrong' and quit
hope you understand, and really appreciate your help
On Windows you can use the msvcrt module's kbhit and getch functions (I modernized this code example a little bit):
import sys
import time
import msvcrt
def read_input(caption, timeout=5):
start_time = time.time()
print(caption)
inpt = ''
while True:
if msvcrt.kbhit(): # Check if a key press is waiting.
# Check which key was pressed and turn it into a unicode string.
char = msvcrt.getche().decode(encoding='utf-8')
# If enter was pressed, return the inpt.
if char in ('\n', '\r'): # enter key
return inpt
# If another key was pressed, concatenate with previous chars.
elif char >= ' ': # Keys greater or equal to space key.
inpt += char
# If time is up, return the inpt.
if time.time()-start_time > timeout:
print('\nTime is up.')
return inpt
# and some examples of usage
ans = read_input('Please type a name', timeout=4)
print('The name is {}'.format(ans))
ans = read_input('Please enter a number', timeout=3)
print('The number is {}'.format(ans))
I'm not sure what exactly you have to do on other operating systems (research termios, tty, select).
Another possibility would be the curses module which has a getch function as well and you can set it to nodelay(1) (non-blocking), but for Windows you first have to download curses from Christopher Gohlke's website.
import time
import curses
def main(stdscr):
curses.noecho() # Now curses doesn't display the pressed key anymore.
stdscr.nodelay(1) # Makes the `getch` method non-blocking.
stdscr.scrollok(True) # When bottom of screen is reached scroll the window.
# We use `addstr` instead of `print`.
stdscr.addstr('Press "q" to exit...\n')
# Tuples of question and answer.
question_list = [('4 + 5 = ', '9'), ('7 - 4 = ', '3')]
question_index = 0
# Unpack the first question-answer tuple.
question, correct_answer = question_list[question_index]
stdscr.addstr(question) # Display the question.
answer = '' # Here we store the current answer of the user.
# A set of numbers to check if the user has entered a number.
# We have to convert the number strings to ordinals, because
# that's what `getch` returns.
numbers = {ord(str(n)) for n in range(10)}
start_time = time.time() # Start the timer.
while True:
timer = time.time() - start_time
inpt = stdscr.getch() # Here we get the pressed key.
if inpt == ord('q'): # 'q' quits the game.
break
if inpt in numbers:
answer += chr(inpt)
stdscr.addstr(chr(inpt), curses.A_BOLD)
if inpt in (ord('\n'), ord('\r')): # Enter pressed.
if answer == correct_answer:
stdscr.addstr('\nCorrect\n', curses.A_BOLD)
else:
stdscr.addstr('\nWrong\n', curses.A_BOLD)
if timer > 3:
stdscr.addstr('\nToo late. Next question.\n')
if timer > 3 or inpt in (ord('\n'), ord('\r')):
# Time is up or enter was pressed; reset and show next question.
answer = ''
start_time = time.time() # Reset the timer.
question_index += 1
# Keep question index in the correct range.
question_index %= len(question_list)
question, correct_answer = question_list[question_index]
stdscr.addstr(question)
# We use wrapper to start the program.
# It handles exceptions and resets the terminal after the game.
curses.wrapper(main)
Use time.time(), it returns the epoch time (that is, the number of seconds since January 1, 1970 UNIX Time). You can compare it to a start time to get the number of seconds:
start = time.time()
while time.time() - start < 60:
# stuff
You can have a timer pull you out of your code at any point (even if the user is inputting info) with signals but it is a little more complicated. One way is to use the signal library:
import signal
def timeout_handler(signal, frame):
raise Exception('Time is up!')
signal.signal(signal.SIGALRM, timeout_handler)
This defines a function that raises an exception and is called when the timeout occurs. Now you can put your while loop in a try catch block and set the timer:
signal.alarm.timeout(60)
try:
while lives > 0
# stuff
except:
# print score

Categories

Resources