78

How can I poll the keyboard from a console python app? Specifically, I would like to do something akin to this in the midst of a lot of other I/O activities (socket selects, serial port access, etc.):

while True:
    # doing amazing pythonic embedded stuff
    # ...

    # periodically do a non-blocking check to see if
    # we are being told to do something else
    x = keyboard.read(1000, timeout = 0)

    if len(x):
        # ok, some key got pressed
        # do something

What is the correct pythonic way to do this on Windows? Also, portability to Linux wouldn't be bad, though it's not required.

Neuron
  • 5,141
  • 5
  • 38
  • 59
K. Brafford
  • 3,755
  • 2
  • 26
  • 30
  • 1
    Just to let other people know, I found that most solutions involving select or thread libraries did not work correctly from IDLE. However, they _**all**_ worked fine on the CLI i.e. `python /home/pi/poll_keyboard.py` – davidhood2 Oct 19 '16 at 11:18
  • In general I think reacting to key presses instead of polling them periodically is more robust solution as you are not potentially missing the key presses. See my answer below. – ilon Oct 27 '21 at 15:28

12 Answers12

38

The standard approach is to use the select module.

However, this doesn't work on Windows. For that, you can use the msvcrt module's keyboard polling.

Often, this is done with multiple threads -- one per device being "watched" plus the background processes that might need to be interrupted by the device.

mmoya
  • 1,901
  • 1
  • 21
  • 30
S.Lott
  • 384,516
  • 81
  • 508
  • 779
  • 1
    Correct me if i’m wrong, but in my experience msvcrt only works when you run the program in a command window, ie. not in IDLE and not with a GUI... – DarthVlader Jun 24 '18 at 08:14
  • @digitalHamster0: Anything that replaces `sys.stdin` with a custom object (e.g. IDLE, most GUIs) would have that effect. When `sys.stdin` isn't a true file, you can't use `select`; when it's not connected to a "real" console, you can't use the `msvcrt` keyboard polling functions (that implicitly rely on a "real" console). – ShadowRanger Aug 29 '18 at 15:52
  • In general I think reacting to key presses instead of polling them periodically is more robust solution as you are not potentially missing the key presses. See my answer below. – ilon Oct 27 '21 at 15:27
  • If you could a minimal working example, that will be great. The link does not provide enough information. – Danyal Mar 01 '23 at 19:27
23

A solution using the curses module. Printing a numeric value corresponding to each key pressed:

import curses

def main(stdscr):
    # do not wait for input when calling getch
    stdscr.nodelay(1)
    while True:
        # get keyboard input, returns -1 if none available
        c = stdscr.getch()
        if c != -1:
            # print numeric value
            stdscr.addstr(str(c) + ' ')
            stdscr.refresh()
            # return curser to start position
            stdscr.move(0, 0)

if __name__ == '__main__':
    curses.wrapper(main)
StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
W. Russell
  • 239
  • 2
  • 2
  • OZ123: It can. See http://stackoverflow.com/questions/32417379/what-is-needed-for-curses-in-python-3-4-on-windows7 – Joshua Clayton May 25 '16 at 18:47
  • 1
    Had issues using curses via SSH term on headless host. Issues were badly messing up the terminal - requiring it to be `reset` between each run. It did work, i.e. detect keypress. There has to be a smarter solution. – Mark Dec 31 '17 at 21:31
18

None of these answers worked well for me. This package, pynput, does exactly what I need.

https://pypi.python.org/pypi/pynput

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()
wroscoe
  • 1,914
  • 3
  • 19
  • 18
  • 3
    This worked for me, except the key pressed was echoed to the screen immediately after being pressed, and there was no way to disable it. https://github.com/moses-palmer/pynput/issues/47 Plus, the characters are buffered and additionally appear on the command line when the program exits too.This appears to be a limitation of the Linux implementation, but it works fine on Windows. – Trevor Sep 06 '17 at 02:35
  • This solution doesn't work when the script runs via ssh. It bombs out with the error: 'Xlib.error.DisplayNameError: Bad display name "".' – David Stein Nov 19 '17 at 04:05
  • As mentioned above by David - this is not a good solution for headless instances as it has a dependency on Xserver. `import Xlib.display` – Mark Dec 31 '17 at 21:29
17

Ok, since my attempt to post my solution in a comment failed, here's what I was trying to say. I could do exactly what I wanted from native Python (on Windows, not anywhere else though) with the following code:

