* 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).
* 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.
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.
* 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>