CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-plover

Open Source Stenography Software providing real-time stenographic typing, machine support, and plugin architecture.

Pending
Overview
Eval results
Files

extensions.mddocs/

Extension Framework

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.

Capabilities

Extension Base Interface

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

class Extension:
    """Base interface for Plover extensions."""
    
    def __init__(self, engine):
        """
        Initialize extension with engine reference.
        
        Args:
            engine: StenoEngine instance providing full API access
            
        Store engine reference and perform initial setup.
        Extensions receive complete access to engine functionality.
        """
    
    def start(self) -> None:
        """
        Start extension operation.
        
        Called when extension is enabled in configuration.
        Perform initialization, connect event hooks, start background
        threads, and begin extension functionality.
        """
    
    def stop(self) -> None:
        """
        Stop extension operation.
        
        Called when extension is disabled or Plover shuts down.
        Disconnect hooks, stop background threads, cleanup resources,
        and ensure graceful shutdown.
        """

Extension Capabilities

Engine API Access

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

Available Engine Features:

  • Machine Control: Start, stop, and configure stenotype machines
  • Dictionary Management: Access, modify, and filter dictionaries
  • Translation Processing: Hook into stroke and translation events
  • Output Control: Enable, disable, and monitor stenographic output
  • Configuration Access: Read and modify all Plover settings
  • State Management: Access translator and machine state

Event Hook System

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

Available Hooks:

  • stroked: Stenotype stroke received from machine
  • translated: Stroke translated to text output
  • machine_state_changed: Machine connection status changed
  • output_changed: Stenographic output enabled/disabled
  • config_changed: Configuration settings updated
  • dictionaries_loaded: Dictionary collection reloaded
  • send_string: Text string sent to system output
  • send_backspaces: Backspace characters sent to output
  • send_key_combination: Key combination sent to system
  • add_translation: Translation added to dictionary
  • focus: Main window focus requested
  • configure: Configuration dialog requested
  • lookup: Lookup dialog requested
  • suggestions: Suggestions dialog requested
  • quit: Application quit requested

Background Processing

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

GUI Integration

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

Extension Development

Basic Extension Implementation

from plover import log

class BasicExtension:
    """Simple extension that logs all strokes."""
    
    def __init__(self, engine):
        self.engine = engine
        
    def start(self):
        """Connect to stroke events."""
        self.engine.hook_connect('stroked', self.on_stroke)
        log.info("Stroke logging extension started")
        
    def stop(self):
        """Disconnect from events."""
        self.engine.hook_disconnect('stroked', self.on_stroke)
        log.info("Stroke logging extension stopped")
        
    def on_stroke(self, stroke):
        """Handle stroke events."""
        log.info(f"Stroke received: {'/'.join(stroke)}")

Advanced Extension with Background Processing

import threading
import time
import json
from pathlib import Path

class StrokeAnalyzer:
    """Extension that analyzes stroke patterns and saves statistics."""
    
    def __init__(self, engine):
        self.engine = engine
        self.stats = {
            'total_strokes': 0,
            'stroke_frequency': {},
            'session_start': None
        }
        self.stats_file = Path('stroke_stats.json')
        self.background_thread = None
        self.running = False
        
    def start(self):
        """Start stroke analysis."""
        # Load existing stats
        self.load_stats()
        
        # Connect to events
        self.engine.hook_connect('stroked', self.on_stroke)
        self.engine.hook_connect('translated', self.on_translation)
        
        # Start background processing
        self.running = True
        self.stats['session_start'] = time.time()
        self.background_thread = threading.Thread(target=self.background_worker)
        self.background_thread.start()
        
    def stop(self):
        """Stop stroke analysis."""
        # Stop background thread
        self.running = False
        if self.background_thread:
            self.background_thread.join()
            
        # Disconnect events
        self.engine.hook_disconnect('stroked', self.on_stroke)
        self.engine.hook_disconnect('translated', self.on_translation)
        
        # Save final stats
        self.save_stats()
        
    def on_stroke(self, stroke):
        """Analyze stroke patterns."""
        stroke_str = '/'.join(stroke)
        self.stats['total_strokes'] += 1
        self.stats['stroke_frequency'][stroke_str] = (
            self.stats['stroke_frequency'].get(stroke_str, 0) + 1
        )
        
    def on_translation(self, old, new):
        """Analyze translation patterns."""
        # Could analyze translation efficiency, common corrections, etc.
        pass
        
    def background_worker(self):
        """Background thread for periodic stats saving."""
        while self.running:
            time.sleep(60)  # Save every minute
            if self.running:  # Check again after sleep
                self.save_stats()
                
    def load_stats(self):
        """Load statistics from file."""
        if self.stats_file.exists():
            with open(self.stats_file, 'r') as f:
                saved_stats = json.load(f)
                self.stats.update(saved_stats)
                
    def save_stats(self):
        """Save statistics to file."""
        with open(self.stats_file, 'w') as f:
            json.dump(self.stats, f, indent=2)

