Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Magic Switch/Model/Store/NetworkDeviceStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -834,6 +834,7 @@ enum ManualAddError: Error {
case selfDial
case legacyPeer
case anotherMacRegistered(String)
case listenerNotReady
case outgoing(OutgoingFailure)

var userMessage: String {
Expand All @@ -845,6 +846,19 @@ enum ManualAddError: Error {
"The other Mac runs an older version of Magic Switch that can't be added by address. Update it first."
case .anotherMacRegistered(let name):
return "Only one Mac can be connected at a time. Remove \(name) first."
case .listenerNotReady:
return "This Mac isn't accepting connections yet. Try again in a moment."
case .outgoing(.connectTimeout), .outgoing(.connectionFailed(_)),
.outgoing(.handshakeFailed(.handshakeTimeout)),
.outgoing(.handshakeFailed(.connectionClosed)),
.outgoing(.handshakeFailed(.sendFailed(_))),
.outgoing(.handshakeFailed(.framingFailed)),
.outgoing(.handshakeFailed(.frameTooLarge)):
// An unpaired peer refuses the connection before the handshake, so it
// is indistinguishable here from a firewall drop, a dead host, or a
// different service answering on the port.
return
"Couldn't reach the other Mac securely. Check that it's running Magic Switch and paired with the same code (Settings → Pairing), and that no firewall is blocking the port."
case .outgoing(let failure):
return failure.userMessage
}
Expand Down Expand Up @@ -1258,6 +1272,12 @@ extension NetworkDeviceStore {
host: String, port: UInt16,
completion: @escaping (Result<NetworkDevice, ManualAddError>) -> Void
) {
// Without a bound listener there's no local identity to INTRODUCE with;
// `executeIntroduce` would report it as a peer-shaped connection failure.
guard servicePublisher.currentIdentity() != nil else {
completion(.failure(.listenerNotReady))
return
}
let target = NetworkDevice(id: host, name: host, host: host, port: Int(port))
executeIntroduce(on: target) { [weak self] result in
DispatchQueue.main.async {
Expand Down
37 changes: 19 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,9 @@ Tick the Magic devices you want Magic Switch to hand back and forth. Each row's
<em>That leading icon is also a picker — Magic Switch auto-detects the type (keyboard, mouse, trackpad, headphones, AirPods, microphone), and you can override it or set it back to Automatic.</em>
</p>

### 3. Macs tab — pick the other Mac
### 3. Pairing tab — link the two Macs (required)

Choose the other Mac under **Macs Found on the Network**. It shows up once it's on the same network running Magic Switch; a greyed-out row means it isn't reachable right now.

If your network blocks Bonjour (some MDM-managed Macs can't advertise; some Wi-Fi networks filter multicast), the other Mac may never appear here. Use the **+** button to add it by IP address instead — the sheet on each Mac shows the address and port to enter on the other one, and one side is enough: the other Mac then lists this one automatically. Magic Switch listens on TCP port **41952**; allow it through any firewall between the two Macs.

<p align="center">
<img src="docs/assets/macs-tab.png" alt="Macs tab showing the connected Mac and available Macs" width="600"><br>
<em>Macs tab — pick the other Mac, sync peripherals to it, and check it's reachable.</em>
</p>

<p align="center">
<img src="docs/assets/add-by-address.png" alt="The Add a Mac by Address sheet" width="600"><br>
<em>Add a Mac by Address (the <strong>+</strong> button) — the sheet shows this Mac's address and port to enter on the other one.</em>
</p>

### 4. Pairing tab — link the two Macs (required)

Generate a twelve-character code on one Mac and enter it on the other; either direction works, since both Macs derive the same key from the same code. They should then show the same eight-character fingerprint — if they differ, the code was mistyped. Until this is done, switching and peripheral sync refuse to talk to the peer.
Generate a twelve-character code on one Mac and enter it on the other; either direction works, since both Macs derive the same key from the same code. They should then show the same eight-character fingerprint — if they differ, the code was mistyped. Until this is done, switching and peripheral sync refuse to talk to the peer, and an unpaired Mac can't be added by address in the next step. (Pairing needs no connection between the Macs; the code alone is enough.)

<p align="center">
<img src="docs/assets/pairing-not-paired.png" alt="Pairing tab before pairing" width="600"><br>
Expand All @@ -81,6 +65,22 @@ Generate a twelve-character code on one Mac and enter it on the other; either di
<em>After pairing — both Macs show the same fingerprint. If they differ, the code was mistyped.</em>
</p>

### 4. Macs tab — pick the other Mac

Choose the other Mac under **Macs Found on the Network**. It shows up once it's on the same network running Magic Switch; a greyed-out row means it isn't reachable right now.

If your network blocks Bonjour (some MDM-managed Macs can't advertise; some Wi-Fi networks filter multicast), the other Mac may never appear here. Use the **+** button to add it by IP address instead — the sheet on each Mac shows the address and port to enter on the other one, and one side is enough: the other Mac then lists this one automatically. Adding by address only works once **both Macs are paired** (step 3) and running Magic Switch — an unpaired Mac refuses the connection, and the add fails. Magic Switch listens on TCP port **41952**; allow it through any firewall between the two Macs.

<p align="center">
<img src="docs/assets/macs-tab.png" alt="Macs tab showing the connected Mac and available Macs" width="600"><br>
<em>Macs tab — pick the other Mac, sync peripherals to it, and check it's reachable.</em>
</p>

<p align="center">
<img src="docs/assets/add-by-address.png" alt="The Add a Mac by Address sheet" width="600"><br>
<em>Add a Mac by Address (the <strong>+</strong> button) — the sheet shows this Mac's address and port to enter on the other one.</em>
</p>

### 5. Sync your peripherals to the other Mac

On the **Macs** tab, find the other Mac under **Your Other Mac** and click its **Sync** button (the circular arrows, beside **Ping**). A "Synced N peripherals to …" line confirms it. The button is greyed out while that Mac is offline.
Expand Down Expand Up @@ -134,6 +134,7 @@ Magic Switch tells you when there's a new version — it never updates itself. A
- Same network; TCP port 41952 not blocked by firewall.
- Bluetooth and Local Network permissions granted in System Settings → Privacy & Security.
- A **greyed-out device** — in the Macs tab or the right-click menu — means it isn't reachable on the network right now (the other Mac is asleep, off Wi-Fi, or not running Magic Switch). Ping, Sync, and switching stay disabled until it's back online.
- **Both Macs changed IP address at once (Bonjour-blocked networks).** An IP change normally heals itself: a reachability probe that gets through teaches the receiving Mac the dialing Mac's current address from the connection itself, so as long as one Mac can still reach the other, both sides end up with working addresses. But when *both* Macs get new addresses at the same time on a network where Bonjour is blocked — a router reassigning every lease, or both Macs moving to another network — each keeps dialing the other's old address and neither probe ever lands. Re-add the other Mac by IP address (the **+** button on the Macs tab) from either side; one side is enough.
- **A switch failed and nothing told you.** Failures — a peripheral that won't connect, a Mac that can't be reached, an identity mismatch — are reported via system notifications, because a hotkey, URL-scheme, or automatic switch has no window to show an error in. If macOS notifications are off for Magic Switch, those failures are completely silent; **Settings → Other** shows a **"Notifications are off"** warning when that's the case. Re-enable them under System Settings → Notifications → Magic Switch. (The Pairing and Macs tabs also show their errors inline, so actions taken *there* still report failures either way.)
- On the **Macs** tab, **Ping** tests whether the two Macs can reach each other over the secure channel.
- **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."**
Expand Down