The fork takes its own name everywhere a user can see it: binaries
(voculinux, voculinux-gui), desktop entry, autostart, tray, D-Bus
namespace (com.voculinux.Voculinux, no alias for the old name),
config/data/cache dirs, single-instance lock, IBus engine identity,
tray icons, resource prefixes, VOCULINUX_* environment names, and the
update-checker user agent. The internal Python module stays vocalinux
and the distribution metadata keeps the upstream URLs as provenance.
On first run a previous Vocalinux install's config.json, custom
dictionary, and portal restore token are copied once into the new
locations (new config always wins, no merge, no write-back); models
re-resolve through the pinned-checksum download path. An offline
naming-sweep test proves no old-name strings remain on user-visible
surfaces outside the allowlist.
Refs #18
* fix(perf): reduce startup and inference overhead
* fix(perf): report effective whisper.cpp fallback settings
* fix(injection): add opt-in X11 paste for long dictations
* perf(vad): lower default silence timeout to 1.2s
* fix(types): resolve mypy errors across application source
* ci: run mypy in the lint job
* ci: include mypy in lint dependency group
* fix(types): narrow evdev timestamp comparison
* refactor(recognition): add SpeechEngine protocol and engine dispatch table
* refactor(settings): move About section and engine tables out of settings_dialog
* docs: add ARCHITECTURE.md and refresh docs index and AGENTS layout
* fix(settings): preserve GTK dialog compatibility after extraction
* style: format touched Python modules
* fix(types): keep local typecheck from syncing dependencies
* test(vad): align main startup expectation with new default
* style: sort engine registry and protocol test imports
* test(settings): follow extracted About builder attribute alias
* test(settings): isolate GTK dialog base in About test
* test(settings): isolate About color scheme build test
* test(settings): restore GTK base after About scheme test
* test(settings): isolate About scheme check from GTK mocks
* test(settings): support extracted dialog mixin methods
* docs: add before/after performance benchmark charts
* fix(injection): type instead of pasting when the clipboard holds non-text data
* fix(injection): send newlines as Return on the xdotool backend
xdotool type maps '\n' to the Linefeed keysym, which GTK targets
silently drop — "new line" produced nothing on X11 even though the
segment reached the injector. Split each chunk on '\n' and press
Return between typed segments so a newline lands as a real keypress
in every app.
Verified against a live GtkTextView on Xvfb: the same "A\nB" input
that previously produced 'AB' now produces 'A\nB'.
Fixes#955
* fix(commands): consume trailing punctuation after mid-utterance commands
"New paragraph." inside a longer utterance substituted correctly but
left the engine's sentence-final period typed after the break
("\\n\\n. Does this work now?"). Text-command substitution now
consumes a run of trailing , . ! ? ; : … when it precedes whitespace
or the end of the segment — the reporter's comma-phrased case
("…, new paragraph, …") stops leaking the stray comma the same way.
A mark glued to following text ("newline.io") is untouched.
Fixes#955
* fix(injection,commands): resume mid-chunk on retry, don't eat produced marks
Greptile review follow-ups on #962:
- A failed Return mid-chunk left 'typed' at the chunk start, so the
retry re-sent text xdotool had already delivered. 'typed' now
advances per typed segment and per Return keypress, so the retry
resumes exactly at the un-typed position — and the clipboard
fallback reports the same progress if retries run out.
- _TRAILING_PUNCTUATION could consume a mark produced by an earlier
command replacement ('hello new line exclamation mark' gave '\\n'
instead of '\\n!'). The eater now only consumes marks before
whitespace; the end-of-utterance case ('hello new line.') is a
pre-pass on the raw segment before any substitution runs.
* style: black-format comment indent and test condition
* fix(commands): strip segment before the end-of-utterance cleanup
Trailing whitespace ('hello new line. ') hid the final mark from the
pre-pass: the raw segment ended in a space, and the stripped segment
left the dot at end-of-string where the mid-utterance eater cannot
touch it. Strip first, then run the cleanup.
Also add the missing type hints on the new test helper.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* fix(commands): keep line breaks from standalone voice commands
Two stacked defects ate the newline "new line" / "new paragraph"
produce when spoken alone:
* _normalize_segment_text stripped all leading whitespace, so the
command's leading newline never reached the injector (a clean
"new line" segment was dropped entirely).
* whisper.cpp transcripts arrive punctuated ("New line."), which
missed the exact-match ladder and left a stray "." after the
substituted break, injected as ". ".
Normalize now strips only spaces/tabs, and the generic path
substitutes on a punctuation-stripped core when the whole segment
is command phrases, so engine edge punctuation cannot leak while
real text keeps it.
Fixes#955
* fix(injection): deliver newline-only segments to the focused app
inject_text treated whitespace-only text as empty and returned early,
so the "\n" a standalone "new line" now correctly produces was
discarded before any backend ran ("Empty text provided, skipping
injection"). Only a truly empty segment is skipped; whitespace-only
input is typed/pasted verbatim (xdotool types "\n" as Return).
Space/tab-only segments never reach this point: segment normalization
strips them upstream.
Fixes#955
* fix(injection): keep skipping space/tab-only segments
The previous commit let every whitespace-only string through, which
also delivered bare spaces and tabs a caller never wants typed.
Narrow the skip to text with no content outside space/tab so "\n"
still reaches the backend while " " stays a no-op (the pre-existing
test_empty_text_injection contract).
Fixes#955
* fix(injection): deliver newline segments over IBus too
IBusTextInjector.inject_text had the same whitespace guard and dropped
"\n" before it reached the engine socket; narrow it the same way so
a newline-only segment is sent while space/tab-only stays a no-op.
Also let capitalize_sentences uppercase the first letter after leading
whitespace, so Vosk + auto_capitalize keeps working on "\n hello".
Covers Greptile's findings: real-socket test for the IBus path, the
capitalize regression via the main() callback seam, and type hints on
the new injector tests.
Fixes#955
* test(injection): add type hints to new IBus socket test
AGENTS.md requires hints on every signature; the Greptile review
flagged the new test for missing them.
---------
Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com>
* feat(commands): localize punctuation voice commands by language
Voice command phrases were English-only even when recognition language
was set to Italian, French, etc. Merge locale punctuation/line-break
aliases from the recognition language into CommandProcessor, keep
English phrases available, and document the behavior.
Fixes#640
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* fix(commands): keep line breaks and French apostrophe aliases
Attach-left punctuation now eats only horizontal whitespace, so a
localized "new line" followed by a period or comma still inserts a
newline. French aliases include ASCII, U+2019, and backtick apostrophes,
and longest-match still wins so "point" cannot fire inside
"point d'interrogation".
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* 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 <jatinkrmalik@users.noreply.github.com>
* 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 <jatinkrmalik@users.noreply.github.com>
---------
Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com>
* chore: bump minimum Python from 3.8 to 3.9 and adopt PEP 585 builtins
- Update requires-python, classifiers, mypy target, and black target in pyproject.toml
- Replace Python 3.8 with 3.9 in CI matrix and nightly workflow
- Replace typing.List/Dict/Tuple/Set with builtin list/dict/tuple/set (PEP 585)
- Update all 3.8 version references in README, docs, website, and install.sh
- Fix test_wayland_no_tools to mock IBus availability
- Remove List from common_types re-exports and update test
* style: reformat test files for Black 26.x and pin version in CI
Black 26.x introduced parenthesized context manager formatting.
Reformat 7 test files and pin Black>=26,<27 in CI to prevent
future drift from unpinned version upgrades.
The command processor was adding a leading space when processing action
commands with text after them (e.g., 'delete that text' -> ' text').
This was causing the first speech to always have an unwanted leading space.
Changes:
- Modified command_processor.py to only add space when there's text BEFORE
the action command, not at the beginning of the transcription
- Updated test cases to reflect the correct behavior without leading spaces
Fixes leading space issue on first speech.
* feat: prepare for v0.2.0-alpha release
* feat: prepare for v0.2.0-alpha release
* chore: remove CHANGELOG.md, use GitHub Releases instead
* refactor: use existing About dialog instead of Settings section
* style: enhance About dialog UI readability
* style: fix formatting with black and isort
* feat: show VAD and silence timeout settings for Whisper engine
- Renamed 'VOSK Settings' to 'Recognition Settings'
- VAD Sensitivity and Silence Timeout now apply to both engines
- Added tooltips explaining each setting
- VOSK-specific model info only shown when VOSK selected
* fix: resolve overlapping UI elements in settings dialog
* fix: save VAD and silence timeout settings for both engines
Previously these settings were only saved when VOSK was selected.
Now they persist correctly for Whisper as well.
* fix: show download progress when selecting undownloaded model
Previously when auto-applying settings, models would download silently
in the background without showing the progress dialog. Now:
- Download progress dialog appears for undownloaded models
- Model icons update correctly after download completes
* feat: add download progress, speed, ETA and cancel button
- Show real download progress percentage for VOSK models
- Display download speed (MB/s) and estimated time remaining
- Add Cancel button to abort downloads
- Update status during extraction phase
- Whisper downloads still pulse (uses internal downloader)
* fix: defer model download until needed, don't block startup
- App now starts instantly even if models aren't downloaded
- Models are downloaded on-demand when selected in Settings
- If user tries to dictate without a model, plays error sound
- Added model_ready property to check if engine is usable
* fix: improve first-run UX without downloaded models
- Settings dialog now defaults to smallest available model, not saved config
- Only show saved model if it's actually downloaded
- Add desktop notification when trying to dictate without a model
- Prevents auto-downloading large models on first launch
* feat: add download progress tracking for Whisper models
- Implement custom Whisper model download with progress/speed/ETA
- Uses OpenAI's CDN URLs directly instead of whisper.load_model()
- Progress callback works the same as VOSK downloads
- Supports cancellation during download
* fix: enable download progress callback for Whisper models
Progress callback was only being set for VOSK downloads, not Whisper.
Now both engines show real-time progress, speed, and ETA.
* fix: update tests for defer_download and config manager mocking
- test_main: Mock ConfigManager to prevent saved config from overriding test values
- test_settings_dialog: Mock ModelDownloadDialog to prevent GTK init errors
- test_speech_recognition: Use proper MockVoskModule class instead of MagicMock, pass defer_download=False for download test
- test_whisper_support: Pass defer_download=False to trigger model loading in test
* fix: resolve test failures for CI
- test_main: Fix ConfigManager patch path to use source module
- test_settings_dialog: Mock _is_vosk_model_downloaded and _is_whisper_model_downloaded to skip download flow, add missing dialog attributes
- test_speech_recognition: Replace recognizer with properly configured mock that returns valid JSON, fixing test isolation issues when running all tests together
* fix: mock _download_whisper_model in whisper test to avoid requests exception handling issue
* fix: add gir1.2-girepository-2.0 dep for PyGObject on Ubuntu 24.04
- Add missing girepository-2.0 package for newer Ubuntu runners
- Use --no-build-isolation for PyGObject to use system packages
* fix: skip PyGObject pip install, use system python3-gi package
- Use --no-deps for editable install to skip PyGObject
- Install other deps explicitly without PyGObject
- Add python3-gi-cairo for complete GTK support
- Removes problematic girepository-2.0 build requirement
* fix: install PyGObject via pip with proper build deps
- Remove python3-gi/python3-gi-cairo (doesn't work with setup-python)
- Install PyGObject from pip after installing build dependencies
- Simplify venv setup without --system-site-packages hack
* Fix GObject
* fix: set GI_TYPELIB_PATH for pip-installed PyGObject to find AppIndicator3
* Implement custom icon for Vocalinux (Closes#10)
* Fix system tray icon display issue
* Temp commit before purging
* refactor: restructure project into proper vocalinux package
* test: fix and improve audio_feedback and tray_indicator tests with proper mocking and isolation
* test: update import paths in test files to use the new package structure
* Remove icon background
* fix: implement robust resource path discovery in audio_feedback module
* fix: improve resource discovery across application for consistent icon and sound loading
* chore: ensure uninstall script is compatible with new resource paths
* Fix black formatting