or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration.mddictionaries.mdengine.mdextensions.mdindex.mdmachines.mdregistry.mdsteno-data.md

extensions.mddocs/

0

# Extension Framework

1

2

Plover's extension framework enables custom functionality through a comprehensive plugin system. Extensions can hook into all stenographic events, access the complete engine API, run background processes, and integrate with the GUI to provide enhanced workflows and automation.

3

4

## Capabilities

5

6

### Extension Base Interface

7

8

Standard interface that all extensions must implement for integration with Plover's plugin system.

9

10

```python { .api }

11

class Extension:

12

"""Base interface for Plover extensions."""

13

14

def __init__(self, engine):

15

"""

16

Initialize extension with engine reference.

17

18

Args:

19

engine: StenoEngine instance providing full API access

20

21

Store engine reference and perform initial setup.

22

Extensions receive complete access to engine functionality.

23

"""

24

25

def start(self) -> None:

26

"""

27

Start extension operation.

28

29

Called when extension is enabled in configuration.

30

Perform initialization, connect event hooks, start background

31

threads, and begin extension functionality.

32

"""

33

34

def stop(self) -> None:

35

"""

36

Stop extension operation.

37

38

Called when extension is disabled or Plover shuts down.

39

Disconnect hooks, stop background threads, cleanup resources,

40

and ensure graceful shutdown.

41

"""

42

```

43

44

## Extension Capabilities

45

46

### Engine API Access

47

48

Extensions have complete access to the StenoEngine API for comprehensive stenographic control.

49

50

**Available Engine Features:**

51

- **Machine Control**: Start, stop, and configure stenotype machines

52

- **Dictionary Management**: Access, modify, and filter dictionaries

53

- **Translation Processing**: Hook into stroke and translation events

54

- **Output Control**: Enable, disable, and monitor stenographic output

55

- **Configuration Access**: Read and modify all Plover settings

56

- **State Management**: Access translator and machine state

57

58

### Event Hook System

59

60

Extensions can connect to all engine events for real-time stenographic monitoring and response.

61

62

**Available Hooks:**

63

- `stroked`: Stenotype stroke received from machine

64

- `translated`: Stroke translated to text output

65

- `machine_state_changed`: Machine connection status changed

66

- `output_changed`: Stenographic output enabled/disabled

67

- `config_changed`: Configuration settings updated

68

- `dictionaries_loaded`: Dictionary collection reloaded

69

- `send_string`: Text string sent to system output

70

- `send_backspaces`: Backspace characters sent to output

71

- `send_key_combination`: Key combination sent to system

72

- `add_translation`: Translation added to dictionary

73

- `focus`: Main window focus requested

74

- `configure`: Configuration dialog requested

75

- `lookup`: Lookup dialog requested

76

- `suggestions`: Suggestions dialog requested

77

- `quit`: Application quit requested

78

79

### Background Processing

80

81

Extensions can run background threads for continuous processing, monitoring, or communication with external systems.

82

83

### GUI Integration

84

85

Extensions can interact with Plover's Qt-based GUI to provide custom tools, dialogs, and interface elements.

86

87

## Extension Development

88

89

### Basic Extension Implementation

90

91

```python

92

from plover import log

93

94

class BasicExtension:

95

"""Simple extension that logs all strokes."""

96

97

def __init__(self, engine):

98

self.engine = engine

99

100

def start(self):

101

"""Connect to stroke events."""

102

self.engine.hook_connect('stroked', self.on_stroke)

103

log.info("Stroke logging extension started")

104

105

def stop(self):

106

"""Disconnect from events."""

107

self.engine.hook_disconnect('stroked', self.on_stroke)

108

log.info("Stroke logging extension stopped")

109

110

def on_stroke(self, stroke):

111

"""Handle stroke events."""

112

log.info(f"Stroke received: {'/'.join(stroke)}")

113

```

114

115

### Advanced Extension with Background Processing

116

117

