0
# Keyboard Control and Monitoring
1
2
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.
3
4
## Capabilities
5
6
### Keyboard Controller
7
8
The Controller class provides programmatic control over keyboard input, enabling automation of key presses, text typing, and complex key combinations.
9
10
```python { .api }
11
class Controller:
12
"""A controller for sending virtual keyboard events to the system."""
13
14
class InvalidKeyException(Exception):
15
"""Raised when an invalid key parameter is passed."""
16
pass
17
18
class InvalidCharacterException(Exception):
19
"""Raised when an untypable character is encountered in type()."""
20
pass
21
22
def __init__(self):
23
"""Initialize the keyboard controller."""
24
...
25
26
def press(self, key: Key | KeyCode | str):
27
"""
28
Press a key.
29
30
Args:
31
key: The key to press. Can be:
32
- Single character string (e.g., 'a', 'A', '1')
33
- Key enum member (e.g., Key.enter, Key.ctrl)
34
- KeyCode instance
35
36
Raises:
37
InvalidKeyException: If the key is invalid
38
ValueError: If key is string with length != 1
39
"""
40
...
41
42
def release(self, key: Key | KeyCode | str):
43
"""
44
Release a key.
45
46
Args:
47
key: The key to release (same format as press())
48
49
Raises:
50
InvalidKeyException: If the key is invalid
51
ValueError: If key is string with length != 1
52
"""
53
...
54
55
def tap(self, key: Key | KeyCode | str):
56
"""
57
Press and immediately release a key.
58
59
Args:
60
key: The key to tap (same format as press())
61
62
Raises:
63
InvalidKeyException: If the key is invalid
64
ValueError: If key is string with length != 1
65
"""
66
...
67
68
def touch(self, key: Key | KeyCode | str, is_press: bool):
69
"""
70
Press or release a key based on boolean flag.
71
72
Args:
73
key: The key to touch
74
is_press (bool): True to press, False to release
75
76
Raises:
77
InvalidKeyException: If the key is invalid
78
"""
79
...
80
81
def pressed(self, *keys) -> ContextManager:
82
"""
83
Context manager that keeps keys pressed for the duration of the block.
84
85
Args:
86
*keys: Keys to keep pressed
87
88
Returns:
89
ContextManager: Context manager for the key press duration
90
91
Example:
92
with keyboard.pressed(Key.ctrl):
93
keyboard.tap('c') # Ctrl+C
94
"""
95
...
96
97
def type(self, string: str):
98
"""
99
Type a string by sending key press and release events.
100
101
Args:
102
string (str): The string to type
103
104
Raises:
105
InvalidCharacterException: If an untypable character is encountered
106
"""
107
...
108
109
@property
110
def modifiers(self) -> ContextManager:
111
"""Context manager for accessing current modifier key state."""
112
...
113
114
@property
115
def alt_pressed(self) -> bool:
116
"""
117
Whether any alt key is pressed.
118
119
Note: This reflects only the internal state of this controller.
120
See modifiers property for more information.
121
"""
122
...
123
124
@property
125
def alt_gr_pressed(self) -> bool:
126
"""
127
Whether altgr is pressed.
128
129
Note: This reflects only the internal state of this controller.
130
See modifiers property for more information.
131
"""
132
...
133
134
@property
135
def ctrl_pressed(self) -> bool:
136
"""
137
Whether any ctrl key is pressed.
138
139
Note: This reflects only the internal state of this controller.
140
See modifiers property for more information.
141
"""
142
...
143
144
@property
145
def shift_pressed(self) -> bool:
146
"""
147
Whether any shift key is pressed, or caps lock is toggled.
148
149
Note: This reflects only the internal state of this controller.
150
See modifiers property for more information.
151
"""
152
...
153
```
154
155
#### Usage Examples
156
157
```python
158
from pynput.keyboard import Key, Controller
159
160
# Create controller
161
keyboard = Controller()
162
163
# Type individual characters
164
keyboard.press('h')
165
keyboard.release('h')
166
167
# Use tap for press+release
168
keyboard.tap('i')
169
170
# Type special keys
171
keyboard.press(Key.enter)
172
keyboard.release(Key.enter)
173
174
# Type strings
175
keyboard.type('Hello, World!')
176
177
# Key combinations using context manager
178
with keyboard.pressed(Key.ctrl):
179
keyboard.tap('c') # Ctrl+C
180
181
# Multiple modifiers
182
with keyboard.pressed(Key.ctrl, Key.shift):
183
keyboard.tap('a') # Ctrl+Shift+A
184
185
# Manual modifier handling
186
keyboard.press(Key.alt)
187
keyboard.tap(Key.tab) # Alt+Tab
188
keyboard.release(Key.alt)
189
190
# Uppercase typing
191
keyboard.tap('A') # Types uppercase A
192
# OR
193
with keyboard.pressed(Key.shift):
194
keyboard.tap('a') # Also types uppercase A
195
```
196
197
### Keyboard Event Listener
198
199
The Listener class monitors keyboard events in real-time, providing callbacks for key press and release events.
200
201
```python { .api }
202
class Listener:
203
"""A listener for keyboard events."""
204
205
def __init__(
206
self,
207
on_press: callable = None,
208
on_release: callable = None,
209
suppress: bool = False,
210
**kwargs
211
):
212
"""
213
Initialize the keyboard event listener.
214
215
Args:
216
on_press (callable): Callback for key press events (key, injected)
217
on_release (callable): Callback for key release events (key, injected)
218
suppress (bool): Whether to suppress events system-wide
219
**kwargs: Platform-specific options
220
"""
221
...
222
223
def start(self):
224
"""Start the listener thread."""
225
...
226
227
def stop(self):
228
"""Stop the listener. Cannot be restarted once stopped."""
229
...
230
231
def wait(self):
232
"""Wait for the listener to become ready."""
233
...
234
235
def join(self, timeout: float = None):
236
"""
237
Wait for the listener thread to complete.
238
239
Args:
240
timeout (float): Maximum time to wait in seconds
241
"""
242
...
243
244
def canonical(self, key) -> Key | KeyCode:
245
"""Convert a key to its canonical representation."""
246
...
247
248
@property
249
def running(self) -> bool:
250
"""Whether the listener is currently running."""
251
...
252
253
@property
254
def suppress(self) -> bool:
255
"""Whether events are being suppressed system-wide."""
256
...
257
```
258
259
#### Usage Examples
260
261
```python
262
from pynput import keyboard
263
264
def on_press(key, injected=False):
265
"""Handle key press events."""
266
try:
267
# Alphanumeric key
268
print(f'Alphanumeric key {key.char} pressed')
269
except AttributeError:
270
# Special key
271
print(f'Special key {key} pressed')
272
273
# Stop on escape
274
if key == keyboard.Key.esc:
275
return False
276
277
def on_release(key, injected=False):
278
"""Handle key release events."""
279
print(f'Key {key} released')
280
281
# Context manager usage (recommended)
282
with keyboard.Listener(
283
on_press=on_press,
284
on_release=on_release
285
) as listener:
286
listener.join()
287
288
# Manual control
289
listener = keyboard.Listener(
290
on_press=on_press,
291
on_release=on_release
292
)
293
listener.start()
294
listener.join()
295
296
# Non-blocking usage
297
listener = keyboard.Listener(on_press=on_press)
298
listener.start()
299
# ... do other work
300
listener.stop()
301
```
302
303
### Keyboard Events Iterator
304
305
The Events class provides synchronous iteration over keyboard events.
306
307
```python { .api }
308
class Events:
309
"""A keyboard event listener supporting synchronous iteration over events."""
310
311
def __init__(self):
312
"""Initialize the events iterator."""
313
...
314
315
def __enter__(self):
316
"""Start the event listener."""
317
...
318
319
def __exit__(self, *args):
320
"""Stop the event listener."""
321
...
322
323
def __iter__(self):
324
"""Return iterator interface."""
325
...
326
327
def __next__(self):
328
"""Get the next event."""
329
...
330
331
def get(self, timeout: float = None):
332
"""
333
Get the next event with optional timeout.
334
335
Args:
336
timeout (float): Maximum time to wait for an event
337
338
Returns:
339
Event or None: The next event, or None if timeout or stopped
340
"""
341
...
342
343
class Press:
344
"""A key press event."""
345
def __init__(self, key: Key | KeyCode, injected: bool):
346
self.key = key
347
self.injected = injected
348
349
class Release:
350
"""A key release event."""
351
def __init__(self, key: Key | KeyCode, injected: bool):
352
self.key = key
353
self.injected = injected
354
```
355
356
#### Usage Examples
357
358
```python
359
from pynput.keyboard import Events
360
361
# Process events synchronously
362
with Events() as events:
363
for event in events:
364
if isinstance(event, Events.Press):
365
print(f'Key {event.key} pressed')
366
if str(event.key) == "'q'": # Exit on 'q'
367
break
368
elif isinstance(event, Events.Release):
369
print(f'Key {event.key} released')
370
```
371
372
### HotKey Support
373
374
The HotKey class enables detection of key combinations, while GlobalHotKeys provides a convenient listener for multiple hotkeys.
375
376
```python { .api }
377
class HotKey:
378
"""A combination of keys acting as a hotkey."""
379
380
def __init__(self, keys: set, on_activate: callable):
381
"""
382
Initialize a hotkey.
383
384
Args:
385
keys (set): Set of keys that must be pressed simultaneously
386
on_activate (callable): Function to call when hotkey is activated
387
"""
388
...
389
390
@staticmethod
391
def parse(keys: str) -> list:
392
"""
393
Parse a key combination string.
394
395
Args:
396
keys (str): Key combination string (e.g., '<ctrl>+<alt>+h', '<cmd>+c')
397
398
Returns:
399
list: List of key objects
400
401
Raises:
402
ValueError: If the key string is invalid
403
"""
404
...
405
406
def press(self, key: Key | KeyCode):
407
"""
408
Update hotkey state for a pressed key.
409
410
Args:
411
key: The key being pressed
412
"""
413
...
414
415
def release(self, key: Key | KeyCode):
416
"""
417
Update hotkey state for a released key.
418
419
Args:
420
key: The key being released
421
"""
422
...
423
424
class GlobalHotKeys(Listener):
425
"""A keyboard listener supporting multiple global hotkeys."""
426
427
def __init__(self, hotkeys: dict, *args, **kwargs):
428
"""
429
Initialize global hotkeys listener.
430
431
Args:
432
hotkeys (dict): Mapping from hotkey strings to callback functions
433
*args, **kwargs: Arguments passed to base Listener class
434
435
Raises:
436
ValueError: If any hotkey description is invalid
437
"""
438
...
439
```
440
441
#### Usage Examples
442
443
```python
444
from pynput.keyboard import HotKey, GlobalHotKeys, Key
445
446
# Single hotkey
447
def on_hotkey():
448
print('Hotkey activated!')
449
450
hotkey = HotKey({Key.ctrl, Key.alt, KeyCode.from_char('h')}, on_hotkey)
451
452
def on_press(key):
453
hotkey.press(key)
454
455
def on_release(key):
456
hotkey.release(key)
457
458
with keyboard.Listener(on_press=on_press, on_release=on_release) as listener:
459
listener.join()
460
461
# Multiple global hotkeys
462
def copy_handler():
463
print('Copy hotkey pressed')
464
465
def paste_handler():
466
print('Paste hotkey pressed')
467
468
def quit_handler():
469
print('Quit hotkey pressed')
470
return False # Stop listener
471
472
hotkeys = {
473
'<ctrl>+c': copy_handler,
474
'<ctrl>+v': paste_handler,
475
'<ctrl>+<alt>+q': quit_handler
476
}
477
478
with GlobalHotKeys(hotkeys) as listener:
479
listener.join()
480
481
# Parse hotkey strings
482
try:
483
keys = HotKey.parse('<ctrl>+<shift>+a')
484
print(f'Parsed keys: {keys}')
485
except ValueError as e:
486
print(f'Invalid hotkey string: {e}')
487
```
488
489
## Types
490
491
### KeyCode Class
492
493
```python { .api }
494
class KeyCode:
495
"""Represents a key code used by the operating system."""
496
497
def __init__(self, vk: int = None, char: str = None, is_dead: bool = False):
498
"""
499
Initialize a KeyCode.
500
501
Args:
502
vk (int): Virtual key code
503
char (str): Character representation
504
is_dead (bool): Whether this is a dead key
505
"""
506
self.vk = vk
507
self.char = char
508
self.is_dead = is_dead
509
self.combining = None # Set for dead keys
510
511
@classmethod
512
def from_vk(cls, vk: int, **kwargs) -> 'KeyCode':
513
"""
514
Create a KeyCode from virtual key code.
515
516
Args:
517
vk (int): Virtual key code
518
**kwargs: Additional parameters
519
520
Returns:
521
KeyCode: New KeyCode instance
522
"""
523
...
524
525
@classmethod
526
def from_char(cls, char: str, **kwargs) -> 'KeyCode':
527
"""
528
Create a KeyCode from character.
529
530
Args:
531
char (str): Single character
532
**kwargs: Additional parameters
533
534
Returns:
535
KeyCode: New KeyCode instance
536
"""
537
...
538
539
@classmethod
540
def from_dead(cls, char: str, **kwargs) -> 'KeyCode':
541
"""
542
Create a dead key KeyCode.
543
544
Args:
545
char (str): Dead key character
546
**kwargs: Additional parameters
547
548
Returns:
549
KeyCode: New dead key KeyCode instance
550
"""
551
...
552
553
def join(self, key: 'KeyCode') -> 'KeyCode':
554
"""
555
Apply this dead key to another key.
556
557
Args:
558
key (KeyCode): Key to join with this dead key
559
560
Returns:
561
KeyCode: Combined key result
562
563
Raises:
564
ValueError: If keys cannot be joined
565
"""
566
...
567
```
568
569
### Key Enumeration
570
571
```python { .api }
572
class Key(enum.Enum):
573
"""Special keys that don't correspond to printable characters."""
574
575
# Modifier keys
576
alt # Generic Alt key
577
alt_l # Left Alt key
578
alt_r # Right Alt key
579
alt_gr # AltGr key
580
ctrl # Generic Ctrl key
581
ctrl_l # Left Ctrl key
582
ctrl_r # Right Ctrl key
583
shift # Generic Shift key
584
shift_l # Left Shift key
585
shift_r # Right Shift key
586
cmd # Generic Command/Windows key
587
cmd_l # Left Command/Windows key
588
cmd_r # Right Command/Windows key
589
590
# Navigation keys
591
up # Up arrow
592
down # Down arrow
593
left # Left arrow
594
right # Right arrow
595
home # Home key
596
end # End key
597
page_up # Page Up
598
page_down # Page Down
599
600
# Special keys
601
space # Spacebar
602
tab # Tab key
603
enter # Enter/Return key
604
esc # Escape key
605
backspace # Backspace key
606
delete # Delete key
607
608
# Lock keys
609
caps_lock # Caps Lock
610
num_lock # Num Lock
611
scroll_lock # Scroll Lock
612
613
# Function keys (F1-F20)
614
f1
615
f2
616
f3
617
f4
618
f5
619
f6
620
f7
621
f8
622
f9
623
f10
624
f11
625
f12
626
f13
627
f14
628
f15
629
f16
630
f17
631
f18
632
f19
633
f20
634
635
# Media keys
636
media_play_pause # Play/Pause toggle
637
media_volume_mute # Volume mute
638
media_volume_up # Volume up
639
media_volume_down # Volume down
640
media_previous # Previous track
641
media_next # Next track
642
643
# System keys (may be undefined on some platforms)
644
insert # Insert key
645
menu # Menu/Application key
646
pause # Pause/Break key
647
print_screen # Print Screen key
648
```
649
650
## Platform-Specific Features
651
652
### Windows (win32)
653
654
```python
655
# Custom event filtering
656
def win32_event_filter(msg, data):
657
"""Filter Windows keyboard events."""
658
# Access to KBDLLHOOKSTRUCT data
659
# Return False to suppress event from reaching listener
660
return True
661
662
listener = keyboard.Listener(
663
on_press=on_press,
664
win32_event_filter=win32_event_filter
665
)
666
```
667
668
### macOS (darwin)
669
670
```python
671
# Event intercepting and modification
672
def darwin_intercept(event_type, event):
673
"""Intercept and modify macOS keyboard events."""
674
# Modify event using Quartz.CGEvent functions
675
# Return None to suppress event system-wide
676
return event
677
678
listener = keyboard.Listener(
679
on_press=on_press,
680
darwin_intercept=darwin_intercept
681
)
682
```
683
684
### Linux (xorg)
685
686
Platform-specific options are available for X11/Xorg systems. The system requires access to the X server and proper DISPLAY environment variable configuration.
687
688
## Advanced Features
689
690
### Dead Key Support
691
692
Dead keys allow composition of accented characters:
693
694
```python
695
from pynput.keyboard import KeyCode
696
697
# Create dead key
698
dead_tilde = KeyCode.from_dead('~')
699
700
# Join with regular key
701
a_key = KeyCode.from_char('a')
702
result = dead_tilde.join(a_key) # Creates 'ã'
703
704
# Join with space or same key to get literal character
705
space_key = KeyCode.from_char(' ')
706
literal = dead_tilde.join(space_key) # Creates '~'
707
```
708
709
### Control Code Mapping
710
711
The keyboard module automatically maps control characters:
712
713
```python
714
# These are equivalent when typing
715
keyboard.type('\n') # Maps to Key.enter
716
keyboard.type('\t') # Maps to Key.tab
717
keyboard.type('\r') # Maps to Key.enter
718
```
719
720
## Error Handling
721
722
### Common Issues
723
724
- **ImportError**: Missing platform dependencies
725
- Windows: Requires win32 extensions
726
- macOS: Requires PyObjC framework bindings
727
- Linux: Requires python-xlib and X11 libraries
728
729
- **InvalidKeyException**: Invalid key parameters passed to controller methods
730
- **InvalidCharacterException**: Untypable characters in type() method
731
- **Permission Issues**: Some operations may require elevated privileges
732
733
### Exception Handling Examples
734
735
```python
736
from pynput.keyboard import Controller, Key
737
738
keyboard = Controller()
739
740
try:
741
keyboard.press('a')
742
keyboard.release('a')
743
except Controller.InvalidKeyException as e:
744
print(f"Invalid key: {e}")
745
746
try:
747
keyboard.type('Hello 🌍') # Emoji might not be typable
748
except Controller.InvalidCharacterException as e:
749
print(f"Cannot type character at position {e.args[0]}: {e.args[1]}")
750
751
# Hotkey parsing errors
752
try:
753
from pynput.keyboard import HotKey
754
keys = HotKey.parse('<invalid>+<key>')
755
except ValueError as e:
756
print(f"Invalid hotkey string: {e}")
757
```
758
759
### Listener Exception Handling
760
761
```python
762
from pynput import keyboard
763
764
def on_press(key):
765
# Raise StopException to gracefully stop listener
766
if key == keyboard.Key.esc:
767
raise keyboard.Listener.StopException()
768
769
def on_error():
770
print("Listener encountered an error")
771
772
try:
773
with keyboard.Listener(
774
on_press=on_press,
775
on_release=lambda key: None
776
) as listener:
777
listener.join()
778
except keyboard.Listener.StopException:
779
print("Listener stopped by user")
780
except Exception as e:
781
print(f"Listener error: {e}")
782
```