* fix: restore clipboard after ydotool clipboard-paste injection When ydotool is used on Wayland to inject non-ASCII text (Arabic, accented letters, CJK, etc.), the injection path copies the text to the clipboard and simulates Ctrl+V. This permanently overwrites whatever the user had on their clipboard, which is surprising and disruptive — especially for users dictating in languages that always trigger this path. This commit adds two changes: 1. `_read_clipboard()` — reads the current clipboard content using the first available tool (wl-paste, xclip, xsel), returning None if the clipboard is empty or unreadable. 2. `_inject_via_clipboard_paste()` now saves the clipboard before overwriting it and schedules a background thread to restore it 300 ms after the Ctrl+V paste completes. The delay gives the target application time to process the paste before the clipboard content changes. The previous note "there is no safe race-free way to restore" was accurate in the strict sense, but a best-effort restore after a short delay is far better than never restoring, and covers all practical use cases (the user is not expected to trigger another paste within 300 ms of a dictation commit). Fixes the UX issue where every dictation commit silently destroyed the user's clipboard content. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * tests: add unit tests for clipboard save/restore (PR #588) 15 tests covering _read_clipboard() (tool fallback chain, error handling, UTF-8 decoding) and the restore behaviour in _inject_via_clipboard_paste (content restored after delay, no restore on empty clipboard or paste failure, daemon thread assertion). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * style: apply ruff formatting to test_clipboard_restore.py Fixes CI lint failure — ruff format requires line length ≤ 88 chars. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * style: apply black formatting to fix CI lint check ruff format and black disagree on some style choices; the CI runs black. No logic changes. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * fix: use text=True in _read_clipboard and respect copy_to_clipboard setting Two issues: 1. _read_clipboard() called subprocess.run without text=True, meaning stdout was bytes and .decode() was called. Existing tests that mock subprocess.run return stdout="" (str), causing AttributeError on .decode(). Switch to text=True + encoding/errors params so stdout is always a str and no .decode() is needed. 2. When the user has copy_to_clipboard=true, inject_text() copies the dictated text to clipboard after injection. The restore thread in _inject_via_clipboard_paste was firing 300ms later and overwriting that with the old content, silently breaking the setting. Skip the restore when _should_copy_to_clipboard() returns True. Tests: adds test_no_restore_when_copy_to_clipboard_setting_enabled, updates all _read_clipboard mock stdout to str (was bytes). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * fix: Python 3.9 compat and cover restore-failure branch Use Optional[str] instead of str | None for _read_clipboard return type so the code runs on Python 3.9 (PEP 604 union syntax requires 3.10+). Add test_restore_failure_is_handled_gracefully to exercise the else-branch inside the _restore() closure, covering the two previously uncovered lines reported by Codecov. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * fix: restore clipboard on paste failure and handle empty clipboard Fix 1 (comment #1): when Ctrl+V simulation fails the clipboard was already overwritten with dictated text but the original content was silently lost. Now restore it immediately in the except block before returning False. Fix 3 (comment #3): wl-paste and xclip signal an empty clipboard with a non-zero exit code, causing _read_clipboard() to return None and skip the restore — leaving dictated text in the clipboard forever. Detect the "Nothing is copied" / "target STRING not available" stderr patterns and return "" so the restore path can clear the clipboard back to empty. Tests added for both behaviours; existing test updated to assert the correct post-fix behaviour for paste failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * test: cover paste-failure branch when clipboard is unreadable Add test for the case where Ctrl+V fails and previous_clipboard is None (clipboard was unreadable), ensuring the restore is correctly skipped. Closes the 1298->1300 branch gap reported by Codecov. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * test: cover wl-paste fallback on X11 (XWayland) path Add a test for the `not host_is_wayland and shutil.which("wl-paste")` branch in _read_clipboard(), which was showing as yellow/red on Codecov. Simulates an X11 session with wl-paste available (XWayland environments). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * fix: proper clipboard clear and race guard on restore Fix 5 (comment #5): add _clear_clipboard() that uses each backend's native clear command (wl-copy --clear, xsel --clear, xclip with empty input) instead of passing "" to _copy_to_clipboard(). Called when previous_clipboard is "" to truly clear rather than set an empty offer. Fix 6 (comment #6): before restoring, read the clipboard inside the _restore() thread and skip if it no longer holds the injected text — protecting against the user manually copying something else during the 300ms window. Tests added for _clear_clipboard() per backend and for both new restore-path behaviours. Existing restore tests updated to use side_effect lists since _read_clipboard() is now called twice. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz * test: cover remaining _clear_clipboard() branches for Codecov Add tests for: - xclip path in _clear_clipboard() (line 931) - health-check skip (line 911) - exception fallthrough to next tool (line 947) - _clear_clipboard() called immediately when paste fails with empty previous clipboard (line 1354) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
Linux has always punched above its weight, except when it comes to voice typing. Vocalinux fixes that.
It's a free, GPLv3-licensed desktop app that lets you dictate text into any application, on X11 or Wayland, using fully offline speech recognition. Pick from three engines (whisper.cpp, OpenAI Whisper, or VOSK), get automatic GPU acceleration via Vulkan, and control it all with customizable keyboard shortcuts: toggle or push-to-talk.
No internet required. No data leaves your machine. Just speak and type.
📚 What's New in v0.15.0
0.15.0 adds searchable sidebar settings, AppImage packages, a much larger speech-language catalog (including Hungarian), cleaner continuous dictation (capitalization + trailing spaces), power-saving model unload, smarter Vulkan GPU selection, and Wayland IBus improvements on top of the 0.14 packaging line.
Highlights
| Feature | Description |
|---|---|
| Searchable settings | Sidebar navigation with search replaces the seven-tab notebook (#601) |
| AppImage | Self-contained x86_64 and aarch64 builds on GitHub Releases (#573, #602) |
| More languages | ~33 selectable speech languages (plus Auto-detect), including Hungarian; VOSK only lists languages with official models (#616, fixes #565) |
| Dictation polish | Auto-capitalize after sentence punctuation; trailing space so the next utterance does not glue on (#554, #608) |
| Auto-pause + keep-alive | Pause/unload while configured apps run; unload after idle timeout (#592) |
| Vulkan GPU selection | Prefer discrete GPUs automatically; pick a device in Advanced settings (#590) |
| ibus-wayland | Use IBus on “unbridged” compositors when ibus-wayland is running (#614) |
Also in this release
- Settings: dictation status / mic level / Test Dictation / Close live in the sidebar footer (#618)
- Settings: Custom Shortcut Record/Set controls show again (#619)
- Languages: English (India) maps to Whisper code
en(#617) - CLI
--version(#563) - Bluetooth mic probing no longer corrupts the heap / crashes on SCO capture devices (#599)
- IBus engine teardown when parent destroy fails (#613)
- Settings info notices flattened to match the rest of the dialog (#615)
- KDE Plasma Wayland unbridged-IBus skip when ibus-wayland is absent (#577)
- xdotool focus preserve; installer / uninstall / AUR reliability fixes (#564, #583, #569, #597, #579, #586)
See docs/UPDATE.md and the full changelog.
Features
- 🎤 Toggle or Push-to-Talk activation modes
- ⚡ Real-time transcription with minimal latency
- 🌎 Universal compatibility across all Linux applications
- 🔒 100% Offline operation for privacy and reliability
- 🤖 whisper.cpp by default - High-performance C++ speech recognition
- 🎮 Universal GPU support - Vulkan acceleration for AMD, Intel, and NVIDIA
- 🎨 System tray integration with visual status indicators
- 🚀 Start on login support via XDG autostart (desktop-session startup)
- 🔊 Pleasant audio feedback - smooth gliding tones, headphone-friendly
- ⚙️ Graphical settings dialog for easy configuration
- 📦 3 engine choices - whisper.cpp (default), OpenAI Whisper, or VOSK
📸 Screenshots
Vocalinux in action. Settings gallery shots may lag the searchable sidebar UI from v0.15.0 — full gallery on the website screenshots page.
Product
![]() Real-time voice-to-text transcription |
![]() System tray with listening indicator |
![]() About & Updates in Settings |
![]() Log viewer for debugging |
Settings
![]() Speech Engine |
![]() Recognition |
![]() Audio |
![]() Performance |
![]() General |
![]() Advanced |
🚀 Quick Install
Interactive Install (Recommended)
Our new interactive installer guides you through setup with intelligent hardware detection:
curl -fsSL raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh
Choose your engine:
- whisper.cpp ⭐ (Recommended) - Fast, works with any GPU via Vulkan
- Whisper (OpenAI) - PyTorch-based, NVIDIA GPU only
- VOSK - Lightweight, works on older systems
The installer will:
- Auto-detect your hardware (GPU, RAM, Vulkan support)
- Recommend the best engine for your system
- Download the appropriate model (~74MB for the default whisper.cpp tiny model)
- Install neural VAD support when ONNX Runtime is available
- Install in ~1-2 minutes (vs 5-10 min with old Whisper)
Note
: Always installs the latest release. For a specific version, check GitHub Releases.
Installation Options
Default (whisper.cpp - recommended):
curl -fsSL raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh
Fastest installation (~1-2 min), universal GPU support via Vulkan.
Whisper (OpenAI) - if you prefer PyTorch:
curl -fsSL raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --engine=whisper
NVIDIA GPU only (~5-10 min, downloads PyTorch + CUDA).
VOSK only - for low-RAM systems:
curl -fsSL raw.githubusercontent.com/jatinkrmalik/vocalinux/main/install.sh -o /tmp/vl.sh && bash /tmp/vl.sh --engine=vosk
Lightweight option (~40MB), works on systems with 4GB RAM.
Arch Linux (AUR)
yay -S vocalinux
See docs/AUR.md.
Flatpak (any distro)
For a sandboxed, distro-independent install (great for NixOS, Fedora Silverblue, Steam Deck, and anywhere else), build the Flatpak from the bundled manifest:
flatpak install flathub org.gnome.Platform//50 org.gnome.Sdk//50
flatpak-builder --user --install --force-clean build-dir \
packaging/flatpak/com.vocalinux.Vocalinux.yml
flatpak run com.vocalinux.Vocalinux
The Flatpak ships the whisper.cpp engine with Vulkan GPU support and runs through
XWayland on Wayland sessions. See packaging/flatpak/README.md
for build details, permissions, and Flathub submission notes. Flathub publishing
is in progress.
Alternative: Install from Source
# Clone the repository
git clone https://github.com/jatinkrmalik/vocalinux.git
cd vocalinux
# Run the installer (will prompt for Whisper)
./install.sh
# Or with Whisper support
./install.sh --with-whisper
The installer handles everything: system dependencies, Python environment, speech models, and desktop integration.
🌙 Nightly Releases (Bleeding Edge)
For developers and early adopters who want to test the latest features, check out our GitHub Releases page which includes both beta and nightly builds.
⚠️ Warning: Nightly releases contain the absolute latest code and may be unstable. For production use, we recommend using the latest beta release.
Nightly builds are automatically generated from the main branch every day. They include all merged changes but haven't undergone the same testing as beta releases.
Release Channels:
- Beta (Recommended) - Tested pre-releases with known features
- Nightly - Untested bleeding edge with latest commits
After Installation
# If ~/.local/bin is in your PATH (recommended):
vocalinux
# Or activate the virtual environment first:
source ~/.local/bin/activate-vocalinux.sh
vocalinux
# Or run directly:
~/.local/share/vocalinux/venv/bin/vocalinux
Or launch it from your application menu!
📋 Requirements
- OS: Linux (tested on Ubuntu 22.04+, Debian 11+, Fedora 39+, Arch Linux, openSUSE Tumbleweed)
- Python: 3.9 or newer
- Display: X11 or Wayland
- Hardware: Microphone for voice input
Note: See Distribution Compatibility for distribution-specific information and experimental support for Gentoo, Alpine, Void, Solus, and more.
🎙️ Usage
Voice Dictation
- Toggle mode: Double-tap the shortcut key (default Ctrl) to start recording
- Speak clearly into your microphone
- Toggle mode: Double-tap again (or pause speaking) to stop, or Push-to-Talk mode: release the key to stop
Voice Commands
| Command | Action |
|---|---|
| "new line" | Inserts a line break |
| "period" / "full stop" | Types a period (.) |
| "comma" | Types a comma (,) |
| "question mark" | Types a question mark (?) |
| "exclamation mark" | Types an exclamation mark (!) |
| "delete that" | Deletes the last sentence |
| "capitalize" | Capitalizes the next word |
Command Line Options
vocalinux --help # Show all options
vocalinux --debug # Enable debug logging
vocalinux --engine whisper_cpp # Use whisper.cpp engine (default)
vocalinux --engine whisper # Use OpenAI Whisper engine
vocalinux --engine vosk # Use VOSK engine
vocalinux --model medium # Use medium-sized model
vocalinux --model medium.en-q5_0 # Use exact whisper.cpp model variant
vocalinux --model large-v3-turbo # Use large-v3 Turbo with whisper.cpp
vocalinux --wayland # Force Wayland mode
vocalinux --start-minimized # Start without first-run modal prompts
Autostart on Login
Vocalinux uses the Linux desktop standard for autostart:
- Mechanism: XDG autostart desktop entry (
vocalinux.desktop) - Path:
$XDG_CONFIG_HOME/autostart/or~/.config/autostart/(fallback) - Launch mode: Starts as a regular user desktop app in your graphical session
- Not used: No
systemdunit/service is created by Vocalinux for autostart
How to enable/disable:
- First-run welcome dialog
- Tray menu: Start on Login
- Settings dialog: Start on Login
Compatibility notes:
- Works on mainstream desktop environments (GNOME, KDE, Xfce, Cinnamon, MATE, LXQt)
- On minimal/custom window-manager sessions, an autostart handler may be required
(for example DE-specific startup hooks or tools like
dex)
⚙️ Configuration
Configuration is stored in ~/.config/vocalinux/config.json:
{
"speech_recognition": {
"engine": "whisper_cpp",
"model_size": "tiny",
"vad_sensitivity": 3,
"silence_timeout": 2.0
}
}
For whisper.cpp, model_size may be a size such as tiny or an exact ggml model ID
such as medium.en-q5_0 or large-v3-turbo. You can also configure this through
the graphical Settings dialog, where whisper.cpp models are split into Model Size
and Specialization controls.
Neural Voice Activity Detection
Vocalinux ships with a Silero VAD model and uses it automatically when onnxruntime is available. The official installer attempts to install this support automatically. Without it, recording falls back to the simpler amplitude-threshold VAD.
For manual or PyPI installs, enable neural VAD with:
pip install "vocalinux[vad]"
Restart Vocalinux after install. The Recognition tab in Settings shows which backend is active. The same vad_sensitivity (1-5) works for both -- it's mapped to a Silero probability threshold internally (1 = 0.8, 5 = 0.3).
🔧 Development Setup
# Clone and install in dev mode
git clone https://github.com/jatinkrmalik/vocalinux.git
cd vocalinux
./install.sh --dev
# Activate environment
source venv/bin/activate
# Run tests
pytest
# Run from source with debug
python -m vocalinux.main --debug
📁 Project Structure
vocalinux/
├── src/vocalinux/ # Main application code
│ ├── speech_recognition/ # Speech recognition engines (VOSK, Whisper, whisper.cpp)
│ │ └── recognition_manager.py # Unified engine interface
│ ├── text_injection/ # Text injection (X11/Wayland)
│ ├── ui/ # GTK UI components
│ └── utils/ # Utility functions
│ ├── whispercpp_model_info.py # whisper.cpp model metadata & hardware detection
│ └── vosk_model_info.py # VOSK model metadata
├── tests/ # Test suite
├── scripts/ # Development utilities
│ └── generate_sounds.py # Sound generation script
├── resources/ # Icons and sounds
├── docs/ # Documentation
└── web/ # Website source
📖 Documentation
- Installation Guide - Detailed installation instructions
- Update Guide - How to update Vocalinux
- User Guide - Complete user documentation
- Distribution Compatibility - Distro/session behavior and caveats
- Contributing - Development setup and contribution guidelines
Repository mirrors
GitHub is the primary forge for issues, pull requests, CI, and releases.
| Role | URL |
|---|---|
| Primary | https://github.com/jatinkrmalik/vocalinux |
| Read-only mirror (Codeberg) | https://codeberg.org/jatinkrmalik/vocalinux |
The Codeberg copy is a read-only source backup. Open issues and PRs on GitHub only.
🔊 Sound Customization
Vocalinux uses smooth, pleasant gliding tones for audio feedback:
- Start: Ascending F4→A4 (0.6s) - positive, uplifting
- Stop: Descending A4→F4 (0.6s) - resolves completion
- Error: Lower descending E4→C4 (0.7s) - gentle but noticeable
All sounds use pure sine waves with smoothstep interpolation for buttery smooth pitch transitions - perfect for headphone use!
Regenerate Sounds
To modify or regenerate the notification sounds:
python scripts/generate_sounds.py
This script generates all three sounds using the same smooth glide algorithm. You can edit the frequencies, durations, and amplitudes in the script to customize the sounds to your preference.
🗺️ Roadmap
Custom icon design✅Graphical settings dialog✅Whisper AI support✅Multi-language support (FR, DE, RU)✅whisper.cpp integration (default engine)✅Vulkan GPU support✅- In-app update mechanism ✅
Wayland support via IBus✅Flatpak packaging✅ (Flathub submission in progress)- Application-specific commands
- Debian/Ubuntu package (.deb)
- Voice command customization
🌐 The Voca Ecosystem
Vocalinux is part of a family of privacy-first, offline voice dictation tools. Same mission, every operating system.
| Platform | Project | Website | GitHub | Status |
|---|---|---|---|---|
| 🐧 Linux | VocaLinux | vocalinux.com | jatinkrmalik/vocalinux | ✅ Stable v0.15.0 |
| 🍎 macOS | VocaMac | vocamac.com | jatinkrmalik/vocamac | 🚀 Beta |
| 🪟 Windows | VocaWin | vocawin.com | jatinkrmalik/vocawin | 📋 Planned |
Each platform uses native technologies for the best possible integration, while sharing the same privacy-first philosophy and offline-only architecture.
🤝 Contributing
We welcome contributions! Whether it's bug reports, feature requests, or code contributions, please check out our Contributing Guide.
Contributors
Thanks to everyone who has contributed to Vocalinux! 🙌
Quick Links
⭐ Support
If you find Vocalinux useful, please consider:
- ⭐ Starring this repository
- 🐛 Reporting bugs you encounter
- 📖 Improving documentation
- 🔀 Contributing code
📜 License
This project is licensed under the GNU General Public License v3.0 - see the LICENSE file for details.
Star Chart
Made with ❤️ for the Linux community









