51

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.

Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
ollien
  • 4,418
  • 9
  • 35
  • 58
  • OSX, But I don't think that would matter. , I would like it to work in any os, but if that is not possible, it is ok with me – ollien Aug 12 '12 at 01:27
  • 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 Aug 12 '12 at 01: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 Aug 12 '12 at 01:37
  • 2
    @icktoofay: On Linux, you can monitor `/dev/input/*` and extract keypresses directly. – Blender Aug 12 '12 at 01:37
  • 2
    @Blender: Unless you're running it over SSH... – icktoofay Aug 12 '12 at 01:40
  • Would that happen to work on OSX? They are both UNIX based so it is always a possibility – ollien Aug 12 '12 at 01:40
  • 1
    @njk828: Arrow keys are possible, but tricky, since they're an escape sequence (usually). – icktoofay Aug 12 '12 at 01:40
  • 1
    Differentiate between a key listener (which interfaces directly with a keyboard, or at least with the OS-level keyboard driver) and a file reader (which reads the *characters* that a terminal emulator produces from the keyboard). Reading arrow keys via a key listener is no harder than reading any other key; it's the terminal which produces some sequence of bytes in *response* to an arrow key being pressed. – chepner Oct 29 '19 at 19:09

7 Answers7

56

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
kynan
  • 13,235
  • 6
  • 79
  • 81
Xiangrui Li
  • 2,386
  • 2
  • 13
  • 17
  • 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 Jun 27 '21 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. – Sylvester Kruin Sep 12 '21 at 20:11
31

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()
icktoofay
  • 126,289
  • 21
  • 250
  • 231
  • 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 Aug 12 '12 at 01: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 Aug 12 '12 at 02: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? – Prakash Palnati Apr 25 '17 at 06: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 Jun 20 '17 at 02: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 Nov 28 '17 at 10:09
  • @ollien consider accepting one of the answers recommending the much more intuitive `pynput` library instead of this one. – kynan Jan 11 '20 at 19:30
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
kynan
  • 13,235
  • 6
  • 79
  • 81
Jayk
  • 191
  • 1
  • 5
  • 10
    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: https://pypi.python.org/pypi/pynput – rien333 Sep 06 '16 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)
CMCDragonkai
  • 6,222
  • 12
  • 56
  • 98
  • 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. – Musixauce3000 Apr 28 '16 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('@@', 'my.long.email@example.com')
# Block forever.
keyboard.wait()
Anton Tarasenko
  • 8,099
  • 11
  • 66
  • 91
  • 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 Mar 18 '21 at 14:54
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()
Sipher_
  • 71
  • 9
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()
Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88