从terminal中检测python键盘input的最简单方法是什么?
我有一个简单的Python脚本,它有一些循环运行的函数(我正在读取传感器)。
while True: print "Doing a function"
如果键盘被按下,我想打印“按键”。
在Python中做这个最简单的方法是什么? 我search了高和低。 我已经发现如何用pygame做到这一点,但我宁愿不这样做。 如果我不得不使用pygame,可能没有一个单独的窗口的应用程序?
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)
编辑:
我已经考虑过这个问题了,并且有一些人可能想要的不同的行为。 我一直在为Unix和Windows实现其中的大部分,并且一旦完成就将它们发布到这里。
同步/阻止密钥捕获:
- 一个简单的
input
或raw_input
,一个阻止函数,它返回用户一旦按下换行符键入的文本。 - 一个简单的阻塞function,等待用户按下一个键,然后返回该键
asynchronous密钥捕获:
- 每当用户在命令提示符中键入一个键时,即使在键入事件到一个解释器(一个键盘logging器)时,
- 在用户按下input之后用键入文本调用的callback(实时键盘logging器较less)
- 用程序运行时按下的键调用的callback函数(例如,在for循环或while循环中)
轮询:
-
用户只是希望能够按某个键时能够做某事,而不必等待该键(所以这应该是非阻塞的)。 因此,他们调用一个poll()函数,并返回一个键,或返回None。 这可能是有损的(如果他们需要太长的时间来轮询之间,他们可能会错过一个键)或非有损(轮询器将存储按下的所有键的历史,所以当poll()函数要求他们,他们将永远返回按顺序)。
-
与1相同,只不过轮询只会在用户按下换行符时返回。
机器人:
这些东西可以通过编程方式调用触发键盘事件。 这可以与关键捕获一起用来将它们回显给用户
实现
同步/阻止密钥捕获:
一个简单的input
或raw_input
,一个阻止函数,它返回用户一旦按下换行符键入的文本。
typedString = raw_input()
一个简单的阻塞函数,等待用户按下一个键,然后返回该键
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密钥捕获:
每当用户在命令提示符中键入一个键时,即使在键入事件到一个解释器(一个键盘logging器)时,
在用户按下input之后用键入文本调用的callback(实时键盘logging器较less)
视窗:
这使用下面给出的Windows Robot,命名脚本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.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()
用程序运行时按下的键调用的callback函数(例如,在for循环或while循环中)
视窗:
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()
轮询:
用户只是希望能够按某个键时能够做某事,而不必等待该键(所以这应该是非阻塞的)。 因此,他们调用一个poll()函数,并返回一个键,或返回None。 这可能是有损的(如果他们需要太长的时间来轮询之间,他们可能会错过一个键)或非有损(轮询器将存储按下的所有键的历史,所以当poll()函数要求他们,他们将永远返回按顺序)。
Windows和OS X(也可能是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
简单的使用案例:
with KeyPoller() as keyPoller: while True: c = keyPoller.poll() if not c is None: if c == "c": break print c
与上面相同,只是轮询只返回一些用户按下换行符。
机器人:
这些东西可以通过编程方式调用触发键盘事件。 这可以与关键捕获一起用来将它们回显给用户
视窗:
# 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 }
Python文档提供了这个片段来获取键盘上的单个字符:
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) print "Got character", repr(c) except IOError: pass finally: termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags)
你也可以使用PyHook模块来完成你的工作。
如果您在Windows上,可以使用http://docs.python.org/2/library/msvcrt.html中的方法。;
import msvcrt .... while True: print "Doing a function" if msvcrt.kbhit(): print "Key pressed: %s" % msvcrt.getch()
基于上述的这些函数似乎对从键盘获取字符(阻塞和非阻塞)很有效:
import termios, fcntl, sys, os def get_char_keyboard(): 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) c = None try: c = sys.stdin.read(1) except IOError: pass termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) return c def get_char_keyboard_nonblock(): 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) c = None try: c = sys.stdin.read(1) except IOError: pass termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) return c
我发现最简单的方法之一是使用pynput模块。 可以在这里find很好的例子
from pynput import keyboard def 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 released with keyboard.Listener( on_press=on_press, on_release=on_release) as listener: listener.join()
以上是我为之制定的例子,并安装,去
sudo pip install pynput (pip3 if python3.*)
我写了一个更容易使用的实现@ enrico.bacis的答案。 它支持Linux(python2.7和python3.5)和Windows(python2.7)。 它可能支持Mac OS,但我没有testing它。 如果你在Mac上试过,请告诉我结果。
''' Author: Yu Lou Date: 2017-02-23 Based on the answer by @enrico.bacis in http://stackoverflow.com/a/13207724/4398908 and @Phylliida in http://stackoverflow.com/a/31736883/4398908 ''' # Import modules try: try: import termios, fcntl, sys, os, curses # Import modules for Linux except ImportError: import msvcrt # Import module for Windows except ImportError: raise Exception('This platform is not supported.') class KeyGetterLinux: ''' Implemented kbhit(), getch() and getchar() in Linux. Tested on Ubuntu 16.10(Linux 4.8.0), Python 2.7.12 and Python 3.5.2 ''' def __init__(self): self.buffer = '' # A buffer to store the character read by kbhit self.started = False # Whether initialization is complete def kbhit(self, echo = False): ''' Return whether a key is hitten. ''' if not self.buffer: if echo: self.buffer = self.getchar(block = False) else: self.buffer = self.getch(block = False) return bool(self.buffer) def getch(self, block = True): ''' Return a single character without echo. If block is False and no input is currently available, return an empty string without waiting. ''' try: curses.initscr() curses.noecho() return self.getchar(block) finally: curses.endwin() def getchar(self, block = True): ''' Return a single character and echo. If block is False and no input is currently available, return an empty string without waiting. ''' self._start() try: return self._getchar(block) finally: self._stop() def _getchar(self, block = True): ''' Return a single character and echo. If block is False and no input is currently available, return a empty string without waiting. Should be called between self._start() and self._end() ''' assert self.started, ('_getchar() is called before _start()') # Change the terminal setting if block: fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags & ~os.O_NONBLOCK) else: fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags | os.O_NONBLOCK) if self.buffer: # Use the character in buffer first result = self.buffer self.buffer = '' else: try: result = sys.stdin.read(1) except IOError: # In python 2.7, using read() when no input is available will result in IOError. return '' return result def _start(self): ''' Initialize the terminal. ''' assert not self.started, '_start() is called twice' self.fd = sys.stdin.fileno() self.old_attr = termios.tcgetattr(self.fd) new_attr = termios.tcgetattr(self.fd) new_attr[3] = new_attr[3] & ~termios.ICANON termios.tcsetattr(self.fd, termios.TCSANOW, new_attr) self.old_flags = fcntl.fcntl(self.fd, fcntl.F_GETFL) self.started = True def _stop(self): ''' Restore the terminal. ''' assert self.started, '_start() is not called' termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old_attr) fcntl.fcntl(self.fd, fcntl.F_SETFL, self.old_flags) self.started = False # Magic functions for context manager def __enter__(self): self._start() self.getchar = self._getchar # No need for self._start() now return self def __exit__(self, type, value, traceback): self._stop() return False class KeyGetterWindows: ''' kbhit() and getchar() in Windows. Tested on Windows 7 64 bit, Python 2.7.1 ''' def kbhit(self, echo): return msvcrt.kbhit() def getchar(self, block = True): if not block and not msvcrt.kbhit(): return '' return msvcrt.getchar() def getch(self, block = True): if not block and not msvcrt.kbhit(): return '' return msvcrt.getch() _getchar = getchar # Magic functions for context manager def __enter__(self): return self def __exit__(self, type, value, traceback): return False try: import termios KeyGetter = KeyGetterLinux # Use KeyGetterLinux if termios exists except ImportError: KeyGetter = KeyGetterWindows # Use KeyGetterWindows otherwise
这是一个例子(假设你把上面的代码保存在'key_getter.py'中):
from key_getter import KeyGetter import time def test1(): # Test with block=False print('test1') k = KeyGetter() try: while True: if k.kbhit(): print('Got', repr(k.getch(False))) print('Got', repr(k.getch(False))) else: print('Nothing') time.sleep(0.5) except KeyboardInterrupt: pass print(input('Enter something:')) def test2(): # Test context manager with block=True print('test2') with KeyGetter() as k: try: while True: if k.kbhit(): print('Got', repr(k.getchar(True))) print('Got', repr(k.getchar(True))) else: print('Nothing') time.sleep(0.5) except KeyboardInterrupt: pass print(input('Enter something:')) test1() test2()
从上面的代码启发(学分),简单的阻塞(又名不是CPU消耗)macOS版本我正在寻找:
import termios import sys import fcntl import os def getKeyCode(blocking = True): 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) if not blocking: oldflags = fcntl.fcntl(fd, fcntl.F_GETFL) fcntl.fcntl(fd, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) try: return ord(sys.stdin.read(1)) except IOError: return 0 finally: termios.tcsetattr(fd, termios.TCSAFLUSH, oldterm) if not blocking: fcntl.fcntl(fd, fcntl.F_SETFL, oldflags) def getKeyStroke(): code = getKeyCode() if code == 27: code2 = getKeyCode(blocking = False) if code2 == 0: return "esc" elif code2 == 91: code3 = getKeyCode(blocking = False) if code3 == 65: return "up" elif code3 == 66: return "down" elif code3 == 68: return "left" elif code3 == 67: return "right" else: return "esc?" elif code == 127: return "backspace" elif code == 9: return "tab" elif code == 10: return "return" elif code == 195 or code == 194: code2 = getKeyCode(blocking = False) return chr(code)+chr(code2) # utf-8 char else: return chr(code) while True: print getKeyStroke()
编辑 :没有testing与Python 3
这在macOS Sierra和Python 2.7.10和3.6.3上适用于我
import sys,tty,os,termios def getkey(): old_settings = termios.tcgetattr(sys.stdin) tty.setcbreak(sys.stdin.fileno()) try: while True: b = os.read(sys.stdin.fileno(), 3).decode() if len(b) == 3: k = ord(b[2]) else: k = ord(b) key_mapping = { 127: 'backspace', 10: 'return', 32: 'space', 9: 'tab', 27: 'esc', 65: 'up', 66: 'down', 67: 'right', 68: 'left' } return key_mapping.get(k, chr(k)) finally: termios.tcsetattr(sys.stdin, termios.TCSADRAIN, old_settings) try: while True: k = getkey() if k == 'esc': quit() else: print(k) except (KeyboardInterrupt, SystemExit): os.system('stty sane') print('stopping.')