54

Is there a way to do key listeners in python without a huge bloated module such as pygame?

An example would be, when I pressed the a key it would print to the console

The a key was pressed!

It should also listen for the arrow keys/spacebar/shift key.

8
  • 1
    Unfortunately, you usually can't detect whether the shift key is held or not in a terminal. The only time you'll get that information is when you get a character which is affected by the shift key, and then you'll have to guess whether shift was held to make that character or not.
    – icktoofay
    Commented Aug 12, 2012 at 1:35
  • 1
    What about the arrow keys? I know this is possible (even with the shift key!) in some other programming languages, sigh Oh well
    – ollien
    Commented Aug 12, 2012 at 1:37
  • 2
    @icktoofay: On Linux, you can monitor /dev/input/* and extract keypresses directly.
    – Blender
    Commented Aug 12, 2012 at 1:37
  • 2
    @Blender: Unless you're running it over SSH...
    – icktoofay
    Commented Aug 12, 2012 at 1:40
  • 1
    @njk828: Arrow keys are possible, but tricky, since they're an escape sequence (usually).
    – icktoofay
    Commented Aug 12, 2012 at 1:40

7 Answers 7

60

I was searching for a simple solution without window focus. Jayk's answer, pynput, works perfect for me. Here is the example how I use it.

from pynput import keyboard

def on_press(key):
    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.keys
2
  • 4
    One notable limitation of pynput is that it depends on a graphical environment, so on Linux this would be X. Therefore it won't work on a remote VPS for instance
    – tombh
    Commented Jun 27, 2021 at 22:16
  • 1
    For those of you who want to know, you can run this while running a loop or a gui. Try adding while True: pass at the end of the code here. Commented Sep 12, 2021 at 20:11
33

It's unfortunately not so easy to do that. If you're trying to make some sort of text user interface, you may want to look into curses. If you want to display things like you normally would in a terminal, but want input like that, then you'll have to work with termios, which unfortunately appears to be poorly documented in Python. Neither of these options are that simple, though, unfortunately. Additionally, they do not work under Windows; if you need them to work under Windows, you'll have to use PDCurses as a replacement for curses or pywin32 rather than termios.


I was able to get this working decently. It prints out the hexadecimal representation of keys you type. As I said in the comments of your question, arrows are tricky; I think you'll agree.

#!/usr/bin/env python
import sys
import termios
import contextlib


@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 ^C or ^D'
    with raw_mode(sys.stdin):
        try:
            while True:
                ch = sys.stdin.read(1)
                if not ch or ch == chr(4):
                    break
                print '%02x' % ord(ch),
        except (KeyboardInterrupt, EOFError):
            pass


if __name__ == '__main__':
    main()
6
  • 1
    Could you give an example as to what you would do for listening for the arrow keys? The Python Doc for this isn't so clear
    – ollien
    Commented Aug 12, 2012 at 1:52
  • @njk828: Yes, as I said, documentation is a little scarce. I posted some code that can put the terminal in the right mode and can read raw characters. From there, you can try to figure out how arrow keys are represented.
    – icktoofay
    Commented Aug 12, 2012 at 2:04
  • @icktoofay I think this will only work when your'e in the context of terminal rt? otherwise , if the keypress happens outside... it will not be written to stdin rt? Commented Apr 25, 2017 at 6:52
  • @Prakash047: That's right, it'll only work when you're focused in the terminal window. If you wanted to listen for key-presses elsewhere you'd have to look into window-system-specific ways of doing it, like with X11 or Wayland on Linux.
    – icktoofay
    Commented Jun 20, 2017 at 2:04
  • Thank you, that worked also with Mac OS. It's only a bit troublesome for some non-printable characters: Esc is read as 0x1B while some others are read as 0x1B as well, followed by additional characters, for example F1 yields 0x1B 0x4F 0x50, Arrow Up 0x1B 0x5B 0x41, and Del (Fn-Backspace on Mac Keyboard) 0x1B 0x5B 0x33 0x7E.
    – Lars
    Commented Nov 28, 2017 at 10:09
18

There is a way to do key listeners in python. This functionality is available through pynput.

Command line:

$ pip install pynput

Python code:

from pynput import keyboard
# your code here
1
  • 11
    Excellent recommendation because it's a very easy to use and pythonic package, but you could have provided a more clear example. To anyone looking for information on how to use it, have a look at the documentation: pypi.python.org/pypi/pynput
    – rien333
    Commented Sep 6, 2016 at 15:00
16

Here's how can do it on Windows:

"""

    Display series of numbers in infinite loop
    Listen to key "s" to stop
    Only works on Windows because listening to keys
    is platform dependent

"""

# msvcrt is a windows specific native module
import msvcrt
import time

# asks whether a key has been acquired
def kbfunc():
    #this is boolean for whether the keyboard has bene hit
    x = msvcrt.kbhit()
    if x:
        #getch acquires the character encoded in binary ASCII
        ret = msvcrt.getch()
    else:
        ret = False
    return ret

#begin the counter
number = 1

#infinite loop
while True:

    #acquire the keyboard hit if exists
    x = kbfunc() 

    #if we got a keyboard hit
    if x != False and x.decode() == 's':
        #we got the key!
        #because x is a binary, we need to decode to string
        #use the decode() which is part of the binary object
        #by default, decodes via utf8
        #concatenation auto adds a space in between
        print ("STOPPING, KEY:", x.decode())
        #break loop
        break
    else:
        #prints the number
        print (number)
        #increment, there's no ++ in python
        number += 1
        #wait half a second
        time.sleep(0.5)
1
  • I wrote the loop a bit differently. while True: newline x = kbfunc() newline if x != False: newline print("keypress:", x.decode()). This works swell for me. I wonder if you know how one might have the terminal watch for key presses that occur while it is not in focus. Commented Apr 28, 2016 at 12:28
10

keyboard

Take full control of your keyboard with this small Python library. Hook global events, register hotkeys, simulate key presses and much more.

Global event hook on all keyboards (captures keys regardless of focus). Listen and sends keyboard events. Works with Windows and Linux (requires sudo), with experimental OS X support (thanks @glitchassassin!). Pure Python, no C modules to be compiled. Zero dependencies. Trivial to install and deploy, just copy the files. Python 2 and 3. Complex hotkey support (e.g. Ctrl+Shift+M, Ctrl+Space) with controllable timeout. Includes high level API (e.g. record and play, add_abbreviation). Maps keys as they actually are in your layout, with full internationalization support (e.g. Ctrl+ç). Events automatically captured in separate thread, doesn't block main program. Tested and documented. Doesn't break accented dead keys (I'm looking at you, pyHook). Mouse support available via project mouse (pip install mouse).

From README.md:

import keyboard

keyboard.press_and_release('shift+s, space')

keyboard.write('The quick brown fox jumps over the lazy dog.')

# Press PAGE UP then PAGE DOWN to type "foobar".
keyboard.add_hotkey('page up, page down', lambda: keyboard.write('foobar'))

# Blocks until you press esc.
keyboard.wait('esc')

# Record events until 'esc' is pressed.
recorded = keyboard.record(until='esc')
# Then replay back at three times the speed.
keyboard.play(recorded, speed_factor=3)

# Type @@ then press space to replace with abbreviation.
keyboard.add_abbreviation('@@', '[email protected]')
# Block forever.
keyboard.wait()
2
  • 1
    “To avoid depending on X, the Linux parts reads raw device files (/dev/input/input*) but this requires root.” That is sadly not usable for many day-to-day tools…
    – feeela
    Commented Mar 18, 2021 at 14:54
  • This doesn't work on Cygwin :-(
    – swdev
    Commented Mar 17 at 6:36
2

Although I like using the keyboard module to capture keyboard events, I don't like its record() function because it returns an array like [KeyboardEvent("A"), KeyboardEvent("~")], which I find kind of hard to read. So, to record keyboard events, I like to use the keyboard module and the threading module simultaneously, like this:

import keyboard
import string
from threading import *


# I can't find a complete list of keyboard keys, so this will have to do:
keys = list(string.ascii_lowercase)
"""
Optional code(extra keys):

keys.append("space_bar")
keys.append("backspace")
keys.append("shift")
keys.append("esc")
"""
def listen(key):
    while True:
        keyboard.wait(key)
        print("[+] Pressed",key)
threads = [Thread(target=listen, kwargs={"key":key}) for key in keys]
for thread in threads:
    thread.start()
0
1

I like to use Pynput. It has many options and allows for simpler and more elegant solutions.

Example:

from pynput import keyboard

def on_activate_a():
    print('A pressed')

def on_activate_b():
    print('B pressed')

def on_activate_c():
    print('C pressed')

def quit():
    print('QUIT')
    h.stop()

with keyboard.GlobalHotKeys({
        'a': on_activate_a,
        'b': on_activate_b,
        'c': on_activate_c,
        '<ctrl>+c': quit}) as h:
    h.join()

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.