Repository navigation
feat(mac): one MeowDisplay app that can also be a display - #26
Draft
raiseCatError wants to merge 12 commits into
Draft
raiseCatError wants to merge 12 commits into
raiseCatError wants to merge 12 commits into
Conversation
Prepare for one Mac app with both roles. TrustStore gains namespaces: shared keeps the original Keychain names, so every existing identity and pin is found as before, and TrustStore.receiverRole is the store the receiver path (StreamReceiver, its pairing and QUIC listeners, receiver settings) now uses. It is the same object as shared everywhere except an app that declares MeowDisplayReceiverRoleTrust = separate, where the receiver role gets its own identity and pins — the separation the two standalone apps had. PairingNetwork takes the role's store. The receiver's Auto-Reconnect setting gets a role-scoped key in that app, and SameAppPeerFilter lets each role ignore the other's advertisements. The iPhone/iPad app and standalone receiver behave exactly as before.
MeowDisplay.app now also contains the Mac Receiver (its code, not its app entry point or bundle resources) behind one switch, Use This Mac as a Display, in Overview and the menu bar. It is off by default, so an existing install never starts advertising a receiver it didn't ask for. Exactly one role has live sessions: turning it on ends this Mac's sharing sessions without suppressing their reconnect and starts the receiver; while on, startSession — the gate every auto-connect, Connect, receiver connect request and Remote knock goes through — refuses, and turning it off stops the receiver and lets the devices reconnect. That rules out a Mac streaming to itself or two Macs streaming into each other. Each role also filters the other's Bonjour advertisements, the app becomes a regular Command-Tab-able app while the display window is up, quitting sends the receiver's closing message first, and the role doesn't start while the standalone MeowDisplay Receiver is running. The sender's bundle id, macOS 14 floor, Keychain names, settings and Sparkle feed are unchanged; the standalone receiver still builds for macOS 12 with its own id and feed.
Record why there were two Mac apps, the measured case against one macOS 12+ bundle, how the roles and their trust are kept apart, what existing sender and receiver users see, rollback, and what still needs physical testing.
In the unified Mac app both roles read and wrote one "Automatically Allow Connections" key and one per-peer policy dictionary. The sender's setting (on by default) therefore auto-approved any paired Mac connecting to this Mac as a display, and the receiver role's choices changed the sender's. IncomingSessionPolicyStore, RemoteEndpointStore and WakeMetadataStore now take an explicit RolePreferences at every call site. The sender and the receiver-only apps (iPhone/iPad, standalone Mac Receiver) keep the original key names and defaults. The unified app's receiver role uses receiver.- prefixed keys, and its automatic approval defaults to off. Pairing records routing hints in the role of the trust store it pinned into, and the receiver's settings toggle binds the same key the receiver decides with. InputAutoRequestStore's receiver-only key is scoped the same way. RolePreferencesIsolationTests reproduce the leak (12 of 14 failed with the shared keys) and pass with the fix.
…itching Settings gains a "This Mac as a Display" page, separate from the sender's pages: the role switch and status, the receiver name, the sending Macs paired with this role (Automatically Allow Connections, each Mac's Connection Requests, Connect, Forget) and audio with A/V Sync. Its sections are the standalone receiver's own, extracted so both apps share them (ReceiverPairedMacsSection, ReceiverAudioSection, ReceiverNameField). With the role off, paired Macs can still be forgotten (StreamReceiver. removeStoredTrust) without putting this Mac on the network. Overview links to the page. - The Mirror-unavailable "Use Extend" offer is back: the receiver's attention hook opens Settings, which shows the standalone receiver's alert (now MirrorUnavailableOfferAlert). - Changing Dock/menu-bar presentation while the display window is up no longer drops the app out of Command-Tab (MacAppActivationPolicy). - Turning the role off and straight back on waits for the old receiver to release its listeners before starting a new one. - The receiver's own-sender discovery filter reads the sender's id on every update instead of caching a possibly missing one. Tests cover the activation rule, the settings page and search, and guard that the receiver's settings never reach the sender's trust or keys.
…e updated checklist
MeowDisplay's display role and the standalone MeowDisplay Receiver bind the same ports and Bonjour services. A second bind fails with EADDRINUSE (measured; the port is never shared), but that failure was silent: the second receiver retried forever while saying "Waiting for a Mac…", and its pairing listener never retried, so pairing stayed dead after the first quit. Both now take ReceiverOwnershipLock (a kernel flock on /Users/Shared/.meowdisplay-receiver.lock, released by the kernel on quit or crash) before creating any listener. Whichever starts second binds and advertises nothing, shows what to quit, checks again every 2 s and starts by itself; the owner releases only after its listeners closed. MeowDisplay also waits while an already-released standalone receiver (no lock) runs. A stopped StreamReceiver is retired, so a listener retry scheduled before stop() can't reopen a port, and the pairing listener retries a failed bind like the TLS listener.
With "Use This Mac as a Display" on, the sender kept advertising _opendisplay-mac-pair._tcp, so other receivers listed this Mac as one that would share with them, and their Connect went nowhere. The role now withdraws that advertisement and restores it when turned off; receivers use it only for listing and pairing, so reconnect is unaffected. The display role no longer refuses to turn on (or switches itself off at launch) while MeowDisplay Receiver runs: it stays on, says what to quit, and starts by itself afterwards.
…il closed The ownership lock lived in /Users/Shared, where another account could pre-create it, plant a symlink, or hold it to block this Mac's display. - Lock at ~/Library/Application Support/MeowDisplay/receiver.lock, reached with openat(O_DIRECTORY | O_NOFOLLOW) one component at a time from the account's home directory. Every folder must be this user's and not writable by others; the file is opened O_NOFOLLOW | O_NONBLOCK, created 0600 and must be a single-link regular file this user owns (fstat). One location, no fallback, and release never unlinks it. - An unusable lock no longer lets the receiver start: it says what's wrong and where, and Try Again checks again. No automatic retry. - Media and pairing listener bind failures are reported (ReceiverPortHealth) so a port held by another account or app shows as "in use" instead of "Waiting for a Mac". - Tests use a real second process for the lock (both orders, normal exit, SIGKILL, release and reacquire) and for a taken port, plus symlink, folder symlink, hard link, FIFO, foreign owner and permission cases.
This was referenced Oct 25, 2026
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.
Stacked on #25 (which is stacked on #24). Merge #24, then #25, then retarget this PR. Its own diff is 9 commits. Draft: waiting for hardware QA of all three PRs. Not for release.
What changed
com.raisecaterror.meowdisplay.mac, macOS 14, same Sparkle feed) now contains the Mac Receiver role behind Use This Mac as a Display (Overview + menu bar). It's off by default and remembered across launches.startSessiongate refuses every sender session. No self-streaming, no A↔B loops.RolePreferences, required at every call site). The receiver role's automatic approval defaults to off. See below._opendisplay-mac-pair._tcp), so other receivers don't offer it as a Mac to share from.ReceiverOwnershipLock). Whichever starts second waits, says what to quit, and starts by itself afterwards. It also waits for an already-released standalone receiver (found by bundle ID).closingfirst.UNIFIED_MAC_APP.mdfor the comparison, migration, rollback and checklist.Security fix (was the draft blocker)
IncomingSessionPolicyStorekept one global auto-allow key and one per-peer policy dictionary for both roles. The sender's setting (on by default) therefore auto-approved any paired Mac connecting to this Mac as a display, and the receiver's choices changed the sender's. The receiver role now usesreceiver.-prefixed keys with its own off default. Existing sender, iPhone/iPad and standalone receiver keys and defaults are unchanged.RemoteEndpointStoreandWakeMetadataStorehad the same shared-key shape. A peer pinned in one role under an id known to the other could redirect or clear the other role's hints, and the sender's auto-connect enumerated the receiver's hints. They're per role now too.InputAutoRequestStoreis receiver-only but scoped the same way.RolePreferencesIsolationTestsreproduce the leak: 12 of 14 failed with the shared keys, and all pass with the fix.Verification
xcodebuild test -scheme OpenSidecarMac: 1989 tests, 0 failures (43 new on this PR). The lock was also run as two separate processes, in both orders and with the owner killed withkill -9.OpenSidecarMac,OpenSidecarMacReceiver(macOS 12) andOpenSidecariOSbuilds ✅.Built bundle inspected: bundle ID, min 14.0,
LSUIElement,MeowDisplayReceiverRoleTrust = separate, new settings code linked.Not verified:
The 14-step physical checklist (step 13 now covers both launch orders) is in
UNIFIED_MAC_APP.md.Known limitations