```python

118

import threading

119

import time

120

import json

121

from pathlib import Path

122

123

class StrokeAnalyzer:

124

"""Extension that analyzes stroke patterns and saves statistics."""

125

126

def __init__(self, engine):

127

self.engine = engine

128

self.stats = {

129

'total_strokes': 0,

130

'stroke_frequency': {},

131

'session_start': None

132

}

133

self.stats_file = Path('stroke_stats.json')

134

self.background_thread = None

135

self.running = False

136

137

def start(self):

138

"""Start stroke analysis."""

139

# Load existing stats

140

self.load_stats()

141

142

# Connect to events

143

self.engine.hook_connect('stroked', self.on_stroke)

144

self.engine.hook_connect('translated', self.on_translation)

145

146

# Start background processing

147

self.running = True

148

self.stats['session_start'] = time.time()

149

self.background_thread = threading.Thread(target=self.background_worker)

150

self.background_thread.start()

151

152

def stop(self):

153

"""Stop stroke analysis."""

154

# Stop background thread

155

self.running = False

156

if self.background_thread:

157

self.background_thread.join()

158

159

# Disconnect events

160

self.engine.hook_disconnect('stroked', self.on_stroke)

161

self.engine.hook_disconnect('translated', self.on_translation)

162

163

# Save final stats

164

self.save_stats()

165

166

def on_stroke(self, stroke):

167

"""Analyze stroke patterns."""

168

stroke_str = '/'.join(stroke)

169

self.stats['total_strokes'] += 1

170

self.stats['stroke_frequency'][stroke_str] = (

171

self.stats['stroke_frequency'].get(stroke_str, 0) + 1

172

)

173

174

def on_translation(self, old, new):

175

"""Analyze translation patterns."""

176

# Could analyze translation efficiency, common corrections, etc.

177

pass

178

179

def background_worker(self):

180

"""Background thread for periodic stats saving."""

181

while self.running:

182

time.sleep(60) # Save every minute

183

if self.running: # Check again after sleep

184

self.save_stats()

185

186

def load_stats(self):

187

"""Load statistics from file."""

188

if self.stats_file.exists():

189

with open(self.stats_file, 'r') as f:

190

saved_stats = json.load(f)

191

self.stats.update(saved_stats)

192

193

def save_stats(self):

194

"""Save statistics to file."""

195

with open(self.stats_file, 'w') as f:

196

json.dump(self.stats, f, indent=2)

197

```

198

199

### Extension with Dictionary Integration

200

201

```python

202

class CustomDictionaryManager:

203

"""Extension that manages custom dictionary features."""

204

205

def __init__(self, engine):

206

self.engine = engine

207

self.custom_translations = {}

208

209

def start(self):

210

"""Start custom dictionary management."""

211

# Add custom dictionary filter

212

self.engine.add_dictionary_filter(self.custom_filter)

213

214

# Connect to translation events

215

self.engine.hook_connect('add_translation', self.on_add_translation)

216

217

def stop(self):

218

"""Stop custom dictionary management."""

219

# Remove filter

220

self.engine.remove_dictionary_filter(self.custom_filter)

221

222

# Disconnect events

223

self.engine.hook_disconnect('add_translation', self.on_add_translation)

224

225

def custom_filter(self, strokes, translation):

226

"""Apply custom filtering logic."""

227

# Example: Convert all translations to title case

228

if translation and isinstance(translation, str):

229

return translation.title()

230

return translation

231

232

def on_add_translation(self):

233

"""Handle translation additions."""

234

# Could log new translations, sync with external systems, etc.

235

pass

236

237

def add_temporary_translation(self, strokes, translation):

238

"""Add temporary translation that doesn't persist."""

239

# Store in memory only

240

self.custom_translations[strokes] = translation

241

242

# Could temporarily modify dictionary collection

243

first_dict = self.engine.dictionaries.first_writable()

244

if first_dict:

245

first_dict[strokes] = translation

246

```

247

248

### Extension with GUI Integration

249

250

```python

251

from PyQt5.QtWidgets import QDialog, QPushButton, QVBoxLayout, QLabel

252

from PyQt5.QtCore import QTimer

253

254

class StrokeDisplay(QDialog):

255

"""GUI extension showing real-time stroke display."""

256

257

def __init__(self, engine):

258

super().__init__()

259

self.engine = engine

260

self.stroke_label = None

261

self.setup_ui()

262

263

def setup_ui(self):

264

"""Setup the GUI interface."""

265

self.setWindowTitle("Live Stroke Display")

266

self.setGeometry(100, 100, 300, 150)

267

268

layout = QVBoxLayout()

269

270

self.stroke_label = QLabel("No strokes yet...")

271

self.stroke_label.setStyleSheet("font-size: 18px; font-weight: bold;")

272

layout.addWidget(self.stroke_label)

273

274

close_button = QPushButton("Close")

275

close_button.clicked.connect(self.close)

276

layout.addWidget(close_button)

277

278

self.setLayout(layout)

279

280

def start(self):

281

"""Start the stroke display."""

282

self.engine.hook_connect('stroked', self.on_stroke)

283

self.show()

284

285

def stop(self):

286

"""Stop the stroke display."""

287

self.engine.hook_disconnect('stroked', self.on_stroke)

288

self.close()

289

290

def on_stroke(self, stroke):

291

"""Update display with new stroke."""

292

stroke_text = '/'.join(stroke)

293

self.stroke_label.setText(f"Last stroke: {stroke_text}")

294

295

# Auto-clear after 3 seconds

296

QTimer.singleShot(3000, lambda: self.stroke_label.setText("Waiting for strokes..."))

297

```

