c461cf51478b5bdebe6ee86a1f95d505aa8f3daa
36
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
b59797f1af |
chore(dev): widen uv range and harden Fedora/AppImage builds (#952)
* chore(deps): allow uv versions below 0.14 * fix(ci): align uv version and generated exports * fix(dev): check Fedora build headers and AppImage GI bundle Fail early with distro-specific native dependency hints before dev sync. Verify the AppImage's PyGObject installation and Python path after linuxdeploy, and report isolated GI import failures with actionable path diagnostics. Guard the CI uv pin and cover both preflights with tests. |
||
|
|
498d876627 |
ci(release): pin build tooling and gate reproducible wheels (#945)
* feat(build): add locked reproducible wheel gate Build independently in clean source trees and virtualenvs, compare wheel bytes, and stage only verified canonical distributions. Safely clean recognized prior outputs on repeat runs. Part of #701 * ci(release): verify wheel reproducibility before publishing Run the shared gate in PR and tag workflows, preserve failure diagnostics, and keep verification artifacts out of release checksums. Make Snap Store publication wait for the gate and reuse the canonical distributions for PyPI. Part of #701 * docs(release): document reproducible wheel validation Describe the locked toolchain, canonical publication boundary, same-platform scope, diagnostic outputs, and safe automatic cleanup. * chore(scripts): update executable permissions * fix(build): isolate release tooling and preserve cleanup retries Exclude host site-packages from build-only virtualenvs so unlocked setuptools plugins cannot affect verified distributions. Delete staged artifacts before canonical ownership evidence to recover safely from interrupted cleanup. Add regression tests and document the build-only environment exception. * fix(release): make reproducible builds retryable and refresh tools Regenerate release build exports from uv.lock. Hold a checkout lock through cleanup and staging, and atomically rename complete archives into dist. Cover interrupted staging, competing processes, and lock release with regression tests. --------- Co-authored-by: Kanishk Pachauri <itskanishkp.py@gmail.com> Co-authored-by: sesav <1636488+sesav@users.noreply.github.com> |
||
|
|
566a228858 |
fix(installer): pin sherpa-onnx-core so Parakeet installs import (#1000)
sherpa-onnx 1.13.7 wheels require sherpa-onnx-core, which ships libonnxruntime.so, but the armv7l wheels omit that metadata and uv.lock recorded no dependency. install.sh installs the export with --no-deps, so sherpa_onnx failed to import, Settings hid Parakeet and the installer quietly switched config.json to whisper.cpp while still reporting Parakeet. List sherpa-onnx-core in the parakeet extra, verify engine imports right after installing them, and keep SELECTED_ENGINE in sync when the config is repaired so the summary names the engine actually configured. Fixes #973 |
||
|
|
65de3427fb |
perf: faster Settings, typing and dictation; mypy in CI, engine interface, architecture docs (#968)
* 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 |
||
|
|
775863fd30 |
feat(settings): add proxy support for model downloads and update checks (#909)
* feat(settings): add proxy support for model downloads and update checks Add a Proxy page to Settings (above About) with Off / System / Manual modes. Manual mode supports SOCKS5 and HTTPS (HTTP CONNECT) proxies with optional username/password auth; the password is stored in config.json in plaintext like the other settings. System mode honors the *_proxy environment variables and falls back to the GNOME org.gnome.system.proxy manual configuration. The resolved proxy is applied to every in-app outbound path that fetches models or checks updates: the shared requests download path in recognition_manager (_stream_model_download, used by the whisper.cpp, whisper, Vosk, Parakeet and faster-whisper downloaders) and the release checks in update_checker. A small Test Connection button on the Proxy page verifies reachability through the configured proxy. SOCKS5 needs PySocks, added as a runtime dependency; uv.lock, the requirements/*.txt exports and the Flatpak dependency manifest are regenerated by just lock (with a fix in sync_flatpak_deps.py for packages like PySocks that publish more than one universal wheel). Fixes #655 * fix(proxy): bracket IPv6 hosts, escape test errors, declare pysocks for AUR --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
c0217a8aec |
refactor(installer): generate distro package map (#872)
* refactor(installer): generate distro package map * fix(installer): handle Debian derivative package maps * docs(installer): document generated package map * fix(installer): validate package map and refresh derivative probes * fix(installer): name openSUSE package roles |
||
|
|
fb9166ed9e |
feat(settings): run local VocaGateway from Advanced (podman-first) (#774)
* feat(settings): run local VocaGateway from Speech Engine (podman-first) Add an optional Local VocaGateway panel next to Remote Server so Linux users can start the pinned v0.1.0 compose project with podman (docker fallback), watch Live/Pairable/Ready status, pair a phone without loopback QR URLs, and preset remote_api to the gateway transcriptions endpoint. Stop preserves volumes; Flatpak fails closed; tray offers Stop only when this session manages the gateway. Default on-device engine stays whisper.cpp. * fix(gateway-embed): drop nonexistent compose cpu profile VocaGateway v0.1.0 exposes default service gateway plus native/cuda/vulkan profiles, not cpu. Start and stop the default service without --profile, keep the latest image pin rejection, and prefer save_config for the remote_api preset. * fix(gateway-embed): keep compose --profile cpu per embed contract v0.1.0 has no cpu profile entry, but Compose still starts the unprofiled gateway service when --profile cpu is set, and keeps cuda/vulkan/native off. That matches the frozen desktop embed contract and stays forward-compatible. * fix(gateway-embed): harden embed path for settings and compose Keep podman/docker probing off the GTK thread, reject link-local and loopback public URLs for phone QR, sanitize compose .env image overrides, and cap bootstrap token reads so a hostile file cannot blow memory. * fix(gateway-embed): keep LAN toggle and phone QR in sync When Allow LAN flips while compose is already managed, rewrite publish host and PUBLIC_URL and recreate the gateway before Pairable comes back. Also reject default docker/podman bridge addresses for phone QR. * test(gateway-embed): pin bridge and LAN-gate pairing cases Cover 172.17.0.2 / link-local not becoming Pairable, and the mid-run LAN toggle path that must wait for compose republish before advertising a phone URL. * style(gateway-embed): black format embed modules * fix(gateway-embed): reject short-form loopback and localhost.* for QR Align Pairable/QR host checks with vocagateway desktop-embed policy so 127.1 / localhost.* never become phone-reachable display URLs. * fix(gateway-embed): detach Settings status listener on destroy Gateway poll callbacks were left registered after Settings closed, so idle_add could touch destroyed GTK widgets while compose stayed up. * fix(gateway-embed): pass host_env to bridge ip probe _bridge_iface_ips shells out to host ip; without host_env the AST gate fails and AppImage bundles can hand the wrong GLib to ip. * fix(packaging): ship gateway_embed in the AppImage The explicit setuptools packages list omitted the new package, so the bundle booted from source trees but died with ModuleNotFoundError on every distro boot test. * style(packaging): black the gateway_embed package guard * fix(settings): pack local VocaGateway into the Advanced island The rebase onto main auto-merged textually onto the simple Speech Model page. Keep the gateway card on advanced_box, next to Remote Server. * fix(gateway-embed): treat missing Compose as unavailable When podman or docker is present but Compose is not, stop advertising Run. Return empty compose_args, show an install-Compose hint, and cover it with a unit test. Also add module loggers on the new gateway_embed modules and annotate the gateway Settings/tray callbacks Greptile flagged. just lock found no lockfile drift. * fix(gateway-embed): keep Allow LAN bind matched to the switch Snapshot the LAN flag for start/republish, record what compose actually got, then republish again if the switch moved mid-flight so 0.0.0.0 never lingers while Allow LAN reads off. * fix(gateway-embed): detect leftover compose after Quit Quit does not stop compose, so a LAN-published gateway can keep running. Settings and the tray treated that as Stopped because managed_by_us is only process memory. Probe compose after runtime detection, show Stop when it is actually up, and let Stop work even if this session did not start it. * fix(gateway-embed): restore LAN switch from leftover compose Quit leaves compose running, so the next launch now reads VOCAGATEWAY_PUBLISH_HOST from the embed .env, adopts the project as managed, and keeps Allow LAN honest so republish can close 0.0.0.0. * fix(gateway-embed): restore .env if LAN republish fails compose recreate can fail after write_env_file already pointed VOCAGATEWAY_PUBLISH_HOST at loopback. Restore the previous .env so orphan adopt still sees a live 0.0.0.0 bind, and snap Allow LAN back to the running publish mode. * style(gateway-embed): type helper args and black MagicMock * refactor(types): move Readable protocol to common_types AGENTS.md keeps Protocol interfaces in common_types. Pairing now imports the shared Readable surface instead of a local _Readable helper. * fix(gateway-embed): satisfy mypy on rebased embed code * fix(audio): keep duck restore point when a set reports failure |
||
|
|
eef2554f7c |
feat(audio): lower other audio while dictating (#861)
* feat(audio): lower other audio while dictating Opt-in setting lowers the default PipeWire or PulseAudio sink while the microphone is open, then restores that exact level. It stays off by default. * fix(test): keep pytest.approx working when numpy is mocked Other test modules replace sys.modules["numpy"] with a MagicMock at import and leave it there. pytest.approx then crashes on isinstance(..., numpy.bool_). * fix(audio): restore every channel and do not duck if it cannot be undone A failed restore-point write no longer lowers the sink. PulseAudio puts each channel back, and a dead microphone restores playback instead of waiting for an explicit stop. The recognition test patches the real audio module when an older test left a mock behind. * fix(test): put the audio-feedback mock back after the ducking test The recognition test has to patch the real module, but later tests still expect the mock that test_text_injector installs. Restore that mock when the test ends, and annotate the new helpers. * fix(test): annotate the playback-duck tests AGENTS.md requires a type hint on every new function. These tests were the ones still missing them. * fix(audio): skip arming playback duck after recording already failed * chore: retrigger review after rebase |
||
|
|
f4e86f6325 | fix(deps): clear open dependency advisories (#869) | ||
|
|
acd90fdd05 |
fix(installer): install hash-pinned dependencies and build tools (#856)
* fix(installer): install hash-pinned dependencies and build tools * fix(installer): add cairo-devel for the pinned pycairo build * fix(installer): reject missing or empty bootstrap exports * fix(installer): preserve remote behavior for older tags --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
dd3bd69add |
Pin the ydotoold probe in the wtype fallback tests (#841)
* test(injection): pin the ydotoold probe so local runs match CI * chore(pytest): filter the three known warnings out of the summary * fix(pytest): stop hiding save_settings deprecation globally Keep the ignore on the one test that still exercises the deprecated alias so the rest of the suite can surface new save_settings() calls. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
b90bdb075d |
fix(deps): export and cap every engine extra (#828)
* fix(deps): export and cap every engine extra * fix(deps): docstring main and tighten export pairing tests Give check_exports.main the required public docstring, annotate the dependency export test helpers, and require --extra to sit with its -o on the same just lock line (whisper pip-compile stays special-cased). --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
cdfe541c25 |
feat(speech_recognition): add faster-whisper engine backend (#543)
* feat(speech_recognition): add EngineType enum and Engine Protocol Introduce a typed EngineType enum and an Engine Protocol in common_types.py, plus an engines/ registry so new speech recognition backends can be plugged in without scattering string comparisons across the manager. * feat(speech_recognition): add faster-whisper engine backend Add a faster-whisper engine implementation using CTranslate2/INT8 for fast CPU inference. Includes model metadata utility, wiring through the recognition manager, CLI, settings UI, config, optional dependency, and unit tests. Also updates the suspend-handler test fixture for the new engine attribute. * style(ui): format faster-whisper imports for isort 8 * test(speech_recognition): expand faster-whisper coverage for codecov * test(speech_recognition): fix failing faster-whisper coverage tests - Import missing get_compute_type helper - Add tests for English-only detection, cache exceptions, and import-error registry path - Clear _has_torch_cuda lru_cache around exception test to avoid cached True - Replace broad builtins.__import__ patch with a fake engine module for registry test - Restore registry module state after test * test(speech_recognition): add coverage for faster-whisper edge cases Cover previously missing lines in: - faster_whisper_model_info.py: HF cache path/none, hf_hub lookup, fallback traversal, torch CUDA true, psutil exception - faster_whisper_engine.py: model None guard after is_ready() - engine registry import-error path * feat(install): add faster_whisper engine option to install.sh - Add faster_whisper to --engine help text and examples - Add faster-whisper as interactive option 5 with description - Install faster-whisper package and verify import at install time - Add fallback to whisper.cpp if faster-whisper installation fails - Add smoke test for faster-whisper import - Update welcome message engine display name - Note: faster-whisper models download automatically on first use * refactor(faster_whisper): apply ponytail review cleanups - Inline FASTER_WHISPER_MODEL_INFO dict literal; drop unused AVAILABLE_MODELS/MODEL_SIZES - Reuse _has_torch_cuda() in FasterWhisperEngine.device property - Remove redundant model None guard after is_ready() verification - Shrink faster-whisper default config in install.sh to engine + model_size - Update device tests to manage _has_torch_cuda lru_cache - Remove test for deleted dead-code guard * fix(faster_whisper): integrate optional engine with current main Keep whisper_cpp as default and Parakeet as an extra. Put engine selection in the Advanced island, install faster-whisper via the optional extra, and refuse unpinned Hugging Face downloads until model_checksums.txt pins them. * fix(faster_whisper): download verified models and refresh uv.lock Settings already opened a download dialog for uncached faster-whisper, but init only raised ChecksumError. Wire a Parakeet-style downloader that stores bundles under models_dir, verifies each file against model_checksums.txt, and loads WhisperModel from that path. Regenerate uv.lock for the optional faster-whisper extra, pin digests for all eleven Systran models, and add the missing -> None return annotations. * fix(faster_whisper): clear engine on unload and fix auto-pause fixture Auto-pause tests stub __init__, so they never set _faster_whisper_engine. reinitialize_after_resume then AttributeErrors once Test Python runs. Mirror the suspend-handler fixture, and release the faster-whisper engine inside unload_model the same way resume already does. * fix(faster_whisper): restore previous engine after a failed switch reconfigure() selected the new engine and released the live model before init. A failed faster-whisper download then left the manager in ERROR on the new backend while Settings reverted the pickers to the saved engine, so dictation stayed dead until restart. Reload the previous engine from disk (no download) before re-raising so the UI and live manager agree. * fix(speech_recognition): restore all live settings after failed switch Failed reconfigure already put the previous engine back, but VAD, device, remote API, and whisper.cpp knobs stayed on the attempted values while config on disk did not. Snapshot every field reconfigure writes and restore the lot before reloading the old backend. Also bind the real _init_selected_engine onto the MagicMock in the force_reinit test. * fix(speech_recognition): restore every live field after failed reconfigure A failed engine switch already reloaded the previous backend, but Greptile could still miss that VAD, audio device, remote API, and whisper.cpp knobs were part of the snapshot. Keep one explicit attr list for snapshot/restore, including the derived voice-commands flag, so live state matches persisted config after a failed Settings apply. * chore(speech_recognition): sign tip after rollback follow-up |
||
|
|
8f4aeca2f1 |
feat(asr): add Parakeet TDT 0.6B engine via sherpa-onnx (#802)
* feat(asr): add Parakeet engine via sherpa-onnx Adds a "parakeet" engine backed by sherpa-onnx, running NVIDIA Parakeet TDT 0.6B int8 models on CPU: v3 (25 European languages, auto-detected) and v2 (English). Output is punctuated and capitalized. On an Intel Core Ultra 7 155H it decodes at about 12x realtime on CPU (RTF 0.08), roughly 8x faster than whisper.cpp large-v3-turbo over the same clips with its Vulkan GPU backend (RTF 0.59). Being a transducer it also has no 30s window to pad, so a short push-to-talk utterance returns in ~0.3s instead of ~2.4s. Follows the existing engine layout: utils/parakeet_model_info.py holds the catalog and the get_model_path / is_model_downloaded / list_downloaded_models / delete_model helpers, while _init_parakeet / _transcribe_with_parakeet / _download_parakeet_model follow the same shape as their whisper.cpp counterparts and reuse _stream_model_download for progress and cancellation. Registered in the engine dispatch, the settings engine/model/language pickers, the model info card, the download prompts, the unused-downloads list and install.sh. Models download to <models_dir>/parakeet/<model>/ via temp files renamed on success; is_model_downloaded requires all four files, so an interrupted download is retried rather than loaded. Requires the new "parakeet" extra (sherpa-onnx). * fix(install): write only the engine key for parakeet The parakeet branch duplicated a full default config.json. ConfigManager deep-merges DEFAULT_CONFIG on load, so every other key was redundant: a config holding only "engine": "parakeet" still resolves parakeet_model_size to v3-european along with the VAD, shortcut and text-injection defaults. * fix(install): keep push-to-talk defaults and switch existing configs Writing only the engine key left config.json without a shortcuts section, which _migrate_shortcuts_config reads as a pre-push-to-talk upgrade and pins back to ctrl+ctrl/toggle - contradicting the installer's own "Hold Right Alt while you speak" message. Seed that section too. Point the config at parakeet with set_configured_engine rather than only on first create, so --engine=parakeet also switches a machine that already has a config.json, and give print_welcome_message a parakeet arm so the summary is not blank. The interactive picker overwrites SELECTED_ENGINE, so document the flag combination that reaches this branch. * fix(asr): pin Parakeet bundles and report one progress bar Pin each bundle to a Hugging Face commit instead of resolve/main, so a file replaced upstream cannot change what an install downloads. This is the same approach whispercpp_model_info takes with its pinned revision. Remap each file's progress into its slice of the bundle. The four files were reported through the single-file downloader independently, so the fraction ran to 1.0 and restarted four times; it now advances once and names the file being fetched. * fix(asr): reject a truncated Parakeet download _stream_model_download only rejects an empty body, so a truncated but nonempty response was renamed into the model directory and then reported as installed, because is_model_downloaded checks existence. Record the byte size of each file at the pinned revision and check it before the rename, the same order whisper.cpp verifies its digest in. A short file is a truncated transfer rather than an upstream change, since the revision is pinned, so it raises and the temp file is removed. * style(asr): annotate the Parakeet model catalog mypy inferred the catalog values as object, so reading file_sizes out of an entry raised attr-defined and no-any-return under `just typecheck`. * fix(asr): pin Parakeet digests in model_checksums.txt AGENTS.md requires every speech model to be verified against a pinned digest before install, failing closed on an unpinned one. Parakeet was checking only the byte size. Teach scripts/generate-model-checksums.py to emit Parakeet entries: the ONNX files are LFS so Hugging Face publishes their sha256, and tokens.txt is hashed from the downloaded bytes the way VOSK archives are. Several bundles ship the same file names, so a key carries the model name. _download_parakeet_model now calls verify_model_file before the rename and catches ChecksumError alongside the other download failures, matching the whisper.cpp downloader. * fix(asr): verify on-disk Parakeet models before load Existing bundle files were trusted by path alone, so a leftover or copied-in tree could reach sherpa-onnx unchecked. Hash each file against its pin on download skip and on init, and cover the Parakeet pins in CI. * fix(ui): hide Parakeet language picker that ignores the setting Parakeet language coverage is the model choice, not a catalog language. Hide the picker and persist auto so Settings does not write a lying value. * fix(asr): normalize Parakeet language to auto at startup CLI --language and stale saved languages still reached SpeechRecognitionManager even though Parakeet ignores catalog language. Force auto on the shared helper used by main and the manager so stored settings stay honest. * fix(ui): restore language after leaving Parakeet Remember the prior Whisper/cpp language across a Parakeet visit so switching back does not leave recognition stuck on auto. Keep Parakeet forced to auto and the picker hidden. Also satisfy black on the startup normalizer path. * fix(ui): keep language memory across Vosk fallback Vosk's unsupported-language en-us coercion was writing into _last_non_parakeet_language, so Whisper→Parakeet→Vosk→Whisper restored en-us instead of the user's Greek (or other) preference. Remember the pre-coercion catalog language and prefer it when leaving Vosk. * fix(ui): keep language memory when entering Parakeet from Vosk Vosk's coerced en-us fallback was still written into _last_non_parakeet_language on Parakeet entry, so Vosk→Parakeet→Whisper restored en-us instead of the pre-coercion preference (Greek or auto). Share one coerced-fallback predicate across entry, leave-Vosk, and language sync, and cover those paths with regression tests. * fix(tests): set initializing flags false on language-change stubs Plain Mock attributes are truthy and were short-circuiting the handler. --------- Co-authored-by: Alex Thunder <AlexThunder@gmail.com> |
||
|
|
21b8ee8d05 |
fix(aur): build on Arch setuptools 84 and skip 1.4 context_params (#757)
AUR --no-isolation uses extra python-setuptools 84, so the <82 cap blocked the 0.16.0 rebuild. AUR pywhispercpp backends are still 1.4 and crash if we pass context_params; skip that argument below 1.5. |
||
|
|
f4e9fcb0ee |
fix(installer): survive a release without a checksum manifest, and stop inventing GPUs (#736)
* installer: leave model downloads to first run when the release pins no checksums * installer: refuse an unpinned VOSK model before spending the download * installer: require a real device before reporting a Vulkan GPU * build: replace the deprecated license table and complete the packages list * installer: treat software Vulkan renderers as no GPU, not as one * installer: keep diagnostics off stdout so captured detection output stays clean |
||
|
|
582d494633 |
chore(build): uv tooling, a Python 3.11 floor, and verified model downloads (epic #701, phases 2.5 & 5) (#713)
* chore(build): migrate justfile tooling to uv All recipes now run through 'uv run' against .venv, with a shared DEV_EXTRAS variable (dev + vad) because uv syncs the environment exactly and would otherwise uninstall the dev tools between recipes. 'deps' installs dev+vad; new 'deps-all' covers every extra. 'build' switches to 'uv build'; 'version' uses --no-sync. Bump [tool.uv] required-version to >=0.12,<0.13 for uv 0.12.x, and raise the mypy python_version target to 3.12: mypy 1.19 in the lock cannot target 3.9 and numpy 2.x stubs need a 3.12 target to parse (mypy is not run in CI or pre-commit; this only affects 'just typecheck'). * fix(tests): isolate test runs from real XDG state; fix Arch model-path crash Local test runs failed en masse (185 failures) while CI stayed green: tests picked up the developer's real ~/.local/share/vocalinux models via the default XDG paths and switched IBus/keyboard code paths through the desktop session env (XDG_CURRENT_DESKTOP/ XDG_SESSION_TYPE on GNOME/Wayland, issue #478 branches). conftest now points XDG base dirs at a throwaway session-wide directory and clears session variables at import time, mirroring a fresh CI runner (paths like MODELS_DIR freeze at first import during collection, so fixture-time isolation is too late). Also guard the Arch branch of _get_system_model_paths(): it removed /usr/local/share/vocalinux/models unconditionally and crashed with ValueError on Arch hosts when a custom XDG_DATA_DIRS never added that path. Add regression tests, and make the lock-file test assert the actual $XDG_DATA_HOME/vocalinux contract instead of the hardcoded ~/.local/share default. * fix(tests): keep the IBus engine double a plain class Python 3.14's unittest.mock re-instantiates a user subclass when it builds child mocks (klass(name=...)). VocalinuxEngine inherits from whatever IBus.Engine resolves to, and the test module bound that to the MagicMock class itself, so every engine instantiation raised TypeError: VocalinuxEngine.__init__() got an unexpected keyword argument 'name' on 3.14 while passing on 3.13. Replace it with a plain stand-in class that still hands out per-instance MagicMocks for the IBus API surface the engine touches. The same pattern remains in test_ibus_engine.py, test_ibus_engine_utils.py, test_ibus_active_engine_guard.py and test_ibus_xkb_wayland.py; those never instantiate the engine, so they are unaffected for now. * fix(install): build the venv from the system Python install.sh looked up a bare `python3` from PATH. Started from an activated virtualenv — a shell left in uv's .venv after `just deps` — it built venv/ from that interpreter, where distro PyGObject is invisible even with --system-site-packages, and the run aborted with Distro PyGObject (python3-gi / python3-gobject) is not importable in the venv. A mismatched venv/ then survived every retry, because the installer reuses whatever venv it finds. - drop an inherited VIRTUAL_ENV (and its bin from PATH) before any interpreter lookup - select the interpreter explicitly: $SYSTEM_PYTHON (default /usr/bin/python3) or PATH python3, preferring one that can import gi, and create the venv with it instead of `python3` - recreate venv/ when it was built by a different interpreter - name the interpreter in the PyGObject guard's error Documents the two environments (uv's .venv for tooling, venv/ for the installed app) in CONTRIBUTING, the justfile header and AGENTS.md, and adds the mismatch symptom to the INSTALL troubleshooting section. * chore(python): raise the supported floor to 3.11 requires-python promised 3.9 while the CI matrix ran 3.9/3.10/3.11/3.13, the classifiers advertised up to 3.14, mypy targeted 3.12 and install.sh enforced 3.9 — five different answers to "which Python does this support". Settle on 3.11 everywhere: pyproject (requires-python, classifiers, black targets), install.sh's MIN_VERSION, check-system-deps.sh, the distro package map, the CI matrix (now 3.11-3.14, matching the classifiers), the release and nightly workflows, and the docs. mypy now targets the floor instead of 3.12, so type checking covers what we promise; numpy 2.x ships stubs in 3.12 syntax that a 3.11 target cannot parse, so its imports are skipped. (mypy has 71 pre-existing errors either way; no CI job gates on it.) uv.lock and the requirements/* exports are regenerated — dropping 3.9/3.10 removes most of the version-conditional resolution branches. tests/test_python_version_policy.py pins the invariant: install.sh's floor, requires-python, the classifiers and the CI matrix must agree. * ci: run lint and tests from uv.lock The lock file and the hash-pinned exports were not enforced anywhere: CI still built a venv by hand and installed unpinned `PyGObject` plus `-e ".[dev,vad]"`, so the tool versions it checked against had nothing to do with the ones `just lint` uses locally. Both Python jobs now use astral-sh/setup-uv and run through `uv sync --locked` / `uv run --locked`, which also fails the build when uv.lock drifts from pyproject.toml. Partially covers phase 5 of #701 (tests install from the lock, lock freshness gate); trusted publishing, artifact checksums and action SHA pinning are still open there. * ci: lint from a dedicated dependency group The lint job failed on this branch: `uv run --extra dev` syncs the whole project, so it tried to build pyaudio from source on a runner that installs no system dependencies. src/pyaudio/device_api.c:9:10: fatal error: portaudio.h: No such file or directory Move black/isort/flake8 out of the `dev` extra into a `lint` dependency group. CI runs `uv run --locked --only-group lint`, which installs the three pure-Python tools and skips the project entirely — no portaudio or girepository headers needed. `just deps` and DEV_EXTRAS request the group alongside the dev extras, so the local environment still carries test and lint tooling at once and `just lint` matches CI version for version. pre-commit is unaffected: its hooks pin their own tool versions upstream. * fix(install): never configure an engine the venv cannot import vosk became an optional extra in #705, but the installer still writes `"engine": "vosk"` into config.json on the whisper.cpp fallback path even when installing the extra only produced a warning. config.json also survives reinstalls — every writer guards on "file does not exist" — so an engine picked by an earlier attempt outlives the venv that supported it. Either way Vocalinux dies at startup: ModuleNotFoundError: No module named 'vosk' - the fallback now writes the vosk config only when `import vosk` actually succeeds, and otherwise leaves the engine configuration alone - a new verify_configured_engine() runs near the end of the install: if config.json selects vosk and the venv cannot import it, it says so and prints both fixes instead of leaving a broken install behind * feat(models): verify every model download against a pinned digest Pins a sha256/md5 per downloadable speech model in src/vocalinux/utils/model_checksums.txt and verifies against it in both install.sh and the runtime downloaders, before the file is moved into place. Verification fails closed. Covers phase 2.5 of #701; see the PR for details. * docs(agents): rebase AGENTS.md onto the #727 rewrite * fix(install): hash models that are already on disk instead of trusting them * refactor(models): one download path per engine, and fix Whisper's * docs(install): drop distros that cannot meet the 3.11 floor * docs: retire the distros and Python versions the 3.11 floor rules out * fix(install): abort instead of warning when Python is below the floor * fix(install): fall back to the system Python, not the one on PATH * fix(install): match an existing venv by installation, not by X.Y * fix(install): repair a config naming an engine the venv cannot import * fix(models): pin VOSK by the sha256 of bytes we fetched, not upstream's md5 * fix(justfile): request the dev set in every recipe that syncs * test(version): scan the docs and website trees for a stale Python floor * docs: correct the stale model-checksum and distro claims * fix(models): stamp the VOSK tree the app extracts, as the installer does * fix(install): replace an unverified VOSK model only once its successor is ready * docs(install): align the Fedora floor with the rest of the tree * test(install): drop an f-string with no placeholders * test(models): stub the downloader probe the VOSK installer test relies on * docs: drop the long dashes from two model-checksum comments * fix(install): gate on the interpreter, not on the distro release label * fix(justfile): export the lint group into requirements/dev.txt * fix(models): unpack the VOSK tree into staging and stamp it before the swap * fix(whisper): adopt a checkpoint an earlier release stored under the catalog name * docs(settings): state what a stray Whisper copy really means for deletion * fix(models): hash a whisper.cpp model found on disk before it reaches ctypes * docs(contributing): stop telling readers to uninstall their linters * fix(install): stop warning off distros the project documents as supported * docs(distros): file the supported derivatives under supported |
||
|
|
95d135ebbf |
chore(deps): lock dependencies with uv and pin every build input
Phases 0-1 of #701. Drops dead deps, moves vosk to an optional extra, adds uv.lock and hash-pinned requirements exports, pins pywhispercpp to 1.5.0. Closes #705. Refs #701. |
||
|
|
32a17a9659 |
chore: point GitHub URLs at VocaHQ (#696)
Repo links, install one-liners, and release checks now use github.com/VocaHQ/vocalinux. The site mentions VocaHQ without a redesign. The Codeberg mirror, personal email, and X handle are unchanged. |
||
|
|
068530535b |
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. |
||
|
|
fbb81238f5 |
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 <jatinkrmalik@users.noreply.github.com> * 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 <jatinkrmalik@users.noreply.github.com> * 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 <jatinkrmalik@users.noreply.github.com> * 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 <jatinkrmalik@users.noreply.github.com> * 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 <jatinkrmalik@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> |
||
|
|
3f5a5effff |
feat(vad): add Silero VAD and drop silence-only buffers (#447)
* feat(vad): replace amplitude-based VAD with Silero neural network Replace the simple amplitude threshold VAD with Silero VAD, a lightweight neural network model that classifies audio as speech/non-speech. This addresses the root cause of #302 (silence not stopping recording) and - Add silero_vad.py wrapper using ONNX Runtime with bundled Silero v5 model - Accumulate 512-sample chunks with 64-sample context window per official API - Map existing vad_sensitivity (1-5) to Silero thresholds (0.8-0.3) - Use max() across sub-chunks to avoid missing speech onsets - Fall back to amplitude VAD when onnxruntime is unavailable - Add onnxruntime as optional dependency (pip install vocalinux[vad]) Closes #344 * address review: add tests, LICENSE, logging, validation fixes - Add unit tests for SileroVAD (process, reset, fallback, sensitivity mapping) - Add MIT LICENSE for bundled Silero VAD model in data/ - Log which VAD backend is active on init - Validate chunk length in SileroVAD.process() - Skip is_speech check until a full Silero chunk is processed - Add thread safety note to SileroVAD docstring - Document resampling rate effect on VAD resolution - Comment explaining why load_silero_vad() catches broad Exception * address review: bundle license, document [vad] extra, surface backend in UI - Pack data/LICENSE into the wheel (pyproject package-data) so attribution travels with the model - README: short note on enabling neural VAD via vocalinux[vad] - Settings: VAD Sensitivity row subtitle shows the active backend; if onnxruntime is missing, an info-row beneath the group spells out the install command and the restart requirement - silero_vad.is_silero_available() probes onnxruntime + model presence w/o instantiating an InferenceSession - CI installs .[dev,vad] so Silero paths get exercised - Integration test for _record_audio Silero branch (mocked pyaudio + scripted SileroVAD), covering silence flush, speech reset, and sensitivity threshold mapping at sens=1 vs sens=5 - Tests no longer skip when other modules globally mock numpy: pop the top-level numpy ref so a fresh import gives the real package, and swap sys.modules["numpy"] inside the _record_audio call so the lazy import inside the function picks up real numpy too * fix(vad): drop silence-only recording buffers * test(vad): align stop buffer expectations * test(vad): preserve real numpy module * fix(install): install vad support opportunistically * test(vad): cover recognition manager vad edges --------- Co-authored-by: Sbenazar <127324689+Sbenazar@users.noreply.github.com> |
||
|
|
c44b06996e |
fix: add Python 3.14 support and lxml>=6.1.0 (fixes #404)
- Add Python 3.14 to classifiers - Add lxml>=6.1.0 to dependencies to fix install on Python 3.14 (CachyOS, Arch) - Update black target-version to include py314 Fixes #404 Closes #405 |
||
|
|
860a9f9ae6 |
fix: Bundle resources in package to fix missing system tray icons (fixes #349) (#354)
* fix: bundle resources in package to fix missing icons in system tray (fixes #349) The resources directory (icons and sounds) was not included in pip-installed packages because it lived outside the src/ package directory. This caused the resource_manager to find an empty icon directory at runtime, resulting in the ellipsis (three dots) icon in the GNOME system tray. Changes: - Copy resources into src/vocalinux/resources/ so setuptools bundles them - Add 'vocalinux.resources/**/*' to package-data in pyproject.toml - Add MANIFEST.in for sdist builds - Update resource_manager to check package-bundled resources first (module_dir.parent / 'resources') before checking external paths - Add install_resources_to_venv() to install.sh for curl-based installs, copying resources to /share/vocalinux/resources This fixes both pip installs (resources bundled in the wheel) and curl-based installs (resources copied to venv share directory). * fix: ensure bundled resources are included in sdist and pip installs - Add 'vocalinux.resources' to setuptools packages list so the subpackage (and its __init__.py) is recognized by setuptools - Add __init__.py to src/vocalinux/resources/ to make it a proper Python subpackage for reliable discovery - Add 'recursive-include src/vocalinux/resources *' to MANIFEST.in so sdist archives include the bundled icons and sounds |
||
|
|
fa17b88f74 |
chore: bump minimum Python from 3.8 to 3.9 (#336)
* 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. |
||
|
|
fa4da40e7b |
test(coverage): improve test coverage from 57% to 80%+ (#327)
* test(coverage): improve test coverage from 57% to 80%+ Add 30 new test files with 861 additional test cases to improve overall test coverage from 57% to 80%+ (statement coverage). Key modules covered: - recognition_manager.py: 43% → 69% (audio device detection, transcription, model download, engine initialization, buffer management) - text_injector.py: improved coverage for X11/Wayland text injection, environment detection, clipboard fallback, shortcut injection - ibus_engine.py: 22% → 73% (engine lifecycle, text injection, socket server) - evdev_backend.py: 11% → 78% (device detection, key event handling) - pynput_backend.py: 43% → 76% (start/stop, key handling, callbacks) - keyboard_backends/__init__.py: 54% → improved (backend creation, env detection) - whispercpp_model_info.py: 32% → improved (GPU detection, model recommendations) - about_dialog.py, first_run_dialog.py, main.py, single_instance.py, common_types.py, config_manager.py, autostart_manager.py: various improvements All 1433 tests pass with proper test isolation to prevent mock pollution between test modules. * test(quality): audit and fix test quality issues Code review audit identified and fixed the following issues: - Remove bare try/except blocks that swallow all errors (7 tests fixed in recognition_manager_deep, replaced with proper assertions or deleted) - Delete source code inspection tests that read .py files as strings instead of testing behavior (deleted test_about_dialog_coverage.py and test_first_run_dialog_coverage.py entirely - ~35 useless tests removed) - Add meaningful assertions replacing weak assertIsNotNone/assertIsInstance checks (common_types_coverage, ibus_engine_coverage, keyboard_backends_extra) - Fix tests that mock the system under test instead of its dependencies (text_injector_extra now mocks subprocess.run instead of injection methods) - Add missing assertions to tests with no verification (ibus_engine focus_out, log window info, copy to clipboard) - Verify download tests check requests.get was called with correct URLs (coverage_boost download tests) - Fix pytest warning by renaming test_audio_input import to avoid pytest collecting it as a test function All 1356 tests pass with 0 failures and 0 warnings. Statement coverage: 80.3% (3257/4054 lines covered). * style(tests): fix black formatting and isort import ordering Auto-format all new test files with black and isort to pass CI lint checks. * fix(tests): resolve CI hangs and rename test files for clarity - Fix CI hangs by properly mocking gi/GTK/IBus in all test files - Fix sys.modules pollution in recognition manager tests by using tmp_path - Add pytest-timeout to prevent future hangs - Rename 28 test files to use descriptive, consistent names mapping to source modules * fix(tests): resolve Python 3.13 CI failures - Fix _load_stdlib_module() in test_recognition_manager_downloads.py to handle stdlib modules that are packages (directories with __init__.py) rather than single .py files. In Python 3.13, zipfile became a package, causing FileNotFoundError during test collection (exit code 2). - Fix invalid escape sequence '$' in test_cross_distro_compatibility.py by using a raw string. Python 3.13 reports this as SyntaxWarning and Python 3.14 will make it a SyntaxError. * fix(tests): use direct import for zipfile instead of _load_stdlib_module The _load_stdlib_module approach fails on Python 3.13 where zipfile is a package (zipfile/__init__.py). Loading it via spec_from_file_location with a custom name breaks relative imports inside the package (e.g. 'from ._path import ...' resolves to '_stdlib_zipfile._path' which doesn't exist). Simply import zipfile at module level before any test mocks can pollute sys.modules — this is safe because the module-level code runs during test collection, before any test fixtures or patches are applied. * fix(tests): mock gi/GTK in conftest to prevent CI hangs On CI, real PyGObject is installed and module-level imports in vocalinux source files (ibus_engine, tray_indicator, settings_dialog, etc.) trigger GTK initialization or IBus daemon connections that hang indefinitely in headless environments. pytest-timeout's signal-based timeout cannot interrupt these C-level blocking calls. Fix by mocking gi and gi.repository in conftest.py (which runs before any test module is collected), ensuring all test files are protected regardless of whether they remember to mock individually. Also add timeout-minutes: 10 to the CI test job as a safety net so that any remaining hangs fail fast instead of running indefinitely. * fix(tests): unconditionally mock gi in conftest for CI The previous 'if gi not in sys.modules' guard was ineffective on CI because PyGObject is installed via pip, so the real gi module is already loaded. This caused GTK initialization and IBus daemon connections that hang indefinitely in headless CI environments. Now unconditionally replace gi and gi.repository with mocks during test collection, regardless of whether the real package is installed. * fix(tests): reduce timeout to 10s and use thread-based timeout Use timeout_method=thread instead of signal (default) so that pytest-timeout can interrupt C-level blocking calls (GTK operations, socket accepts, subprocess waits) that the signal-based method cannot handle. Also reduce per-test timeout from 30s to 10s since no legitimate unit test should take longer, and hanging tests were consuming the entire CI job timeout budget. * fix(tests): set global socket timeout to prevent blocking on CI Tests in test_ibus_engine.py create real Unix sockets with daemon threads that block on socket.accept(). When tests fail or timeout, these threads remain blocked indefinitely, preventing pytest from completing. The thread-based timeout catches the hang but cannot terminate the blocking accept() call. Set socket.setdefaulttimeout(5) in conftest.py so all socket operations (accept, connect, recv) will raise socket.timeout after 5 seconds instead of blocking forever. * fix(tests): add cleanup fixture for IBus socket server threads Add autouse fixture that stops any leftover VocalinuxEngine socket server threads after each test. Tests that trigger _start_socket_server() spawn daemon threads blocking on socket.accept() that persist after the test completes, eventually causing pytest-timeout to kill the process. The cleanup fixture sets _server_running=False and closes the server socket, which unblocks the accept() call and allows the daemon thread to terminate. * fix(tests): reduce socket timeout to 0.1s to avoid slowing tests The 5-second socket timeout caused socket operations in daemon threads to block for 5 seconds per iteration before timing out, significantly slowing the test suite on CI. Reduce to 0.1s so blocking socket calls fail fast without noticeably impacting test execution time. * fix(tests): use 2s socket timeout and increase CI timeout to 15min The 0.1s socket timeout was too aggressive and caused legitimate test socket operations to fail. Use 2s which is long enough for test sockets to work but short enough to unblock daemon threads. Increase CI job timeout to 15 minutes to accommodate the socket timeout overhead from daemon threads that retry every 2 seconds. * fix(tests): block known-blocking daemon threads to prevent CI hangs Instead of using socket timeouts (which either break tests or are too slow), prevent specific known-blocking daemon threads from starting: - ibus_engine.server_thread: blocks on socket.accept() - evdev_backend._monitor_devices: blocks on select.select() These threads are identified by their target function name and skipped during Thread.start(). The thread is marked as started/stopped so join() calls in cleanup code don't raise RuntimeError. Test-created daemon threads (e.g. mock socket servers) are unaffected since they use different target function names. * fix(tests): patch Thread.join for Python 3.13 compatibility Python 3.13 changed threading internals — join() now uses _handle.join() which raises 'thread not started' for threads that were never actually started. Instead of trying to fake internal state, patch join() to be a no-op for threads that were skipped by our _safe_thread_start guard. This fixes the 2 remaining test_evdev_backend_ext failures on Python 3.13. |
||
|
|
d3e1bd7128 |
Fix #221: Add missing psutil dependency (#223)
pywhispercpp requires psutil internally but doesn't declare it as a dependency, causing vocalinux to fail on fresh installs with: Failed to import pywhispercpp: No module named 'psutil' Adding psutil>=5.9.0 to vocalinux's dependencies as a workaround. |
||
|
|
57a0ddd0b8 |
feat(speech): add whisper.cpp with Vulkan support for GPU acceleration (#179)
* feat(speech): add whisper.cpp with Vulkan support for GPU acceleration Add whisper.cpp as a new speech recognition engine with the following features: - Vulkan GPU acceleration support (AMD, Intel, NVIDIA) - Fallback to CUDA for NVIDIA GPUs - CPU-only mode with automatic thread optimization - Support for all model sizes: tiny, base, small, medium, large - Hardware detection to recommend best model/backend - Models downloaded from Hugging Face (ggml format) - Updated UI to show compute backend info in settings - pywhispercpp as the Python binding library - whisper_cpp as the new default engine Changes: - New whispercpp_model_info.py with model metadata and hardware detection - Updated recognition_manager.py with whisper.cpp init and transcription - Updated settings_dialog.py with whisper.cpp UI support - Updated config_manager.py with whisper_cpp_model_size config - Updated main.py with whisper_cpp CLI option - Updated pyproject.toml with pywhispercpp dependency - Updated install.sh with vulkan-tools for GPU detection Closes #104 * style: fix black and isort formatting issues * test: update config manager test for whisper_cpp default engine * feat(install): interactive mode with 3 engine choices and whisper.cpp as default Major changes to install.sh: - Interactive mode is now the DEFAULT (not --auto) - Added --auto flag for non-interactive automatic installation - New --engine flag to specify engine directly - Interactive mode shows 3 engine options with visual boxes: 1. whisper.cpp (recommended) - Vulkan/CUDA/CPU support 2. whisper (OpenAI) - PyTorch-based, NVIDIA-only 3. VOSK - lightweight option - Hardware detection with recommendations: - Detects Vulkan support (AMD/Intel/NVIDIA) - Detects NVIDIA CUDA - Checks available RAM - Recommends best engine based on hardware - Updated help text with new options and examples - Removed old --with-whisper, --whisper-cpu, --no-whisper flags - whisper.cpp is now the default engine for automatic mode * feat(uninstall): kill running vocalinux processes before uninstall * fix(whisper.cpp): auto-download model, use all CPU cores, hide unavailable engines Three fixes for whisper.cpp implementation: 1. Auto-download model during install - Added install_whispercpp_model() function to install.sh - Downloads ggml-tiny.bin (~39MB) from Hugging Face - Runs automatically when whisper.cpp engine is selected 2. Use all CPU cores for transcription - Added n_threads parameter to Model initialization - Uses multiprocessing.cpu_count() to get all cores - Significantly improves transcription speed 3. Hide unavailable engines from dropdown - Added get_available_engines() function to detect installed engines - Settings dialog now only shows engines that are actually installed - Falls back to first available engine if current not available Fixes user-reported issues with whisper.cpp integration. * ui(settings): display whisper.cpp correctly and increase dialog height - Changed engine display name from 'whisper_cpp' to 'whisper.cpp' - Added format_engine_display_name() helper function - Dialog now uses 75% of screen height for better visibility - More options visible by default without scrolling * revert(settings): remove display name change, keep dialog height increase - Reverted engine display name change (was breaking engine selection) - Kept dialog height at 75% of screen for better visibility * style: fix black and isort formatting for whisper.cpp logging * style: fix black formatting in settings_dialog.py * ci: fix Fedora 39 container package installation Add dnf cache update and fallback with --nogpgcheck for Fedora containers that may have stale or missing package metadata. |
||
|
|
d004a401af |
chore(release): prepare v0.5.0-beta (#148)
This commit prepares the repository for the v0.5.0-beta release, transitioning from alpha to beta status. Changes made: - Update version from 0.4.1-alpha to 0.5.0-beta in version.py - Update Development Status classifier from Alpha to Beta in pyproject.toml and setup.py - Update status badges from Alpha (orange) to Beta (blue) in README.md - Update all installation command examples to use v0.5.0-beta tag - Update release announcement from Alpha to Beta in README.md - Add RELEASE_PROCESS.md documentation for future releases - Update docs/INSTALL.md with new version references - Add What's New section for v0.5.0-beta in docs/UPDATE.md - Update SECURITY.md supported versions table - Update website schema softwareVersion in layout.tsx - Update website version badges in page.tsx - Update website package.json version to 0.5.0 Testing: - All 463 tests passing (100%) - Version consistency verified across all files Release process documentation added to docs/RELEASE_PROCESS.md |
||
|
|
e46c37d80e |
fix: Add missing keyboard_backends package to pyproject.toml (#126)
The vocalinux.ui.keyboard_backends module was not being installed because it wasn't listed in the packages configuration. This caused a ModuleNotFoundError when trying to launch Vocalinux after installation. Fixes installation issue where the app fails to start with: ModuleNotFoundError: No module named 'vocalinux.ui.keyboard_backends' |
||
|
|
f8338929c1 | Revert direct push to main - will create PR instead | ||
|
|
2bd7969143 |
fix: Add missing keyboard_backends package to pyproject.toml
The vocalinux.ui.keyboard_backends module was not being installed because it wasn't listed in the packages configuration. This caused a ModuleNotFoundError when trying to launch Vocalinux after installation. Fixes installation issue where the app fails to start with: ModuleNotFoundError: No module named 'vocalinux.ui.keyboard_backends' |
||
|
|
6f54dd80e0 |
Fix Python 3.13 and Wayland compatibility (#125)
* Fix Python 3.13 and Wayland compatibility This commit addresses installation failures on modern Linux distributions (Ubuntu 25.10+) with Python 3.13 and Wayland-only sessions. Changes: 1. **Add Python 3.13 support** (pyproject.toml) - Add Python 3.13 to classifiers - Update black target versions to include py312, py313 - No code changes needed - already compatible 2. **Use system site-packages for PyGObject** (install.sh) - Change venv creation to use --system-site-packages flag - Avoids PyGObject build failures with Python 3.13 - System PyGObject (python3-gi) is already installed as dependency - Building from source is unnecessary and fragile with new Python versions 3. **Improve Wayland text injection support** (install.sh) - Install ydotool as fallback (wtype doesn't work with GNOME Wayland) - Add user to 'input' group for uinput device access - Install udev rule for /dev/uinput permissions - Provide clear warning about logout requirement Technical details: - wtype uses Wayland virtual keyboard protocol (not supported by GNOME) - ydotool uses Linux uinput kernel module (works with all compositors) - System PyGObject 3.50.0+ supports Python 3.13 - Building PyGObject 3.54.5 from source fails with Python 3.13 build tools Fixes installation on: - Ubuntu 25.10 (Questing Quokka) - Wayland-only, Python 3.13 - Arch Linux with Python 3.13 - Fedora 41+ with Python 3.13 Tested on: Ubuntu 25.10 with Python 3.13.7, GNOME 49 Wayland Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * Prioritize ydotool over wtype for universal Wayland compatibility wtype uses the virtual-keyboard Wayland protocol which is not supported by GNOME/Mutter compositors. ydotool uses the Linux uinput kernel module which works with all Wayland compositors including GNOME. This change swaps the priority order to check ydotool first, ensuring text injection works on GNOME Wayland (Ubuntu 25.10+) while still falling back to wtype for compositors that support it. Tested on Ubuntu 25.10 with GNOME 49 Wayland - text injection now works perfectly with ydotool. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * ci: trigger workflow run * ci: run lint/tests on python 3.13 --------- Co-authored-by: ACT900 <179795730+ACT900@users.noreply.github.com> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com> |
||
|
|
9e5b632710 |
test: Improve test coverage to 80%+ (#122)
* test: Improve test coverage to 80% Increased test coverage from ~33% to 80.15% statement coverage. New test files added: - test_action_handler.py (100% coverage) - test_version.py (100% coverage) - test_logging_manager.py (100% coverage) - test_common_types.py - test_speech_recognition_init.py (100% coverage) - test_resource_manager.py (93% coverage) Significantly expanded existing tests: - test_tray_indicator.py: 76% -> 98% - test_main.py: 50% -> 92% - test_text_injector.py: 60% -> 94% - test_recognition_manager.py: 30% -> 48% - test_command_processor.py: 85% -> 98% - test_keyboard_shortcuts.py: 88% -> 95% - test_config_manager.py: improved to 98% - test_audio_feedback.py: improved to 92% Configuration changes: - Updated pyproject.toml to exclude untestable GTK dialog files (settings_dialog.py, logging_dialog.py) from coverage metrics Key testing improvements: - Added comprehensive GTK mocking for UI components - Added edge case tests for text injection on X11/Wayland - Added keyboard shortcut injection tests - Added error handling and callback tests - Added configuration management tests * fix: Use runtime CI mode detection to fix Python 3.8/3.10 tests Change CI_MODE from module-level constant to _is_ci_mode() function that is evaluated at runtime. This ensures pytest detection works correctly on all Python versions, since pytest modules may not be loaded at import time. * fix: Add PYTEST_RUNNING env var for reliable pytest detection in CI The previous approach to detect pytest at runtime wasn't working reliably on Python 3.8/3.10 in CI environments. This adds a PYTEST_RUNNING=1 environment variable set early in conftest.py that _is_ci_mode() can check to reliably determine if tests are running. * fix: Use patch.object for reliable mocking in audio_feedback tests The previous approach using patch() with module paths didn't work reliably because the module reimport caused the patched objects to not be the same as the ones the functions referenced. Using patch.object() on the imported module's attributes ensures the patches are applied to the actual objects the functions use. * fix: Use patch.object for reliable mocking in test_settings_callback The test was failing on Python 3.8/3.10 because the module-level patch for SettingsDialog wasn't being applied reliably across all Python versions due to import timing differences. Using patch.object() on the imported module ensures the mock is applied to the actual reference that _on_settings_clicked() uses, regardless of Python version or module caching behavior. |
||
|
|
22ab96cea7 |
feat: add Wayland support for keyboard shortcuts using evdev backend (#81)
* feat: add Wayland support for keyboard shortcuts using evdev backend This commit adds multi-backend support for keyboard shortcuts, enabling them to work on Wayland sessions where pynput does not work. **Changes:** - New `keyboard_backends/` package with modular backend system - `base.py`: Abstract base class for keyboard backends - `pynput_backend.py`: Existing pynput implementation (X11/XWayland) - `evdev_backend.py`: New evdev implementation (X11 & Wayland) - `__init__.py`: Backend factory with auto-detection - Updated `keyboard_shortcuts.py`: - Refactored to use backend system - Auto-detects session type (X11 vs Wayland) - Selects appropriate backend automatically - Provides helpful error messages when shortcuts unavailable - Added `evdev>=1.6.0` to dependencies (Linux only) - Comprehensive test updates: - 21 tests pass covering all backends - Tests for session detection - Tests for backend factory - Tests for permission hints **User Impact:** - Keyboard shortcuts now work on Wayland when user is in 'input' group - Clear error messages guide users to fix permission issues - Backward compatible with X11 (pynput backend) **Setup for Wayland:** ```bash sudo usermod -a -G input $USER # Log out and log back in ``` Related to #80 * style: fix flake8 issues in evdev_backend.py - Remove unused import 'categorize' - Remove unused variable 'dev' * style: fix remaining flake8 issues in tests - Remove unused 'time' import - Remove unused 'result' variable - Remove unused 'backend_cls' variable - Break long line to stay under 100 characters * style: apply black and isort formatting to pass CI * feat: auto-configure Wayland keyboard shortcuts during installation Automatically detect Wayland sessions during installation and add the user to the 'input' group required for evdev-based keyboard shortcuts. This eliminates the need for users to manually configure permissions after installation on Wayland systems. * fix: escape commit message in Detect Changes step to handle special characters The commit message was being directly inserted into the bash comparison, which broke when the message contained special characters like parentheses. Fix by assigning to a variable first. * chore: drop unrelated installer/web changes * fix: fall back to evdev backend * style: format keyboard shortcut imports |
||
|
|
1dc1d44665 |
feat: Alpha Launch v0.2.0 - Complete UX Overhaul 🚀🎤 (#39)
* 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 |