Configuration
All settings live under ~/.config/voxctrl/. The Settings UI is a thin layer over
these files — they're the source of truth. All three config files are hot-reloaded on save.
Files
| File | Format | Purpose |
|---|---|---|
| config.json | JSON | Application settings — engine, audio, UI, features, OpenAI, TTS, MCP, AT-SPI2 |
| targets.toml | TOML | Output target definitions |
| bindings.toml | TOML | Hotkey binding definitions |
Data directory
Models and TTS voices are stored under ~/.local/share/voxctrl/:
~/.local/share/voxctrl/models/— downloaded Whisper GGUF model files~/.local/share/voxctrl/piper-voices/— downloaded Piper TTS voice files~/.local/share/voxctrl/pocket-tts-voices/— custom Pocket-TTS reference voice clips
Migration
VoxCtrl auto-migrates older config formats on load — unrecognized fields are silently ignored, and missing
fields fall back to their defaults. This keeps things compatible across upgrades and downgrades. One known
migration: features.show_notification moved to ui.show_notification in an early
release; the migrated config is immediately re-saved to disk to clean up the old key.
engine
The engine config has two top-level fields plus a nested sub-object per backend.
| Key | Default | Description |
|---|---|---|
| backend | "auto" | "auto" | "whisper-cpp" | "moonshine" — auto selects based on GPU availability |
| inference_mode | "Balanced" | "Balanced" | "Aggressive" — Balanced is recommended |
| whisper_cpp.model_size | "large-v3" | tiny, tiny.en, base, base.en, small, small.en, medium, medium.en, large-v2, large-v3, large-v3-turbo |
| whisper_cpp.device | "auto" | auto | cpu | cuda | vulkan |
| whisper_cpp.threads | 0 | CPU thread count; 0 = half of logical cores |
| whisper_cpp.model_dir | "" | Custom model directory; empty = ~/.local/share/voxctrl/models/ |
| moonshine.model_size | "base" | "base" | "tiny" (only used when backend = "moonshine") |
| moonshine.language | "en" | BCP-47 language code |
audio
| Key | Default | Description |
|---|---|---|
| vad_threshold | 0.5 | Voice Activity Detection sensitivity (0.0–1.0); higher = more sensitive |
| min_silence_duration_ms | 500 | Milliseconds of silence before stopping a recording session |
| input_device_index | null | CPAL device index; null = auto-detect |
| evdev_device | null | Linux evdev keyboard device path for hotkeys, e.g. "/dev/input/event4" |
| noise_suppression | false | Enable basic noise suppression pre-processing |
| gain | 1.0 | Microphone amplification multiplier |
| dynamic_stream | true | Open mic on-demand (true) vs. always-on (false) |
ui
| Key | Default | Description |
|---|---|---|
| show_overlay | true | Whether the overlay window is currently visible |
| overlay_style | "blue_wave" | blue_wave | voice_card | waveform | pulse | mono_bars | spectrum | terminal | vinyl | none |
| overlay_position | "center" | top | center | bottom |
| overlay_monitor | "primary" | "primary" or a specific monitor name |
| auto_show_settings | true | Auto-show Settings window on startup |
| show_notification | false | Desktop toast notification after text delivery |
| history_enabled | false | Enable transcription history tracking |
features
| Key | Default | Description |
|---|---|---|
| remove_fillers | true | Strip filler words (uh, um, hmm, er, ah, etc.) |
| spoken_punctuation | true | Convert spoken punctuation words to symbols (e.g. "period" → ".") |
| auto_format_lists | true | Detect "first/second/third" patterns and reformat as a numbered list |
| quiet_mode | false | Suppress overlay notifications during transcription |
| custom_vocabulary | [] | Custom words corrected post-transcription via fuzzy Levenshtein matching |
| snippets | {} | Short code → expansion text map |
openai
LLM post-processing through any OpenAI-compatible API server — local or hosted. Exposed in the GUI under
Settings → OpenAI API. Each request sends a system prompt (how to transform the text) and a
user prompt containing {text}, replaced with the dictated speech. Configs written before this
section was renamed used the key ollama; that key is still accepted via a serde alias.
| Key | Default | Description |
|---|---|---|
| enabled | false | Enable LLM post-processing |
| endpoint | "http://localhost:11434" | OpenAI-compatible API base URL; /v1 suffix added automatically when missing |
| api_key | null | Sent as a Bearer token; required by most remote providers |
| model | "llama3.2:1b" | Model name |
| mode | "clean" | clean | formal | casual | bullet | concise | custom |
| system_prompt | "Fix grammar and punctuation only…" | System message describing the transformation; empty = none |
| user_prompt | "{text}" | User message template; must contain {text} |
| timeout_secs | 8 | HTTP request timeout in seconds |
tts
| Key | Default | Description |
|---|---|---|
| enabled | false | Enable TTS subsystem |
| engine | "piper" | piper | pocket_tts | espeak |
| voice | "en-us-lessac-medium" | Active Piper voice name |
| voice_dir | "" | Piper voice directory; empty = ~/.local/share/voxctrl/piper-voices/ |
| stop_key | [KEY_ESCAPE] | Keys that cancel current TTS playback |
| response_overlay | true | Show overlay indicator while TTS is speaking |
| speed | 1.0 | Speech speed multiplier (0.5–2.0); not used by Pocket-TTS |
| gpu | false | Enable GPU acceleration (CUDA) for Piper |
| pocket_tts.voice | "alba" | Bundled voice ID (alba, anna, vera, charles, michael) or custom clip stem |
| pocket_tts.prewarm | false | Pre-warm model on startup so first speech is instantaneous |
| pocket_tts.hf_token | null | HuggingFace token to download the gated kyutai/pocket-tts weights |
| pocket_tts.voice_dir | "" | Directory scanned for custom .wav voice clips; empty = default directory |
Pocket-TTS is a voice-cloning neural TTS engine — each voice is a short reference audio clip that conditions synthesis, rather than a fixed precomputed embedding.
mcp
| Key | Default | Description |
|---|---|---|
| server_enabled | false | Start the MCP socket server on launch |
| record_timeout | 15.0 | Max seconds for transcribe_voice to wait for speech |
| visual_feedback | true | Show overlay indicator while MCP server is listening to microphone |
atspi
| Key | Default | Description |
|---|---|---|
| injection | true | Use AT-SPI2 for text insertion when available |
| context_prompt | true | Read focused widget text to use as Whisper context prompt |
| auto_code_mode | true | Detect code editors/terminals and enable code-mode processing automatically |
targets.toml
Each output destination is a [[target]] block, identified by id and routed to by
hotkey bindings. Common fields: label, delivery, append_newline
(default true), strip_newlines (default false, inject only), send_on_release
(default true), initial_prompt (Whisper context override), and a per-target
[target.processing] block that can override any global feature flag
(remove_fillers, spoken_punctuation, auto_format_lists,
apply_snippets, code_mode, quiet_mode, atspi_context,
noise_suppression).
Delivery types: inject, clipboard, file (with
file_path/file_prefix/file_timestamp/file_mode),
http (http_url, http_method), webhook
(webhook_url, webhook_secret), exec (command),
socket (socket_unix or socket_host/socket_port),
pipe (pipe_path), and speak. Any target can also set a
response_pipe FIFO path for TTS output.
bindings.toml
Each hotkey is a [[binding]] block with id, label,
keys (evdev key names), gesture, and target_ids (ordered list of
target IDs to route to).
| Field | Default | Description |
|---|---|---|
| gesture | — | hold | toggle | double_tap | double_tap_hold | chord |
| subkey | — | Trigger key for the chord gesture, pressed after base keys are held |
| hold_threshold_ms | 200 | Min hold duration in ms for hold / double-tap-hold gestures |
| tap_ms | 250 | Double-tap inter-press window in ms |
| disabled | false | Disable without deleting |
| openai_enabled | null | Enable/disable LLM post-processing for this hotkey (null = inherit global) |
| openai_mode | null | LLM mode override for this hotkey (clean/formal/casual/bullet/concise/custom) |
| openai_model | null | Model override for this hotkey (null = inherit global) |
| openai_prompt | null | User prompt template override for this hotkey; must contain {text} (null = inherit global) |
| openai_system_prompt | null | System prompt override for this hotkey (null = inherit global) |
The per-hotkey field names were renamed from ollama_* to openai_*; the legacy
names are still accepted via serde aliases.