Is there any way to detect key press in Python? - python

I am wanting to make a program with different responses when you press a key. Is there any way that you can detect a key press in Python?
The program is like when you press 0, it will say you pressed 0 and when you pressed ctrl-c it will say that you interrupted the program, so on.
Can you do this in a while True: loop as well?
Also, I don't want it to be like input and I am using Linux (I don't want to have to use root).

Assuming you want a command line application
you should be able to do it with
import sys
import termios
import tty
custom_messages = {'a': 'some other message'}
custom_messages['b'] = 'what?';
stdin = sys.stdin.fileno()
tattr = termios.tcgetattr(stdin)
try:
tty.setcbreak(stdin, termios.TCSANOW)
while True:
char = sys.stdin.read(1)
print(custom_messages.get(char, 'You pressed ' + char))
except KeyboardInterrupt:
print('You interrupted')
sys.exit() ## ?
finally:
termios.tcsetattr(stdin, termios.TCSANOW, tattr)
I based my answer on disabling buffering in stdin

Related

detect key press in python, where each iteration can take more than a couple of seconds?

Edit: The below answer to use keyboard.on_press(callback, suppress=False) works fine in ubuntu without any issues.
But in Redhat/Amazon linux, it fails to work.
I have used the code snippet from this thread
import keyboard # using module keyboard
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
break # if user pressed a key other than the given key the loop will break
But the above code requires the each iteration to be executed in nano-seconds. It fails in the below case:
import keyboard # using module keyboard
import time
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
You can make use of event handlers in keyboard module to achieve the desired result.
One such handler is keyboard.on_press(callback, suppress=False):
Which invokes a callback for every key_down event.
You can refer more at keyboard docs
Here is the code you can try:
import keyboard # using module keyboard
import time
stop = False
def onkeypress(event):
global stop
if event.name == 'q':
stop = True
# ---------> hook event handler
keyboard.on_press(onkeypress)
# --------->
while True: # making a loop
try: # used try so that if user pressed other than the given key error will not be shown
print("sleeping")
time.sleep(5)
print("slept")
if stop: # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
except:
print("#######")
break # if user pressed a key other than the given key the loop will break
for people that might need this in the future, you can use keyboard.wait() which will basically wait untill the key gets pressed
keyboard.wait("o")
print("you pressed the letter o")
Do keep in mind that it blocks code execution after it. if you want to run code if the key is not being pressed i'd suggest doing
if keyboard.is_pressed("0"):
#do stuff
else:
#do other stuff
Edit: never mind, the other answer uses pretty much the same approach
This is what i could come up with, using the same "keyboard" module, see in-code comments below
import keyboard, time
from queue import Queue
# keyboard keypress callback
def on_keypress(e):
keys_queue.put(e.name)
# tell keyboard module to tell us about keypresses via callback
# this callback happens on a separate thread
keys_queue = Queue()
keyboard.on_press(on_keypress)
try:
# run the main loop until some key is in the queue
while keys_queue.empty():
print("sleeping")
time.sleep(5)
print("slept")
# check if its the right key
if keys_queue.get()!='q':
raise Exception("terminated by bad key")
# still here? good, this means we've been stoped by the right key
print("terminated by correct key")
except:
# well, you can
print("#######")
finally:
# stop receiving the callback at this point
keyboard.unhook_all()
You could use a thread
import threading
class KeyboardEvent(threading.Thread):
def run(self):
if keyboard.is_pressed('q'): # if key 'q' is pressed
print('You Pressed A Key!')
break # finishing the loop
keyread = KeyboardEvent()
keyread.start()
This would run in parallel to anything in the main thread and be dedicated to listening for that key press essentially.

Printing current time after pressing enter

Currently I am working on a keylogger as final project for my Python course. My idea is that when a user presses the return/enter button the time appears next to the previous typed text. Additionally, I would also like to stop the running when ESC is pressed. This is what I exactly mean:
www.gmail.com 02:20:50
www.google.com 02:21:42
stackoverflow 02:21:44
My code so far is something like this:
import sys, termios, tty, os
from datetime import datetime
def getch():
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd , termios.TCSADRAIN , old_settings)
return ch
file=open('register.txt','a')
while True:
char = getch()
if (char != "q"):
file.write(char)
if (char == "\n"):
file.write('\t'+datetime.now().strftime('%H:%M:%S'))
else:
print("Quit!")
exit(0)
file.close()
The problems here are although everything is successfully written in the file register.txt the time doesn't show up, and also I do not have any idea how to specify the character ESC to exit the program
Thanks in advance!

Interrupting a timer with spacebar

