What's the simplest way of detecting keyboard input in a script from the terminal? What's the simplest way of detecting keyboard input in a script from the terminal? python python

What's the simplest way of detecting keyboard input in a script from the terminal?


Edit:

I've thought about this problem a lot, and there are a few different behaviors one could want. I've been implementing most of them for Unix and Windows, and will post them here once they are done.

Synchronous/Blocking key capture:

  1. A simple input or raw_input, a blocking function which returns text typed by a user once they press a newline.
  2. A simple blocking function that waits for the user to press a single key, then returns that key

Asynchronous key capture:

  1. A callback that is called with the pressed key whenever the user types a key into the command prompt, even when typing things into an interpreter (a keylogger)
  2. A callback that is called with the typed text after the user presses enter (a less realtime keylogger)
  3. A callback that is called with the keys pressed when a program is running (say, in a for loop or while loop)

Polling:

  1. The user simply wants to be able to do something when a key is pressed, without having to wait for that key (so this should be non-blocking). Thus they call a poll() function and that either returns a key, or returns None. This can either be lossy (if they take too long to between poll they can miss a key) or non-lossy (the poller will store the history of all keys pressed, so when the poll() function requests them they will always be returned in the order pressed).

  2. The same as 1, except that poll only returns something once the user presses a newline.

Robots:

These are something that can be called to programmatically fire keyboard events. This can be used alongside key captures to echo them back out to the user

Implementations

Synchronous/Blocking key capture:

A simple input or raw_input, a blocking function which returns text typed by a user once they press a newline.

typedString = raw_input()

A simple blocking function that waits for the user to press a single key, then returns that key

class _Getch:    """Gets a single character from standard input.  Does not echo to thescreen. From http://code.activestate.com/recipes/134892/"""    def __init__(self):        try:            self.impl = _GetchWindows()        except ImportError:            try:                self.impl = _GetchMacCarbon()            except(AttributeError, ImportError):                self.impl = _GetchUnix()    def __call__(self): return self.impl()class _GetchUnix:    def __init__(self):        import tty, sys, termios # import termios now or else you'll get the Unix version on the Mac    def __call__(self):        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 chclass _GetchWindows:    def __init__(self):        import msvcrt    def __call__(self):        import msvcrt        return msvcrt.getch()class _GetchMacCarbon:    """    A function which returns the current ASCII key that is down;    if no ASCII key is down, the null string is returned.  The    page http://www.mactech.com/macintosh-c/chap02-1.html was    very helpful in figuring out how to do this.    """    def __init__(self):        import Carbon        Carbon.Evt #see if it has this (in Unix, it doesn't)    def __call__(self):        import Carbon        if Carbon.Evt.EventAvail(0x0008)[0]==0: # 0x0008 is the keyDownMask            return ''        else:            #            # The event contains the following info:            # (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]            #            # The message (msg) contains the ASCII char which is            # extracted with the 0x000000FF charCodeMask; this            # number is converted to an ASCII character with chr() and            # returned            #            (what,msg,when,where,mod)=Carbon.Evt.GetNextEvent(0x0008)[1]            return chr(msg & 0x000000FF)def getKey():    inkey = _Getch()    import sys    for i in xrange(sys.maxint):        k=inkey()        if k<>'':break    return k

Asynchronous key capture:

A callback that is called with the pressed key whenever the user types a key into the command prompt, even when typing things into an interpreter (a keylogger)

A callback that is called with the typed text after the user presses enter (a less realtime keylogger)

Windows:

This uses the windows Robot given below, naming the script keyPress.py