298

299

## Extension Registration

300

301

### Entry Point Registration

302

303

Extensions are registered through Python entry points in `setup.py` or `pyproject.toml`:

304

305

```python

306

# setup.py

307

setup(

308

name="my-plover-extension",

309

entry_points={

310

'plover.extension': [

311

'my_extension = my_package.extension:MyExtension',

312

],

313

},

314

)

315

```

316

317

```toml

318

# pyproject.toml

319

[project.entry-points."plover.extension"]

320

my_extension = "my_package.extension:MyExtension"

321

```

322

323

### Manual Registration

324

325

Extensions can also be registered programmatically:

326

327

```python

328

from plover.registry import registry

329

330

# Register extension class

331

registry.register_plugin('extension', 'my_extension', MyExtension)

332

```

333

334

## Extension Configuration

335

336

### Configuration Integration

337

338

Extensions can integrate with Plover's configuration system:

339

340

```python

341

class ConfigurableExtension:

342

def __init__(self, engine):

343

self.engine = engine

344

345

def start(self):

346

# Access extension-specific configuration

347

config = self.engine.config

348

self.setting1 = config.get('my_extension_setting1', 'default_value')

349

self.setting2 = config.get('my_extension_setting2', True)

350

351

def update_config(self, **kwargs):

352

"""Update extension configuration."""

353

config = self.engine.config

354

for key, value in kwargs.items():

355

config[f'my_extension_{key}'] = value

356

config.save()

357

```

358

359

### Extension-Specific Settings

360

361

Extensions can define their own configuration schema:

362

363

```python

364

class AdvancedExtension:

365

DEFAULT_CONFIG = {

366

'enabled_features': ['feature1', 'feature2'],

367

'update_interval': 30,

368

'log_level': 'info',

369

'custom_dictionary_path': None

370

}

371

372

def __init__(self, engine):

373

self.engine = engine

374

self.config = self.DEFAULT_CONFIG.copy()

375

376

def load_config(self):

377

"""Load extension configuration."""

378

engine_config = self.engine.config

379

for key, default in self.DEFAULT_CONFIG.items():

380

config_key = f'advanced_extension_{key}'

381

self.config[key] = engine_config.get(config_key, default)

382

```

383

384

## Extension Examples

385

386

### Keystroke Logger Extension

387

388

```python

389

class KeystrokeLogger:

390

"""Logs all keystrokes to a file for analysis."""

391

392

def __init__(self, engine):

393

self.engine = engine

394

self.log_file = None

395

396

def start(self):

397

self.log_file = open('keystrokes.log', 'a')

398

self.engine.hook_connect('stroked', self.log_stroke)

399

400

def stop(self):

401

self.engine.hook_disconnect('stroked', self.log_stroke)

402

if self.log_file:

403

self.log_file.close()

404

405

def log_stroke(self, stroke):

406

timestamp = time.strftime('%Y-%m-%d %H:%M:%S')

407

stroke_str = '/'.join(stroke)

408

self.log_file.write(f"{timestamp}: {stroke_str}\n")

409

self.log_file.flush()

410

```

411

412

### Auto-Correction Extension

413

414

```python

415

class AutoCorrector:

416

"""Automatically corrects common stenographic errors."""

417

418

CORRECTIONS = {

419

('T', 'E', 'H'): ('T', 'H', 'E'), # Common chord error

420

('S', 'T', 'A', 'O', 'P'): ('S', 'T', 'O', 'P'), # Remove accidental A

421

}

422

423

def __init__(self, engine):

424

self.engine = engine

425

self.last_stroke = None

426

427

def start(self):

428

self.engine.hook_connect('stroked', self.check_correction)

429

430

def stop(self):

431

self.engine.hook_disconnect('stroked', self.check_correction)

432

433

def check_correction(self, stroke):

434

stroke_tuple = tuple(stroke)

435

if stroke_tuple in self.CORRECTIONS:

436

# Apply correction by simulating corrected stroke

437

corrected = self.CORRECTIONS[stroke_tuple]

438

# Would need access to machine interface to inject corrected stroke

439

pass

440

self.last_stroke = stroke_tuple

441

```

442

443

## Types

444

445

```python { .api }

446

from typing import Dict, List, Any, Callable, Optional, Union, Tuple

447

from threading import Thread

448

from PyQt5.QtWidgets import QWidget

449

450

ExtensionConfig = Dict[str, Any]

451

HookCallback = Callable[..., None]

452

StrokeData = List[str]

453

TranslationData = Tuple[List, List]

454

455

BackgroundWorker = Thread

456

ConfigurationDict = Dict[str, Any]

457

ExtensionInstance = Any

458

459

GuiWidget = QWidget

460

ExtensionState = Dict[str, Any]

461

```