import msvcrt 

def kbfunc(): 
   x = msvcrt.kbhit()
   if x: 
      ret = ord(msvcrt.getch()) 
   else: 
      ret = 0 
   return ret
K. Brafford
  • 3,755
  • 2
  • 26
  • 30
16
import sys
import select

def heardEnter():
    i,o,e = select.select([sys.stdin],[],[],0.0001)
    for s in i:
        if s == sys.stdin:
            input = sys.stdin.readline()
            return True
    return False
Neuron
  • 5,141
  • 5
  • 38
  • 59
  • no worky. got error: select.error: (10093, 'Either the application has not called WSAStartup, or WSAStartup failed') – DarenW Dec 17 '12 at 23:17
  • 1
    I've heard, more than a couple times, that the select system call on MS Windows doesn't support regular file descriptors and only works on sockets. (I don't know if the Python implementation of select() has ever worked around that under the hood). – Jim Dennis Jul 13 '13 at 19:54
  • what is this weird timeout for? It works for me with timeout=0 but not with 0.0001 as shown.. – frans Mar 17 '16 at 15:47
  • Windows 7 + python 2.7 returns error: i,o,e = select.select([sys.stdin],[],[],0) select.error: (10038, 'An operation was attempted on something that is not a socket') – Vit Bernatik Oct 25 '16 at 16:25
  • 3
    For me, this only detects keypresses after I press Enter. – Mark Smith Jul 13 '17 at 15:50
  • @frans: Presumably the timeout is there for when the input pipe has just been drained, but whatever is feeding the pipe has more data to send that wouldn't fit in the pipe buffer; you allow a small amount of time for the input process to refill the buffer before giving up. – ShadowRanger Aug 29 '18 at 15:54
  • Works on Mac, but only indicates pressing enter (or returning a typed line if changing the return statement). Would it be possible to return keypresses immediately? Combining with e.g. http://code.activestate.com/recipes/134892/ (which returns individual keypresses but blocks)? – Sampo Oct 26 '18 at 18:12
  • There's actually a non-blocking version of the `getch` method in comments, but it misses keypresses which occur whenever the program is outside of the `select` timeout. http://code.activestate.com/recipes/134892/#c12 – Sampo Oct 26 '18 at 18:21
  • On Ubuntu 18.10, this does not detect anything until is pressed. :-\ – Jonathan Hartley Mar 18 '19 at 19:45
  • 1
    @MarkSmith: That's because the program hasn't received the input until either enter or control-D (*) is pressed, it's still in the kernel's "line-editing buffer". (If you press control-D with no characters in the buffer it will close the terminal.) // For this to work on unix-like systems, the terminal must be set to 'raw' or 'cbreak' mode instead of 'cooked'. I think this is done through some ioctl on stdin. – Oskar Skog Jan 05 '22 at 14:31
  • 1
    @JonathanHartley: (See my previous comment.) – Oskar Skog Jan 05 '22 at 14:31
6

From the comments:

import msvcrt # built-in module

def kbfunc():
    return ord(msvcrt.getch()) if msvcrt.kbhit() else 0

Thanks for the help. I ended up writing a C DLL called PyKeyboardAccess.dll and accessing the crt conio functions, exporting this routine:

#include <conio.h>

int kb_inkey () {
   int rc;
   int key;

   key = _kbhit();

   if (key == 0) {
      rc = 0;
   } else {
      rc = _getch();
   }

   return rc;
}

And I access it in python using the ctypes module (built into python 2.5):

import ctypes
import time

# first, load the DLL
try:
    kblib = ctypes.CDLL("PyKeyboardAccess.dll")
except:
    raise ("Error Loading PyKeyboardAccess.dll")

# now, find our function
try:
    kbfunc = kblib.kb_inkey
except:
    raise ("Could not find the kb_inkey function in the dll!")

# Ok, now let's demo the capability  
while True:
    x = kbfunc()

    if x != 0:
        print "Got key: %d" % x
    else:
        time.sleep(.01)
Neuron
  • 5,141
  • 5
  • 38
  • 59
K. Brafford
  • 3,755
  • 2
  • 26
  • 30
  • 2
    How is this better than the built-in msvcrt.kbhit()? What advantage does it have? – S.Lott Nov 16 '08 at 03:33
  • You are absolutely right! I misread your post; I didn't realize there is a python module called msvcrt! I just thought you meant "use the ms crt," and then I got drawn into thinking about threads and didn't connect the dots. You are absolutely right. – K. Brafford Nov 16 '08 at 04:33
  • 1
    I did the same thing with: import msvcrt def kbfunc(): x = msvcrt.kbhit() if x: ret = ord(msvcrt.getch()) else: ret = 0 return ret – K. Brafford Nov 16 '08 at 04:34
  • 1
    Please, do not use a lambda like that. "x = lambda" is supposed to be spelled "def x():" Saving a lambda confuses the n00bz and drives the experienced crazy trying to explain it. – S.Lott Nov 16 '08 at 13:02
  • LOL! That's not a lambda. that's how the "comments" field reformatted my attempt to drop code into a comment. BTW saving a lambda confuses me too, and I am not a python n00b :-) – K. Brafford Nov 20 '08 at 00:28
  • This solution really intrigues me. I find the msvcrt approach to be slow, even in "optimized" micro loops as many solutions here provide. About this refer to [link](http://stackoverflow.com/questions/29954038/why-is-msvcrt-very-very-slow). Though this solution is not very portable. – DevPlayer Jun 30 '15 at 20:20
5

I've come across a cross-platform implementation of kbhit at http://home.wlu.edu/~levys/software/kbhit.py (made edits to remove irrelevant code):

import os
if os.name == 'nt':
    import msvcrt
else:
    import sys, select

def kbhit():
    ''' Returns True if a keypress is waiting to be read in stdin, False otherwise.
    '''
    if os.name == 'nt':
        return msvcrt.kbhit()
    else:
        dr,dw,de = select.select([sys.stdin], [], [], 0)
        return dr != []

Make sure to read() the waiting character(s) -- the function will keep returning True until you do!

ivan_pozdeev
  • 33,874
  • 19
  • 107
  • 152
4

You might look at how pygame handles this to steal some ideas.

Rizwan Kassim
  • 7,931
  • 3
  • 24
  • 34
4

I am using this for checking for key presses, can't get much simpler:

#!/usr/bin/python3
# -*- coding: UTF-8 -*-

import curses, time

def main(stdscr):
    """checking for keypress"""
    stdscr.nodelay(True)  # do not wait for input when calling getch
    return stdscr.getch()

while True:
    print("key:", curses.wrapper(main)) # prints: 'key: 97' for 'a' pressed
                                        # '-1' on no presses
    time.sleep(1)

While curses is not working on windows, there is a 'unicurses' version, supposedly working on Linux, Windows, Mac but I could not get this to work

ullix
  • 333
  • 1
  • 3
  • 14
2

One more option would be to use sshkeyboard library to enable reacting to key presses instead of polling them periodically, and potentially missing the key press:

from sshkeyboard import listen_keyboard, stop_listening

def press(key):
    print(f"'{key}' pressed")
    if key == "z":
        stop_listening()

listen_keyboard(on_press=press)

Simply pip install sshkeyboard to use it.

ilon
  • 171
  • 1
  • 5
1

This can be done using 'pynput' module in python, You press a key and it gets printed It's that easy!

  1. PIP Install the module in command prompt, write following text and press enter

    pip install pynput

  2. Run the following code:

    from pynput.keyboard import Key, Listener
    
    def pressed(key):
        print('Pressed:',key)
    
    def released(key):
        print('Released:',key)
        if key == Key.enter:
            # Stop detecting when enter key is pressed
            return False
    
    # Below loop for Detcting keys runs until enter key is pressed
    with Listener(on_press=pressed, on_release=released) as detector:
        detector.join()
    
  3. You can end the loop with any key you want by changing Key.enter to some other key in the 8th line of the code.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Prashantzz
  • 301
  • 2
  • 7
-1

If you combine time.sleep, threading.Thread, and sys.stdin.read you can easily wait for a specified amount of time for input and then continue, also this should be cross-platform compatible.

t = threading.Thread(target=sys.stdin.read(1) args=(1,))
t.start()
time.sleep(5)
t.join()

You could also place this into a function like so

def timed_getch(self, bytes=1, timeout=1):
    t = threading.Thread(target=sys.stdin.read, args=(bytes,))
    t.start()
    time.sleep(timeout)
    t.join()
    del t

Although this will not return anything so instead you should use the multiprocessing pool module you can find that here: how to get the return value from a thread in python?

Community
  • 1
  • 1
Andria
  • 4,712
  • 2
  • 22
  • 38