# Some if this is from http://nullege.com/codes/show/src@e@i@einstein-HEAD@Python25Einstein@Lib@subprocess.py/380/win32api.GetStdHandle# and# http://nullege.com/codes/show/src@v@i@VistA-HEAD@Python@Pexpect@winpexpect.py/901/win32console.GetStdHandle.PeekConsoleInputfrom ctypes import *import timeimport threadingfrom win32api import STD_INPUT_HANDLE, STD_OUTPUT_HANDLEfrom win32console import GetStdHandle, KEY_EVENT, ENABLE_WINDOW_INPUT, ENABLE_MOUSE_INPUT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUTimport keyPressclass CaptureLines():    def __init__(self):        self.stopLock = threading.Lock()        self.isCapturingInputLines = False        self.inputLinesHookCallback = CFUNCTYPE(c_int)(self.inputLinesHook)        self.pyosInputHookPointer = c_void_p.in_dll(pythonapi, "PyOS_InputHook")        self.originalPyOsInputHookPointerValue = self.pyosInputHookPointer.value        self.readHandle = GetStdHandle(STD_INPUT_HANDLE)        self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)    def inputLinesHook(self):        self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)        inputChars = self.readHandle.ReadConsole(10000000)        self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_PROCESSED_INPUT)        if inputChars == "\r\n":            keyPress.KeyPress("\n")            return 0        inputChars = inputChars[:-2]        inputChars += "\n"        for c in inputChars:            keyPress.KeyPress(c)        self.inputCallback(inputChars)        return 0    def startCapture(self, inputCallback):        self.stopLock.acquire()        try:            if self.isCapturingInputLines:                raise Exception("Already capturing keystrokes")            self.isCapturingInputLines = True            self.inputCallback = inputCallback            self.pyosInputHookPointer.value = cast(self.inputLinesHookCallback, c_void_p).value        except Exception as e:            self.stopLock.release()            raise        self.stopLock.release()    def stopCapture(self):        self.stopLock.acquire()        try:            if not self.isCapturingInputLines:                raise Exception("Keystrokes already aren't being captured")            self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)            self.isCapturingInputLines = False            self.pyosInputHookPointer.value = self.originalPyOsInputHookPointerValue        except Exception as e:            self.stopLock.release()            raise        self.stopLock.release()

A callback that is called with the keys pressed when a program is running (say, in a for loop or while loop)

Windows:

import threadingfrom win32api import STD_INPUT_HANDLEfrom win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUTclass KeyAsyncReader():    def __init__(self):        self.stopLock = threading.Lock()        self.stopped = True        self.capturedChars = ""        self.readHandle = GetStdHandle(STD_INPUT_HANDLE)        self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)    def startReading(self, readCallback):        self.stopLock.acquire()        try:            if not self.stopped:                raise Exception("Capture is already going")            self.stopped = False            self.readCallback = readCallback            backgroundCaptureThread = threading.Thread(target=self.backgroundThreadReading)            backgroundCaptureThread.daemon = True            backgroundCaptureThread.start()        except:            self.stopLock.release()            raise        self.stopLock.release()    def backgroundThreadReading(self):        curEventLength = 0        curKeysLength = 0        while True:            eventsPeek = self.readHandle.PeekConsoleInput(10000)            self.stopLock.acquire()            if self.stopped:                self.stopLock.release()                return            self.stopLock.release()            if len(eventsPeek) == 0:                continue            if not len(eventsPeek) == curEventLength:                if self.getCharsFromEvents(eventsPeek[curEventLength:]):                    self.stopLock.acquire()                    self.stopped = True                    self.stopLock.release()                    break                curEventLength = len(eventsPeek)    def getCharsFromEvents(self, eventsPeek):        callbackReturnedTrue = False        for curEvent in eventsPeek:            if curEvent.EventType == KEY_EVENT:                    if ord(curEvent.Char) == 0 or not curEvent.KeyDown:                        pass                    else:                        curChar = str(curEvent.Char)                        if self.readCallback(curChar) == True:                            callbackReturnedTrue = True        return callbackReturnedTrue    def stopReading(self):        self.stopLock.acquire()        self.stopped = True        self.stopLock.release()

Polling:

The user simply wants to be able to do something when a key is pressed, without having to wait for that key (so this should be non-blocking). Thus they call a poll() function and that either returns a key, or returns None. This can either be lossy (if they take too long to between poll they can miss a key) or non-lossy (the poller will store the history of all keys pressed, so when the poll() function requests them they will always be returned in the order pressed).

Windows and OS X (and maybe Linux):

