Fix server command listener lost after daemon reconnect#250
Merged
Conversation
Copilot
AI
changed the title
[WIP] Fix hook-based volume controller on reconnection
Restore daemon volume command handling after client-initiated reconnects
May 11, 2026
dec0976 to
c32ccb3
Compare
In client-initiated mode, _handle_disconnect was unsubscribing the server command listener on every reconnect cycle, but the connection loop never re-registered it. This caused volume/mute commands from the server to stop working after the first reconnect. The listener is scoped to the client's lifetime (which persists across reconnects), not the connection's, so it should only be torn down on full teardown — matching the TUI's existing behavior. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
c32ccb3 to
1679ce1
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR aims to restore daemon-side volume/mute command handling across client-initiated reconnects by refactoring how the daemon attaches/detaches a SendspinClient and its listeners, ensuring connection-scoped cleanup is handled consistently.
Changes:
- Refactors daemon client wiring into
_attach_client()/_detach_client()helpers (audio handler, listeners, MPRIS). - Updates server-initiated connection handling to use the new attach/detach helpers and ensure cleanup in
finally. - Adjusts client-initiated reconnect handling to only reset audio state on disconnect (instead of tearing down broader client-scoped state).
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
379
to
385
|
|
||
| # Connection dropped | ||
| logger.info("Disconnected from server") | ||
| # Keep MPRIS alive across reconnects in client-initiated mode. | ||
| await self._handle_disconnect(stop_mpris=False) | ||
| await self._handle_disconnect() | ||
|
|
||
| logger.info("Reconnecting to %s", url) | ||
|
|
Comment on lines
379
to
385
|
|
||
| # Connection dropped | ||
| logger.info("Disconnected from server") | ||
| # Keep MPRIS alive across reconnects in client-initiated mode. | ||
| await self._handle_disconnect(stop_mpris=False) | ||
| await self._handle_disconnect() | ||
|
|
||
| logger.info("Reconnecting to %s", url) | ||
|
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
In client-initiated mode,
_handle_disconnectwas unsubscribing the server command listener on every reconnect cycle, but the connection loop never re-registered it. This caused volume/mute commands from the server to stop working after the first reconnect.The listener is scoped to the client's lifetime (which persists across reconnects), not the connection's. Refactored the daemon to use
_attach_client/_detach_clientpairs (matching the TUI's existing pattern) so the lifecycle is explicit:_attach_client— registers listeners, attaches audio handler, starts MPRIS_detach_client— the inverse, called on server switch and full teardown_handle_disconnect— just resets audio state, called on every disconnect_handle_disconnect, so listeners survive