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) 2 12 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:
- A simple
inputorraw_input, a blocking function which returns text typed by a user once they press a newline. - A simple blocking function that waits for the user to press a single key, then returns that key
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)
- A callback that is called with the keys pressed when a program is running (say, in a for loop or while loop)
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).
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 """ 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 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 kAsynchronous 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
# and
#
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 NoneSimple use case:
with KeyPoller() as keyPoller: while True: c = keyPoller.poll() if not c is None: if c == "c": break print cThe 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
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, 0OS 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
# and from
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
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
} 11 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.
2One of the simplest way I found is to use pynput module.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()above is the example worked out for me and to install, go
for python 2:
pip install pynputfor python 3:
pip3 install pynput 4 You can use methods from if you are on Windows.
import msvcrt
....
while True: print "Doing a function" if msvcrt.kbhit(): print "Key pressed: %s" % msvcrt.getch() 3 This worked for me on macOS Sierra and Python 2.7.10 and 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.') 0 These functions, based on the above, seem to work well for getting characters from the keyboard (blocking and non-blocking):
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 I wrote a more easy-to-use implementation for @enrico.bacis's answer. It supports both Linux(python2.7 and python3.5) and Windows(python2.7). It may support Mac OS, but I didn't test it. If you tried it on Mac, please tell me the result.
'''
Author: Yu Lou
Date: 2017-02-23
Based on the answer by @enrico.bacis in
and @Phylliida in
'''
# 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 otherwiseThis is an example(assume that you saved the codes above in '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() Inspired from code found above (credits), the simple blocking (aka not CPU consuming) macOS version I was looking for:
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, EDITED: Not tested with Python 3
4from 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() This needs run as root: (Warning, this is a system-wide keylogger)
#!/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) Well, since the date of this question post, a Python library addressed this topic. pynput library, from Moses Palmer, is GREAT to catch keyboard and mouse events in a very simple way.
- GitHub repository is here:
- PyPi project is there: enter link description here
- and installation of lib is easy: 'pip install pynput'
(mind the missing 'i' in pynput - I missed it too... ;-) )
import turtle
wn = turtle.Screen()
turtle = turtle.Turtle()
def printLetter(): print("a")
turtle.listen()
turtle.onkey(printLetter, "a")