or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

hotkeys.mdindex.mdinteractive-input.mdkey-simulation.mdkeyboard-events.mdrecording-playback.mdtext-processing.md

recording-playback.mddocs/

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.