Open Source Stenography Software providing real-time stenographic typing, machine support, and plugin architecture.
—
Plover's machine interface system provides abstracted communication with stenotype hardware through a unified API. It supports various connection methods including serial, USB, and keyboard input, enabling integration with both commercial stenotype machines and alternative input devices.
Abstract base class defining the standard interface for all stenotype machine implementations.
class StenotypeBase:
"""Base class for stenotype machine interfaces."""
KEYS_LAYOUT: str = ''
"""String describing the physical key layout of the machine."""
ACTIONS: tuple = ()
"""Tuple of available machine-specific actions."""
KEYMAP_MACHINE_TYPE: str = None
"""Machine type identifier for keymap compatibility."""
def __init__(self):
"""
Initialize machine interface.
Sets up machine instance with default configuration,
ready for keymap assignment and capture initialization.
"""
def set_keymap(self, keymap: dict) -> None:
"""
Set key mapping configuration.
Args:
keymap: Dictionary mapping physical keys to steno keys
Configures how physical machine keys map to stenographic keys.
"""
def start_capture(self) -> None:
"""
Start capturing strokes from machine.
Begins communication with machine hardware and starts
processing input for stroke detection.
Raises:
ConnectionError: If unable to connect to machine
"""
def stop_capture(self) -> None:
"""
Stop capturing strokes from machine.
Ceases communication with machine hardware and stops
all input processing.
"""
def add_stroke_callback(self, callback) -> None:
"""
Add callback for stroke events.
Args:
callback: Function to call when stroke detected
Callback signature: callback(stroke_keys: list[str])
"""
def remove_stroke_callback(self, callback) -> None:
"""
Remove previously added stroke callback.
Args:
callback: Function to remove from callbacks
"""
def add_state_callback(self, callback) -> None:
"""
Add callback for machine state changes.
Args:
callback: Function to call when state changes
Callback signature: callback(state: str)
States: 'stopped', 'initializing', 'connected', 'disconnected'
"""
def remove_state_callback(self, callback) -> None:
"""
Remove previously added state callback.
Args:
callback: Function to remove from callbacks
"""
def set_suppression(self, enabled: bool) -> None:
"""
Set output suppression state.
Args:
enabled: True to suppress machine output, False to allow
Controls whether machine generates its own output
in addition to Plover's processing.
"""
def suppress_last_stroke(self, send_backspaces: bool) -> None:
"""
Suppress the last stroke's output.
Args:
send_backspaces: Whether to send backspaces to undo output
Undoes the effect of the most recent stroke.
"""
@classmethod
def get_actions(cls) -> tuple:
"""
Get available machine actions.
Returns:
Tuple of action strings available for this machine type
Actions vary by machine and may include special functions.
"""
@classmethod
def get_keys(cls) -> tuple:
"""
Get available machine keys.
Returns:
Tuple of key strings available on this machine
Keys correspond to physical keys that can be mapped.
"""
@classmethod
def get_option_info(cls) -> dict:
"""
Get machine-specific option information.
Returns:
Dictionary describing available configuration options
Provides metadata for GUI configuration interfaces.
"""Base class adding threading support for machines requiring background processing.
class ThreadedStenotypeBase(StenotypeBase, threading.Thread):
"""Base class with threading support for background processing."""
def run(self) -> None:
"""
Main thread execution method.
Override this method to implement machine-specific
background processing loop.
"""Specialized base class for machines using serial port communication.
class SerialStenotypeBase(ThreadedStenotypeBase):
"""Base class for serial port stenotype machines."""
SERIAL_PARAMS: dict = {
'baudrate': 9600,
'bytesize': 8,
'parity': 'N',
'stopbits': 1,
'timeout': 2.0,
'xonxoff': False,
'rtscts': False
}
"""Default serial port parameters."""Standard machine state identifiers used throughout the system.
STATE_STOPPED: str = 'stopped'
"""Machine is not running or connected."""
STATE_INITIALIZING: str = 'initializing'
"""Machine is starting up or connecting."""
STATE_RUNNING: str = 'connected'
"""Machine is connected and receiving strokes."""
STATE_ERROR: str = 'disconnected'
"""Machine encountered error or lost connection."""Uses computer keyboard as stenotype input device.
Plugin Name: Keyboard
Connection: Direct keyboard input capture
Configuration: Keyboard layout selection, key mapping
Use Case: Practice, accessibility, no dedicated hardware
Supports machines using the TX Bolt communication protocol.
Plugin Name: TX Bolt
Connection: Serial port communication
Protocol: TX Bolt binary format
Machines: Many commercial stenotype machines
Supports machines using the Gemini PR communication protocol.
Plugin Name: Gemini PR
Connection: Serial or USB communication
Protocol: Gemini PR packet format
Machines: Neutrino Group machines, some others
Support for ProCAT stenotype machines.
Plugin Name: ProCAT
Connection: Serial port communication
Protocol: ProCAT-specific format
Machines: ProCAT stenotype models
Support for Stentura stenotype machines.
Plugin Name: Stentura
Connection: Serial port communication
Protocol: Stentura-specific format
Machines: Stentura stenotype models
Support for Passport stenotype machines.
Plugin Name: Passport
Connection: Serial port communication
Protocol: Passport-specific format
Machines: Passport stenotype models
from plover.registry import registry
from plover.machine.base import STATE_RUNNING, STATE_ERROR
# Get available machines
machines = registry.list_plugins('machine')
for machine in machines:
print(f"Available machine: {machine.name}")
# Get specific machine
keyboard_plugin = registry.get_plugin('machine', 'Keyboard')
KeyboardMachine = keyboard_plugin.obj
# Create machine instance
machine = KeyboardMachine()
# Set up callbacks
def on_stroke(stroke_keys):
print(f"Stroke received: {stroke_keys}")
def on_state_change(state):
if state == STATE_RUNNING:
print("Machine connected and ready")
elif state == STATE_ERROR:
print("Machine connection error")
machine.add_stroke_callback(on_stroke)
machine.add_state_callback(on_state_change)
# Configure keymap
keymap = {
'q': 'S-',
'w': 'T-',
'e': 'K-',
'r': 'P-',
't': 'W-',
'y': 'H-',
'u': 'R-',
'i': 'A-',
'o': 'O-',
'p': '*',
# ... more key mappings
}
machine.set_keymap(keymap)
# Start machine
try:
machine.start_capture()
print("Machine started successfully")
except ConnectionError as e:
print(f"Failed to start machine: {e}")
# Later, stop machine
machine.stop_capture()
# Get machine information
keys = machine.get_keys()
actions = machine.get_actions()
options = machine.get_option_info()
print(f"Machine keys: {keys}")
print(f"Machine actions: {actions}")
print(f"Configuration options: {options}")layout: Keyboard layout ('QWERTY', 'Dvorak', 'Colemak')arpeggiate: Enable arpeggiate mode for chordingport: Serial port device path or namebaudrate: Communication speed (9600, 19200, 38400, etc.)bytesize: Data bits (7, 8)parity: Parity setting ('N', 'E', 'O')stopbits: Stop bits (1, 2)timeout: Read timeout in secondsxonxoff: Software flow controlrtscts: Hardware flow controlvendor_id: USB vendor ID for device identificationproduct_id: USB product ID for device identificationinterface: USB interface numberfrom plover.machine.base import StenotypeBase
class CustomMachine(StenotypeBase):
KEYS_LAYOUT = 'STENO_KEYS'
ACTIONS = ('action1', 'action2')
def __init__(self):
super().__init__()
self._stroke_callbacks = []
self._state_callbacks = []
def start_capture(self):
# Initialize hardware connection
# Start background processing
self._notify_state('connected')
def stop_capture(self):
# Stop hardware communication
self._notify_state('stopped')
def _notify_stroke(self, keys):
for callback in self._stroke_callbacks:
callback(keys)
def _notify_state(self, state):
for callback in self._state_callbacks:
callback(state)
@classmethod
def get_option_info(cls):
return {
'port': {
'type': 'choice',
'choices': ['COM1', 'COM2', '/dev/ttyUSB0'],
'default': 'COM1'
}
}from plover.machine.base import ThreadedStenotypeBase
import threading
import time
class ThreadedCustomMachine(ThreadedStenotypeBase):
def __init__(self):
super().__init__()
self._running = False
def start_capture(self):
self._running = True
self.start() # Start thread
def stop_capture(self):
self._running = False
self.join() # Wait for thread
def run(self):
"""Background thread for stroke processing."""
while self._running:
# Read from hardware
# Process strokes
# Notify callbacks
time.sleep(0.01)from plover.machine.base import SerialStenotypeBase
import serial
class SerialCustomMachine(SerialStenotypeBase):
SERIAL_PARAMS = {
'baudrate': 19200,
'timeout': 1.0
}
def __init__(self):
super().__init__()
self._serial = None
def start_capture(self):
port = self._settings.get('port', 'COM1')
self._serial = serial.Serial(port, **self.SERIAL_PARAMS)
super().start_capture()
def stop_capture(self):
super().stop_capture()
if self._serial:
self._serial.close()
def run(self):
while self._running and self._serial:
data = self._serial.read(10)
if data:
stroke = self._parse_stroke(data)
if stroke:
self._notify_stroke(stroke)from typing import Dict, List, Tuple, Optional, Callable, Any, Union
from threading import Thread
StrokeKeys = List[str]
StrokeCallback = Callable[[StrokeKeys], None]
StateCallback = Callable[[str], None]
MachineState = str
MachineKeymap = Dict[str, str]
MachineOptions = Dict[str, Any]
MachineActions = Tuple[str, ...]
OptionInfo = Dict[str, Dict[str, Any]]
SerialParams = Dict[str, Union[int, float, str, bool]]
CallbackList = List[Callable]Install with Tessl CLI
npx tessl i tessl/pypi-plover