# Hyprvoice - Voice-Powered Typing for Hyprland / Wayland Press a toggle key, speak, and get instant text input. Built natively for Wayland/Hyprland - no X11 hacks or workarounds, just clean integration with modern Linux desktops. ## Features - **Toggle workflow**: Press once to start recording, press again to stop and inject text - **LLM post-processing**: Automatically cleans up transcriptions - removes stutters, fixes grammar, adds punctuation (enabled by default) - **Wayland native**: Purpose-built for Wayland compositors - no legacy X11 dependencies or hacky workarounds - **Real-time feedback**: Desktop notifications for recording states and transcription status - **Multiple transcription backends**: OpenAI Whisper, Groq, Mistral Voxtral, and Eleven Labs Scribe (99 languages, excellent accuracy) - **Smart text injection**: Clipboard save/restore with direct typing fallback - **Daemon architecture**: Lightweight control plane with efficient pipeline management **Status:** Beta - core functionality complete and tested, ready for early adopters ## Installation ### From AUR (Arch Linux) - Recommended ```bash # Install hyprvoice and all dependencies automatically yay -S hyprvoice-bin # or paru -S hyprvoice-bin ``` The AUR package automatically installs all dependencies (`pipewire`, `wl-clipboard`, `wtype`, etc.) and sets up the systemd service. Follow the post-install instructions to complete setup. ### Alternative: Download Binary For non-Arch users or testing: ```bash # Download and install binary wget https://github.com/leonardotrapani/hyprvoice/releases/latest/download/hyprvoice-linux-x86_64 mkdir -p ~/.local/bin mv hyprvoice-linux-x86_64 ~/.local/bin/hyprvoice chmod +x ~/.local/bin/hyprvoice # Add to PATH (add to ~/.bashrc or ~/.zshrc) export PATH="$HOME/.local/bin:$PATH" # You'll need to manually install dependencies and create systemd service # See Requirements section above ``` ### Build from Source ```bash git clone https://github.com/leonardotrapani/hyprvoice.git cd hyprvoice go mod download go build -o hyprvoice ./cmd/hyprvoice # Install locally mkdir -p ~/.local/bin cp hyprvoice ~/.local/bin/ export PATH="$HOME/.local/bin:$PATH" ``` ## Requirements - **Wayland desktop** (Hyprland, Niri, GNOME, KDE, etc.) - **PipeWire audio system** with tools - **API key for transcription**: OpenAI, Groq, Mistral, or Eleven Labs API key (check each provider's pricing) **System packages** (automatically installed with AUR package): - `pipewire`, `pipewire-pulse`, `pipewire-audio` - Audio capture - `wl-clipboard` - Clipboard integration - `wtype` - Text typing (Wayland) - `ydotool` - Text typing (universal, recommended for Chromium apps) - `libnotify` - Desktop notifications - `systemd` - User service management For manual installation on other distros: ```bash # Ubuntu/Debian sudo apt install pipewire-pulse pipewire-bin wl-clipboard wtype ydotool libnotify-bin # Fedora sudo dnf install pipewire-utils wl-clipboard wtype ydotool libnotify # For ydotool, you also need to start the daemon: systemctl --user enable --now ydotool # Or add user to input group for uinput access: sudo usermod -aG input $USER ``` ## Quick Start After installing via AUR: 1. **Configure hyprvoice interactively:** ```bash hyprvoice configure ``` This wizard will guide you through setting up your transcription provider, API key, audio preferences, and other settings. 2. **Enable and start the service:** ```bash systemctl --user enable --now hyprvoice.service ``` 3. **Add keybinding to your window manager:** ```bash # For Hyprland, add to ~/.config/hypr/hyprland.conf bind = SUPER, R, exec, hyprvoice toggle ``` 4. **Test voice input:** ```bash # Check daemon status hyprvoice status # Toggle recording (or use your keybind) hyprvoice toggle # Speak something... hyprvoice toggle # Stop and transcribe ``` ## Quick Reference ### Common Commands ```bash # Interactive configuration wizard hyprvoice configure # Start the daemon hyprvoice serve # Toggle recording on/off hyprvoice toggle # Cancel current operation hyprvoice cancel # Check current status hyprvoice status # Get protocol version hyprvoice version # Stop the daemon (if not using systemd service) hyprvoice stop ``` ### Keybinding Pattern Most setups use this toggle pattern in window manager config: ```bash bind = SUPER, R, exec, hyprvoice toggle bind = SUPER SHIFT, R, exec, hyprvoice cancel # Optional: cancel current operation ``` ## Keyboard Shortcuts Setup ### Hyprland Add to your `~/.config/hypr/hyprland.conf`: ```bash # Hyprvoice - Voice to Text (toggle recording) bind = SUPER, R, exec, hyprvoice toggle # Optional: Cancel current operation bind = SUPER SHIFT, C, exec, hyprvoice cancel # Optional: Status check bind = SUPER SHIFT, R, exec, hyprvoice status && notify-send "Hyprvoice" "$(hyprvoice status)" ``` ## Usage Examples ### Basic Toggle Workflow 1. **Press keybind** → Recording starts (notification appears) 2. **Speak your text** → Audio captured in real-time 3. **Press keybind again** → Recording stops, transcription begins 4. **Text appears** → Injected at cursor position or clipboard **Cancel anytime:** Press your cancel keybind (e.g., `SUPER+SHIFT+C`) to abort the current operation and return to idle. ### CLI Usage ```bash # Start daemon manually (if not using systemd service) hyprvoice serve # In another terminal: toggle recording hyprvoice toggle # ... speak ... hyprvoice toggle # Check what's happening hyprvoice status ``` ## Configuration Use the interactive configuration wizard: ```bash hyprvoice configure ``` This will guide you through setting up: - Provider API keys (OpenAI, Groq, Mistral, ElevenLabs) - Transcription provider and model - LLM post-processing options (enabled by default) - Keywords for domain-specific terms - Text injection method (clipboard/typing/fallback) - Notification settings Configuration is stored in `~/.config/hyprvoice/config.toml` and can also be edited manually. Changes are applied immediately without restarting the daemon. ### Unified Provider System Hyprvoice uses a unified provider system where API keys are configured once and shared between transcription and LLM features: ```toml # Configure API keys for providers you want to use [providers.openai] api_key = "sk-..." # Or set OPENAI_API_KEY env var [providers.groq] api_key = "gsk_..." # Or set GROQ_API_KEY env var [providers.mistral] api_key = "..." # Or set MISTRAL_API_KEY env var [providers.elevenlabs] api_key = "..." # Or set ELEVENLABS_API_KEY env var ``` **API key resolution order:** 1. `[providers.X]` section in config 2. Legacy `transcription.api_key` (backward compatible) 3. Environment variable (`OPENAI_API_KEY`, `GROQ_API_KEY`, etc.) ### Transcription Providers Hyprvoice supports multiple transcription backends: #### OpenAI Whisper API Cloud-based transcription using OpenAI's Whisper API: ```toml [transcription] provider = "openai" language = "" # Empty for auto-detect, or "en", "es", "fr", etc. model = "whisper-1" ``` **Features:** - High-quality transcription - Supports 50+ languages - Auto-detection or specify language for better accuracy #### Groq Whisper API (Transcription) Fast cloud-based transcription using Groq's Whisper API: ```toml [transcription] provider = "groq-transcription" language = "" # Empty for auto-detect, or "en", "es", "fr", etc. model = "whisper-large-v3" # Or "whisper-large-v3-turbo" for faster processing ``` **Features:** - Ultra-fast transcription (significantly faster than OpenAI) - Same Whisper model quality - Supports 50+ languages - Free tier available with generous limits #### Groq Translation API Fast translation of audio to English using Groq's Whisper API: ```toml [transcription] provider = "groq-translation" language = "es" # Optional: hint source language for better accuracy model = "whisper-large-v3-turbo" ``` **Features:** - Translates any language audio → English text - Ultra-fast processing - Language field hints at source language (improves accuracy) - Always outputs English regardless of input language ### LLM Post-Processing LLM post-processing is **enabled by default** and significantly improves transcription quality. After transcription, the text is processed by an LLM to: - Remove stutters and repeated words ("I I I want" → "I want") - Add proper punctuation - Fix grammar errors - Remove filler words ("um", "uh", "like", "you know", etc.) #### Basic Configuration ```toml [llm] enabled = true # Disable with false if you want raw transcriptions provider = "openai" # "openai" or "groq" model = "gpt-4o-mini" # OpenAI: "gpt-4o-mini", Groq: "llama-3.3-70b-versatile" ``` #### Post-Processing Options All options are enabled by default. Disable specific ones as needed: ```toml [llm.post_processing] remove_stutters = true # "I I I want" → "I want" add_punctuation = true # Adds periods, commas, etc. fix_grammar = true # Fixes grammatical errors remove_filler_words = true # Removes "um", "uh", "like", "you know" ``` #### Custom Prompts Add custom instructions for specific use cases: ```toml [llm.custom_prompt] enabled = true prompt = "Format as bullet points" ``` **Use cases for custom prompts:** - "Format as bullet points" - for note-taking - "Keep technical terms exactly as spoken" - for programming dictation - "Use formal language" - for professional documents - "Translate to Spanish" - for translation workflows #### LLM Provider Recommendations | Provider | Model | Best For | | -------- | ----- | -------- | | OpenAI | gpt-4o-mini | Best quality/cost balance (default) | | Groq | llama-3.3-70b-versatile | Fastest processing, free tier | Both providers use the same API key as transcription if you're using OpenAI or Groq for transcription. ### Keywords Keywords help both transcription and LLM understand domain-specific terms, names, and technical vocabulary: ```toml keywords = ["Hyprland", "Wayland", "PipeWire", "Claude", "TypeScript"] ``` **How keywords work:** - **Transcription**: Passed as initial_prompt to Whisper, improving recognition of these terms - **LLM**: Included in the system prompt to ensure correct spelling **When to use keywords:** - Names of people, companies, or products - Technical terminology specific to your field - Acronyms or abbreviations - Words commonly misheard by speech-to-text ### Example Configurations #### Fast Transcription Only (No LLM) ```toml [providers.groq] api_key = "gsk_..." [transcription] provider = "groq-transcription" model = "whisper-large-v3-turbo" [llm] enabled = false ``` #### High Quality with OpenAI (Default) ```toml [providers.openai] api_key = "sk-..." [transcription] provider = "openai" model = "whisper-1" [llm] enabled = true provider = "openai" model = "gpt-4o-mini" ``` #### Budget-Friendly with Groq ```toml [providers.groq] api_key = "gsk_..." [transcription] provider = "groq-transcription" model = "whisper-large-v3-turbo" [llm] enabled = true provider = "groq" model = "llama-3.3-70b-versatile" ``` #### Mixed Providers (Groq Transcription + OpenAI LLM) ```toml [providers.openai] api_key = "sk-..." [providers.groq] api_key = "gsk_..." [transcription] provider = "groq-transcription" model = "whisper-large-v3-turbo" [llm] enabled = true provider = "openai" model = "gpt-4o-mini" ``` ### Migration from Old Config Format If you're upgrading from an older version with `transcription.api_key`: **Old format (still works):** ```toml [transcription] provider = "openai" api_key = "sk-..." # Legacy location model = "whisper-1" ``` **New format (recommended):** ```toml [providers.openai] api_key = "sk-..." # Unified location [transcription] provider = "openai" model = "whisper-1" [llm] enabled = true provider = "openai" model = "gpt-4o-mini" ``` Run `hyprvoice configure` to interactively update your config to the new format. #### whisper.cpp Local (Planned) -> Not yet implemented Private, offline transcription using local models: ```toml [transcription] provider = "whisper_cpp" model_path = "~/models/ggml-base.en.bin" threads = 4 ``` #### Recording Configuration Audio capture settings: ```toml [recording] sample_rate = 16000 # Audio sample rate in Hz channels = 1 # Number of audio channels (1 for mono) format = "s16" # Audio format (s16 recommended) buffer_size = 8192 # Internal buffer size in bytes device = "" # PipeWire device (empty for default) channel_buffer_size = 30 # Audio frame buffer size timeout = "5m" # Maximum recording duration (prevents runaway recordings) ``` **Recording Timeout:** - Prevents accidental long recordings that could consume resources - Default: 5 minutes (`"5m"`) - Format: Go duration strings like `"30s"`, `"2m"`, `"10m"` - Recording automatically stops when timeout is reached #### Text Injection Configurable text injection with multiple backends: ```toml [injection] backends = ["ydotool", "wtype", "clipboard"] # Ordered fallback chain ydotool_timeout = "5s" wtype_timeout = "5s" clipboard_timeout = "3s" ``` **Injection Backends:** - **`ydotool`**: Uses ydotool (requires `ydotoold` daemon for ydotool v1.0.0+). Most compatible with Chromium/Electron apps. - **`wtype`**: Uses wtype for Wayland. May have issues with some Chromium-based apps (known upstream bug). - **`clipboard`**: Copies text to clipboard only. Most reliable, but requires manual paste. **Fallback Chain:** Backends are tried in order. The first successful one wins. Example configurations: ```toml # Clipboard only (safest, always works) backends = ["clipboard"] # wtype with clipboard fallback backends = ["wtype", "clipboard"] # Full fallback chain (default) - best compatibility backends = ["ydotool", "wtype", "clipboard"] # ydotool only (if you have it set up) backends = ["ydotool"] ``` **ydotool Setup:** ydotool requires the `ydotoold` daemon running (for ydotool v1.0.0+) and access to `/dev/uinput`: ```bash # Start ydotool daemon (systemd) systemctl --user enable --now ydotool # Or add user to input group sudo usermod -aG input $USER # Then logout/login # For Hyprland, add to config to set correct keyboard layout: # device:ydotoold-virtual-device { # kb_layout = us # } ``` **Behavior:** - Backends are tried in order until one succeeds - Include `clipboard` in the chain if you want text copied to clipboard as fallback #### Notifications Desktop notification settings: ```toml [notifications] enabled = true # Enable/disable notifications type = "desktop" # "desktop", "log", or "none" ``` **Notification Types:** - **`desktop`**: Use notify-send for desktop notifications - **`log`**: Log messages to console only - **`none`**: Disable all notifications Always keep `type = "desktop"` unless debugging. ##### Custom Notification Messages You can customize notification text via the `[notifications.messages]` section. ```toml [notifications.messages] [notifications.messages.recording_started] title = "Hyprvoice" body = "Recording Started" [notifications.messages.transcribing] title = "Hyprvoice" body = "Recording Ended... Transcribing" [notifications.messages.llm_processing] title = "Hyprvoice" body = "Processing..." [notifications.messages.config_reloaded] title = "Hyprvoice" body = "Config Reloaded" [notifications.messages.operation_cancelled] title = "Hyprvoice" body = "Operation Cancelled" [notifications.messages.recording_aborted] body = "Recording Aborted" [notifications.messages.injection_aborted] body = "Injection Aborted" ``` ### Configuration Hot-Reloading The daemon automatically watches the config file for changes and applies them immediately: - **Notification settings**: Applied instantly - **Injection settings**: Applied to current and future operations - **Recording/Transcription/LLM settings**: Applied to new recording sessions - **Invalid configs**: Rejected with error notification, daemon continues with previous config ### Service Management The systemd user service is automatically installed with the AUR package: ```bash # Check service status systemctl --user status hyprvoice.service # Start/stop service systemctl --user start hyprvoice.service systemctl --user stop hyprvoice.service # Enable/disable autostart systemctl --user enable hyprvoice.service systemctl --user disable hyprvoice.service # View logs journalctl --user -u hyprvoice.service -f ``` ### File Locations - **Socket**: `~/.cache/hyprvoice/control.sock` - IPC communication - **PID file**: `~/.cache/hyprvoice/hyprvoice.pid` - Process tracking - **Config**: `~/.config/hyprvoice/config.toml` - User settings (planned) ## Development Status | Component | Status | Notes | | ---------------------- | ------ | ----------------------------------------------------- | | Core daemon & IPC | ✅ | Unix socket control plane | | Recording workflow | ✅ | Toggle recording via PipeWire | | Audio capture | ✅ | Efficient PipeWire integration | | Desktop notifications | ✅ | Status feedback via notify-send | | OpenAI transcription | ✅ | HTTP API integration | | Groq transcription | ✅ | Fast Whisper API with transcription and translation | | Mistral transcription | ✅ | Voxtral API for European languages | | ElevenLabs transcription| ✅ | Scribe API with 99 language support | | LLM post-processing | ✅ | OpenAI/Groq text cleanup (enabled by default) | | Text injection | ✅ | Clipboard + wtype/ydotool with fallback | | Configuration system | ✅ | TOML-based user settings with hot-reload | | Interactive TUI setup | ✅ | `hyprvoice configure` wizard with section editing | | Unit test coverage | ✅ | Comprehensive test suite (100% pass) | | CI/CD Pipeline | ✅ | Automated builds and releases via GitHub Actions | | Installation (AUR etc) | ✅ | AUR package with automated dependency installation | | Light dictation models | ⏳ | Alternatives to whispers for light and fast dictation | | whisper.cpp support | ⏳ | Local model inference | **Legend**: ✅ Complete · ⏳ Planned ## Architecture Overview Hyprvoice uses a **daemon + pipeline** architecture for efficient resource management: - **Control Daemon**: Lightweight IPC server managing lifecycle - **Pipeline**: Stateful audio processing (recording → transcribing → processing → injecting) - **State Machine**: `idle → recording → transcribing → processing → injecting → idle` ### System Architecture ```mermaid flowchart LR subgraph Client CLI["CLI/Tool"] end subgraph Daemon D["Control Daemon (lifecycle + IPC)"] end subgraph Pipeline A["Audio Capture"] T["Transcribing"] I["Injecting (wtype + clipboard)"] end N["notify-send/log"] CLI -- unix socket --> D D -- start/stop --> A A -- frames --> T T -- status --> D D -- events --> N D -- inject action --> T T --> I I -->|done| D ``` ```mermaid stateDiagram-v2 [*] --> idle idle --> recording: toggle recording --> transcribing: first_frame transcribing --> processing: llm_enabled transcribing --> injecting: llm_disabled processing --> injecting: inject_action injecting --> idle: done recording --> idle: abort injecting --> idle: abort ``` ### How It Works 1. **Toggle recording** → Pipeline starts, audio capture begins 2. **Audio streaming** → PipeWire frames buffered for transcription 3. **Toggle stop** → Recording ends, transcription starts 4. **LLM processing** → Text cleaned up (if enabled, which is the default) 5. **Text injection** → Result typed or copied to clipboard 6. **Return to idle** → Pipeline cleaned up, ready for next session ### Data Flow 1. `toggle` (daemon) → create pipeline → recording 2. First frame arrives → transcribing (daemon may notify `Transcribing` later) 3. Audio frames → audio buffer (collect all audio during session) 4. Second `toggle` during transcribing → transcribe collected audio 5. If LLM enabled → processing → clean up text with LLM 6. injecting → type or paste text 7. Complete → idle; pipeline stops; daemon clears reference 8. Notifications at key transitions ## Troubleshooting ### Common Issues #### Daemon Issues **Daemon won't start:** ```bash # Check if already running hyprvoice status # Check for stale files ls -la ~/.cache/hyprvoice/ # Clean up and restart rm -f ~/.cache/hyprvoice/hyprvoice.pid rm -f ~/.cache/hyprvoice/control.sock hyprvoice serve ``` **Command not found:** ```bash # Check installation which hyprvoice # Add to PATH if using ~/.local/bin echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc source ~/.bashrc ``` #### Audio Issues **No audio recording:** ```bash # Check PipeWire is running systemctl --user status pipewire # Test microphone pw-record --help pw-record test.wav # Check microphone permissions and levels ``` **Audio device issues:** ```bash # List available audio devices pw-cli list-objects | grep -A5 -B5 Audio # Check microphone is not muted in system settings ``` #### Notification Issues **No desktop notifications:** ```bash # Test notify-send directly notify-send "Test" "This is a test notification" # Install if missing sudo pacman -S libnotify # Arch sudo apt install libnotify-bin # Ubuntu/Debian ``` #### Text Injection Issues **Text not appearing:** - Ensure cursor is in a text field when toggling off recording - Check that `wtype` and `wl-clipboard` tools are installed: ```bash # Test wtype directly wtype "test text" # Test clipboard tools echo "test" | wl-copy wl-paste ``` - Verify Wayland compositor supports text input protocols - Check injection mode in configuration (fallback mode is most robust) **Clipboard issues:** ```bash # Install wl-clipboard if missing sudo pacman -S wl-clipboard # Arch sudo apt install wl-clipboard # Ubuntu/Debian # Test clipboard functionality wl-copy "test text" wl-paste ``` ### Debug Mode ```bash # Run daemon with verbose output hyprvoice serve # Check logs from systemd service (or just see results from hyprvoice serve) journalctl --user -u hyprvoice.service -f # Test individual commands hyprvoice toggle hyprvoice status ``` ## Development ### Building from Source ```bash git clone https://github.com/leonardotrapani/hyprvoice.git cd hyprvoice go mod download go build -o hyprvoice ./cmd/hyprvoice # Install locally mkdir -p ~/.local/bin cp hyprvoice ~/.local/bin/ export PATH="$HOME/.local/bin:$PATH" ``` ## For Maintainers ### Publishing to AUR See [`packaging/RELEASE.md`](packaging/RELEASE.md) for complete release process including AUR deployment. Quick start for AUR: ```bash # After creating your first GitHub release cd packaging/ ./setup-aur.sh # One-time AUR repository setup ``` ### Project Structure ``` hyprvoice/ ├── cmd/hyprvoice/ # CLI application entry point ├── internal/ │ ├── bus/ # IPC (Unix socket) + PID management │ ├── config/ # Configuration loading and validation │ ├── daemon/ # Control daemon (lifecycle management) │ ├── injection/ # Text injection (clipboard + wtype + ydotool) │ ├── llm/ # LLM post-processing adapters (OpenAI, Groq) │ ├── notify/ # Desktop notification integration │ ├── pipeline/ # Audio processing pipeline + state machine │ ├── provider/ # Provider registry and capability detection │ ├── recording/ # PipeWire audio capture │ ├── transcriber/ # Transcription adapters (OpenAI, Groq, Mistral, ElevenLabs) │ └── tui/ # Interactive configuration wizard ├── go.mod # Go module definition └── README.md ``` ### Development Workflow ```bash # Terminal 1: Run daemon with logs go run ./cmd/hyprvoice serve # Terminal 2: Test commands go run ./cmd/hyprvoice toggle go run ./cmd/hyprvoice status go run ./cmd/hyprvoice stop ``` ### IPC Protocol Simple single-character commands over Unix socket: - `t` - Toggle recording on/off - `c` - Cancel current operation - `s` - Get current status - `v` - Get protocol version - `q` - Quit daemon gracefully ## Contributing Contributions welcome! Please: - Follow existing code conventions and patterns - Add tests for new functionality when available - Update documentation for user-facing changes - Test on Hyprland/Wayland before submitting PRs ## License MIT License - see [LICENSE.md](LICENSE.md) for details.