【问题标题】:What's the simplest way of detecting keyboard input in a script from the terminal?从终端检测脚本中键盘输入的最简单方法是什么?
【发布时间】:2012-10-23 20:19:26
【问题描述】:

我有一个简单的 python 脚本,它有一些循环运行的函数(我正在获取传感器读数)。

while True:
    print "Doing a function"

如果键盘被按下,我想打印“key press”。

在 Python 中执行此操作的最简单方法是什么?我搜索了高低。我已经找到了如何使用 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)

【问题讨论】:

  • 阅读here 以获得仅使用标准库的替代方案。无论如何,我认为使用 pygame 或 curses 实际上是最简单的事情。
  • 我认为你可能是对的。 Pygame还具有工作pc/mac的优势。

标签: python keyboard terminal


【解决方案1】:

编辑:

我已经对这个问题进行了很多思考,并且可能需要一些不同的行为。我已经为 Unix 和 Windows 实现了其中的大部分,一旦完成就会在这里发布。

同步/阻塞键捕获:

  1. 一个简单的inputraw_input,一个阻塞函数,一旦用户按下换行符,它就会返回用户键入的文本。
  2. 一个简单的阻塞函数,等待用户按下单个键,然后返回该键

异步键捕获:

  1. 当用户在命令提示符中键入一个键时,即使在解释器(键盘记录器)中键入内容时,也会使用按下的键调用回调
  2. 在用户按下回车后使用键入的文本调用的回调(不太实时的键盘记录器)
  3. 在程序运行时(例如,在 for 循环或 while 循环中)通过按键调用的回调

轮询:

  1. 用户只是希望能够在按下某个键时执行某些操作,而不必等待该键(因此这应该是非阻塞的)。因此,他们调用 poll() 函数,该函数要么返回一个键,要么返回 None。这可能是有损的(如果他们在轮询之间花费太长时间,他们可能会错过一个键)或非有损的(轮询器将存储所有按下的键的历史记录,因此当 poll() 函数请求它们时,它们将始终被返回按按下的顺序)。

  2. 与 1 相同,只是 poll 仅在用户按下换行符后返回。

机器人:

这些是可以调用以编程方式触发键盘事件的东西。这可以与关键捕获一起使用,以将它们回显给用户

实现

同步/阻塞键捕获:

一个简单的inputraw_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

异步键捕获:

当用户在命令提示符中键入键时,即使在解释器(键盘记录器)中键入内容时,也会使用按下的键调用回调

在用户按下回车后使用键入的文本调用的回调(不太实时的键盘记录器)

窗户:

这使用下面给出的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()

在程序运行时(例如,在 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

和上面一样,只是 poll 只在用户按下换行符后才返回。

机器人:

可以调用这些以编程方式触发键盘事件。这可以与关键捕获一起使用,以将它们回显给用户

窗户:

# 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

操作系统:

#!/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
}

【讨论】:

  • 有史以来最好和最有帮助的答案!如果我可以投票两次,我会的。
  • 只有一件事,getAsync 与打印混淆,它使它们缩进(在 Python3 中)
  • 这太棒了 - 我有一个适用于 Mac OS/X 的解决方案,但在不摆弄的情况下无法正常关闭。您是否考虑过将其作为一个小型开源项目发布?
  • 谢谢大家 =) @TomSwirly,如果你认为这对我有用,我并没有计划这样做,但如果有足够多的人想要它,我很乐意将它扔到 github 或其他地方.但是,我遇到了一个我想首先解决的主要错误,我发布了问题here 如果你们中的任何人愿意提供帮助。
  • 好吧,我实际上并没有让它工作(在 OS/X 上)所以这更有理由把它放在 github 上并弄清楚细节! :-) 我赞成你的问题,但没有 Windows 机器...
【解决方案2】:

Python Documentation 提供了这个 sn-p 从键盘获取单个字符:

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)

您还可以使用PyHook 模块来完成您的工作。

【讨论】:

  • 介意解释所有额外的代码吗? (oldterm, newattr, termios, oldflags, fcntl)
  • 这是我找到的最佳答案,用于创建一种真正的非阻塞方式来获取键盘输入(对 while 结构进行修改)。几天来,我一直在努力寻找一种方法来区分普通 ESC 和转义字符(以 ESC 为前缀)。我可以将它用于我的解决方案。
【解决方案3】:

我发现的最简单的方法之一是使用 pynput 模块。can be found here with nice examples as well

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()

上面是为我制定的示例并安装,去吧
对于蟒蛇 2:

    pip install pynput

对于python 3:

    pip3 install pynput

【讨论】:

  • 这是基于 X 系统:文件“/usr/local/lib/python3.7/dist-packages/Xlib/support/unix_connect.py”,第 76 行,在 get_display 中引发错误。DisplayNameEr
  • 实际上不推荐这个。这是阻塞代码
  • 是的,如何将此脚本与我当前的脚本集成以使用此侦听器并运行我的其他 asyncio.get_event_loop() 函数?
  • 实际上很有帮助。谢谢!
【解决方案4】:

如果您使用的是 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()

【讨论】:

  • 不幸的是,我在使用 Mac,但会很干净。
  • 这个方法被阻塞了。
  • kbhit() 是非阻塞的。 getch() 仅在 kbhit() 为 false 时才会阻塞,这在此处是不可能的。
【解决方案5】:

这在 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.')

【讨论】:

    【解决方案6】:

    这些函数,基于上述,似乎很适合从键盘获取字符(阻塞和非阻塞):

    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
    

    【讨论】:

      【解决方案7】:

      我为@enrico.bacis 的回答编写了一个更易于使用的实现。它同时支持 Linux(python2.7 和 python3.5)和 Windows(python2.7)。它可能支持Mac OS,但我没有测试它。如果你在 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()
      

      【讨论】:

        【解决方案8】:

        受上述代码的启发(学分),我正在寻找的简单阻塞(也就是不消耗 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()
        

        2017-11-09,已编辑:未使用 Python 3 测试

        【讨论】:

        • 在 python 2.7 和 python 3 space 中不起作用,在 python 3 中 esc 给出错误:TypeError: ord() expected a character, but string of length 0 found
        • @neoDev space 不工作?,你观察到什么?我没有用python 3(只有python 2.7)进行测试,我想我应该提到它。
        • 看看我发布的答案,它适用于 Python 2 和 3
        • 你的意思是当按下空格键时你期望"space"字符串而不是" "(空格字符)?
        【解决方案9】:

        这需要以 root 身份运行:(警告,这是一个系统范围的键盘记录器)

        #!/usr/bin/python3
        
        import signal
        import keyboard
        import time
        import os
        
        
        if not os.geteuid() == 0:
          print("This script needs to be run as root.")
          exit()
        
        def exitNice(signum, frame):
          global running 
          running = False
        
        def keyEvent(e):
          global running
          if e.event_type == "up":
            print("Key up: " + str(e.name))
          if e.event_type == "down":
            print("Key down: " + str(e.name))
          if e.name == "q":
            exitNice("", "")
            print("Quitting")
        
        running = True
        signal.signal(signal.SIGINT, exitNice)
        keyboard.hook(keyEvent)
        
        print("Press 'q' to quit")
        fps = 1/24
        while running:
          time.sleep(fps)
        
        

        【讨论】:

          【解决方案10】:

          好吧,自从这个问题发布之日起,一个 Python 库就解决了这个话题。 来自 Moses Palmer 的 pynput 库非常适合以非常简单的方式捕获键盘和鼠标事件。

          (请注意 pynput 中缺少的 'i' - 我也错过了... ;-))

          【讨论】:

            【解决方案11】:
            from time import sleep
            import keyboard
                    
            flag = True
                    
            def main():
               global flag
               while flag:
                    print('sleeping')
                    sleep(2)
                    
            def changeFlag():
                global flag
                flag = False
                    
            keyboard.on_press_key("r", lambda _:changeFlag())
                    
            main()
            

            【讨论】:

              【解决方案12】:
              import turtle
              
              wn = turtle.Screen()
              turtle = turtle.Turtle()
              
              def printLetter():
                  print("a")
              
              turtle.listen()
              turtle.onkey(printLetter, "a")
              

              【讨论】:

                猜你喜欢
                • 2010-11-08
                • 2022-12-29
                • 1970-01-01
                • 2012-07-26
                • 1970-01-01
                • 1970-01-01
                • 2014-01-04
                相关资源
                最近更新 更多