CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-keyboard

Hook and simulate keyboard events on Windows and Linux

Pending
Overview
Eval results
Files

interactive-input.mddocs/

Interactive Input

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.

Capabilities

Event Waiting

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

Single Event Reading

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
    """

Hotkey Reading

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'
    """

Utility Functions

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
    """

Usage Examples

Simple Interactive Menu

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

Hotkey Configuration Tool

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

Real-time Key Monitor

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

Interactive Game Controls

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

Input Validation System

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

Async-Style Event Handling

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

Interactive Input Considerations

Blocking Behavior

  • All interactive input functions block program execution
  • Use threading or async patterns for non-blocking alternatives
  • Consider timeout mechanisms for user responsiveness

Event Suppression

  • Suppressing events prevents them from reaching other applications
  • Use carefully to avoid interfering with system functionality
  • Some platforms have limitations on event suppression

Cross-Platform Compatibility

  • Key names may vary between platforms
  • Special keys (function keys, media keys) may not be available on all systems
  • Consider platform-specific fallbacks

Error Handling

  • Input functions may fail silently in restricted environments
  • Handle cases where expected keys are not available
  • Provide alternative input methods when possible

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

docs

hotkeys.md

index.md

interactive-input.md

key-simulation.md

keyboard-events.md

recording-playback.md

text-processing.md

tile.json