0
# Recording and Playback
1
2
Capture sequences of keyboard events and replay them later with precise timing control and selective event filtering. The keyboard package provides comprehensive recording capabilities for automation, testing, and user interface scripting.
3
4
## Capabilities
5
6
### Event Recording
7
8
Capture keyboard events for later replay with flexible start/stop control.
9
10
```python { .api }
11
def record(until='escape', suppress=False, trigger_on_release=False):
12
"""
13
Records all keyboard events from all keyboards until the user presses the
14
given hotkey. Then returns the list of events recorded.
15
16
Parameters:
17
- until: Hotkey to stop recording (default: 'escape')
18
- suppress: If True, suppress recorded events from reaching other applications
19
- trigger_on_release: If True, stop on key release instead of press
20
21
Returns:
22
list[KeyboardEvent]: List of recorded keyboard events
23
24
Note: This is a blocking function that waits for the stop condition.
25
"""
26
27
def start_recording(recorded_events_queue=None):
28
"""
29
Starts recording all keyboard events into a global variable, or the given
30
queue if any. Returns the queue of events and the hooked function.
31
32
Parameters:
33
- recorded_events_queue: Optional queue for events (creates new if None)
34
35
Returns:
36
tuple: (event_queue, hook_function) for manual control
37
38
Use stop_recording() or unhook(hooked_function) to stop.
39
"""
40
41
def stop_recording():
42
"""
43
Stops the global recording of events and returns a list of the events
44
captured.
45
46
Returns:
47
list[KeyboardEvent]: List of recorded events
48
49
Raises:
50
ValueError: If start_recording() was not called first
51
"""
52
```
53
54
### Event Playback
55
56
Replay recorded events with timing and speed control.
57
58
```python { .api }
59
def play(events, speed_factor=1.0):
60
"""
61
Plays a sequence of recorded events, maintaining the relative time
62
intervals. If speed_factor is <= 0 then the actions are replayed as fast
63
as the OS allows.
64
65
Parameters:
66
- events: List of KeyboardEvent objects to replay
67
- speed_factor: Speed multiplier (1.0 = normal, 2.0 = double speed, 0.5 = half speed)
68
69
Notes:
70
- The current keyboard state is cleared at the beginning and restored at the end
71
- Events are replayed with original timing relationships preserved
72
- Pairs well with record()
73
"""
74
75
def replay(events, speed_factor=1.0):
76
"""Alias for play()."""
77
```
78
79
## Usage Examples
80
81
### Basic Recording and Playback
82
83
```python
84
import keyboard
85
86
print('Recording keyboard events. Press ESC to stop.')
87
recorded_events = keyboard.record(until='esc')
88
89
print(f'Recorded {len(recorded_events)} events.')
90
print('Press SPACE to replay, or ESC to exit.')
91
92
keyboard.wait('space')
93
print('Replaying...')
94
keyboard.play(recorded_events)
95
print('Replay complete.')
96
```
97
98
### Manual Recording Control
99
100
```python
101
import keyboard
102
import time
103
104
# Start recording manually
105
print('Starting manual recording...')
106
queue, hook_func = keyboard.start_recording()
107
108
# Let recording run for 5 seconds
109
time.sleep(5)
110
111
# Stop recording
112
recorded_events = keyboard.stop_recording()
113
114
print(f'Recorded {len(recorded_events)} events in 5 seconds.')
115
116
# Replay at double speed
117
print('Replaying at 2x speed...')
118
keyboard.play(recorded_events, speed_factor=2.0)
119
```
120
121
### Speed-Controlled Playback
122
123
```python
124
import keyboard
125
126
# Record some events
127
print('Record some typing, then press ESC.')
128
events = keyboard.record()
129
130
# Replay at different speeds
131
print('Normal speed:')
132
keyboard.play(events, speed_factor=1.0)
133
134
keyboard.wait('space')
135
print('Double speed:')
136
keyboard.play(events, speed_factor=2.0)
137
138
keyboard.wait('space')
139
print('Half speed:')
140
keyboard.play(events, speed_factor=0.5)
141
142
keyboard.wait('space')
143
print('Maximum speed:')
144
keyboard.play(events, speed_factor=0) # As fast as possible
145
```
146
147
### Filtered Recording
148
149
```python
150
import keyboard
151
152
def filter_recording():
153
"""Record only specific types of events."""
154
155
# Custom recording with filtering
156
recorded_events = []
157
158
def record_filter(event):
159
# Only record letter keys and space
160
if event.name and (len(event.name) == 1 or event.name == 'space'):
161
if event.event_type == 'down': # Only key presses
162
recorded_events.append(event)
163
164
print('Recording only letter keys and space. Press ESC to stop.')
165
166
# Install custom hook
167
hook_func = keyboard.hook(record_filter)
168
keyboard.wait('esc')
169
keyboard.unhook(hook_func)
170
171
return recorded_events
172
173
# Use filtered recording
174
filtered_events = filter_recording()
175
print(f'Recorded {len(filtered_events)} filtered events.')
176
177
print('Replaying filtered events...')
178
keyboard.play(filtered_events)
179
```
180
181
### Macro Recording System
182
183
```python
184
import keyboard
185
import json
186
import os
187
188
class MacroRecorder:
189
def __init__(self):
190
self.macros = {}
191
self.recording = False
192
self.current_recording = []
193
194
def start_macro_recording(self, name):
195
"""Start recording a named macro."""
196
if self.recording:
197
print('Already recording a macro!')
198
return
199
200
self.recording = True
201
self.current_recording = []
202
print(f'Recording macro "{name}". Press F9 to stop.')
203
204
def record_event(event):
205
if not self.recording:
206
return
207
self.current_recording.append({
208
'event_type': event.event_type,
209
'name': event.name,
210
'scan_code': event.scan_code,
211
'time': event.time
212
})
213
214
self.hook_func = keyboard.hook(record_event)
215
216
def stop_recording():
217
self.stop_macro_recording(name)
218
219
keyboard.add_hotkey('f9', stop_recording)
220
221
def stop_macro_recording(self, name):
222
"""Stop recording and save the macro."""
223
if not self.recording:
224
return
225
226
self.recording = False
227
keyboard.unhook(self.hook_func)
228
keyboard.remove_hotkey('f9')
229
230
self.macros[name] = self.current_recording.copy()
231
print(f'Macro "{name}" recorded with {len(self.current_recording)} events.')
232
233
def play_macro(self, name):
234
"""Play a recorded macro."""
235
if name not in self.macros:
236
print(f'Macro "{name}" not found!')
237
return
238
239
events = self.macros[name]
240
print(f'Playing macro "{name}" with {len(events)} events...')
241
242
# Convert back to KeyboardEvent objects for playback
243
keyboard_events = []
244
base_time = events[0]['time'] if events else 0
245
246
for event_data in events:
247
# Create a mock event for playback
248
if event_data['event_type'] == 'down':
249
keyboard.press(event_data['name'] or event_data['scan_code'])
250
else:
251
keyboard.release(event_data['name'] or event_data['scan_code'])
252
253
def save_macros(self, filename):
254
"""Save macros to file."""
255
with open(filename, 'w') as f:
256
json.dump(self.macros, f, indent=2)
257
print(f'Macros saved to {filename}')
258
259
def load_macros(self, filename):
260
"""Load macros from file."""
261
if os.path.exists(filename):
262
with open(filename, 'r') as f:
263
self.macros = json.load(f)
264
print(f'Loaded {len(self.macros)} macros from {filename}')
265
266
# Usage example
267
recorder = MacroRecorder()
268
269
def start_login_macro():
270
recorder.start_macro_recording('login')
271
272
def start_email_macro():
273
recorder.start_macro_recording('email_signature')
274
275
def play_login():
276
recorder.play_macro('login')
277
278
def play_email():
279
recorder.play_macro('email_signature')
280
281
# Set up hotkeys for macro system
282
keyboard.add_hotkey('ctrl+f1', start_login_macro)
283
keyboard.add_hotkey('ctrl+f2', start_email_macro)
284
keyboard.add_hotkey('f1', play_login)
285
keyboard.add_hotkey('f2', play_email)
286
287
print('Macro system ready!')
288
print('Ctrl+F1: Record login macro')
289
print('Ctrl+F2: Record email macro')
290
print('F1: Play login macro')
291
print('F2: Play email macro')
292
print('ESC: Exit')
293
294
keyboard.wait('esc')
295
keyboard.unhook_all_hotkeys()
296
297
# Save macros before exit
298
recorder.save_macros('my_macros.json')
299
```
300
301
### Automation Testing
302
303
```python
304
import keyboard
305
import time
306
307
def test_application_workflow():
308
"""Record and replay a complete application workflow."""
309
310
print('=== Application Workflow Test ===')
311
312
# Step 1: Record the workflow
313
print('Step 1: Record your workflow')
314
print('Perform the complete workflow, then press F12 to finish.')
315
316
workflow_events = keyboard.record(until='f12')
317
print(f'Recorded workflow with {len(workflow_events)} events.')
318
319
# Step 2: Replay for testing
320
print('Step 2: Replaying workflow for testing...')
321
print('Starting in 3 seconds...')
322
time.sleep(3)
323
324
# Replay the workflow
325
keyboard.play(workflow_events)
326
327
print('Workflow replay complete!')
328
329
# Step 3: Stress test with multiple replays
330
choice = input('Run stress test with 5 replays? (y/n): ')
331
if choice.lower() == 'y':
332
for i in range(5):
333
print(f'Stress test run {i+1}/5...')
334
time.sleep(2) # Brief pause between runs
335
keyboard.play(workflow_events, speed_factor=1.5) # Slightly faster
336
337
print('Stress test complete!')
338
339
# Run the test
340
test_application_workflow()
341
```
342
343
### Event Analysis
344
345
```python
346
import keyboard
347
from collections import Counter
348
349
def analyze_recording():
350
"""Analyze a keyboard recording for patterns."""
351
352
print('Record some typing for analysis. Press ESC when done.')
353
events = keyboard.record()
354
355
# Analyze the recording
356
print(f'\n=== Recording Analysis ===')
357
print(f'Total events: {len(events)}')
358
359
# Count key presses vs releases
360
press_events = [e for e in events if e.event_type == 'down']
361
release_events = [e for e in events if e.event_type == 'up']
362
363
print(f'Key presses: {len(press_events)}')
364
print(f'Key releases: {len(release_events)}')
365
366
# Most common keys
367
key_counts = Counter(e.name for e in press_events if e.name)
368
print(f'\nMost common keys:')
369
for key, count in key_counts.most_common(10):
370
print(f' {key}: {count} times')
371
372
# Typing speed analysis
373
if len(press_events) > 1:
374
duration = events[-1].time - events[0].time
375
keys_per_second = len(press_events) / duration
376
print(f'\nTyping speed: {keys_per_second:.1f} keys/second')
377
378
# Time between events
379
if len(events) > 1:
380
intervals = [events[i+1].time - events[i].time for i in range(len(events)-1)]
381
avg_interval = sum(intervals) / len(intervals)
382
print(f'Average time between events: {avg_interval:.3f} seconds')
383
384
return events
385
386
# Run analysis
387
analyzed_events = analyze_recording()
388
```
389
390
## Event Data Structure
391
392
Recorded events contain complete timing and key information:
393
394
```python { .api }
395
class KeyboardEvent:
396
"""Recorded keyboard event with complete metadata."""
397
event_type: str # 'down' or 'up'
398
scan_code: int # Hardware scan code
399
name: str # Key name (e.g., 'a', 'space', 'ctrl')
400
time: float # Timestamp in seconds since epoch
401
device: int # Device identifier (platform-specific)
402
modifiers: list # Active modifier keys at time of event
403
is_keypad: bool # True if from numeric keypad
404
405
def to_json(self, ensure_ascii=False) -> str:
406
"""Convert event to JSON for serialization."""
407
```
408
409
## Recording Considerations
410
411
### Performance
412
- Recording captures all keyboard events, which can generate large event lists
413
- Long recordings consume significant memory
414
- High-frequency typing creates many events
415
416
### Timing Accuracy
417
- Event timestamps preserve original timing relationships
418
- Playback maintains relative timing between events
419
- System load can affect playback timing precision
420
421
### Platform Differences
422
- Event suppression during recording may not work on all platforms
423
- Some keys may not be recordable on certain systems
424
- Device information varies by platform
425
426
### Security and Privacy
427
- Recordings capture all keystrokes including passwords
428
- Store recordings securely and clear sensitive data appropriately
429
- Be aware of keylogger-like behavior in security-conscious environments
430
431
## Error Handling
432
433
Recording and playback may encounter:
434
- Insufficient privileges for global keyboard access
435
- Platform limitations on event suppression
436
- Invalid event data during playback
437
- System security restrictions
438
439
The package will raise `ValueError` for invalid recording states and may silently fail to capture or replay events in restricted environments.