0
# Interactive Input
1
2
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.
3
4
## Capabilities
5
6
### Event Waiting
7
8
Block program execution until specific keyboard conditions are met.
9
10
```python { .api }
11
def wait(hotkey=None, suppress=False, trigger_on_release=False):
12
"""
13
Blocks the program execution until the given hotkey is pressed or,
14
if given no parameters, blocks forever.
15
16
Parameters:
17
- hotkey: Hotkey string to wait for (if None, blocks forever)
18
- suppress: If True, suppress the hotkey from reaching other applications
19
- trigger_on_release: If True, wait for key release instead of press
20
21
Examples:
22
- wait('esc') # Wait for ESC key
23
- wait('ctrl+c') # Wait for Ctrl+C
24
- wait() # Block forever (until program termination)
25
"""
26
```
27
28
### Single Event Reading
29
30
Read individual keyboard events with blocking behavior.
31
32
```python { .api }
33
def read_event(suppress=False):
34
"""
35
Blocks until a keyboard event happens, then returns that event.
36
37
Parameters:
38
- suppress: If True, suppress the event from reaching other applications
39
40
Returns:
41
KeyboardEvent: The captured keyboard event
42
"""
43
44
def read_key(suppress=False):
45
"""
46
Blocks until a keyboard event happens, then returns that event's name or,
47
if missing, its scan code.
48
49
Parameters:
50
- suppress: If True, suppress the key from reaching other applications
51
52
Returns:
53
str or int: Key name or scan code
54
"""
55
```
56
57
### Hotkey Reading
58
59
Read complete hotkey combinations as strings.
60
61
```python { .api }
62
def read_hotkey(suppress=True):
63
"""
64
Similar to read_key(), but blocks until the user presses and releases a
65
hotkey (or single key), then returns a string representing the hotkey
66
pressed.
67
68
Parameters:
69
- suppress: If True, suppress the hotkey from reaching other applications
70
71
Returns:
72
str: Hotkey string representation (e.g., 'ctrl+shift+a')
73
74
Example:
75
read_hotkey() # User presses Ctrl+Shift+P -> returns 'ctrl+shift+p'
76
"""
77
```
78
79
### Utility Functions
80
81
Helper functions for working with current keyboard state and delayed execution.
82
83
```python { .api }
84
def call_later(fn, args=(), delay=0.001):
85
"""
86
Calls the provided function in a new thread after waiting some time.
87
Useful for giving the system some time to process an event, without blocking
88
the current execution flow.
89
90
Parameters:
91
- fn: Function to call
92
- args: Arguments to pass to function
93
- delay: Delay in seconds before calling function
94
"""
95
96
def get_hotkey_name(names=None):
97
"""
98
Returns a string representation of hotkey from the given key names, or
99
the currently pressed keys if not given.
100
101
Parameters:
102
- names: List of key names (if None, uses currently pressed keys)
103
104
Returns:
105
str: Standardized hotkey string
106
"""
107
```
108
109
## Usage Examples
110
111
### Simple Interactive Menu
112
113
```python
114
import keyboard
115
116
def interactive_menu():
117
"""Simple menu system using keyboard input."""
118
119
print("=== Interactive Menu ===")
120
print("1. Option One")
121
print("2. Option Two")
122
print("3. Option Three")
123
print("ESC. Exit")
124
print("\nPress a key to select:")
125
126
while True:
127
key = keyboard.read_key()
128
129
if key == '1':
130
print("You selected Option One")
131
break
132
elif key == '2':
133
print("You selected Option Two")
134
break
135
elif key == '3':
136
print("You selected Option Three")
137
break
138
elif key == 'esc':
139
print("Exiting...")
140
break
141
else:
142
print(f"Invalid selection: {key}. Try again.")
143
144
interactive_menu()
145
```
146
147
### Hotkey Configuration Tool
148
149
```python
150
import keyboard
151
152
def configure_hotkeys():
153
"""Interactive hotkey configuration."""
154
155
hotkeys = {}
156
157
def get_user_hotkey(prompt):
158
"""Get a hotkey from user input."""
159
print(f"\n{prompt}")
160
print("Press the desired key combination:")
161
162
hotkey = keyboard.read_hotkey()
163
print(f"You pressed: {hotkey}")
164
return hotkey
165
166
def confirm_hotkey(hotkey, action):
167
"""Confirm hotkey assignment."""
168
print(f"\nAssign '{hotkey}' to '{action}'?")
169
print("Press 'y' to confirm, 'n' to retry, 'esc' to skip:")
170
171
while True:
172
key = keyboard.read_key()
173
if key == 'y':
174
return True
175
elif key == 'n':
176
return False
177
elif key == 'esc':
178
return None
179
180
# Configure multiple hotkeys
181
actions = ['Quick Save', 'Quick Load', 'Screenshot', 'Toggle Mode']
182
183
for action in actions:
184
while True:
185
hotkey = get_user_hotkey(f"Configure hotkey for: {action}")
186
187
# Check for conflicts
188
if hotkey in hotkeys:
189
print(f"Warning: '{hotkey}' is already assigned to '{hotkeys[hotkey]}'")
190
print("Press 'y' to overwrite, 'n' to choose different key:")
191
192
if keyboard.read_key() != 'y':
193
continue
194
195
confirmation = confirm_hotkey(hotkey, action)
196
if confirmation is True:
197
hotkeys[hotkey] = action
198
print(f"✓ '{hotkey}' assigned to '{action}'")
199
break
200
elif confirmation is None:
201
print(f"Skipped '{action}'")
202
break
203
# If confirmation is False, retry
204
205
print(f"\n=== Final Hotkey Configuration ===")
206
for hotkey, action in hotkeys.items():
207
print(f"{hotkey} -> {action}")
208
209
return hotkeys
210
211
# Run configuration
212
configured_hotkeys = configure_hotkeys()
213
```
214
215
### Real-time Key Monitor
216
217
```python
218
import keyboard
219
import time
220
from datetime import datetime
221
222
def key_monitor():
223
"""Real-time keyboard event monitoring."""
224
225
print("=== Real-time Key Monitor ===")
226
print("Press keys to see events. ESC to exit.")
227
print("Format: [timestamp] event_type key_name (scan_code)")
228
print("-" * 50)
229
230
while True:
231
event = keyboard.read_event()
232
233
timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3]
234
event_type = "PRESS" if event.event_type == "down" else "RELEASE"
235
key_name = event.name or "unknown"
236
scan_code = event.scan_code
237
238
print(f"[{timestamp}] {event_type:7} {key_name:15} ({scan_code})")
239
240
# Exit on ESC release
241
if event.name == 'esc' and event.event_type == 'up':
242
print("Monitoring stopped.")
243
break
244
245
key_monitor()
246
```
247
248
### Interactive Game Controls
249
250
```python
251
import keyboard
252
import time
253
import random
254
255
class SimpleGame:
256
def __init__(self):
257
self.player_x = 5
258
self.player_y = 5
259
self.score = 0
260
self.running = True
261
self.board_size = 10
262
263
def draw_board(self):
264
"""Draw the game board."""
265
print("\033[2J\033[H") # Clear screen
266
print(f"Score: {self.score}")
267
print("-" * (self.board_size + 2))
268
269
for y in range(self.board_size):
270
row = "|"
271
for x in range(self.board_size):
272
if x == self.player_x and y == self.player_y:
273
row += "P" # Player
274
else:
275
row += " "
276
row += "|"
277
print(row)
278
279
print("-" * (self.board_size + 2))
280
print("Use WASD to move, Q to quit")
281
282
def move_player(self, dx, dy):
283
"""Move player with boundary checking."""
284
new_x = max(0, min(self.board_size - 1, self.player_x + dx))
285
new_y = max(0, min(self.board_size - 1, self.player_y + dy))
286
287
if new_x != self.player_x or new_y != self.player_y:
288
self.player_x = new_x
289
self.player_y = new_y
290
self.score += 1
291
292
def handle_input(self):
293
"""Handle keyboard input."""
294
key = keyboard.read_key(suppress=True)
295
296
if key == 'w':
297
self.move_player(0, -1) # Up
298
elif key == 's':
299
self.move_player(0, 1) # Down
300
elif key == 'a':
301
self.move_player(-1, 0) # Left
302
elif key == 'd':
303
self.move_player(1, 0) # Right
304
elif key == 'q' or key == 'esc':
305
self.running = False
306
307
def run(self):
308
"""Main game loop."""
309
print("=== Simple Movement Game ===")
310
print("Loading...")
311
time.sleep(1)
312
313
while self.running:
314
self.draw_board()
315
self.handle_input()
316
317
print(f"\nGame Over! Final Score: {self.score}")
318
319
# Run the game
320
game = SimpleGame()
321
game.run()
322
```
323
324
### Input Validation System
325
326
```python
327
import keyboard
328
import re
329
330
class InputValidator:
331
def __init__(self):
332
self.patterns = {
333
'email': r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$',
334
'phone': r'^\+?[\d\s\-\(\)]{10,}$',
335
'number': r'^\d+$',
336
'alpha': r'^[a-zA-Z\s]+$'
337
}
338
339
def get_validated_input(self, prompt, pattern_name, max_attempts=3):
340
"""Get validated input using keyboard reading."""
341
342
pattern = self.patterns.get(pattern_name)
343
if not pattern:
344
raise ValueError(f"Unknown pattern: {pattern_name}")
345
346
attempts = 0
347
348
while attempts < max_attempts:
349
print(f"\n{prompt}")
350
print("Type your input and press ENTER (ESC to cancel):")
351
352
input_text = ""
353
354
while True:
355
event = keyboard.read_event()
356
357
if event.event_type == 'down': # Only handle key presses
358
if event.name == 'enter':
359
break
360
elif event.name == 'esc':
361
print("Input cancelled.")
362
return None
363
elif event.name == 'backspace':
364
if input_text:
365
input_text = input_text[:-1]
366
print(f"\rInput: {input_text + ' ' * 10}", end="")
367
elif event.name and len(event.name) == 1:
368
input_text += event.name
369
print(f"\rInput: {input_text}", end="")
370
371
print() # New line after input
372
373
# Validate input
374
if re.match(pattern, input_text.strip()):
375
print(f"✓ Valid {pattern_name}: {input_text}")
376
return input_text.strip()
377
else:
378
attempts += 1
379
remaining = max_attempts - attempts
380
print(f"✗ Invalid {pattern_name} format.")
381
382
if remaining > 0:
383
print(f"Please try again. {remaining} attempts remaining.")
384
else:
385
print("Maximum attempts reached.")
386
387
return None
388
389
def collect_user_data(self):
390
"""Collect validated user data."""
391
print("=== User Data Collection ===")
392
393
data = {}
394
395
# Collect various types of input
396
data['name'] = self.get_validated_input(
397
"Enter your full name:", 'alpha'
398
)
399
400
if data['name']:
401
data['email'] = self.get_validated_input(
402
"Enter your email address:", 'email'
403
)
404
405
if data.get('email'):
406
data['phone'] = self.get_validated_input(
407
"Enter your phone number:", 'phone'
408
)
409
410
if data.get('phone'):
411
data['age'] = self.get_validated_input(
412
"Enter your age:", 'number'
413
)
414
415
print("\n=== Collected Data ===")
416
for key, value in data.items():
417
if value:
418
print(f"{key.capitalize()}: {value}")
419
420
return data
421
422
# Usage
423
validator = InputValidator()
424
user_data = validator.collect_user_data()
425
```
426
427
### Async-Style Event Handling
428
429
```python
430
import keyboard
431
import time
432
from threading import Event, Thread
433
434
class AsyncInputHandler:
435
def __init__(self):
436
self.stop_event = Event()
437
self.input_queue = []
438
self.input_thread = None
439
440
def start_background_reading(self):
441
"""Start reading input in background thread."""
442
443
def read_loop():
444
while not self.stop_event.is_set():
445
try:
446
# Use timeout to check stop condition
447
event = keyboard.read_event(suppress=False)
448
if event.event_type == 'down': # Only process key presses
449
self.input_queue.append(event.name)
450
except:
451
break
452
453
self.input_thread = Thread(target=read_loop, daemon=True)
454
self.input_thread.start()
455
456
def get_next_input(self, timeout=None):
457
"""Get next input with optional timeout."""
458
start_time = time.time()
459
460
while True:
461
if self.input_queue:
462
return self.input_queue.pop(0)
463
464
if timeout and (time.time() - start_time) > timeout:
465
return None
466
467
if self.stop_event.is_set():
468
return None
469
470
time.sleep(0.01) # Small delay to prevent busy waiting
471
472
def wait_for_any_key(self, valid_keys=None, timeout=None):
473
"""Wait for any key from a set of valid keys."""
474
start_time = time.time()
475
476
while True:
477
key = self.get_next_input(timeout=0.1)
478
479
if key:
480
if valid_keys is None or key in valid_keys:
481
return key
482
else:
483
print(f"Invalid key: {key}")
484
485
if timeout and (time.time() - start_time) > timeout:
486
return None
487
488
def stop(self):
489
"""Stop background input reading."""
490
self.stop_event.set()
491
if self.input_thread:
492
self.input_thread.join(timeout=1.0)
493
494
# Usage example
495
def async_input_demo():
496
handler = AsyncInputHandler()
497
handler.start_background_reading()
498
499
try:
500
print("=== Async Input Demo ===")
501
print("This demo shows non-blocking input handling")
502
503
print("\nPress 1, 2, or 3 (you have 10 seconds):")
504
result = handler.wait_for_any_key(['1', '2', '3'], timeout=10)
505
506
if result:
507
print(f"You pressed: {result}")
508
else:
509
print("Timeout! No valid key pressed.")
510
511
print("\nPress any key or wait 5 seconds for timeout:")
512
result = handler.get_next_input(timeout=5)
513
514
if result:
515
print(f"You pressed: {result}")
516
else:
517
print("Timeout!")
518
519
finally:
520
handler.stop()
521
522
async_input_demo()
523
```
524
525
## Interactive Input Considerations
526
527
### Blocking Behavior
528
- All interactive input functions block program execution
529
- Use threading or async patterns for non-blocking alternatives
530
- Consider timeout mechanisms for user responsiveness
531
532
### Event Suppression
533
- Suppressing events prevents them from reaching other applications
534
- Use carefully to avoid interfering with system functionality
535
- Some platforms have limitations on event suppression
536
537
### Cross-Platform Compatibility
538
- Key names may vary between platforms
539
- Special keys (function keys, media keys) may not be available on all systems
540
- Consider platform-specific fallbacks
541
542
### Error Handling
543
- Input functions may fail silently in restricted environments
544
- Handle cases where expected keys are not available
545
- Provide alternative input methods when possible
546
547
These interactive input functions provide the foundation for creating responsive keyboard-driven applications, from simple command-line interfaces to complex interactive systems.