Skip to content

feat: Prompt peripheral handoff when the other Mac sleeps or vanishes#58

Merged
MegaManSec merged 3 commits into
mainfrom
proactive-sleep-handoff
Jun 18, 2026
Merged

feat: Prompt peripheral handoff when the other Mac sleeps or vanishes#58
MegaManSec merged 3 commits into
mainfrom
proactive-sleep-handoff

Conversation

@MegaManSec

Copy link
Copy Markdown
Owner

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:

  • Case 1 — the other Mac is awake. You unplug/close the clamshell Mac; its peripherals should hop to the Mac you're now using.
  • Case 2 — both Macs were asleep, then you wake one. The peripheral was latched to the still-sleeping Mac and needed an off/on power-cycle to migrate.

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_RELEASED opcode, 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)

  • Completed the menu screenshot caption (the ✓ "on this Mac now" callout was annotated in the image but never explained beside it).
  • Added a Troubleshooting bullet for the "Release peripherals when this Mac sleeps" toggle (named but never explained; the Other-tab "see Troubleshooting" pointer now resolves for both toggles).

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

At sleep Behavior
Peer reachable release locally + push ADOPT_RELEASED → peer grabs immediately
Peer registered but offline release locally → peer adopts on its next wake
No peer registered keep bonded

Whoever wakes reclaims anything the peer didn't take (HOLDS_ONE-gated).

Verification

xcodebuild … build succeeds. 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 shipped takePeripheralFromPeer semantics, 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).

…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.
@MegaManSec MegaManSec changed the title Prompt peripheral handoff when the other Mac sleeps or vanishes feat: Prompt peripheral handoff when the other Mac sleeps or vanishes Jun 18, 2026
@MegaManSec MegaManSec merged commit 3a64df2 into main Jun 18, 2026
2 checks passed
@MegaManSec MegaManSec deleted the proactive-sleep-handoff branch June 18, 2026 19:17
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 2.18.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant