Какой самый простой способ обнаружить ввод с клавиатуры в python из терминала?
У меня есть простой сценарий Python, который имеет некоторые функции, которые работают в цикле (я беру показания датчика).
while True:
print "Doing a function"
Если клавиатура нажата, я хотел бы напечатать "нажата клавиша".
Какой самый простой способ сделать это в 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)
13 ответов
Редактировать:
Я много думал об этой проблеме, и есть несколько вариантов поведения, которые можно пожелать. Я реализовал большинство из них для Unix и Windows и опубликую их здесь, как только они будут готовы.
Синхронный / блокирующий захват ключа:
- Просто
input
или жеraw_input
- блокирующая функция, которая возвращает текст, набранный пользователем после нажатия новой строки. - Простая функция блокировки, которая ждет, пока пользователь нажмет одну клавишу, а затем возвращает эту клавишу
Асинхронный захват ключа:
- Обратный вызов, который вызывается с нажатой клавишей всякий раз, когда пользователь вводит клавишу в командную строку, даже при наборе текста в интерпретаторе (кейлоггер)
- Обратный вызов, который вызывается с набранным текстом после того, как пользователь нажимает ввод (менее кейлоггер в реальном времени)
- Обратный вызов, который вызывается нажатием клавиш во время работы программы (скажем, в цикле 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
Асинхронный захват ключа:
Обратный вызов, который вызывается с нажатой клавишей всякий раз, когда пользователь вводит клавишу в командную строку, даже при наборе текста в интерпретаторе (кейлоггер)
Обратный вызов, который вызывается с набранным текстом после того, как пользователь нажимает ввод (менее кейлоггер в реальном времени)
Окна:
Для этого используется Windows-робот, указанный ниже, с именем скрипта 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
То же, что и выше, за исключением того, что опрос возвращает что-то, только когда пользователь нажимает на новую строку.
Роботы:
Это то, что может быть вызвано для программного запуска событий клавиатуры. Это может быть использовано вместе с захватом клавиш, чтобы вернуть их пользователю
Окна:
# Modified from http://stackru.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://stackru.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://stackru.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, чтобы выполнить свою работу.
Один из самых простых способов, которые я нашел, это использовать модуль pynput. можно найти здесь с хорошими примерами, а также
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.*)
Это сработало для меня на 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.')
Вы можете использовать методы из http://docs.python.org/2/library/msvcrt.html если вы работаете в Windows.
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
Я написал более простую в использовании реализацию ответа @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://stackru.com/a/13207724/4398908
and @Phylliida in http://stackru.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()
2017-11-09, отредактировано: не тестировалось с Python 3
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()
Это нужно запустить как 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)
Чтобы получить ввод, аналогичный pygame, но без окна, вы можете просто установитьpynput
через терминал:pip install pynput
Вам просто нужно добавить оператор with перед циклом. Он также работает во всех операционных системах (для Linux вам нужен только X-сервер), в отличие отkeyboard
модуль.
from pynput import keyboard
keys = set()
def on_press(key):
keys.add(key)
def on_release(key):
keys.remove(key)
listener = keyboard.Listener(on_press=on_press, on_release=on_release)
# Use case
with listener:
while True:
for key in keys:
if key.char == 'a': # or use `if key == keyboard.Key.up` for the up arrow
print('pressed a')
Ну, с момента публикации этого вопроса, библиотека Python обратилась к этой теме. Библиотека pynput от Moses Palmer - это БОЛЬШАЯ возможность очень просто отлавливать события клавиатуры и мыши.
- GitHub репозиторий находится здесь: https://github.com/moses-palmer/pynput
- Проект PyPi есть: введите описание ссылки здесь
- и установка lib проста: 'pip install pynput'
(обратите внимание на пропущенное "я" в pynput - я тоже его пропустил...;-))
import turtle
wn = turtle.Screen()
turtle = turtle.Turtle()
def printLetter():
print("a")
turtle.listen()
turtle.onkey(printLetter, "a")