Hook and simulate keyboard events on Windows and Linux
—
Blocking functions for reading keyboard input in interactive applications, including single key reading, hotkey detection, and event waiting. These functions provide synchronous keyboard input for creating interactive command-line applications, games, and user interfaces.
Block program execution until specific keyboard conditions are met.
def wait(hotkey=None, suppress=False, trigger_on_release=False):
"""
Blocks the program execution until the given hotkey is pressed or,
if given no parameters, blocks forever.
Parameters:
- hotkey: Hotkey string to wait for (if None, blocks forever)
- suppress: If True, suppress the hotkey from reaching other applications
- trigger_on_release: If True, wait for key release instead of press
Examples:
- wait('esc') # Wait for ESC key
- wait('ctrl+c') # Wait for Ctrl+C
- wait() # Block forever (until program termination)
"""Read individual keyboard events with blocking behavior.
def read_event(suppress=False):
"""
Blocks until a keyboard event happens, then returns that event.
Parameters:
- suppress: If True, suppress the event from reaching other applications
Returns:
KeyboardEvent: The captured keyboard event
"""
def read_key(suppress=False):
"""
Blocks until a keyboard event happens, then returns that event's name or,
if missing, its scan code.
Parameters:
- suppress: If True, suppress the key from reaching other applications
Returns:
str or int: Key name or scan code
"""Read complete hotkey combinations as strings.
def read_hotkey(suppress=True):
"""
Similar to read_key(), but blocks until the user presses and releases a
hotkey (or single key), then returns a string representing the hotkey
pressed.
Parameters:
- suppress: If True, suppress the hotkey from reaching other applications
Returns:
str: Hotkey string representation (e.g., 'ctrl+shift+a')
Example:
read_hotkey() # User presses Ctrl+Shift+P -> returns 'ctrl+shift+p'
"""Helper functions for working with current keyboard state and delayed execution.
def call_later(fn, args=(), delay=0.001):
"""
Calls the provided function in a new thread after waiting some time.
Useful for giving the system some time to process an event, without blocking
the current execution flow.
Parameters:
- fn: Function to call
- args: Arguments to pass to function
- delay: Delay in seconds before calling function
"""
def get_hotkey_name(names=None):
"""
Returns a string representation of hotkey from the given key names, or
the currently pressed keys if not given.
Parameters:
- names: List of key names (if None, uses currently pressed keys)
Returns:
str: Standardized hotkey string
"""import keyboard
def interactive_menu():
"""Simple menu system using keyboard input."""
print("=== Interactive Menu ===")
print("1. Option One")
print("2. Option Two")
print("3. Option Three")
print("ESC. Exit")
print("\nPress a key to select:")
while True:
key = keyboard.read_key()
if key == '1':
print("You selected Option One")
break
elif key == '2':
print("You selected Option Two")
break
elif key == '3':
print("You selected Option Three")
break
elif key == 'esc':
print("Exiting...")
break
else:
print(f"Invalid selection: {key}. Try again.")
interactive_menu()import keyboard
def configure_hotkeys():
"""Interactive hotkey configuration."""
hotkeys = {}
def get_user_hotkey(prompt):
"""Get a hotkey from user input."""
print(f"\n{prompt}")
print("Press the desired key combination:")
hotkey = keyboard.read_hotkey()
print(f"You pressed: {hotkey}")
return hotkey
def confirm_hotkey(hotkey, action):
"""Confirm hotkey assignment."""
print(f"\nAssign '{hotkey}' to '{action}'?")
print("Press 'y' to confirm, 'n' to retry, 'esc' to skip:")
while True:
key = keyboard.read_key()
if key == 'y':
return True
elif key == 'n':
return False
elif key == 'esc':
return None
# Configure multiple hotkeys
actions = ['Quick Save', 'Quick Load', 'Screenshot', 'Toggle Mode']
for action in actions:
while True:
hotkey = get_user_hotkey(f"Configure hotkey for: {action}")
# Check for conflicts
if hotkey in hotkeys:
print(f"Warning: '{hotkey}' is already assigned to '{hotkeys[hotkey]}'")
print("Press 'y' to overwrite, 'n' to choose different key:")
if keyboard.read_key() != 'y':
continue
confirmation = confirm_hotkey(hotkey, action)
if confirmation is True:
hotkeys[hotkey] = action
print(f"✓ '{hotkey}' assigned to '{action}'")
break
elif confirmation is None:
print(f"Skipped '{action}'")
break
# If confirmation is False, retry
print(f"\n=== Final Hotkey Configuration ===")
for hotkey, action in hotkeys.items():
print(f"{hotkey} -> {action}")
return hotkeys
# Run configuration
configured_hotkeys = configure_hotkeys()import keyboard
import time
from datetime import datetime
def key_monitor():
"""Real-time keyboard event monitoring."""
print("=== Real-time Key Monitor ===")
print("Press keys to see events. ESC to exit.")
print("Format: [timestamp] event_type key_name (scan_code)")
print("-" * 50)
while True:
event = keyboard.read_event()
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
event_type = "PRESS" if event.event_type == "down" else "RELEASE"
key_name = event.name or "unknown"
scan_code = event.scan_code
print(f"[{timestamp}] {event_type:7} {key_name:15} ({scan_code})")
# Exit on ESC release
if event.name == 'esc' and event.event_type == 'up':
print("Monitoring stopped.")
break
key_monitor()import keyboard
import time
import random
class SimpleGame:
def __init__(self):
self.player_x = 5
self.player_y = 5
self.score = 0
self.running = True
self.board_size = 10
def draw_board(self):
"""Draw the game board."""
print("\033[2J\033[H") # Clear screen
print(f"Score: {self.score}")
print("-" * (self.board_size + 2))
for y in range(self.board_size):
row = "|"
for x in range(self.board_size):
if x == self.player_x and y == self.player_y:
row += "P" # Player
else:
row += " "
row += "|"
print(row)
print("-" * (self.board_size + 2))
print("Use WASD to move, Q to quit")
def move_player(self, dx, dy):
"""Move player with boundary checking."""
new_x = max(0, min(self.board_size - 1, self.player_x + dx))
new_y = max(0, min(self.board_size - 1, self.player_y + dy))
if new_x != self.player_x or new_y != self.player_y:
self.player_x = new_x
self.player_y = new_y
self.score += 1
def handle_input(self):
"""Handle keyboard input."""
key = keyboard.read_key(suppress=True)
if key == 'w':
self.move_player(0, -1) # Up
elif key == 's':
self.move_player(0, 1) # Down
elif key == 'a':
self.move_player(-1, 0) # Left
elif key == 'd':
self.move_player(1, 0) # Right
elif key == 'q' or key == 'esc':
self.running = False
def run(self):
"""Main game loop."""
print("=== Simple Movement Game ===")
print("Loading...")
time.sleep(1)
while self.running:
self.draw_board()
self.handle_input()
print(f"\nGame Over! Final Score: {self.score}")
# Run the game
game = SimpleGame()
game.run()import keyboard
import re
class InputValidator:
def __init__(self):
self.patterns = {
'email': r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
'phone': r'^\+?[\d\s\-\(\)]{10,}$',
'number': r'^\d+$',
'alpha': r'^[a-zA-Z\s]+$'
}
def get_validated_input(self, prompt, pattern_name, max_attempts=3):
"""Get validated input using keyboard reading."""
pattern = self.patterns.get(pattern_name)
if not pattern:
raise ValueError(f"Unknown pattern: {pattern_name}")
attempts = 0
while attempts < max_attempts:
print(f"\n{prompt}")
print("Type your input and press ENTER (ESC to cancel):")
input_text = ""
while True:
event = keyboard.read_event()
if event.event_type == 'down': # Only handle key presses
if event.name == 'enter':
break
elif event.name == 'esc':
print("Input cancelled.")
return None
elif event.name == 'backspace':
if input_text:
input_text = input_text[:-1]
print(f"\rInput: {input_text + ' ' * 10}", end="")
elif event.name and len(event.name) == 1:
input_text += event.name
print(f"\rInput: {input_text}", end="")
print() # New line after input
# Validate input
if re.match(pattern, input_text.strip()):
print(f"✓ Valid {pattern_name}: {input_text}")
return input_text.strip()
else:
attempts += 1
remaining = max_attempts - attempts
print(f"✗ Invalid {pattern_name} format.")
if remaining > 0:
print(f"Please try again. {remaining} attempts remaining.")
else:
print("Maximum attempts reached.")
return None
def collect_user_data(self):
"""Collect validated user data."""
print("=== User Data Collection ===")
data = {}
# Collect various types of input
data['name'] = self.get_validated_input(
"Enter your full name:", 'alpha'
)
if data['name']:
data['email'] = self.get_validated_input(
"Enter your email address:", 'email'
)
if data.get('email'):
data['phone'] = self.get_validated_input(
"Enter your phone number:", 'phone'
)
if data.get('phone'):
data['age'] = self.get_validated_input(
"Enter your age:", 'number'
)
print("\n=== Collected Data ===")
for key, value in data.items():
if value:
print(f"{key.capitalize()}: {value}")
return data
# Usage
validator = InputValidator()
user_data = validator.collect_user_data()import keyboard
import time
from threading import Event, Thread
class AsyncInputHandler:
def __init__(self):
self.stop_event = Event()
self.input_queue = []
self.input_thread = None
def start_background_reading(self):
"""Start reading input in background thread."""
def read_loop():
while not self.stop_event.is_set():
try:
# Use timeout to check stop condition
event = keyboard.read_event(suppress=False)
if event.event_type == 'down': # Only process key presses
self.input_queue.append(event.name)
except:
break
self.input_thread = Thread(target=read_loop, daemon=True)
self.input_thread.start()
def get_next_input(self, timeout=None):
"""Get next input with optional timeout."""
start_time = time.time()
while True:
if self.input_queue:
return self.input_queue.pop(0)
if timeout and (time.time() - start_time) > timeout:
return None
if self.stop_event.is_set():
return None
time.sleep(0.01) # Small delay to prevent busy waiting
def wait_for_any_key(self, valid_keys=None, timeout=None):
"""Wait for any key from a set of valid keys."""
start_time = time.time()
while True:
key = self.get_next_input(timeout=0.1)
if key:
if valid_keys is None or key in valid_keys:
return key
else:
print(f"Invalid key: {key}")
if timeout and (time.time() - start_time) > timeout:
return None
def stop(self):
"""Stop background input reading."""
self.stop_event.set()
if self.input_thread:
self.input_thread.join(timeout=1.0)
# Usage example
def async_input_demo():
handler = AsyncInputHandler()
handler.start_background_reading()
try:
print("=== Async Input Demo ===")
print("This demo shows non-blocking input handling")
print("\nPress 1, 2, or 3 (you have 10 seconds):")
result = handler.wait_for_any_key(['1', '2', '3'], timeout=10)
if result:
print(f"You pressed: {result}")
else:
print("Timeout! No valid key pressed.")
print("\nPress any key or wait 5 seconds for timeout:")
result = handler.get_next_input(timeout=5)
if result:
print(f"You pressed: {result}")
else:
print("Timeout!")
finally:
handler.stop()
async_input_demo()These interactive input functions provide the foundation for creating responsive keyboard-driven applications, from simple command-line interfaces to complex interactive systems.
Install with Tessl CLI
npx tessl i tessl/pypi-keyboard