Monitor and control user input devices across multiple operating systems
Comprehensive keyboard input simulation and monitoring functionality for programmatic key presses, string typing, modifier key combinations, hotkey detection, and real-time keyboard event listening across multiple operating systems.
The Controller class provides programmatic control over keyboard input, enabling automation of key presses, text typing, and complex key combinations.
class Controller:
"""A controller for sending virtual keyboard events to the system."""
class InvalidKeyException(Exception):
"""Raised when an invalid key parameter is passed."""
pass
class InvalidCharacterException(Exception):
"""Raised when an untypable character is encountered in type()."""
pass
def __init__(self):
"""Initialize the keyboard controller."""
...
def press(self, key: Key | KeyCode | str):
"""
Press a key.
Args:
key: The key to press. Can be:
- Single character string (e.g., 'a', 'A', '1')
- Key enum member (e.g., Key.enter, Key.ctrl)
- KeyCode instance
Raises:
InvalidKeyException: If the key is invalid
ValueError: If key is string with length != 1
"""
...
def release(self, key: Key | KeyCode | str):
"""
Release a key.
Args:
key: The key to release (same format as press())
Raises:
InvalidKeyException: If the key is invalid
ValueError: If key is string with length != 1
"""
...
def tap(self, key: Key | KeyCode | str):
"""
Press and immediately release a key.
Args:
key: The key to tap (same format as press())
Raises:
InvalidKeyException: If the key is invalid
ValueError: If key is string with length != 1
"""
...
def touch(self, key: Key | KeyCode | str, is_press: bool):
"""
Press or release a key based on boolean flag.
Args:
key: The key to touch
is_press (bool): True to press, False to release
Raises:
InvalidKeyException: If the key is invalid
"""
...
def pressed(self, *keys) -> ContextManager:
"""
Context manager that keeps keys pressed for the duration of the block.
Args:
*keys: Keys to keep pressed
Returns:
ContextManager: Context manager for the key press duration
Example:
with keyboard.pressed(Key.ctrl):
keyboard.tap('c') # Ctrl+C
"""
...
def type(self, string: str):
"""
Type a string by sending key press and release events.
Args:
string (str): The string to type
Raises:
InvalidCharacterException: If an untypable character is encountered
"""
...
@property
def modifiers(self) -> ContextManager:
"""Context manager for accessing current modifier key state."""
...
@property
def alt_pressed(self) -> bool:
"""
Whether any alt key is pressed.
Note: This reflects only the internal state of this controller.
See modifiers property for more information.
"""
...
@property
def alt_gr_pressed(self) -> bool:
"""
Whether altgr is pressed.
Note: This reflects only the internal state of this controller.
See modifiers property for more information.
"""
...
@property
def ctrl_pressed(self) -> bool:
"""
Whether any ctrl key is pressed.
Note: This reflects only the internal state of this controller.
See modifiers property for more information.
"""
...
@property
def shift_pressed(self) -> bool:
"""
Whether any shift key is pressed, or caps lock is toggled.
Note: This reflects only the internal state of this controller.
See modifiers property for more information.
"""
...from pynput.keyboard import Key, Controller
# Create controller
keyboard = Controller()
# Type individual characters
keyboard.press('h')
keyboard.release('h')
# Use tap for press+release
keyboard.tap('i')
# Type special keys
keyboard.press(Key.enter)
keyboard.release(Key.enter)
# Type strings
keyboard.type('Hello, World!')
# Key combinations using context manager
with keyboard.pressed(Key.ctrl):
keyboard.tap('c') # Ctrl+C
# Multiple modifiers
with keyboard.pressed(Key.ctrl, Key.shift):
keyboard.tap('a') # Ctrl+Shift+A
# Manual modifier handling
keyboard.press(Key.alt)
keyboard.tap(Key.tab) # Alt+Tab
keyboard.release(Key.alt)
# Uppercase typing
keyboard.tap('A') # Types uppercase A
# OR
with keyboard.pressed(Key.shift):
keyboard.tap('a') # Also types uppercase AThe Listener class monitors keyboard events in real-time, providing callbacks for key press and release events.
class Listener:
"""A listener for keyboard events."""
def __init__(
self,
on_press: callable = None,
on_release: callable = None,
suppress: bool = False,
**kwargs
):
"""
Initialize the keyboard event listener.
Args:
on_press (callable): Callback for key press events (key, injected)
on_release (callable): Callback for key release events (key, injected)
suppress (bool): Whether to suppress events system-wide
**kwargs: Platform-specific options
"""
...
def start(self):
"""Start the listener thread."""
...
def stop(self):
"""Stop the listener. Cannot be restarted once stopped."""
...
def wait(self):
"""Wait for the listener to become ready."""
...
def join(self, timeout: float = None):
"""
Wait for the listener thread to complete.
Args:
timeout (float): Maximum time to wait in seconds
"""
...
def canonical(self, key) -> Key | KeyCode:
"""Convert a key to its canonical representation."""
...
@property
def running(self) -> bool:
"""Whether the listener is currently running."""
...
@property
def suppress(self) -> bool:
"""Whether events are being suppressed system-wide."""
...from pynput import keyboard
def on_press(key, injected=False):
"""Handle key press events."""
try:
# Alphanumeric key
print(f'Alphanumeric key {key.char} pressed')
except AttributeError:
# Special key
print(f'Special key {key} pressed')
# Stop on escape
if key == keyboard.Key.esc:
return False
def on_release(key, injected=False):
"""Handle key release events."""
print(f'Key {key} released')
# Context manager usage (recommended)
with keyboard.Listener(
on_press=on_press,
on_release=on_release
) as listener:
listener.join()
# Manual control
listener = keyboard.Listener(
on_press=on_press,
on_release=on_release
)
listener.start()
listener.join()
# Non-blocking usage
listener = keyboard.Listener(on_press=on_press)
listener.start()
# ... do other work
listener.stop()The Events class provides synchronous iteration over keyboard events.
class Events:
"""A keyboard event listener supporting synchronous iteration over events."""
def __init__(self):
"""Initialize the events iterator."""
...
def __enter__(self):
"""Start the event listener."""
...
def __exit__(self, *args):
"""Stop the event listener."""
...
def __iter__(self):
"""Return iterator interface."""
...
def __next__(self):
"""Get the next event."""
...
def get(self, timeout: float = None):
"""
Get the next event with optional timeout.
Args:
timeout (float): Maximum time to wait for an event
Returns:
Event or None: The next event, or None if timeout or stopped
"""
...
class Press:
"""A key press event."""
def __init__(self, key: Key | KeyCode, injected: bool):
self.key = key
self.injected = injected
class Release:
"""A key release event."""
def __init__(self, key: Key | KeyCode, injected: bool):
self.key = key
self.injected = injectedfrom pynput.keyboard import Events
# Process events synchronously
with Events() as events:
for event in events:
if isinstance(event, Events.Press):
print(f'Key {event.key} pressed')
if str(event.key) == "'q'": # Exit on 'q'
break
elif isinstance(event, Events.Release):
print(f'Key {event.key} released')The HotKey class enables detection of key combinations, while GlobalHotKeys provides a convenient listener for multiple hotkeys.
class HotKey:
"""A combination of keys acting as a hotkey."""
def __init__(self, keys: set, on_activate: callable):
"""
Initialize a hotkey.
Args:
keys (set): Set of keys that must be pressed simultaneously
on_activate (callable): Function to call when hotkey is activated
"""
...
@staticmethod
def parse(keys: str) -> list:
"""
Parse a key combination string.
Args:
keys (str): Key combination string (e.g., '<ctrl>+<alt>+h', '<cmd>+c')
Returns:
list: List of key objects
Raises:
ValueError: If the key string is invalid
"""
...
def press(self, key: Key | KeyCode):
"""
Update hotkey state for a pressed key.
Args:
key: The key being pressed
"""
...
def release(self, key: Key | KeyCode):
"""
Update hotkey state for a released key.
Args:
key: The key being released
"""
...
class GlobalHotKeys(Listener):
"""A keyboard listener supporting multiple global hotkeys."""
def __init__(self, hotkeys: dict, *args, **kwargs):
"""
Initialize global hotkeys listener.
Args:
hotkeys (dict): Mapping from hotkey strings to callback functions
*args, **kwargs: Arguments passed to base Listener class
Raises:
ValueError: If any hotkey description is invalid
"""
...from pynput.keyboard import HotKey, GlobalHotKeys, Key
# Single hotkey
def on_hotkey():
print('Hotkey activated!')
hotkey = HotKey({Key.ctrl, Key.alt, KeyCode.from_char('h')}, on_hotkey)
def on_press(key):
hotkey.press(key)
def on_release(key):
hotkey.release(key)
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
listener.join()
# Multiple global hotkeys
def copy_handler():
print('Copy hotkey pressed')
def paste_handler():
print('Paste hotkey pressed')
def quit_handler():
print('Quit hotkey pressed')
return False # Stop listener
hotkeys = {
'<ctrl>+c': copy_handler,
'<ctrl>+v': paste_handler,
'<ctrl>+<alt>+q': quit_handler
}
with GlobalHotKeys(hotkeys) as listener:
listener.join()
# Parse hotkey strings
try:
keys = HotKey.parse('<ctrl>+<shift>+a')
print(f'Parsed keys: {keys}')
except ValueError as e:
print(f'Invalid hotkey string: {e}')class KeyCode:
"""Represents a key code used by the operating system."""
def __init__(self, vk: int = None, char: str = None, is_dead: bool = False):
"""
Initialize a KeyCode.
Args:
vk (int): Virtual key code
char (str): Character representation
is_dead (bool): Whether this is a dead key
"""
self.vk = vk
self.char = char
self.is_dead = is_dead
self.combining = None # Set for dead keys
@classmethod
def from_vk(cls, vk: int, **kwargs) -> 'KeyCode':
"""
Create a KeyCode from virtual key code.
Args:
vk (int): Virtual key code
**kwargs: Additional parameters
Returns:
KeyCode: New KeyCode instance
"""
...
@classmethod
def from_char(cls, char: str, **kwargs) -> 'KeyCode':
"""
Create a KeyCode from character.
Args:
char (str): Single character
**kwargs: Additional parameters
Returns:
KeyCode: New KeyCode instance
"""
...
@classmethod
def from_dead(cls, char: str, **kwargs) -> 'KeyCode':
"""
Create a dead key KeyCode.
Args:
char (str): Dead key character
**kwargs: Additional parameters
Returns:
KeyCode: New dead key KeyCode instance
"""
...
def join(self, key: 'KeyCode') -> 'KeyCode':
"""
Apply this dead key to another key.
Args:
key (KeyCode): Key to join with this dead key
Returns:
KeyCode: Combined key result
Raises:
ValueError: If keys cannot be joined
"""
...class Key(enum.Enum):
"""Special keys that don't correspond to printable characters."""
# Modifier keys
alt # Generic Alt key
alt_l # Left Alt key
alt_r # Right Alt key
alt_gr # AltGr key
ctrl # Generic Ctrl key
ctrl_l # Left Ctrl key
ctrl_r # Right Ctrl key
shift # Generic Shift key
shift_l # Left Shift key
shift_r # Right Shift key
cmd # Generic Command/Windows key
cmd_l # Left Command/Windows key
cmd_r # Right Command/Windows key
# Navigation keys
up # Up arrow
down # Down arrow
left # Left arrow
right # Right arrow
home # Home key
end # End key
page_up # Page Up
page_down # Page Down
# Special keys
space # Spacebar
tab # Tab key
enter # Enter/Return key
esc # Escape key
backspace # Backspace key
delete # Delete key
# Lock keys
caps_lock # Caps Lock
num_lock # Num Lock
scroll_lock # Scroll Lock
# Function keys (F1-F20)
f1
f2
f3
f4
f5
f6
f7
f8
f9
f10
f11
f12
f13
f14
f15
f16
f17
f18
f19
f20
# Media keys
media_play_pause # Play/Pause toggle
media_volume_mute # Volume mute
media_volume_up # Volume up
media_volume_down # Volume down
media_previous # Previous track
media_next # Next track
# System keys (may be undefined on some platforms)
insert # Insert key
menu # Menu/Application key
pause # Pause/Break key
print_screen # Print Screen key# Custom event filtering
def win32_event_filter(msg, data):
"""Filter Windows keyboard events."""
# Access to KBDLLHOOKSTRUCT data
# Return False to suppress event from reaching listener
return True
listener = keyboard.Listener(
on_press=on_press,
win32_event_filter=win32_event_filter
)# Event intercepting and modification
def darwin_intercept(event_type, event):
"""Intercept and modify macOS keyboard events."""
# Modify event using Quartz.CGEvent functions
# Return None to suppress event system-wide
return event
listener = keyboard.Listener(
on_press=on_press,
darwin_intercept=darwin_intercept
)Platform-specific options are available for X11/Xorg systems. The system requires access to the X server and proper DISPLAY environment variable configuration.
Dead keys allow composition of accented characters:
from pynput.keyboard import KeyCode
# Create dead key
dead_tilde = KeyCode.from_dead('~')
# Join with regular key
a_key = KeyCode.from_char('a')
result = dead_tilde.join(a_key) # Creates 'ã'
# Join with space or same key to get literal character
space_key = KeyCode.from_char(' ')
literal = dead_tilde.join(space_key) # Creates '~'The keyboard module automatically maps control characters:
# These are equivalent when typing
keyboard.type('\n') # Maps to Key.enter
keyboard.type('\t') # Maps to Key.tab
keyboard.type('\r') # Maps to Key.enterImportError: Missing platform dependencies
InvalidKeyException: Invalid key parameters passed to controller methods
InvalidCharacterException: Untypable characters in type() method
Permission Issues: Some operations may require elevated privileges
from pynput.keyboard import Controller, Key
keyboard = Controller()
try:
keyboard.press('a')
keyboard.release('a')
except Controller.InvalidKeyException as e:
print(f"Invalid key: {e}")
try:
keyboard.type('Hello 🌍') # Emoji might not be typable
except Controller.InvalidCharacterException as e:
print(f"Cannot type character at position {e.args[0]}: {e.args[1]}")
# Hotkey parsing errors
try:
from pynput.keyboard import HotKey
keys = HotKey.parse('<invalid>+<key>')
except ValueError as e:
print(f"Invalid hotkey string: {e}")from pynput import keyboard
def on_press(key):
# Raise StopException to gracefully stop listener
if key == keyboard.Key.esc:
raise keyboard.Listener.StopException()
def on_error():
print("Listener encountered an error")
try:
with keyboard.Listener(
on_press=on_press,
on_release=lambda key: None
) as listener:
listener.join()
except keyboard.Listener.StopException:
print("Listener stopped by user")
except Exception as e:
print(f"Listener error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-pynput