global isWindowsisWindows = Falsetry:    from win32api import STD_INPUT_HANDLE    from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT    isWindows = Trueexcept ImportError as e:    import sys    import select    import termiosclass KeyPoller():    def __enter__(self):        global isWindows        if isWindows:            self.readHandle = GetStdHandle(STD_INPUT_HANDLE)            self.readHandle.SetConsoleMode(ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT)            self.curEventLength = 0            self.curKeysLength = 0            self.capturedChars = []        else:            # Save the terminal settings            self.fd = sys.stdin.fileno()            self.new_term = termios.tcgetattr(self.fd)            self.old_term = termios.tcgetattr(self.fd)            # New terminal setting unbuffered            self.new_term[3] = (self.new_term[3] & ~termios.ICANON & ~termios.ECHO)            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.new_term)        return self    def __exit__(self, type, value, traceback):        if isWindows:            pass        else:            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_term)    def poll(self):        if isWindows:            if not len(self.capturedChars) == 0:                return self.capturedChars.pop(0)            eventsPeek = self.readHandle.PeekConsoleInput(10000)            if len(eventsPeek) == 0:                return None            if not len(eventsPeek) == self.curEventLength:                for curEvent in eventsPeek[self.curEventLength:]:                    if curEvent.EventType == KEY_EVENT:                        if ord(curEvent.Char) == 0 or not curEvent.KeyDown:                            pass                        else:                            curChar = str(curEvent.Char)                            self.capturedChars.append(curChar)                self.curEventLength = len(eventsPeek)            if not len(self.capturedChars) == 0:                return self.capturedChars.pop(0)            else:                return None        else:            dr,dw,de = select.select([sys.stdin], [], [], 0)            if not dr == []:                return sys.stdin.read(1)            return None

Simple use case:

with KeyPoller() as keyPoller:    while True:        c = keyPoller.poll()        if not c is None:            if c == "c":                break            print c

The same as above, except that poll only returns something once the user presses a newline.

Robots:

These are something that can be called to programmatically fire keyboard events. This can be used alongside key captures to echo them back out to the user

Windows:

# Modified from http://stackoverflow.com/a/13615802/2924421import ctypesfrom ctypes import wintypesimport timeuser32 = ctypes.WinDLL('user32', use_last_error=True)INPUT_MOUSE    = 0INPUT_KEYBOARD = 1INPUT_HARDWARE = 2KEYEVENTF_EXTENDEDKEY = 0x0001KEYEVENTF_KEYUP       = 0x0002KEYEVENTF_UNICODE     = 0x0004KEYEVENTF_SCANCODE    = 0x0008MAPVK_VK_TO_VSC = 0# C struct definitionswintypes.ULONG_PTR = wintypes.WPARAMSendInput = ctypes.windll.user32.SendInputPUL = ctypes.POINTER(ctypes.c_ulong)class KEYBDINPUT(ctypes.Structure):    _fields_ = (("wVk",         wintypes.WORD),                ("wScan",       wintypes.WORD),                ("dwFlags",     wintypes.DWORD),                ("time",        wintypes.DWORD),                ("dwExtraInfo", wintypes.ULONG_PTR))class MOUSEINPUT(ctypes.Structure):    _fields_ = (("dx",          wintypes.LONG),                ("dy",          wintypes.LONG),                ("mouseData",   wintypes.DWORD),                ("dwFlags",     wintypes.DWORD),                ("time",        wintypes.DWORD),                ("dwExtraInfo", wintypes.ULONG_PTR))class HARDWAREINPUT(ctypes.Structure):    _fields_ = (("uMsg",    wintypes.DWORD),                ("wParamL", wintypes.WORD),                ("wParamH", wintypes.WORD))class INPUT(ctypes.Structure):    class _INPUT(ctypes.Union):        _fields_ = (("ki", KEYBDINPUT),                    ("mi", MOUSEINPUT),                    ("hi", HARDWAREINPUT))    _anonymous_ = ("_input",)    _fields_ = (("type",   wintypes.DWORD),                ("_input", _INPUT))LPINPUT = ctypes.POINTER(INPUT)def _check_count(result, func, args):    if result == 0:        raise ctypes.WinError(ctypes.get_last_error())    return argsuser32.SendInput.errcheck = _check_countuser32.SendInput.argtypes = (wintypes.UINT, # nInputs                             LPINPUT,       # pInputs                             ctypes.c_int)  # cbSizedef KeyDown(unicodeKey):    key, unikey, uniflag = GetKeyCode(unicodeKey)    x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag, 0))    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))def KeyUp(unicodeKey):    key, unikey, uniflag = GetKeyCode(unicodeKey)    extra = ctypes.c_ulong(0)    x = INPUT( type=INPUT_KEYBOARD, ki= KEYBDINPUT( key, unikey, uniflag | KEYEVENTF_KEYUP, 0))    user32.SendInput(1, ctypes.byref(x), ctypes.sizeof(x))def KeyPress(unicodeKey):    time.sleep(0.0001)    KeyDown(unicodeKey)    time.sleep(0.0001)    KeyUp(unicodeKey)    time.sleep(0.0001)def GetKeyCode(unicodeKey):    k = unicodeKey    curKeyCode = 0    if k == "up": curKeyCode = 0x26    elif k == "down": curKeyCode = 0x28    elif k == "left": curKeyCode = 0x25    elif k == "right": curKeyCode = 0x27    elif k == "home": curKeyCode = 0x24    elif k == "end": curKeyCode = 0x23    elif k == "insert": curKeyCode = 0x2D    elif k == "pgup": curKeyCode = 0x21    elif k == "pgdn": curKeyCode = 0x22    elif k == "delete": curKeyCode = 0x2E    elif k == "\n": curKeyCode = 0x0D    if curKeyCode == 0:        return 0, int(unicodeKey.encode("hex"), 16), KEYEVENTF_UNICODE    else:        return curKeyCode, 0, 0

