Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I have a simple python script, that has some functions that run in a loop (I'm taking sensor readings).

while True:     print "Doing a function" 

If the keyboard is pressed I'd like to print "key pressed".

What's the simplest way of doing this in Python? I've searched high and low. I've found out how to do it with pygame, but I'd rather do it without. If I do have to use pygame is it possible to not have a separate window for the application?:

import pygame, time from pygame.locals import *  pygame.init() screen = pygame.display.set_mode((640, 480)) pygame.display.set_caption('Pygame Keyboard Test') pygame.mouse.set_visible(0)   while True:     print "doing a function"      for event in pygame.event.get():       if (event.type == KEYUP) or (event.type == KEYDOWN):          print "key pressed"          time.sleep(0.1) 
like image 294
Tom R Avatar asked Nov 03 '12 09:11

Tom R


People also ask

How do I capture a keyboard input in Python?

Use the input() function to get Python user input from keyboard. Press the enter key after entering the value. The program waits for user input indefinetly, there is no timeout. The input function returns a string, that you can store in a variable.

Which function is used for reading input from keyboard?

Python user input from the keyboard can be read using the input() built-in function. The input from the user is read as a string and can be assigned to a variable. After entering the value from the keyboard, we have to press the “Enter” button. Then the input() function reads the value entered by the user.


2 Answers

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 the screen. 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 ch  class _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@[email protected]/380/win32api.GetStdHandle # and # http://nullege.com/codes/show/src@v@i@VistA-HEAD@Python@[email protected]/901/win32console.GetStdHandle.PeekConsoleInput  from ctypes import * import time import threading  from win32api import STD_INPUT_HANDLE, STD_OUTPUT_HANDLE  from win32console import GetStdHandle, KEY_EVENT, ENABLE_WINDOW_INPUT, ENABLE_MOUSE_INPUT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT  import keyPress   class 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 threading from win32api import STD_INPUT_HANDLE from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT   class 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 isWindows  isWindows = False try:     from win32api import STD_INPUT_HANDLE     from win32console import GetStdHandle, KEY_EVENT, ENABLE_ECHO_INPUT, ENABLE_LINE_INPUT, ENABLE_PROCESSED_INPUT     isWindows = True except ImportError as e:     import sys     import select     import termios   class 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/2924421  import ctypes from ctypes import wintypes import time  user32 = ctypes.WinDLL('user32', use_last_error=True)  INPUT_MOUSE    = 0 INPUT_KEYBOARD = 1 INPUT_HARDWARE = 2  KEYEVENTF_EXTENDEDKEY = 0x0001 KEYEVENTF_KEYUP       = 0x0002 KEYEVENTF_UNICODE     = 0x0004 KEYEVENTF_SCANCODE    = 0x0008  MAPVK_VK_TO_VSC = 0  # C struct definitions wintypes.ULONG_PTR = wintypes.WPARAM  SendInput = ctypes.windll.user32.SendInput  PUL = 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 args  user32.SendInput.errcheck = _check_count user32.SendInput.argtypes = (wintypes.UINT, # nInputs                              LPINPUT,       # pInputs                              ctypes.c_int)  # cbSize  def 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 python  import time from Quartz.CoreGraphics import CGEventCreateKeyboardEvent from Quartz.CoreGraphics import CGEventPost  # Python releases things automatically, using CFRelease will result in a scary error #from Quartz.CoreGraphics import CFRelease  from 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/CGEventCreateKeyboardEvent   def 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-codes  def 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, shiftKey  shiftChars = {     '~': '`',     '!': '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 } 
like image 191
Phylliida Avatar answered Oct 28 '22 22:10

Phylliida


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

import termios, fcntl, sys, os fd = sys.stdin.fileno()  oldterm = termios.tcgetattr(fd) newattr = termios.tcgetattr(fd) newattr[3] = newattr[3] & ~termios.ICANON & ~termios.ECHO termios.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: pass finally:     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.

like image 29
enrico.bacis Avatar answered Oct 28 '22 23:10

enrico.bacis