architecture - JRufer/VoxCtrl GitHub Wiki
VoxCtrl is a Tauri 2 application: a compiled Rust backend that spawns a WebView window running a Svelte SPA. The two halves communicate via Tauri's IPC bridge (invoke commands + event emitters).
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β Tauri Desktop App β
β β
β βββββββββββββββββββββ ββββββββββββββββββββββββββββ β
β β Svelte Frontend βββββββΊβ Rust Backend (lib.rs) β β
β β (WebView) β IPC β + crates workspace β β
β βββββββββββββββββββββ ββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β
Settings Audio devices,
windows Filesystem, DBus,
Network (OpenAI API/HTTP)
In addition, the backend opens a second WebviewWindow (window::open_overlay_window in src-tauri/src/window.rs) that renders the always-on-top, click-through recording HUD by loading the /overlay Svelte route β the same Overlay.svelte component tree used for the style preview in Settings. It gets its state through the same app-wide status-tick / audio-level Tauri events every other window uses, rather than a separate protocol; see docs/overlays.md for the window-management details (click-through, always-on-top reassertion, Wayland/XWayland).
The backend is organized as a Cargo workspace of focused, single-responsibility crates:
VoxCtrl/
βββ src-tauri/ # Tauri app entry + IPC command handlers
β βββ src/
β βββ main.rs # App bootstrap
β βββ lib.rs # Pipeline coordinator
β βββ commands.rs# Tauri #[command] handlers
β βββ state.rs # Shared AppState
β
βββ crates/
βββ voxctrl-config/ # AppConfig struct, TOML/JSON persistence
βββ voxctrl-audio/ # Microphone capture, resampling, VU meter
βββ voxctrl-hotkeys/ # Global shortcuts (XDG portal / evdev / Windows hook / D-Bus)
βββ voxctrl-inference/ # whisper.cpp/Moonshine/Parakeet/Remote STT + post-processing
βββ voxctrl-routing/ # OutputTarget + HotkeyBinding data models, 11-target router
βββ voxctrl-inject/ # Text delivery: paste (with clipboard restore) or type; key sending per platform
βββ voxctrl-clipboard/ # Full-format clipboard backup/restore (Windows / X11 / Wayland)
βββ voxctrl-winput/ # Windows SendInput: Unicode typing, paste shortcuts, window class
βββ voxctrl-tts/ # Neural & local TTS (Breeze/Piper/Pocket/Inflect/VoxCPM2/eSpeak)
βββ voxctrl-mcp/ # MCP JSON-RPC server (Unix socket / named pipe)
βββ voxctrl-dbus/ # DBus service (Linux session bus)
βββ voxctrl-llm/ # OpenAI-compatible LLM HTTP client
βββ voxctrl-text/ # Shared snippet expansion + fuzzy vocab correction
βββ voxctrl-update/ # GitHub release check, download, verify, self-replace
βββ voxctrl-bugreport/ # Diagnostic collection, allowlist redaction, telemetry-free reporting
βββ voxctrl-llm-sidecar/# Vulkan-accelerated llama.cpp sidecar for S1-mini dictation cleanup
Hotkey press
β
βΌ
voxctrl-hotkeys ββgesture_txβββΊ lib.rs coordinator
β
ββββββββββββββ€
β β
Start AudioRecorder Determine target from binding
(voxctrl-audio)
β
audio_tx chunks
β
βΌ
Audio Accumulator
(lib.rs buffer)
β
Hotkey release / VAD stop
β
inference_tx
β
βΌ
InferenceEngine.process()
(voxctrl-inference)
β Noise gate (VAD)
β Speech transcription (whisper.cpp / Moonshine / Parakeet / Remote)
β Filler removal
β Spoken punctuation
β Auto-format lists
β Snippet expansion
β Custom vocab fuzzy correction
β Code mode
β Silence hallucination filter
β LLM rewrite via OpenAI API (optional, per-target)
β S1-mini text normalization via voxctrl-llm-sidecar
β
text_tx (InferenceOutput)
β
βΌ
OutputTargetRouter.route()
(voxctrl-routing)
βββ inject β voxctrl-inject (paste: voxctrl-clipboard; Windows keys: voxctrl-winput)
βββ clipboard β arboard
βββ file β tokio::fs
βββ http/webhook β reqwest
βββ exec β std::process
βββ socket β UnixStream / TcpStream
βββ dbus β voxctrl-dbus
βββ mcp β voxctrl-mcp response queue
βββ pipe β named FIFO
βββ speak β voxctrl-tts
βββ chat β voxctrl-llm conversational loop
β
Tauri event β frontend
(status-tick)
VoxCtrl uses Tokio for async I/O plus dedicated OS threads for latency-sensitive work:
| Thread / Task | Type | Purpose |
|---|---|---|
| Main Tauri thread | OS thread | Window management, IPC dispatch |
| Audio capture | OS thread (cpal) | Microphone streaming at hardware rate |
| Audio level emitter | OS thread | Forwards RMS levels to the UI and the overlay, coalesced to one per frame (16 ms); silent unless recording or monitoring |
| Hotkey listener | async task + OS threads | XDG GlobalShortcuts portal, with an evdev/Win32 fallback |
| Inference worker | OS thread | Blocking Whisper computation; WhisperState (KV cache + attention buffers) is allocated once at load and reused across all calls |
| Status ticker | Tokio task | Ticks at 150 ms to animate the tray icon; emits status-tick only when the state changed, plus a 900 ms heartbeat |
| Config watcher | Tokio task |
inotify/kqueue on config files |
| MCP server | Tokio task | Unix socket accept loop |
| DBus service | Tokio task | Session bus method handler |
| TTS FIFO watcher | Tokio task | Named pipe reader for TTS input |
Shared state is an Arc<AppState> with AtomicBool/AtomicU32 for hot-path flags and Mutex for heavier data (targets, TTS handle).
Channels (crossbeam/tokio):
-
audio_tx/audio_rxβVec<f32>chunks -
inference_tx/inference_rxβInferenceRequest -
text_tx/text_rxβInferenceOutput -
audio_level_tx/level_rxβf32RMS -
gesture_tx/gesture_rxβGestureEvent -
hotkey_reloaderβ updated bindings list sent to listener thread (hot-reload)
The Svelte frontend is a single-page app with three logical "pages" rendered as separate Tauri windows:
App.svelte (route switcher)
βββ /settings β Settings component (sidebar with 9 tabs)
β βββ GeneralTab
β βββ EngineTab
β βββ RoutingTab (targets + bindings editor)
β βββ VisualTab
β βββ AudioTab
β βββ TtsTab
β βββ FeaturesTab
β βββ OpenAiTab (labeled "OpenAI API")
β βββ AboutTab
β
βββ /wizard β SetupWizard component (first-run setup, 7 steps)
β βββ WelcomeStep
β βββ EngineStep (engine + model, downloads before continuing)
β βββ HotkeyStep (gesture + key capture, desktop registration)
β βββ OverlayStep (style + position, bundled webm previews)
β βββ TestStep (live end-to-end dictation)
β βββ VoiceStep (optional TTS engine, per-card download)
β βββ DoneStep (summary + anything that failed)
β
βββ /overlay β Overlay component (the on-screen recording HUD, loaded into
β β its own WebviewWindow β see "High-Level Design" above)
β βββ BlueWave (default β "Ocean Wave" tide pool)
β βββ VoiceCard ("Voice Card" VU LED matrix card)
β βββ Waveform (green-phosphor oscilloscope)
β βββ Pulse ("Pulse Ring" sonar dial)
β
State management:
-
src/stores/config.tsβ reactiveAppConfigwith 400ms debounced auto-save viasave_configIPC; also listens forconfig-changedevents -
src/stores/status.tsβ live state fromstatus-tickevents + derived stores (recording,speaking,wordCount,activeTargetLabel). Falls back to pollingget_statusonce a second when ticks have been absent for two, so a window the events do not reach still shows live state; while ticks are arriving it does no IPC at all
| Path | Contents |
|---|---|
~/.config/voxctrl/config.json |
Main application config |
~/.config/voxctrl/targets.toml |
Output target definitions |
~/.config/voxctrl/bindings.toml |
Hotkey binding definitions |
~/.local/share/voxctrl/models/ |
Downloaded Whisper GGUF models |
~/.local/share/voxctrl/piper-voices/ |
Downloaded Piper voice packs |
~/.local/share/voxctrl/models/inflect-micro/ |
Inflect-Micro-v2 ONNX graphs and symbol list |
/tmp/voxctrl-mcp.sock |
MCP Unix domain socket (Linux) |
\\.\pipe\voxctrl-mcp |
MCP named pipe (Windows) |