feat: Prompt peripheral handoff when the other Mac sleeps or vanishes#58
Merged
Conversation
…ishes - Proactive handoff on sleep: a Mac releasing peripherals as it sleeps now pushes the freed set to a reachable peer (new ADOPT_RELEASED opcode, acked on receipt) so they land on the awake Mac immediately instead of waiting to be detected gone. Best-effort; falls back to reactive adoption. - Release on sleep whenever a trusted peer is registered, not only when it is reachable that instant, so a peripheral is never left latched to a sleeping host and the other Mac can take it on its next wake without a power-cycle. - Detect a vanished peer fast while this Mac is awake: a first missed reachability poll (or a Bonjour withdraw) schedules a quick confirming re-probe instead of waiting out the 30s interval, arming adoption in seconds. - Full-set menu switch grabs locally and arms the auto-reconnect watcher when the peer is unreachable, mirroring the per-peripheral takeover, instead of erroring and stranding the peripherals.
The annotated menu.png carries a checkmark callout ("it's on this Mac now")
that, unlike every other screenshot's callouts, was not explained beside the
image (only in the Usage table). Complete the caption so it covers all three.
The "Release peripherals when this Mac sleeps" toggle was named in the Other tab but never explained, and the Other-tab "see Troubleshooting" pointer only resolved to the reconnect toggle's writeup. Add a user-level Troubleshooting bullet describing the sleep hand-off (and freeing peripherals for a peer that isn't reachable yet), without the internal mechanics.
|
🎉 This PR is included in version 2.18.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
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.
Summary
Closes the gaps where a Magic peripheral could sit unclaimed after the Mac holding it went away — so it lands on the other Mac promptly (usually automatically) instead of getting stuck and needing a power-cycle.
Two scenarios motivated this:
What changed
Proactive handoff on sleep (
feat)A Mac releasing peripherals as it sleeps now pushes the freed set to a reachable peer over the secure channel (new
ADOPT_RELEASEDopcode, acked on receipt) so they land on the awake Mac in ~1–2s instead of waiting to be detected gone. Best-effort and layered on top of the local release — if the push is missed, reactive adoption still recovers them. The sleep transition is briefly held (≤3s) for the receipt ack so frames flush before the radio powers down; the ack fires on a background queue, so the wait can't deadlock the send.Release on sleep whenever a trusted peer is registered (
feat)Previously a Mac only released on sleep if the peer was reachable that instant. In Case 2 the peer is asleep, so nothing was released and the device stayed latched — the power-cycle case. Now release happens whenever a trusted peer is paired, so the peripheral is freed and the other Mac can take it on its next wake without a power-cycle. A lone Mac with no registered peer still keeps its bond. (See the decision note below.)
Fast vanish-detection while awake (
feat)A first missed reachability poll, or a Bonjour withdraw, now schedules a quick confirming re-probe instead of waiting out the 30s interval — so a peer that vanishes while this Mac is awake arms adoption within seconds rather than ~a minute. The "two consecutive misses" debounce is preserved; only the latency is collapsed.
Full-set menu switch falls back to a local grab (
feat)Left-clicking a Mac row to pull all peripherals used to error out if that Mac was unreachable, stranding them. It now grabs locally and arms the auto-reconnect watcher (mirroring the per-peripheral takeover), and arms on success too so a stuck device keeps retrying.
Docs (
docs)Release-on-sleep gate decision
The broadened gate (release when a peer is registered, not only reachable now) makes the existing toggle finally do what its label says, and is what fixes Case 2. Tradeoff: a two-Mac setup also releases on sleep when the peer is currently offline, so the Mac re-pairs its peripherals on its own next wake (~1–2s) rather than relying on a bonded reconnect. That's the same
-remove/re-pair path already used for the reachable-peer case, just more often, and the wake reclaim is HOLDS_ONE-gated so the two Macs never tug-of-war. Could move behind a separate opt-in if the churn is unwanted, but defaulting it on matches the toggle's name.Trigger model after this change
ADOPT_RELEASED→ peer grabs immediatelyWhoever wakes reclaims anything the peer didn't take (HOLDS_ONE-gated).
Verification
xcodebuild … buildsucceeds. Full behavioral testing needs two physical Macs + Magic peripherals, which wasn't possible in this environment — but the changes either trigger the existing adoption mechanism or mirror the shippedtakePeripheralFromPeersemantics, leaning on proven paths rather than new Bluetooth logic. The sleep-path semaphore was checked for deadlock safety (the send path has no main-thread hop;currentKey()runs before the wait).