37262ced0eda06a45396d80a2afbe9afb8c1a91f
9
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
e66bd218d9 |
fix(audio): prevent dictation hangs on stalled microphones (#974)
* fix(audio): bound capture reads and support cancellation * fix(recognition): bound and serialize capture shutdown * fix(recognition): cancel retries and report stalled microphones * fix(audio): prefer pulse when the ALSA default is unavailable * fix(recognition): guard late callbacks and finalize capture tests * docs(audio): explain input selection and stall recovery * fix(speech): enforce auto-pause across buffered reload timeouts * fix(speech): recover auto-resume after reload timeout * test(speech): fix auto-pause follow-up test regressions * fix(speech): retain auto-resume after reconfigure timeout * fix(tray): preserve auto-pause recovery across suspend * test(capture-shutdown): give the bare tray the suspend path's shortcut managers _on_system_suspend now releases the main and per-language hotkey listeners before sleep (#1025); the fixture builds TrayIndicator without __init__, so it needs the two attributes present. * fix(tray): release hotkey listeners on suspend even while auto-paused Merging #1025's suspend cleanup with this branch's auto-pause handoff left an early return that skipped shortcut_manager.stop() whenever a configured game was active, keeping EVIOCGRABs held through sleep. The paused branch now only owns the model-lifecycle work; listener release and stopping active recognition run for every suspend. * fix(tray): hold the apply lock only around the download's verify and save Wrapping the whole recommended-model download in _apply_settings_lock blocked every GTK-thread settings apply (e.g. Test Dictation) for the length of the transfer. The engine already serializes reconfigures, so the lock only needs to cover verifying the download still owns the receipt and saving it; an apply that lands mid-download now proceeds instead of freezing the app. --------- Co-authored-by: Jatin K Malik <jatinkrmalik@gmail.com> |
||
|
|
138a233892 |
feat(recognition): cancel an in-progress transcription with the recording hotkey (#1004)
* feat(recognition): cancel in-progress transcription with hotkey Adds per-session cancellation across the manager, tray, and Test Dictation. Fixes #991 * fix(settings): keep Test Dictation cancellation responsive Fixes #991 * fix(recognition): keep late workers cancellable (#991) * test(tray): avoid early tray import in cancellation tests * fix(recognition): keep a cancelled capture thread out of the next session A cancel returns to IDLE without joining the capture thread, so a thread still in its reconnection backoff could wake inside a newer recording, reopen that session's device and write into its buffer. Tie the capture loop and the reconnection to the session's cancel token: a cancelled or replaced session stops reading, skips the reopen and exits without raising an error. |
||
|
|
db07590761 |
feat(activation): D-Bus activation for compositor global shortcuts (#568)
Rebased onto latest main (conflict-resolution squash for PR #568). Co-authored-by: Alexander <ab@digital-benefits.de> Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.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 |
||
|
|
4c070ded46 |
feat(offload): auto-pause apps + idle keep-alive model unload (#592)
* feat(auto-pause): unload speech model while selected apps run Add Settings toggle + process list so Vocalinux frees CPU/GPU/RAM while configured games/apps are running, then reloads the model when they exit. Closes the process-list half of #445 (window-focus pause is separate). * test(auto-pause): isolate tray wiring to avoid stale GLib mocks Move TrayIndicator auto-pause tests into test_suspend_handler so test_auto_pause_monitor never imports tray_indicator under a collection-time gi MagicMock. That import pinned GLib.SOURCE_REMOVE and broke resume tests later in the suite (CI on #517). * test(auto-pause): raise patch coverage for codecov target Cover process snapshot I/O, GLib poll scheduling, config coercion, unload edge cases, and tray fallback paths so codecov/patch clears 80%. * feat(keepalive): idle model offload for GPU/battery saving Add opt-in keep-alive that unloads the speech model after inactivity so hybrid-GPU laptops can release VRAM/Vulkan. Next dictation lazy-reloads. Shares unload_model with auto-pause (#445) using distinct reasons so app-pause still blocks dictation while idle unload does not. Fixes #591 (with #445 via shared offload infrastructure). * test(keepalive): mock gi.repository.GLib via sys.modules Match the auto-pause GLib test pattern so CI on Python 3.9/3.10 sees the late `from gi.repository import GLib` import. * test(keepalive): drop post-fire source_remove assertion After the GLib timeout callback runs, _timeout_id is already cleared, so shutdown does not call source_remove. * fix(settings): hide auto-pause empty-state label when apps are listed The dialog-wide show_all() after _load_and_apply_settings() re-showed the 'No apps listed yet' row that _refresh_auto_pause_list had hidden. Mark the row no_show_all and manage visibility explicitly. Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * feat(settings): move auto-pause and idle unload to dedicated Power tab - New Power tab between Shortcuts and General so resource management features are easy to find; General returns to app-behavior toggles only - Rename 'Model Keep-Alive (Battery)' to 'Unload When Idle' — the old name read as the opposite of what the feature does - Clearer copy throughout: row titles, descriptions, tooltips, 'From running…' becomes 'Choose Running App…', empty-state hint - Gray out app-list and idle-timeout controls while their feature toggle is off Co-authored-by: Jatin K Malik <jatinkrmalik@users.noreply.github.com> * fix(settings): show which notebook tab is selected The tab CSS targeted :selected, but GTK3 marks the active notebook tab with :checked, so no selected styling ever applied — all tabs rendered identically. Use :checked and add an accent underline + bold label. 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> |
||
|
|
f417c28efa |
fix(threading): harden thread safety for Remote API, IBus, and text injection (#452)
* fix(threading): harden thread safety for HTTP session, IBus, and text injector Four critical thread-safety fixes identified during PR #335 review: 1. Remote API: snapshot _http_session under _model_lock in _process_audio_buffer() to prevent use-after-close races with reconfigure/reinitialize. Pass session as explicit parameter to _transcribe_with_remote_api, _try_whispercpp_server_api, and _try_openai_api. Call _http_session.close() before setting to None in reinitialize_after_resume(). Remove blocking connection probe from _init_remote_api() (settings dialog has a Test Connection button for that). 2. Text injector: extend _state_lock to protect stop() (writes _ibus_injector=None and _ibus_ready=False) and inject_text() reads of environment/_ibus_injector. Also guard environment/wayland_tool writes in _switch_to_non_ibus_backend() and WAYLAND_XDOTOOL fallback in inject_text's exception handler. 3. IBus engine: snapshot cls._active_instance into a local variable before the do_inject closure in the socket server handler, closing the TOCTOU race with do_destroy(). 4. Test suspend handler: add _http_session=None to _make_manager() fixture so reinitialize_after_resume() finds the attribute. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) * test(threading): update tests for thread-safety changes - test_recognition_manager_remote_api: pass session param to _transcribe_with_remote_api, _try_openai_api, and _try_whispercpp_server_api. Update assert_called_once_with patterns for the new session argument. Simplify init tests that no longer need connection probe mocks (removed from _init_remote_api). - test_text_injector: add threading.Lock to all TextInjector.__new__() instances so _state_lock is available for lock-guarded methods. - test_suspend_handler: add _http_session=None to _make_manager() fixture so reinitialize_after_resume() finds the attribute. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) * test(threading): improve coverage for thread-safety fixes Add tests for _process_audio_buffer remote API dispatch (session snapshot under lock, early return when session is None) and reinitialize_after_resume session close. Mark server_thread as pragma: no cover (blocked by conftest to prevent CI hangs). |
||
|
|
aa2dbc2fee |
fix(suspend): delay keyboard restart for USB re-enumeration (#372)
* fix(suspend): delay keyboard restart to 6s for USB re-enumeration The 2s delay is enough for the speech engine, but USB devices re-enumerate slowly after resume. At T+2s, stale /dev/input nodes still exist and evdev opens them, but the kernel tears them down at ~T+4s, causing another round of disconnections. Split the reinit into two separate timers: - 2s: speech engine reinit (unchanged) - 6s: keyboard backend restart (after USB re-enumeration completes) * fix(suspend): use inotify to detect device settle before keyboard restart Replace hard-coded 6s keyboard restart delay with event-driven Gio.File.monitor_directory() on /dev/input. After resume, a 2s settle timer resets on each inotify event; keyboard shortcuts restart only once devices stop changing. A 10s safety cap and a 6s fallback timer guard against environments where inotify is unavailable. |
||
|
|
5909def48d |
fix(suspend): restart keyboard shortcut backend after resume (#371)
After suspend/resume, evdev devices disconnect and the monitor thread exits when all file descriptors close. The resume handler only reinitialized the speech engine, leaving keyboard shortcuts dead. Restart _setup_keyboard_shortcuts() in _reinit_after_resume() to re-scan /dev/input and open fresh device file descriptors. |
||
|
|
80536c0fa4 |
fix(suspend): auto-recover speech recognition after system resume (#369)
* feat(suspend): add D-Bus based suspend/resume handler Monitors org.freedesktop.login1.Manager.PrepareForSleep via Gio.DBusProxy to detect system suspend/resume. On resume, invokes a callback to reinitialize the audio pipeline and speech model so dictation works immediately without manual intervention. Fixes #367 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> * feat(suspend): wire suspend handler into tray indicator and recognition manager TrayIndicator now creates a SuspendHandler on startup that listens for D-Bus PrepareForSleep signals. On suspend, active recognition is stopped. On resume, a 2-second delay allows audio hardware to stabilize before reinitializing the speech engine and resetting audio reconnection state. Fixes #367 Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> --------- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai> |