* 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>
* fix(recognition): never lose a stop that races start, and read capture outside the buffer lock
start_recognition() published LISTENING (and played the start cue) before setting should_record. A stop arriving in that window, e.g. a push-to-talk release handled on another thread, was undone by the later should_record = True, leaving the capture thread recording with no way to stop it. The session is now armed under _buffer_lock before LISTENING is visible.
The capture loop held _buffer_lock across source.read_chunk(). When a read blocks (the audio server reconfigures the device, a USB/Bluetooth mic stalls), stop_recognition() gives up its bounded join and then blocks forever on that lock, stuck in PROCESSING. The read now happens outside the lock and the session is re-checked before the chunk is appended.
* test(recognition): wait for the stalled read before stopping
Signal from inside the blocking read instead of sleeping after the first, non-blocking one, so the test always exercises the stalled-read path.
* test(recognition): run the stalled-read test against the real NumPy
Older test modules leave sys.modules["numpy"] as a MagicMock. Under that leak the capture loop's first chunk raised before the stalled read was reached, so the test failed in the full-suite order on CI. Restore the real module for these tests, as test_playback_ducker.py already does.
---------
Co-authored-by: AAG Projects Team <293887937+aagprojectsteam-max@users.noreply.github.com>