Vocalinux

vocalinux.com
Vocalinux

Offline system-wide voice dictation for Linux (X11 and Wayland). Uses local models (whisper.cpp, Whisper, or VOSK) so microphone audio never leaves the device. Tray app with hotkeys; GPLv3. Currently beta software.

Open Source

Vocalinux Source Code

Author

VocaHQ

Description

Free, open-source, 100% offline voice dictation for Linux. Speak and type anywhere via whisper.cpp, Whisper & VOSK engines, GPU-accelerated, works on X11 + Wayland!

#accessibility#dictation#gpu-acceleration#linux#offline-first#privacy-first#python#speech-recognition#speech-to-text#voice#voice-typing#vosk#wayland#whisper#whisper-cpp

Homepage

https://vocalinux.com/

Repository

  • LicenseAGPL-3.0
  • Created12 Apr 25
  • Primary languagePython
  • Size26,762 KB
  • Stars732
  • Forks80
  • Watchers732

Language Usage

Language Usage

Project Health

  • Last commit7 days ago
  • Open issues48
  • Latest releasev0.15.0

Recent Commits

  • Jatin K Malik(08 Aug 26)

    chore(license): migrate from GPL-3.0 to AGPL-3.0 (#660) Align Vocalinux with the other VocaHQ distribution projects (VocaMac, VocaPhone, VocaServer) under AGPL-3.0.

  • Jatin K Malik(08 Aug 26)

    feat(shortcuts): default to hold Right Alt (push-to-talk) for new installs (#648) * feat(shortcuts): default to hold Right Alt push-to-talk on first install Double-tap Ctrl conflicts with many desktop and app shortcuts, so new installs now use hold Right Alt (Option) in push-to-talk mode. Existing config.json values are preserved unchanged. Updates README, user guide, AGENTS.md, first-run dialog, website copy, and tests to match the new defaults. * refactor(shortcuts): trim redundant default-change test and comments Drop the fresh-install assertion that duplicated DEFAULT_CONFIG coverage, remove the duplicated Right-Alt rationale from config_manager, and drop a vacuous source-string check from the settings shortcuts test. * fix(shortcuts): align install.sh seeds and preserve legacy toggle mode install.sh still wrote ctrl+ctrl without mode, so merges with the new push_to_talk default became hold-Ctrl. Seed right_alt + push_to_talk for new installs, and migrate existing shortcut-only configs to mode=toggle so prior behavior is unchanged. * style(config): fix black formatting after shortcuts migration * fix(shortcuts): preserve legacy defaults when shortcuts section is missing Existing config files without a shortcuts section previously inherited ctrl+ctrl toggle from DEFAULT_CONFIG. Pin those historical defaults on upgrade so the new first-install Right Alt push-to-talk defaults do not silently change behavior. * docs(agents): unwrap hard-broken dictation control paragraph Keep the AGENTS.md paragraph as a single line like the surrounding prose.

  • Jatin K Malik(08 Aug 26)

    feat(updates): notify when a newer GitHub release is available (#645) * feat(updates): notify when a newer GitHub release is available Check the configured release channel shortly after startup and every six hours. When an update exists, show a tray menu item and a green New badge on Settings → About, plus one desktop notification per version, so users can open release notes and update themselves. Co-authored-by: Jatin K Malik <[email protected]> * refactor(updates): slim UpdateMonitor after ponytail review Drop unused APIs (tick/check_now/version injector/last_* state), integer-only GLib scheduling, and the local demo launcher. Keep tray notify-send local to avoid coupling to recognition_manager. Co-authored-by: Jatin K Malik <[email protected]> * fix(updates): address Bugbot and harden notify persistence Persist last_notified_version only after notify-send spawns successfully, and sync the tray Update Available item from About-page checks without re-notifying. Also merge latest main and cover the new paths in tests. Co-authored-by: Jatin K Malik <[email protected]> * test(tray): pin config mock for update-notification assertions CI on 3.9/3.10 showed empty MagicMock set call history after a successful notify-send spawn. Pin tray.config_manager to the test mock and record set() via side_effect so persistence checks are reliable across Python versions. Co-authored-by: Jatin K Malik <[email protected]> * fix(updates): clear pending About update on failed lookup A failed About check hid the New badge but left _pending_update set, so clearing settings search could revive the badge while status still showed the failed lookup. Clear dialog pending state on failure (tray unchanged) and restore the badge via _set_about_update_badge. * fix(updates): ignore stale UpdateMonitor results after channel switch Bind the channel for each background check and discard the callback when the configured channel changed mid-flight, then re-check immediately — matching About-page stale-channel handling so the tray cannot show the wrong channel's update state. --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(06 Aug 26)

    fix(clipboard): text-only reads and safe overlapping restore (#646) * fix(clipboard): text-only reads and safe overlapping restore Force wl-paste/xclip to request text MIME types so image/file clipboards are not decoded and "restored" as corrupted text. Stop treating xclip's "target not available" as an empty clipboard. Coordinate overlapping ydotool pastes with a generation counter and a pending restore target so a second paste within the 300ms window restores the original pre-injection content, not intermediate text. Co-authored-by: Jatin K Malik <[email protected]> * refactor(clipboard): simplify restore ownership after ponytail review Bump the restore generation only after a successful clipboard overwrite so a failed copy cannot cancel an in-flight restore. Clear pending target on paste failure unconditionally. Drop redundant state reads in the restore thread and the artificial stale-restore unit test. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Mohamed Hashim(06 Aug 26)

    fix: restore clipboard after ydotool clipboard-paste injection (#588) * 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 <[email protected]> * 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Claude-Session: https://claude.ai/code/session_01Qg9NpTr6QaeXEyEBc5fvvz --------- Co-authored-by: Claude Sonnet 4.6 <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(06 Aug 26)

    docs(user-guide): note CUDA device 0 limit for dual NVIDIA (#644) Document that CUDA-backed pywhispercpp always uses device 0, and point to CUDA_VISIBLE_DEVICES or a Vulkan build as workarounds. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Ariel Flesler(06 Aug 26)

    fix(whisper): use CUDA device 0 when pywhispercpp is CUDA-backed (#636) * fix(whisper): use CUDA device 0 when pywhispercpp is CUDA-backed Vulkan GPU enumeration on hybrid laptops lists the iGPU as GPU0 and the dGPU as GPU1, but CUDA ordinals always place the first NVIDIA GPU at 0. Auto mode and the settings UI were passing Vulkan indices into CUDA pywhispercpp, which silently fell back to CPU inference. Detect the actual pywhispercpp backend before selecting gpu_device and map CUDA installs to device 0. Vulkan-backed installs keep Vulkan indices. * test(whisper): cover CUDA gpu_device selection branches for codecov --------- Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(06 Aug 26)

    fix(ibus): restore engine after register_component teardown (#558) (#643) * fix(ibus): restore engine after register_component teardown Stopping the Vocalinux IBus engine process destroys its register_component registration. IBus then runs check_global_engine(), which only searches register_engine_list and treats XML engines like xkb:es::spa as missing, clearing GlobalEngine even when Vocalinux was never selected. Capture a restorable engine before teardown and reselect it afterward so dead keys keep working after quit. Fixes #558 Co-authored-by: Jatin K Malik <[email protected]> * fix(ibus): wait for engine exit before restoring GlobalEngine Adversarial review of #643 found that stop_engine_process() returned right after SIGTERM, so switch_engine() could race ahead of IBus check_global_engine() and still leave No global engine. Wait for the process to exit (SIGKILL fallback), retry engine restore, capture a shutdown fallback during prepare_engine, and re-apply XKB after the post-teardown engine switch. Addresses review of #558 / #643. Co-authored-by: Jatin K Malik <[email protected]> * fix(ibus): prefer live engine over stale warmup on quit Bugbot on #643: stop() restored the warmup-cached engine even when the user had switched input source afterward, and inject_text refused to refresh that cache once set. Prefer the live non-Vocalinux engine at quit; fall back to the cache only when stuck on vocalinux/unavailable. Always refresh the shutdown fallback from inject_text. Also cover SIGKILL teardown escalation and restore-retry failure paths to lift patch coverage. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(04 Aug 26)

    fix(appimage): ship transitive GI typelibs for non-Debian hosts (#637) * fix(appimage): ship transitive GI typelibs for non-Debian hosts The 0.15.0 AppImage pruned girepository down to a partial allowlist and dropped xlib/Dbusmenu/GModule. On Ubuntu the missing typelibs fell back to /usr/lib/x86_64-linux-gnu/girepository-1.0; on openSUSE/Fedora that path does not exist, so Gtk failed to import and startup reported missing GTK3/AppIndicator (#585). Keep linuxdeploy's typelib extras, seed the full transitive set, bundle AppIndicator/Notify shared libs, and smoke-test GI imports with host typelibs hidden. Co-authored-by: Jatin K Malik <[email protected]> * style(test): format appimage packaging regression test Co-authored-by: Jatin K Malik <[email protected]> * fix(ci): avoid libappindicator/ayatana apt conflict Ubuntu packages libappindicator3-1 and libayatana-appindicator3-1 conflict. Rely on gir1.2-* to pull a single implementation and keep libdbusmenu/libnotify explicit for bundling. Co-authored-by: Jatin K Malik <[email protected]> * fix(ci): install gir1.2-dbusmenu-glib for AppImage typelib seed Dbusmenu-0.4 is a required transitive typelib for AppIndicator; without the GIR package the build fails the required-typelib check. Co-authored-by: Jatin K Malik <[email protected]> * fix(appimage): require only one AppIndicator typelib variant Runtime accepts AppIndicator3 or AyatanaAppIndicator3. Treat them as alternates in the AppImage typelib check so single-stack build hosts do not fail require_all. Co-authored-by: Jatin K Malik <[email protected]> * fix(appimage): prefer Ayatana after rebase onto main Align AppImage typelib seeding and GI smoke order with main's Ayatana-first tray preference from #621/#638. Co-authored-by: Jatin K Malik <[email protected]> * fix(appimage): accept lowercase AyatanaAppindicator3 typelib Match tray_indicator.py's three-step fallback so AppImage builds and GI smoke succeed on hosts that only ship AyatanaAppindicator3-0.1. Co-authored-by: Jatin K Malik <[email protected]> * fix(main): accept lowercase AyatanaAppindicator3 in dep check Align check_dependencies with tray_indicator.py and the AppImage GI smoke path so a lowercase-only typelib host does not fail at startup. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(03 Aug 26)

    docs(packaging): prefer Ayatana AppIndicator in Fedora/Arch hints (#638) * docs(packaging): prefer Ayatana AppIndicator in Fedora/Arch hints Follow-up to #621: align docs, AUR depends, package map, dependency check messages, and troubleshooting copy with the Ayatana-first install path so manual installs and AUR users get a working KDE tray icon. Co-authored-by: Jatin K Malik <[email protected]> * style(main): black-format Ayatana install hint lines CI black --check failed on the multi-line logger.error calls for the Fedora/Arch package hints. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Kacper Paczos(03 Aug 26)

    Fix missing KDE tray icon by preferring Ayatana AppIndicator (#621) * Fix missing KDE tray icon by preferring Ayatana AppIndicator Fedora and Arch installs pull libappindicator-gtk3, Canonical's original AppIndicator library that hasn't had a real release since ~2013. It loads fine and Indicator.new_with_path()/set_status() run without raising, but it never registers a StatusNotifierItem with KDE's StatusNotifierWatcher, so the tray icon silently never shows up on Plasma. Confirmed on Fedora 44 / Plasma 6.7 by checking RegisteredStatusNotifierItems over D-Bus directly before and after the fix. The actively maintained Ayatana fork registers correctly and is already what the Debian, Ubuntu, openSUSE and Gentoo branches of install.sh prefer. Reorder the gi import in tray_indicator.py to try AyatanaAppIndicator3 first and fall back to the legacy AppIndicator3 typelib only if neither Ayatana variant is available. Update install.sh so Fedora and Arch installs pull the Ayatana package too, via a shared install_preferred_appindicator() helper that falls back to the legacy package if Ayatana isn't in the repos (e.g. RHEL/CentOS without EPEL). * Add tests for the AppIndicator/Ayatana import fallback chain The reordered try/except in tray_indicator.py wasn't exercised by the existing tests: they replace gi.repository with a MagicMock, which auto-creates any attribute on access and so can never raise the ImportError the fallback chain is built to catch. Add a small test class that swaps in a plain object exposing only a chosen subset of AppIndicator symbols, so importing anything else raises a real ImportError, and verify each of the three branches (Ayatana, lowercase Ayatana variant, legacy AppIndicator3) resolves correctly. * Retry Ayatana install even when legacy AppIndicator is already present install_preferred_appindicator() previously skipped the Ayatana install whenever either package was already present, so re-running install.sh on an existing Fedora/Arch setup with libappindicator-gtk3 from a prior install would never pick up Ayatana, leaving the KDE tray broken for exactly the users this fix targets. Now it only short-circuits when Ayatana itself is installed, tries Ayatana first regardless of legacy presence, and only falls back to a warning about the already-installed legacy package if the Ayatana install fails. --------- Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(03 Aug 26)

    Update task list in README Updated README to reflect completed tasks and removed outdated items.

  • Jatin K Malik(03 Aug 26)

    docs(web): refresh screenshots for v0.15 (light/dark) (#630) * docs(web): refresh screenshots for v0.15 sidebar settings UI Recapture product and settings gallery shots against the searchable sidebar settings dialog, keeping the previous asset filenames and exact pixel dimensions with wallpaper padding around each window. * fix(web): correct screenshot semantics after adversarial review Keep the existing transcription and tray images because the replacements did not show their claimed flows. Populate the Logs dialog and unlock Advanced settings so those refreshed images demonstrate the features described by the gallery. * fix(web): retake screenshots as square crops with uniform padding Resize settings dialogs to a near 1:1 window, capture with ~28px wallpaper padding on all sides, open About from the tray menu, and skip redoing the awkward XFCE system-tray asset. * docs(web): retake v0.15 screenshots on GNOME (light/dark) Window-only captures from Ubuntu/GNOME at 960x720 for settings and logs, with matching dark variants under screenshots/dark/. Replace the old Shortcuts shot with Performance to match the sidebar. * feat(web): theme-aware light/dark screenshot switching Serve paired GNOME captures and swap them with the site theme via dark:hidden / hidden dark:block. Replace Shortcuts gallery entry with Performance to match the v0.15 sidebar. * docs(web): refresh screenshots after About moves into Settings Rebase onto main brought the in-app update checker: About is now a sidebar settings page. Recapture light/dark GNOME panels at 960x720 and update gallery copy/metadata accordingly. * docs: use single screenshot source for README and website Point README images at web/public/screenshots so product shots live in one place, drop the unused resources/screenshots tree (including the old demo video and about-updates set), and replace the missing shortcuts shot with performance. * docs(web): refresh light/dark screenshots and product grid Replace the screenshot set with fresh captures for both themes, and lay out the three Product shots in a single equal-height row. * fix(web): align screenshot metadata, sizes, and theme loading Restore dictation copy for the 00-transcription asset, set width/height from the recaptured PNGs, preload only the light next/image twin when priority is set, and eager-load both theme variants by default so dark mode does not flash empty on first toggle. * fix(web): show full settings screenshot without crop Use object-contain so the homepage settings dialog fits the frame instead of object-cover clipping the sidebar.

  • Jatin K Malik(03 Aug 26)

    fix(ci): force-push tags in Codeberg mirror workflow (#633) git push --tags fails when a tag already exists on Codeberg (e.g. v0.15.0 from a prior sync or retag). GitHub is the source of truth for this read-only mirror, so force-push tags to keep them aligned. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(03 Aug 26)

    feat(settings): in-app update checker with stable/nightly channels (#631) * feat(settings): add in-app update checker with stable/nightly channels Move About into the Settings sidebar and check GitHub Releases for updates. Stable uses /releases/latest; Nightly follows nightly-YYYY-MM-DD tags. Remove the standalone About dialog and wire tray About to Settings. Fixes #183 * docs(readme): update About screenshot caption for Settings page * fix(settings): address update-checker review and About polish Discard stale in-flight results when the update channel changes, page through GitHub releases for nightly tags, restore the About logo next to the title, and replace the em dash in the app description. * docs(screenshots): add About update-checker state captures Host PR review screenshots on the branch so GitHub can render them from raw.githubusercontent.com (Cursor artifact URLs 403 in PR bodies). * fix(updates): recognize .devYYYYMMDD nightlies and clear Open style Parse CI nightly versions like 0.14.2.dev20260802+sha when checking for updates, and drop the suggested-action style from Open after a failed GitHub lookup.

  • Jatin K Malik(03 Aug 26)

    fix(audio): filter unsafe virtual devices that crash capture (#629) * fix(audio): filter unsafe virtual devices that crash capture This is a defensive fix for #624 heap corruption seen when PortAudio opens PipeWire pseudo-devices such as DeepFilterNet, default, pipewire, paplay, monitor, null, dummy, or filter-chain sources. The root cause is not fully proven, but avoiding explicit capture from these unsafe virtual rows should keep dictation on real microphones while still allowing System Default through device_index=None. If this still crashes with a physical microphone selected, please confirm with the exact physical device name and audio stack details so the capture path can be narrowed further. Fixes #624 * fix(settings): sync audio device fallback with raw name restore Address Bugbot review on #629: match legacy "(default)" saved names and clear stale config/engine state when a filtered device falls back to System Default. * fix(audio): keep system default capture off pseudo device indices Stop converting System Default to an explicit PortAudio index in the mic test, and fall back to host default capture when every enumerated input is filtered as unsafe. * test(audio): expect system-default fallback on reconnect * test(audio): cover system-default fallback paths for capture

  • Jatin K Malik(03 Aug 26)

    feat(tray): allow disabling missing tray warning dialog (#628) * feat(tray): allow disabling missing tray warning dialog Fixes #620 * test(tray): stabilize missing-watcher opt-out coverage Default the D-Bus ListNames mock to include StatusNotifierWatcher so TrayIndicator construction no longer opens the warning dialog during setUp, and bind the don't-show-again checkbox test to the module Gtk mock. * test(tray): make missing-watcher opt-out tests deterministic

  • Jatin K Malik(03 Aug 26)

    fix(whispercpp): skip unsupported context_params on pywhispercpp 1.4 (#626) * fix(whispercpp): skip unsupported context_params on pywhispercpp 1.4 Fixes #625 * test(whispercpp): cover context_params signature inspection failure

  • Jatin K Malik(03 Aug 26)

    fix(injection): stop typing test during wtype probe (#627) Fixes #622

  • Scrates1(03 Aug 26)

    fix(ibus): require a restorable engine for scoped injection (#623) * fix(ibus): require a restorable engine for scoped injection * fix(ibus): preserve registered XKB restore targets Resolve GNOME XKB sources against registered IBus engine IDs so bare-XKB Wayland sessions keep scoped injection without fabricated restore names. Keep unknown states on the safe fallback path. * test(ibus): cover GNOME restore target failures

  • Jatin K Malik(02 Aug 26)

    chore(release): prepare v0.15.0 (#609) * chore(release): prepare v0.15.0 Bump version across app, web, AUR, and Flatpak metainfo. Document GitHub release-notes rules, refresh README/UPDATE/INSTALL/USER_GUIDE for the 0.15 series (sidebar settings, AppImage, auto-pause/keepalive, Vulkan device selection), and align website product surfaces with shipped features. Co-authored-by: Jatin K Malik <[email protected]> * docs(release): fold latest main into v0.15.0 notes Include dictation capitalization/trailing-space, ibus-wayland injection, IBus teardown, and settings notice polish landed on main after the initial release prep rebase. Co-authored-by: Jatin K Malik <[email protected]> * docs(release): include Bluetooth mic crash fix in v0.15.0 notes Fold #599 (PortAudio Bluetooth SCO probe heap corruption) into README, UPDATE, changelog, AppStream, and release history after rebasing onto main. Co-authored-by: Jatin K Malik <[email protected]> * docs(release): include language catalog expansion in v0.15.0 notes Fold #616 (Hungarian and expanded speech languages, Fixes #565) into README, UPDATE, changelog, AppStream, PRODUCT.md, and release history after rebasing onto latest main. Co-authored-by: Jatin K Malik <[email protected]> * docs(release): fold settings footer and language follow-ups into v0.15.0 Include #618 sidebar dictation footer, #619 Custom Shortcut Record/Set restore, and #617 English (India) Whisper language mapping after rebasing onto latest main. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(30 Jul 26)

    fix(settings): restore custom shortcut Record/Set controls (#619) * fix(settings): restore custom shortcut Record/Set controls Gtk.Widget.show_all() is a no-op while no_show_all is True, so the contextual Custom Shortcut row never appeared after selecting it in the searchable settings UI. Clear the flag before showing and restore it on hide. Co-authored-by: Jatin K Malik <[email protected]> * fix(settings): sync custom row when reverting shortcut separators Selecting Custom Shortcut then a combo group separator restored the preset id but left Record/Set visible. Reuse _sync_shortcut_selection_ui so combo and custom-row visibility stay aligned. Co-authored-by: Jatin K Malik <[email protected]> * fix(settings): clear stale Record/Set hint on separator revert Selecting Custom Shortcut sets a temporary info hint; clicking a combo separator already restored the row visibility but left that hint in place. Refresh the mode description after syncing. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(29 Jul 26)

    feat(settings): move dictation controls into sidebar (#618) Keep the persistent test and close actions accessible without an awkward bottom action row, while using portable sidebar icons.

  • Jatin K Malik(29 Jul 26)

    fix(languages): map English (India) to Whisper code `en` (#617) * fix(languages): map en-in to Whisper code en English (India) used the catalog id as the Whisper language argument, which is invalid. Resolve Whisper/whisper.cpp/remote_api codes via the SUPPORTED_LANGUAGES whisper field so en-us and en-in both become en. Co-authored-by: Jatin K Malik <[email protected]> * test(languages): cover whisper language resolver fallbacks Exercise unknown catalog ids and legacy en-us fallback paths so codecov/patch covers resolve_whisper_language fully. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(29 Jul 26)

    feat(languages): expand speech catalog with Hungarian and more (#616) Expose Hungarian and other high-demand Whisper languages in Settings/CLI, wire official VOSK models where Alphacephei ships them, keep CLI choices derived from SUPPORTED_LANGUAGES, and update the languages marketing page with honest per-engine support. Fixes #565 Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(28 Jul 26)

    fix(audio): stop Bluetooth mic probing from corrupting the heap (#599) * fix(audio): prevent Bluetooth mic probe heap corruption Open PortAudio streams only once when negotiating capture format, and always stop_stream() before close(). Separate channel/rate probing was aborting on Bluetooth SCO devices (e.g. FreeBuds) with malloc corruption. Fixes #567 * refactor(audio): open capture stream once instead of probe-close-reopen Adversarial review of the first fix found it still closed the probe stream and immediately reopened the device — the same rapid open/close pattern blamed for the Bluetooth SCO heap corruption, just fewer cycles. _open_capture_stream() now returns the successfully negotiated stream and all three callers (mic test, recording, reconnect) use it directly, so each capture session performs exactly one PortAudio open. Also drops the dead known_working_rate parameter. * test(audio): cover negotiation fallback paths for codecov patch Add tests for mic-test, recording, and reconnect fallback branches when _open_capture_stream returns no stream, plus Bluetooth settle-on-failure and empty-read reconnect cleanup. Brings patch diff coverage above 80%.

  • Jatin K Malik(28 Jul 26)

    feat(dictation): append trailing space after completed transcription (#608) * feat(dictation): append trailing space after completed transcription Fix cross-session glueing ("Hello.This") for push-to-talk and toggle by appending a trailing space to each injected segment instead of relying on in-session leading-space memory that is cleared on IDLE. Adds an optional Settings toggle (default on) and preserves legacy leading-space behavior when disabled. Newlines from voice commands are left without a trailing space. Fixes #605 Co-authored-by: Jatin K Malik <[email protected]> * fix(dictation): honor trailing-space setting from disk live Read append_trailing_space from config.json on each injection so the Settings toggle applies without restart. main() and TrayIndicator each own a ConfigManager, so an in-memory read missed Settings writes — same pattern as TextInjector.copy_to_clipboard. Co-authored-by: Jatin K Malik <[email protected]> * test(dictation): align capitalize tests with trailing-space default Update auto-capitalize expectations to include the default trailing space, and add edge-case coverage for legacy mode, newlines, empty input, and config-read failures so patch coverage clears 80%. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(28 Jul 26)

    feat: auto-capitalize sentences after punctuation (#554) * feat: auto-capitalize sentences after punctuation Add capitalize_sentences() function that uppercases the first letter of dictated text and after sentence-ending punctuation (., !, ?) followed by whitespace. Preserves URLs, decimals, and abbreviations without trailing space. - Config option text_injection.auto_capitalize (default: true) - Settings toggle in General tab to opt out - 17 tests covering edge cases Fixes #553 * style: black-format auto-capitalize helpers for CI lint Fixes the Lint Python check failure on the auto-capitalize PR. * refactor: make auto-capitalize Vosk-only with clear UI indication - Gate auto-capitalize feature to Vosk engine only (Whisper outputs proper casing) - Update settings tooltip to explain Vosk-only behavior - Update subtitle to show '(Vosk only)' for user clarity * test(main): cover Vosk-only auto-capitalize injection path Add callback wiring tests for enabled/disabled auto-capitalize on Vosk and the Whisper skip path so codecov patch coverage clears the 80% bar. Co-authored-by: Jatin K Malik <[email protected]> * refactor(settings): move Auto-Capitalize to Recognition tab Auto-capitalize is dictation output post-processing, not application lifecycle behavior, so it belongs alongside Voice Commands rather than with autostart/clipboard settings in General. Co-authored-by: Jatin K Malik <[email protected]> --------- Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Jatin K Malik(28 Jul 26)

    fix(ui): flatten settings info notices (#615) Drop the thick left accent strip on .info-box helpers so they read as muted flat surfaces with a thin full border, matching the preference cards instead of looking like callout banners. Co-authored-by: Cursor Agent <[email protected]> Co-authored-by: Jatin K Malik <[email protected]>

  • Benjamin Eisele(28 Jul 26)

    fix(ibus): keep engine teardown correct when parent destroy fails (#613) * fix(ibus): keep engine teardown correct when parent destroy fails `do_destroy` passed `super().do_destroy` to `_handle_engine_destroy`. PyGObject binds that vfunc to the GType rather than to the instance, so calling it with no arguments raises: TypeError: IBus.Object.destroy() takes exactly 1 argument (0 given) This fired on every engine teardown. The exception escaped `_handle_engine_destroy` before its return, so neither `_active_instance = ...` nor `_focus_event.clear()` in the caller ran, leaving a destroyed engine registered as active with focus still set -- the two values `_ibus_ping_status` (#523) reads to decide commit readiness. Two changes: - Bind the parent destroy to the instance, falling back to `IBus.Object.destroy(self)` only when the no-arg call raises TypeError, so this keeps working if PyGObject binds the vfunc correctly later. - Guard the parent destroy call so no failure there can skip teardown. Fixes #606 * refactor(ibus): extract parent-destroy workaround into a testable helper Codecov flagged the new code as uncovered (36% patch, below the 80% target). The whole `if IBUS_AVAILABLE:` branch of `do_destroy` is unreachable from the suite, which only ever exercises that method with IBUS_AVAILABLE False -- the single line it replaced was already uncovered on main for the same reason. Move the workaround into a module-level `_invoke_parent_destroy`, the same pattern `_handle_engine_destroy` already uses to make `do_destroy` testable, and bind it with functools.partial at the call site. Behaviour is unchanged. Coverage of the branch goes from 0 to all but the one-line partial, and three tests now pin the actual contract: pass-through when the binding is correct, retry with the instance on the GType-binding TypeError, and no masking of other exceptions.

Vocalinux Website

Website

Vocalinux: Offline Voice Dictation for Linux

Free and open-source voice dictation for Linux. Convert speech to text with whisper.cpp, VOSK, Remote API servers, Silero VAD, X11, and Wayland.

Redirects

Does not redirect

Security Checks

All 65 security checks passed

Server Details

  • IP Address185.199.110.153
  • Hostnamecdn-185-199-110-153.github.com
  • LocationFrancisco,Indiana,United States of America,NA
  • ISPGitHub Inc.
  • ASNAS54113

Associated Countries

  • USUS

Safety Score

Website marked as safe

100%

Blacklist Check

vocalinux.com was found on 0 blacklists

  • AntiSocial Blacklist
  • Artists Against 419
  • Badbitcoin
  • Bambenek Consulting
  • CERT Polska
  • CoinBlockerLists
  • CRDF
  • CryptoScamDB
  • EtherAddressLookup
  • EtherScamDB
  • Fake Website Buster
  • MetaMask EthPhishing
  • NABP Not Recommended Sites
  • OpenPhish
  • PetScams
  • PhishFeed
  • PhishFort
  • Phishing.Database
  • PhishStats
  • PhishTank
  • Phishunt
  • RPiList Not Serious
  • Scam.Directory
  • SecureReload Phishing List
  • Spam404
  • StopGunScams
  • Suspicious Hosting IP
  • ThreatFox
  • ThreatLog
  • TweetFeed
  • URLhaus
  • ViriBack C2 Tracker

Website Preview

Website preview

Vocalinux Reviews

More Accessibility

⚠️ This section is still a work in progress ⚠️
Check back soon, or help us complete it by submiting a pull request on GitHub.
Or submit an entry here

About the Data: Vocalinux

Change History

Edit Vocalinux Data

You can edit Vocalinux's entry in this section of awesome-privacy.yml by submitting a PR to our GitHub repo.
Note that some of the information shown above has been aggregated from external sources, a list of these can be found data documentation.

Origin Data

Modify Data

API

You can access Vocalinux's data programmatically via our API. Simply make a GET request to:

https://api.awesome-privacy.xyz/v1/services/vocalinux

The REST API is free, no-auth and CORS-enabled. To learn more, view the API Docs or read the API Usage Guide.

Share Vocalinux

Help your friends compare Accessibility, and pick privacy-respecting software and services.
Share Vocalinux and Awesome Privacy with your network!