Fix reconnect replay and recover from HID transport failures#82
Conversation
|
Hey @hieshima, I pulled this PR locally and tested the reconnect/recovery flow on my side, including disconnect/reconnect scenarios and recovery after a HID read failure. I also verified that the saved device state is restored correctly after reconnect, Smart Shift comes back, DPI is replayed correctly, and gesture handling keeps working as expected. Everything looks good from my testing. I also want to say I’m genuinely very impressed by the quality of this PR, and honestly by all of your PRs so far. They’ve been thoughtful, well-scoped, and very helpful. If this is something that would interest you, I’d be very happy to talk about making you a maintainer. If you want, feel free to ping me somewhere easier for real-time chat, however you prefer. |
|
Hey @TomBadash, thank you for reviewing my PRs. I'd like to be a maintainer. |
Problem
After sleep, reconnect, or cold start, Mouser could get stuck in a half-recovered state:
_apply_device_settings(), which had zero callers).IOHIDDeviceSetReport failed: 0xE00002C2on macOS), the app stayed "connected" and kept polling Smart Shift every 15 seconds instead of reconnecting.smartShiftChangedcould fire without actually updating config.Fix
Phased replay
Deleted the dead
_apply_device_settings()helper and moved its settle/retry semantics into the live replay worker:Also moved
_emit_status()outside_replay_lockto avoid callback-under-lock.Smart Shift serialization
Replaced the unsynchronized
_pending_smart_shiftbusy-wait path withthreading.Eventplus separate call/slot locks. Reconnect cleanup now aborts pending Smart Shift requests so waiters never read stale state. Smart Shift polling is also suppressed while replay is in flight.Payload normalization
All engine-to-backend Smart Shift callbacks now use:
{"mode": str, "enabled": bool, "threshold": int}Backend now returns early on non-dict payloads, and
smartShiftChangedonly fires after a valid update.Transport failure recovery
In
_request(), active-session tx/rx exceptions now raiseIOError, which drives the existing cleanup/reconnect path through_main_loop(). Discovery-time probe failures still returnNone.This is intentionally error-code-agnostic and does not depend on any specific IOKit error code.
Regression coverage
Added regressions for:
_divert_extras()re-arming on reconnect and held state clearing before disconnect callbacksScope
engine.set_smart_shift()andset_dpi()remain synchronous; async dispatch is follow-up workIOHIDManagerRegisterDeviceRemovalCallbackwould be a more complete disconnect signal on macOS, but that is also follow-up workTested on
222tests)217tests)