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

interactive-input.mddocs/

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.