From 3e2433e1f552d65370884820c8ed085efcc653db Mon Sep 17 00:00:00 2001 From: Joshua Rogers Date: Mon, 28 Sep 2026 19:12:07 +0200 Subject: [PATCH] feat: take peripherals at launch when a marked display is connected A marked display that was already connected when Magic Switch started used to be treated as the baseline and never triggered a switch. Now the monitor starts from an empty set and reconciles after the usual debounce, so launching while docked takes the peripherals like a fresh dock. The existing guards still apply: nothing happens if they're already here, Bluetooth is unhealthy, or a switch is in flight. --- Magic Switch/Manager/DisplayMonitor.swift | 12 ++++++------ README.md | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Magic Switch/Manager/DisplayMonitor.swift b/Magic Switch/Manager/DisplayMonitor.swift index 61f3fdb..7e6aa79 100644 --- a/Magic Switch/Manager/DisplayMonitor.swift +++ b/Magic Switch/Manager/DisplayMonitor.swift @@ -15,9 +15,8 @@ import SwiftUI /// and works for any display, not just Apple's. /// /// Only a genuine absent → present *edge* fires the trigger: -/// - The set of displays online at launch is the baseline. A display that's -/// already attached when the app starts never fires — an app (re)start must -/// not yank peripherals from a Mac the user is actively working on. +/// - Launch counts as a dock: a trigger display that's already attached when +/// the app starts fires once, after the same debounce as a live change. /// - Reconfiguration callbacks are debounced and then *reconciled* against /// `CGGetOnlineDisplayList`, so the remove/add churn of a resolution change /// or a wake-time renegotiation nets out to no edge. @@ -109,17 +108,18 @@ final class DisplayMonitor: ObservableObject { loadTriggerDisplays() } - /// Begin monitoring. Snapshots the currently-online displays as the - /// no-edge baseline, then registers for reconfiguration callbacks and the + /// Begin monitoring. Populates the Settings rows right away, schedules a + /// reconcile against an empty present set (so trigger displays already + /// attached fire), then registers for reconfiguration callbacks and the /// sleep/wake notifications the settle logic needs. func start() { guard !started else { return } started = true let online = Self.onlineExternalDisplays() - presentUUIDs = Set(online.map { $0.id }) connectedDisplays = online refreshTriggerNames(from: online) + scheduleReconcile() let refCon = Unmanaged.passUnretained(self).toOpaque() let error = CGDisplayRegisterReconfigurationCallback(Self.reconfigurationCallback, refCon) diff --git a/README.md b/README.md index 0f56477..dff343d 100644 --- a/README.md +++ b/README.md @@ -139,7 +139,7 @@ Magic Switch tells you when there's a new version — it never updates itself. A - **Closing or sleeping one Mac hands its peripherals to the other.** When this Mac sleeps (or you close its lid), it hands the peripherals it holds to your other Mac — or, if that Mac isn't reachable yet, frees them so it can pick them up the moment it wakes. That's why you can close one Mac and find the keyboard and mouse already on the other. This is on by default; you can turn it off under **Settings → Other → "Release peripherals when this Mac sleeps."** - **A peripheral didn't come back after sleep or a lid-close.** Apple's Magic devices sometimes get stuck once the Bluetooth radio sleeps and won't reconnect — even a manual reconnect fails until you switch the peripheral **off and on** with its power switch. Magic Switch keeps watching for anything that was on this Mac before it slept: the moment the device reappears (which a power-cycle triggers), it reconnects automatically — as long as your other Mac isn't actively using it. This is on by default; you can turn it off under **Settings → Other → "Reconnect peripherals if they drop."** - **Both Macs slept, and now a peripheral connects to neither.** When this Mac sleeps, it frees its peripherals for your other Mac to take (see above). If the other Mac never takes them — it was asleep too, or off the network the whole time — they end up attached to no Mac at all, and a Magic device in that state stops listening for connections after a short while; no Mac can reach it until it's woken up. When this Mac wakes it immediately tries to grab everything it freed, so a round trip usually recovers on its own — clicking or pressing a key on the peripheral within the first minute after opening the lid helps it get caught. If a peripheral still won't respond, this is the one situation where you have to help: flick its power switch **off and on**, and Magic Switch reconnects it the moment it comes back up. If that trade-off doesn't suit how you use your Macs, turn off **Settings → Other → "Release peripherals when this Mac sleeps"** — peripherals then stay bonded to this Mac across sleep (and reconnect instantly on wake), at the cost of the other Mac needing the same power-cycle help if you switch desks while this Mac sleeps. -- **Docking to a marked display switches peripherals automatically.** Under **Settings → Other → "Take peripherals when a display connects,"** every external display connected to this Mac gets a toggle. Mark your desk's display and, whenever it connects to this Mac — plugging in the cable, or opening the lid of a MacBook that was docked while it slept — Magic Switch switches your peripherals to this Mac, taking them from your other Mac if needed. Displays are recognized individually, so two identical monitors aren't confused; waking while already docked doesn't count as docking; and a display that's already connected when Magic Switch starts doesn't trigger a switch. This is off until you mark a display. +- **Docking to a marked display switches peripherals automatically.** Under **Settings → Other → "Take peripherals when a display connects,"** every external display connected to this Mac gets a toggle. Mark your desk's display and, whenever it connects to this Mac — plugging in the cable, or opening the lid of a MacBook that was docked while it slept — Magic Switch switches your peripherals to this Mac, taking them from your other Mac if needed. Displays are recognized individually, so two identical monitors aren't confused; waking while already docked doesn't count as docking; and if a marked display is already connected when Magic Switch starts, it switches your peripherals over just as if you had docked. This is off until you mark a display. ## Developer notes