Say I want to time how long I can hold my breath, and I want to do that with Python. I have short script:
start = time()
try:
while True: pass
except KeyboardInterrupt:
print(time() - start)
This has the basic functionality I want, but it has an fatal shortcoming. After a long period of holding my breath, my mind might be a little fuzzy, and I might not find the coordination to hit Ctrl+c right away, and I might loose important data about my training.
The spacebar is much easier target to hit. Is there a simple way to make the loop stop when I press it?
EDIT: I'm on OSX
You need to put the console's keyboard (pty) driver into raw mode.
It is explained in this answer: What is the easiest way to detect key presses in python 3 on a linux machine?
Quoting liberally from that answer:
#! /usr/bin/env python3
import sys
import termios
import time
import tty
def hold_breath(fin):
orig_setting = termios.tcgetattr(fin)
tty.setraw(fin)
start = time.time()
try:
ch = fin.read(1)[0]
assert ch == ' '
finally:
print('You lasted %.03f seconds.\r' % (time.time() - start))
termios.tcsetattr(fin, termios.TCSADRAIN, orig_setting)
if __name__ == '__main__':
print('Hit space.')
hold_breath(sys.stdin)
Works fine on OS/X and Linux. If you wind up interrupting the program before it restores the original setting, then $ stty sane is your friend.
The answer depends on your OS. On Windows, this will stop on any key press, but you could look at the return value of msvcrt.getch() to determine if it was space. Now when you pass out and your face hits the keyboard it will stop on any key.
import time
import msvcrt
start = time.time()
while not msvcrt.kbhit(): # Indicates a key is waiting to be read
pass
end = time.time()
msvcrt.getch() # read and (in this case) throw away the key press.
print(end-start)
import sys
import termios
import contextlib
SPACE_BAR = 32
#contextlib.contextmanager
def raw_mode(file):
old_attrs = termios.tcgetattr(file.fileno())
new_attrs = old_attrs[:]
new_attrs[3] = new_attrs[3] & ~(termios.ECHO | termios.ICANON)
try:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, new_attrs)
yield
finally:
termios.tcsetattr(file.fileno(), termios.TCSADRAIN, old_attrs)
def main():
print 'exit with spacebar'
with raw_mode(sys.stdin):
try:
while True:
if sys.stdin.read(1) == chr(SPACE_BAR):
break
except (KeyboardInterrupt, EOFError):
pass
if __name__ == '__main__':
main()

Getch and progressbar

I'm trying to combine the getch and progressbar Python modules on Linux, but I cannot get it to work. I want to use getch to listen for keyboard input to interrupt the progress bar, but when I insert the getch statement, the progress bar refuses to update automatically, only updating when I press a button on the keyboard.
The code I'm currently using is below. I'm using ProgressBar2 and the getch module, but I've tried using tqdm and my own getch method with no avail.
bar = progressbar.ProgressBar()
for i in range(101):
sleep(0.01)
bar.update(i)
ch = getch.getch()
When using my own implementation of getch, I've narrowed down the problem to be with the 'sys.stdin.read(1)' line in the following code.
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno(), termios.TCSADRAIN)
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
On Windows, using the msvcrt module, I have no problems whatsoever.
I've had the same issue on Linux, so I opted for a different solution: capture the SIGINT signal (CTRL+C) to exit gracefully, or immediately after two presses (the regular SIGINT way).
import signal
import time
signal.signal(signal.SIGINT, signal_handler)
sigint_again = False
asked_termination = False
def signal_handler(self, signal, frame):
"""Handles SIGINT signal, blocks it to terminate gracefully"""
print('You pressed Ctrl+C!:', signal, frame)
if is_sigint_called_twice():
print("\nForced terminating script!")
sys.exit(0)
asked_termination = True
def is_sigint_called_twice(self):
"""Check if pressing ctrl+c a second time to terminate immediately"""
if not sigint_again:
sigint_again = True
return False
else:
return True
while not asked_termination:
print("do_stuff()")
time.sleep(1)

unexpected indent outputs of print function with multithreads in python

I have been trying to write a keyboard listener without installing any packages. What I wanted was to create a non-blocking way of reading only one character of user input. So I created another thread besides the main one. Here is my code:
import sys, os
import thread
import time
try:
from msvcrt import getch
except ImportError:
def getch():
import sys, tty, termios
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
char = None;
def key_listener():
global char;
while True:
char = getch()
# escape key to exit
if ord(char) == 27:
break
#print char #comment this line for test
thread.start_new_thread(key_listener, ())
while True:
print("Whatever")
time.sleep(1);
And the printed strings are a bit weird:
Yinans-MacBook-Pro:anaconda game Yinan$ python inputtest.py
Whatever
Whatever
Whatever
Whatever
Whatever
Whatever
See those indents? I never expected to have that. And I have been trying a whole afternoon to solve it but failed. Does anybody know how to solve this? I will be so grateful. (btw I'm using a macbook pro.)
Putting STDIN in raw mode put STDOUT in raw mode as well, so the normal \n is not expanded to a CRLF. You will need to print a \r at the end of your string in order to return the cursor to the first column.

Categories