Extension with Dictionary Integration

class CustomDictionaryManager:
    """Extension that manages custom dictionary features."""
    
    def __init__(self, engine):
        self.engine = engine
        self.custom_translations = {}
        
    def start(self):
        """Start custom dictionary management."""
        # Add custom dictionary filter
        self.engine.add_dictionary_filter(self.custom_filter)
        
        # Connect to translation events
        self.engine.hook_connect('add_translation', self.on_add_translation)
        
    def stop(self):
        """Stop custom dictionary management."""
        # Remove filter
        self.engine.remove_dictionary_filter(self.custom_filter)
        
        # Disconnect events
        self.engine.hook_disconnect('add_translation', self.on_add_translation)
        
    def custom_filter(self, strokes, translation):
        """Apply custom filtering logic."""
        # Example: Convert all translations to title case
        if translation and isinstance(translation, str):
            return translation.title()
        return translation
        
    def on_add_translation(self):
        """Handle translation additions."""
        # Could log new translations, sync with external systems, etc.
        pass
        
    def add_temporary_translation(self, strokes, translation):
        """Add temporary translation that doesn't persist."""
        # Store in memory only
        self.custom_translations[strokes] = translation
        
        # Could temporarily modify dictionary collection
        first_dict = self.engine.dictionaries.first_writable()
        if first_dict:
            first_dict[strokes] = translation

Extension with GUI Integration

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

class StrokeDisplay(QDialog):
    """GUI extension showing real-time stroke display."""
    
    def __init__(self, engine):
        super().__init__()
        self.engine = engine
        self.stroke_label = None
        self.setup_ui()
        
    def setup_ui(self):
        """Setup the GUI interface."""
        self.setWindowTitle("Live Stroke Display")
        self.setGeometry(100, 100, 300, 150)
        
        layout = QVBoxLayout()
        
        self.stroke_label = QLabel("No strokes yet...")
        self.stroke_label.setStyleSheet("font-size: 18px; font-weight: bold;")
        layout.addWidget(self.stroke_label)
        
        close_button = QPushButton("Close")
        close_button.clicked.connect(self.close)
        layout.addWidget(close_button)
        
        self.setLayout(layout)
        
    def start(self):
        """Start the stroke display."""
        self.engine.hook_connect('stroked', self.on_stroke)
        self.show()
        
    def stop(self):
        """Stop the stroke display."""
        self.engine.hook_disconnect('stroked', self.on_stroke)
        self.close()
        
    def on_stroke(self, stroke):
        """Update display with new stroke."""
        stroke_text = '/'.join(stroke)
        self.stroke_label.setText(f"Last stroke: {stroke_text}")
        
        # Auto-clear after 3 seconds
        QTimer.singleShot(3000, lambda: self.stroke_label.setText("Waiting for strokes..."))

Extension Registration

Entry Point Registration

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