OS X:

#!/usr/bin/env pythonimport timefrom Quartz.CoreGraphics import CGEventCreateKeyboardEventfrom Quartz.CoreGraphics import CGEventPost# Python releases things automatically, using CFRelease will result in a scary error#from Quartz.CoreGraphics import CFReleasefrom Quartz.CoreGraphics import kCGHIDEventTap# From http://stackoverflow.com/questions/281133/controlling-the-mouse-from-python-in-os-x# and from https://developer.apple.com/library/mac/documentation/Carbon/Reference/QuartzEventServicesRef/index.html#//apple_ref/c/func/CGEventCreateKeyboardEventdef KeyDown(k):    keyCode, shiftKey = toKeyCode(k)    time.sleep(0.0001)    if shiftKey:        CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, True))        time.sleep(0.0001)    CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, True))    time.sleep(0.0001)    if shiftKey:        CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, False))        time.sleep(0.0001)def KeyUp(k):    keyCode, shiftKey = toKeyCode(k)    time.sleep(0.0001)    CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, False))    time.sleep(0.0001)def KeyPress(k):    keyCode, shiftKey = toKeyCode(k)    time.sleep(0.0001)    if shiftKey:        CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, True))        time.sleep(0.0001)    CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, True))    time.sleep(0.0001)    CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, keyCode, False))    time.sleep(0.0001)    if shiftKey:        CGEventPost(kCGHIDEventTap, CGEventCreateKeyboardEvent(None, 0x38, False))        time.sleep(0.0001)# From http://stackoverflow.com/questions/3202629/where-can-i-find-a-list-of-mac-virtual-key-codesdef toKeyCode(c):    shiftKey = False    # Letter    if c.isalpha():        if not c.islower():            shiftKey = True            c = c.lower()    if c in shiftChars:        shiftKey = True        c = shiftChars[c]    if c in keyCodeMap:        keyCode = keyCodeMap[c]    else:        keyCode = ord(c)    return keyCode, shiftKeyshiftChars = {    '~': '`',    '!': '1',    '@': '2',    '#': '3',    '$': '4',    '%': '5',    '^': '6',    '&': '7',    '*': '8',    '(': '9',    ')': '0',    '_': '-',    '+': '=',    '{': '[',    '}': ']',    '|': '\\',    ':': ';',    '"': '\'',    '<': ',',    '>': '.',    '?': '/'}keyCodeMap = {    'a'                 : 0x00,    's'                 : 0x01,    'd'                 : 0x02,    'f'                 : 0x03,    'h'                 : 0x04,    'g'                 : 0x05,    'z'                 : 0x06,    'x'                 : 0x07,    'c'                 : 0x08,    'v'                 : 0x09,    'b'                 : 0x0B,    'q'                 : 0x0C,    'w'                 : 0x0D,    'e'                 : 0x0E,    'r'                 : 0x0F,    'y'                 : 0x10,    't'                 : 0x11,    '1'                 : 0x12,    '2'                 : 0x13,    '3'                 : 0x14,    '4'                 : 0x15,    '6'                 : 0x16,    '5'                 : 0x17,    '='                 : 0x18,    '9'                 : 0x19,    '7'                 : 0x1A,    '-'                 : 0x1B,    '8'                 : 0x1C,    '0'                 : 0x1D,    ']'                 : 0x1E,    'o'                 : 0x1F,    'u'                 : 0x20,    '['                 : 0x21,    'i'                 : 0x22,    'p'                 : 0x23,    'l'                 : 0x25,    'j'                 : 0x26,    '\''                : 0x27,    'k'                 : 0x28,    ';'                 : 0x29,    '\\'                : 0x2A,    ','                 : 0x2B,    '/'                 : 0x2C,    'n'                 : 0x2D,    'm'                 : 0x2E,    '.'                 : 0x2F,    '`'                 : 0x32,    'k.'                : 0x41,    'k*'                : 0x43,    'k+'                : 0x45,    'kclear'            : 0x47,    'k/'                : 0x4B,    'k\n'               : 0x4C,    'k-'                : 0x4E,    'k='                : 0x51,    'k0'                : 0x52,    'k1'                : 0x53,    'k2'                : 0x54,    'k3'                : 0x55,    'k4'                : 0x56,    'k5'                : 0x57,    'k6'                : 0x58,    'k7'                : 0x59,    'k8'                : 0x5B,    'k9'                : 0x5C,    # keycodes for keys that are independent of keyboard layout    '\n'                : 0x24,    '\t'                : 0x30,    ' '                 : 0x31,    'del'               : 0x33,    'delete'            : 0x33,    'esc'               : 0x35,    'escape'            : 0x35,    'cmd'               : 0x37,    'command'           : 0x37,    'shift'             : 0x38,    'caps lock'         : 0x39,    'option'            : 0x3A,    'ctrl'              : 0x3B,    'control'           : 0x3B,    'right shift'       : 0x3C,    'rshift'            : 0x3C,    'right option'      : 0x3D,    'roption'           : 0x3D,    'right control'     : 0x3E,    'rcontrol'          : 0x3E,    'fun'               : 0x3F,    'function'          : 0x3F,    'f17'               : 0x40,    'volume up'         : 0x48,    'volume down'       : 0x49,    'mute'              : 0x4A,    'f18'               : 0x4F,    'f19'               : 0x50,    'f20'               : 0x5A,    'f5'                : 0x60,    'f6'                : 0x61,    'f7'                : 0x62,    'f3'                : 0x63,    'f8'                : 0x64,    'f9'                : 0x65,    'f11'               : 0x67,    'f13'               : 0x69,    'f16'               : 0x6A,    'f14'               : 0x6B,    'f10'               : 0x6D,    'f12'               : 0x6F,    'f15'               : 0x71,    'help'              : 0x72,    'home'              : 0x73,    'pgup'              : 0x74,    'page up'           : 0x74,    'forward delete'    : 0x75,    'f4'                : 0x76,    'end'               : 0x77,    'f2'                : 0x78,    'page down'         : 0x79,    'pgdn'              : 0x79,    'f1'                : 0x7A,    'left'              : 0x7B,    'right'             : 0x7C,    'down'              : 0x7D,    'up'                : 0x7E}


The Python Documentation provides this snippet to get single characters from the keyboard:

import termios, fcntl, sys, osfd = sys.stdin.fileno()oldterm = termios.tcgetattr(fd)newattr = termios.tcgetattr(fd)newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHOtermios.tcsetattr(fd, termios.TCSANOW, newattr)oldflags = fcntl.fcntl(fd, fcntl.F_GETFL)fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK)try:    while 1:        try:            c = sys.stdin.read(1)            if c:                print("Got character", repr(c))        except IOError: passfinally:    termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm)    fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)

You can also use the PyHook module to get your job done.


One of the simplest way I found is to use pynput module.can be found here with nice examples as well

from pynput import keyboarddef 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 releasedwith keyboard.Listener(        on_press=on_press,        on_release=on_release) as listener:    listener.join()

above is the example worked out for me and to install, go
for python 2:

    pip install pynput

for python 3:

    pip3 install pynput