How to detect key presses?
Asked Answered
A

17

205

I am making a stopwatch type program in Python and I would like to know how to detect if a key is pressed (such as p for pause and s for stop), and I would not like it to be something like raw_input, which waits for the user's input before continuing execution.

Anyone know how to do this in a while loop?

I would like to make this cross-platform but, if that is not possible, then my main development target is Linux.

Awake answered 6/6, 2014 at 1:26 Comment(1)
L
143

Python has a keyboard module with many features. Install it, perhaps with this command:

pip3 install keyboard

Then use it in code like:

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
Lashelllasher answered 26/6, 2017 at 6:35 Comment(14)
I am not sure for linux but it works on Windows for me.Lashelllasher
keyboard apparently requires root in linux :/Ob
I tried this solution but when I try to import the module after install it, I get an "ImportError: No module named 'keyboard'", so it didn't work. I checked in the GitHub repo and I find a related issue, but it doesn't solve me the problem. Then, I tried downloading the repo and executing some of its examples but I get and "ImportError: You must be root to use this library on linux", as @Ob commented before. Apparently it seems a full module to manage keyboard with Python, but the requirement of root is a big lack :(Teapot
I tried this solution, but took me a while to realize that it triggers when your application is running in a terminal and it's not in focus. Probably fine for a lot of applications, but a deal-breaker for me since I am running more than one process.Madeleinemadelena
"To avoid depending on X, the Linux parts reads raw device files (/dev/input/input*) but this requries root."Monogenetic
I don't see why would the try: except: be useful.Percy
@Percy Indeed. keyboard.is_pressed() doesn't seem to raise any exceptions.Birdcage
what if is_pressed is not allowed in mac? are there any counter solutions? @GabrielJablonskiBuckling
how do i use this to detect arrow key input?Selfdelusion
This solution seems to be using a lot of CPU. Am I alone on that?Ragi
how do i use this to run alongside another process? e.g. as a killswitch so i can stop the mainloop of my programm if i press q?Ance
sshkeyboard does not require root OR X server on linux, and works on windows too.Infernal
@Infernal Yes, but it will only catch keystrokes sent to the terminal it's in.Jemina
This seems like a pretty poor solution, except maybe on Windows.Wicklund
B
105

For those who are on windows and were struggling to find an working answer here's mine: pynput.

Here is the pynput official "Monitoring the keyboard" source code example:

from pynput.keyboard import Key, Listener

def on_press(key):
    print('{0} pressed'.format(
        key))

def on_release(key):
    print('{0} release'.format(
        key))
    if key == Key.esc:
        # Stop listener
        return False

# Collect events until released
with Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()

The function above will print whichever key you are pressing plus start an action as you release the 'esc' key. The keyboard documentation is here for a more variated usage.

Markus von Broady highlighted a potential issue that is: This answer doesn't require you being in the current window to this script be activated, a solution to windows would be:

from win32gui import GetWindowText, GetForegroundWindow
current_window = (GetWindowText(GetForegroundWindow()))
desired_window_name = "Stopwatch" #Whatever the name of your window should be

#Infinite loops are dangerous.
while True: #Don't rely on this line of code too much and make sure to adapt this to your project.
    if current_window == desired_window_name:

        with Listener(
            on_press=on_press,
            on_release=on_release) as listener:
            listener.join()
Bulletproof answered 8/11, 2018 at 15:3 Comment(10)
@nimig18 ...and doesn't require root :)Hysterics
There's a problem with this solution (not sure about alternatives): the key doesn't have to be pressed inside a console window for it to take effect. Imagine having a script that does some job until ESC is pressed, but then you press it in another program.Diseur
@MarkusvonBroady I guess that win32gui would be enough to solve it, I've edited my answer in a way that would potentially solve it to windows users at least.Bulletproof
@Bulletproof I tried this, but my code stops further execution and is stuck here. It works like input(). I have the code executing in selenium, firefox, but as soon as this sequence is encountered, there's no further action.Krill
@LakshmiNarayanan, put the conditional branch in a loop, I edited a palliative one on the answer. Hope it helps.Bulletproof
Should have been the accepted answer, for it works both in linux and windowsMinor
how can i use this while another function is running, asynchronously?Ance
Maybe you can give instructions on how to install pynput? I get error No module named 'pynput'. And whats listener.join() do?Wandis
I did a pip install pynput, it installed something, but when I ran your code I get this error: ` No connection could be made because the target machine actively refused it`Wandis
on my machine (WIN 10), windows defender marked the code presented in this answer as a keylogger and automatically deleted my python script.Desberg
H
95

More things can be done with keyboard module. You can install this module using pip install keyboard Here are some of the methods:


Method #1:

Using the function read_key():

import keyboard

while True:
    if keyboard.read_key() == "p":
        print("You pressed p")
        break

This is gonna break the loop as the key p is pressed.


Method #2:

Using function wait:

import keyboard

keyboard.wait("p")
print("You pressed p")

It will wait for you to press p and continue the code as it is pressed.


Method #3:

Using the function on_press_key:

import keyboard

keyboard.on_press_key("p", lambda _:print("You pressed p"))

It needs a callback function. I used _ because the keyboard function returns the keyboard event to that function.

Once executed, it will run the function when the key is pressed. You can stop all hooks by running this line:

keyboard.unhook_all()

Method #4:

This method is sort of already answered by user8167727 but I disagree with the code they made. It will be using the function is_pressed but in an other way:

import keyboard

while True:
    if keyboard.is_pressed("p"):
        print("You pressed p")
        break

It will break the loop as p is pressed.


Method #5:

You can use keyboard.record as well. It records all keys pressed and released until you press the escape key or the one you've defined in until arg and returns a list of keyboard.KeyboardEvent elements.

import keyboard

keyboard.record(until="p")
print("You pressed p")

Notes:

  • keyboard will read keypresses from the whole OS.
  • keyboard requires root on linux
Hazelwood answered 25/8, 2019 at 8:15 Comment(10)
The biggest NEGATIVE of using keyboard module is its requirement you run as ROOT user. This makes the module verboten in my code. Just to poll whether a key has been pressed does not require root privileges. I have read the doc and understand why the limitation exits in the module, but look elsewhere if all you need is to poll a key...Hundredth
Very helpful info shared, Sir! I wanted to know whether I can use keyboard.wait() to wait for more than 1 key, and continue if either of them is pressedPostboy
@PreetkaranSingh wait() doesn't give this functionality. You will have to use keyboard.read_key() with an if condition packed in a while loop. See the method #1Hazelwood
Thanks Sir!, would you like to shed some light on the suppress keyword usage in keyboard.read_key(), when to use it and when not....Postboy
@PreetkaranSingh I would but I don't have enough information about the suppress argumentHazelwood
Used n°3 on windows, had to do 'pip install keyboard' first in the terminal to get the import working. Works like a charm now.Wivinia
@Hundredth on most linux systems the event devices in a the group input, so you would only need to add your user to the additional group.Ariannaarianne
@PatrickB. That’s still a terrible solution if all you want is read keyboard input in your application. The keyboard module is a system-wide key logger, i.e. something fundamentally different.Ceruse
sshkeyboard does not require root OR X server on linux, and works on windows too.Infernal
I'm trying to implement functionality like Google's AutoComplete on the command line in Windows. This allows user to categorize multiple phrases that are presented to them one at a time (picture any method above, but instead of "while True" it's a function getInput()). The problem I'm having is that the "You pressed p" chars are sent to the next getInput() call and the user can't enter data more than once. But I do need to send some chars to the terminal so the user can see what category was applied. Thanks for any help you can provide.Cleavers
A
52

As OP mention about raw_input - that means he want cli solution. Linux: curses is what you want (windows PDCurses). Curses, is an graphical API for cli software, you can achieve more than just detect key events.

This code will detect keys until new line is pressed.

import curses
import os

def main(win):
    win.nodelay(True)
    key=""
    win.clear()                
    win.addstr("Detected key:")
    while 1:          
        try:                 
           key = win.getkey()         
           win.clear()                
           win.addstr("Detected key:")
           win.addstr(str(key)) 
           if key == os.linesep:
              break           
        except Exception as e:
           # No input   
           pass         

curses.wrapper(main)
Anthropocentric answered 3/9, 2015 at 22:28 Comment(6)
This is really nice. Had to search forever before coming across it. Seems much cleaner than hacking around with termios and so on ...Archicarp
If you do win.nodelay(False) instead of True, it won't generate a million exceptions per second.Ceja
Ugly as anything but still more beautiful then anything else I've seen. The weird thing is I remember distinctly back in my python2.7 days opening file descriptor 0 (stdin) for read with non-blocking and having it behave itself as a keypress collector, but for the life of me I can't figure out how I did it. I do remember it all started with me detaching stdin, but then realize I could simply open it as a separate stream and not have to worry about crashes or returning it's state to it's original behavior. Still... it was so simple and elegant and now, how??? can't find it.Disoperation
Nice. It doesn't tell me when some keys are pressed, though (like Ctrl and Win).Isatin
This draws a CLI "window" over whatever was in the terminal before - can it be used without this "window" being shown?Reamer
On windows, for this you will need: pip install windows-cursesCordovan
T
30

For Windows you could use msvcrt like this:

import msvcrt
while True:
  if msvcrt.kbhit():
    key = msvcrt.getch()
    print(key)   # just to show the result
Twine answered 2/8, 2016 at 20:16 Comment(4)
msvcrt is a Windows-only module.Battery
I actually use pynput now, that might be a better answerTwine
Note that pynput to work on OS X (don't know about Linux) has to run as root in order to work. That may be a non-starter for some folks.Ostium
I could have sworn the question was for 'cross-platform' or 'linux'...Pate
L
20

neoDev's comment at the question itself might be easy to miss, but it links to a solution not mentioned in any answer here.

There is no need to import keyboard with this solution.

Solution copied from this other question, all credits to @neoDev.

This worked for me on macOS Sierra and Python 2.7.10 and 3.6.3

import sys,tty,os,termios
def getkey():
    old_settings = termios.tcgetattr(sys.stdin)
    tty.setcbreak(sys.stdin.fileno())
    try:
        while True:
            b = os.read(sys.stdin.fileno(), 3).decode()
            if len(b) == 3:
                k = ord(b[2])
            else:
                k = ord(b)
            key_mapping = {
                127: 'backspace',
                10: 'return',
                32: 'space',
                9: 'tab',
                27: 'esc',
                65: 'up',
                66: 'down',
                67: 'right',
                68: 'left'
            }
            return key_mapping.get(k, chr(k))
    finally:
        termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings)
try:
    while True:
        k = getkey()
        if k == 'esc':
            quit()
        else:
            print(k)
except (KeyboardInterrupt, SystemExit):
    os.system('stty sane')
    print('stopping.')
Lobworm answered 11/6, 2021 at 15:5 Comment(3)
Loving this for macOS. Thanks.Crore
Wow, this is perfect. keyboard need root access, pynput need X Server. This right here needs neither and works for non-root users in CLI via ssh. Tested on Debian 11 with Python 3+Jorry
simple, excellent and neatFrida
B
17

Use this code for find the which key pressed

from pynput import keyboard

def on_press(key):
    try:
        print('alphanumeric key {0} pressed'.format(
            key.char))
    except AttributeError:
        print('special key {0} pressed'.format(
            key))

def on_release(key):
    print('{0} released'.format(
        key))
    if key == keyboard.Key.esc:
        # Stop listener
        return False

# Collect events until released
with keyboard.Listener(
        on_press=on_press,
        on_release=on_release) as listener:
    listener.join()
Brause answered 17/1, 2019 at 15:35 Comment(5)
Here's the thing though, i'm using macOS and installed both pynput and keyboard separately, and the program runs without any errors but can only detect (on the python shell) special keys. Alphanumeric keys are not detected and on the contrary, are considered as if i were writing code on the shell. Do you know what might be the issue?Homeric
The same code worked for me in the shell. Please check it. The keyboard package does not need this code.Brause
This is the way to go in linux, as the keyboard lib needs root.Holston
This solution will detect all keystroke; also those happening in a different terminal window. Unfortunately, this severely limits its possible use cases.Commissariat
It just times out for meGuam
A
9

Use PyGame to have a window and then you can get the key events.

For the letter p:

import pygame, sys
import pygame.locals

pygame.init()
BLACK = (0,0,0)
WIDTH = 1280
HEIGHT = 1024
windowSurface = pygame.display.set_mode((WIDTH, HEIGHT), 0, 32)

windowSurface.fill(BLACK)

while True:
    for event in pygame.event.get():
        if event.key == pygame.K_p: # replace the 'p' to whatever key you wanted to be pressed
             pass #Do what you want to here
        if event.type == pygame.locals.QUIT:
             pygame.quit()
             sys.exit()
A1 answered 6/6, 2014 at 3:53 Comment(1)
I couldn't get the above code to run. I first had to check that the event type was one of KEYUP or KEYDOWN: if event.type in (pygame.KEYDOWN, pygame.KEYUP): print("Key: ", event.key) if(event.key == pygame.K_q): pygame.quit()Tourmaline
I
6

Non-root version that works even through ssh: sshkeyboard. Install with pip install sshkeyboard,

then write script such as:

from sshkeyboard import listen_keyboard

def press(key):
    print(f"'{key}' pressed")

def release(key):
    print(f"'{key}' released")

listen_keyboard(
    on_press=press,
    on_release=release,
)

And it will print:

'a' pressed
'a' released

When A key is pressed. ESC key ends the listening by default.

It requires less coding than for example curses, tkinter and getch. And it does not require root access like keyboard module.

Infernal answered 28/10, 2021 at 8:4 Comment(1)
Important: on Windows, this only works (as far as I can tell) if you run the program directly from the Window's command line. It fails if you attempt to run it through an IDE.Ankylose
S
5

I made this kind of game based on this post (using msvcr library and Python 3.7).

The following is the main function of the game, that is detecting the keys pressed:

import msvcrt

def _secret_key(self):
    # Get the key pressed by the user and check if he/she wins.

    bk = chr(10) + "-"*25 + chr(10)

    while True:
        print(bk + "Press any key(s)" + bk)
        #asks the user to type any key(s)

        kp = str(msvcrt.getch()).replace("b'", "").replace("'", "")
        # Store key's value.

        if r'\xe0' in kp:
            kp += str(msvcrt.getch()).replace("b'", "").replace("'", "")
            # Refactor the variable in case of multi press.

        if kp == r'\xe0\x8a':
            # If user pressed the secret key, the game ends.
            # \x8a is CTRL+F12, that's the secret key.

            print(bk + "CONGRATULATIONS YOU PRESSED THE SECRET KEYS!\a" + bk)
            print("Press any key to exit the game")
            msvcrt.getch()
            break
        else:
            print("    You pressed:'", kp + "', that's not the secret key(s)\n")
            if self.select_continue() == "n":
                if self.secondary_options():
                    self._main_menu()
                break

If you want the full source code of the program you can see it or download it from GitHub

The secret keypress is:

Ctrl+F12

Soften answered 6/1, 2019 at 20:41 Comment(1)
str(msvcrt.getch()).replace("b'", "").replace("'", "") is strange, use msvcrt.getch().decode("ascii") to convert binary to text. Or better, ditch the text altogether and handle the data (e.g. b'\xe0\x8a').Hysterics
M
4

You don't mention if this is a GUI program or not, but most GUI packages include a way to capture and handle keyboard input. For example, with tkinter (in Py3), you can bind to a certain event and then handle it in a function. For example:

import tkinter as tk

def key_handler(event=None):
    if event and event.keysym in ('s', 'p'):
        'do something'

r = tk.Tk()
t = tk.Text()
t.pack()
r.bind('<Key>', key_handler)
r.mainloop()

With the above, when you type into the Text widget, the key_handler routine gets called for each (or almost each) key you press.

Manicurist answered 9/1, 2021 at 6:17 Comment(0)
O
3

Using the keyboard package, especially on linux is not an apt solution because that package requires root privileges to run. We can easily implement this with the getkey package. This is analogous to the C language function getchar.

Install it:

pip install getkey

And use it:

from getkey import getkey
while True: #Breaks when key is pressed
    key = getkey()
    print(key) #Optionally prints out the key.
    break

We can add this in a function to return the pressed key.

def Ginput(str):
    """
    Now, this function is like the native input() function. It can accept a prompt string, print it out, and when one key is pressed, it will return the key to the caller.
    """
    print(str, end='')
    while True:
        key = getkey()
        print(key)
        return key

Use like this:

inp = Ginput("\n Press any key to continue: ")
print("You pressed " + inp)
Osborne answered 17/7, 2021 at 21:54 Comment(1)
Per several issues shown on that project, getkey does not appear to be actively maintained any longer, and pip install on Windows is broken.Bonni
C
3

The curses module does that job.

You can test it running this example from the terminal:

import curses

screen = curses.initscr()
curses.noecho()
curses.cbreak()
screen.keypad(True)

try:
    while True:
        char = screen.getch()
        if char == ord('q'):
            break
        elif char == curses.KEY_UP:
            print('up')
        elif char == curses.KEY_DOWN:
            print('down')
        elif char == curses.KEY_RIGHT:
            print('right')
        elif char == curses.KEY_LEFT:
            print('left')
        elif char == ord('s'):
            print('stop')

finally:
    curses.nocbreak(); screen.keypad(0); curses.echo()
    curses.endwin()
Caesarean answered 12/9, 2021 at 19:11 Comment(2)
does this handle left and up arrow pressed at same time?Bibbs
This does not work on WindowsArlettearley
V
3

Here is a cross-platform solution, both blocking and non-blocking, not requiring any external libraries:

import contextlib as _contextlib

try:
    import msvcrt as _msvcrt

    # Length 0 sequences, length 1 sequences...
    _ESCAPE_SEQUENCES = [frozenset(("\x00", "\xe0"))]

    _next_input = _msvcrt.getwch

    _set_terminal_raw = _contextlib.nullcontext

    _input_ready = _msvcrt.kbhit

except ImportError:  # Unix
    import sys as _sys, tty as _tty, termios as _termios, \
        select as _select, functools as _functools

    # Length 0 sequences, length 1 sequences...
    _ESCAPE_SEQUENCES = [
        frozenset(("\x1b",)),
        frozenset(("\x1b\x5b", "\x1b\x4f"))]

    @_contextlib.contextmanager
    def _set_terminal_raw():
        fd = _sys.stdin.fileno()
        old_settings = _termios.tcgetattr(fd)
        try:
            _tty.setraw(_sys.stdin.fileno())
            yield
        finally:
            _termios.tcsetattr(fd, _termios.TCSADRAIN, old_settings)

    _next_input = _functools.partial(_sys.stdin.read, 1)

    def _input_ready():
        return _select.select([_sys.stdin], [], [], 0) == ([_sys.stdin], [], [])

_MAX_ESCAPE_SEQUENCE_LENGTH = len(_ESCAPE_SEQUENCES)

def _get_keystroke():
    key = _next_input()
    while (len(key) <= _MAX_ESCAPE_SEQUENCE_LENGTH and
           key in _ESCAPE_SEQUENCES[len(key)-1]):
        key += _next_input()
    return key

def _flush():
    while _input_ready():
        _next_input()

def key_pressed(key: str = None, *, flush: bool = True) -> bool:
    """Return True if the specified key has been pressed

    Args:
        key: The key to check for. If None, any key will do.
        flush: If True (default), flush the input buffer after the key was found.
    
    Return:
        boolean stating whether a key was pressed.
    """
    with _set_terminal_raw():
        if key is None:
            if not _input_ready():
                return False
            if flush:
                _flush()
            return True

        while _input_ready():
            keystroke = _get_keystroke()
            if keystroke == key:
                if flush:
                    _flush()
                return True
        return False

def print_key() -> None:
    """Print the key that was pressed
    
    Useful for debugging and figuring out keys.
    """
    with _set_terminal_raw():
        _flush()
        print("\\x" + "\\x".join(map("{:02x}".format, map(ord, _get_keystroke()))))

def wait_key(key=None, *, pre_flush=False, post_flush=True) -> str:
    """Wait for a specific key to be pressed.

    Args:
        key: The key to check for. If None, any key will do.
        pre_flush: If True, flush the input buffer before waiting for input.
        Useful in case you wish to ignore previously pressed keys.
        post_flush: If True (default), flush the input buffer after the key was
        found. Useful for ignoring multiple key-presses.
    
    Returns:
        The key that was pressed.
    """
    with _set_terminal_raw():
        if pre_flush:
            _flush()

        if key is None:
            key = _get_keystroke()
            if post_flush:
                _flush()
            return key

        while _get_keystroke() != key:
            pass
        
        if post_flush:
            _flush()

        return key

You can use key_pressed() inside a while loop:

while True:
    time.sleep(5)
    if key_pressed():
        break

You can also check for a specific key:

while True:
    time.sleep(5)
    if key_pressed("\x00\x48"):  # Up arrow key on Windows.
        break

Find out special keys using print_key():

>>> print_key()
# Press up key
\x00\x48

Or wait until a certain key is pressed:

>>> wait_key("a") # Stop and ignore all inputs until "a" is pressed.
Vandusen answered 11/1, 2022 at 9:49 Comment(0)
H
2
import cv2

key = cv2.waitKey(1)

This is from the openCV package. The delay arg is the number of milliseconds it will wait for a keypress. In this case, 1ms. Per the docs, pollKey() can be used without waiting.

Hypochondrium answered 14/2, 2019 at 20:32 Comment(2)
You need to write more about how it is supposed to work. Also, it will be helpful if you explain why does "key" and "1" mean in this example. I cannot make this example to work.Mailand
A 35MB computer-vision module + a dependency on numpy seems like a lot of baggage for this wee bit of functionality.Bonni
H
1

You can use pygame's get_pressed():

import pygame

while True:
    keys = pygame.key.get_pressed()
    if (keys[pygame.K_LEFT]):
        pos_x -= 5
    elif (keys[pygame.K_RIGHT]):
        pos_x += 5
    elif (keys[pygame.K_UP]):
        pos_y -= 5
    elif (keys[pygame.K_DOWN]):
        pos_y += 5
Hasbeen answered 4/11, 2021 at 5:21 Comment(0)
T
0

I was finding how to detect different key presses subsequently until e.g. Ctrl + C break the program from listening and responding to different key presses accordingly.

Using following code,

while True:
    if keyboard.is_pressed("down"):
        print("Reach the bottom!")
    if keyboard.is_pressed("up"):
        print("Reach the top!")
    if keyboard.is_pressed("ctrl+c"):
        break

It will cause the program to keep spamming the response text, if I pressed arrow down or arrow up. I believed because it's in a while-loop, and eventhough you only press once, but it will get triggered multiple times (as written in doc, I am awared of this after I read.)

At that moment, I still haven't went to read the doc, I try adding in time.sleep()

while True:
    if keyboard.is_pressed("down"):
        print("Reach the bottom!")
        time.sleep(0.5)
    if keyboard.is_pressed("up"):
        print("Reach the top!")
        time.sleep(0.5)
    if keyboard.is_pressed("ctrl+c"):
        break

This solves the spamming issue.

But this is not a very good way as of subsequent very fast taps on the arrow key, will only trigger once instead of as many times as I pressed, because the program will sleep for 0.5 second right, meant the "keyboard event" happened at that 0.5 second will not be counted.

So, I proceed to read the doc and get the idea to do this at this part.

while True:
    # Wait for the next event.
    event = keyboard.read_event()
    if event.event_type == keyboard.KEY_DOWN and event.name == 'down':
        # do whatever function you wanna here
    if event.event_type == keyboard.KEY_DOWN and event.name == 'up':
        # do whatever function you wanna here
    if keyboard.is_pressed("ctrl+c"):
        break

Now, it's working fine and great! TBH, I am not deep dive into the doc, used to, but I have really forgetten the content, if you know or find any better way to do the similar function, please enlighten me!

Thank you, wish you have a great day ahead!

Tarnish answered 13/12, 2022 at 4:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.