# setup.py
setup(
    name="my-plover-extension",
    entry_points={
        'plover.extension': [
            'my_extension = my_package.extension:MyExtension',
        ],
    },
)
# pyproject.toml
[project.entry-points."plover.extension"]
my_extension = "my_package.extension:MyExtension"

Manual Registration

Extensions can also be registered programmatically:

from plover.registry import registry

# Register extension class
registry.register_plugin('extension', 'my_extension', MyExtension)

Extension Configuration

Configuration Integration

Extensions can integrate with Plover's configuration system:

class ConfigurableExtension:
    def __init__(self, engine):
        self.engine = engine
        
    def start(self):
        # Access extension-specific configuration
        config = self.engine.config
        self.setting1 = config.get('my_extension_setting1', 'default_value')
        self.setting2 = config.get('my_extension_setting2', True)
        
    def update_config(self, **kwargs):
        """Update extension configuration."""
        config = self.engine.config
        for key, value in kwargs.items():
            config[f'my_extension_{key}'] = value
        config.save()

Extension-Specific Settings

Extensions can define their own configuration schema:

class AdvancedExtension:
    DEFAULT_CONFIG = {
        'enabled_features': ['feature1', 'feature2'],
        'update_interval': 30,
        'log_level': 'info',
        'custom_dictionary_path': None
    }
    
    def __init__(self, engine):
        self.engine = engine
        self.config = self.DEFAULT_CONFIG.copy()
        
    def load_config(self):
        """Load extension configuration."""
        engine_config = self.engine.config
        for key, default in self.DEFAULT_CONFIG.items():
            config_key = f'advanced_extension_{key}'
            self.config[key] = engine_config.get(config_key, default)

Extension Examples

Keystroke Logger Extension

class KeystrokeLogger:
    """Logs all keystrokes to a file for analysis."""
    
    def __init__(self, engine):
        self.engine = engine
        self.log_file = None
        
    def start(self):
        self.log_file = open('keystrokes.log', 'a')
        self.engine.hook_connect('stroked', self.log_stroke)
        
    def stop(self):
        self.engine.hook_disconnect('stroked', self.log_stroke)
        if self.log_file:
            self.log_file.close()
            
    def log_stroke(self, stroke):
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        stroke_str = '/'.join(stroke)
        self.log_file.write(f"{timestamp}: {stroke_str}\n")
        self.log_file.flush()

Auto-Correction Extension

class AutoCorrector:
    """Automatically corrects common stenographic errors."""
    
    CORRECTIONS = {
        ('T', 'E', 'H'): ('T', 'H', 'E'),  # Common chord error
        ('S', 'T', 'A', 'O', 'P'): ('S', 'T', 'O', 'P'),  # Remove accidental A
    }
    
    def __init__(self, engine):
        self.engine = engine
        self.last_stroke = None
        
    def start(self):
        self.engine.hook_connect('stroked', self.check_correction)
        
    def stop(self):
        self.engine.hook_disconnect('stroked', self.check_correction)
        
    def check_correction(self, stroke):
        stroke_tuple = tuple(stroke)
        if stroke_tuple in self.CORRECTIONS:
            # Apply correction by simulating corrected stroke
            corrected = self.CORRECTIONS[stroke_tuple]
            # Would need access to machine interface to inject corrected stroke
            pass
        self.last_stroke = stroke_tuple

Types

from typing import Dict, List, Any, Callable, Optional, Union, Tuple
from threading import Thread
from PyQt5.QtWidgets import QWidget

ExtensionConfig = Dict[str, Any]
HookCallback = Callable[..., None]
StrokeData = List[str]
TranslationData = Tuple[List, List]

BackgroundWorker = Thread
ConfigurationDict = Dict[str, Any]
ExtensionInstance = Any

GuiWidget = QWidget
ExtensionState = Dict[str, Any]

Install with Tessl CLI

npx tessl i tessl/pypi-plover

docs

configuration.md

dictionaries.md

engine.md

extensions.md

index.md

machines.md

registry.md

steno-data.md

tile.json