Repository navigation
Allow pairing QR codes on remote desktops with an opt-in setting - #157
Conversation
|
From a pass over all the open pull requests (nothing is decided yet): the only overlaps with other open PRs are mechanical: |
…check Issue Saffsanity#108 (and Saffsanity#137): the pairing window's sharingType = .none hides it from Screen Sharing, Apple Remote Desktop and VNC apps such as Screens, so a Mac used only through them could pair a device over the USB cable alone. This keeps the PR's opt-in switch, off by default and applied at once, and holds it to what Noah asked for: the pairing window only, as a toggle. - Named plainly: "Show the pairing window to screen sharing" (saved as showPairingWindowToScreenSharing), in Settings › Devices after Require pairing, with a footnote saying why it is off. - The rule is PairingWindowSharing (pure): hidden while off, shown (.readOnly, AppKit's default) while on, whoever opened the window, the Mac's user or a device's ask, since a device's tap is how a Mac seen over Screen Sharing pairs. PairDeviceWindowController applies it as the window is made, as each offer shows, and at once on a change (the app delegate's hook, in place of a closure the controller installed over HostSettings). The cable notice and every other window are untouched. - Tests/checks/pairing-sharing (16 checks, 8 of 8 mutants, in CI's mutants matrix) replaces the XCTest target and its `swift test` CI step: this repository's checks are pure swiftc ones. AppModel's init(defaults:), there only for that test, goes with them. - Sill.log says when it changes, and at launch while it is on; -SillSetAfter takes showPairingWindowToScreenSharing=0|1 (and the hook's list now names shareClipboard, which it already took); the previews draw the pane with the switch off in every state and on in a new one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What a user and a developer need to know about Settings › Devices' "Show the pairing window to screen sharing" (issue Saffsanity#108): - README's Pairing bullet: where it is, that it is off by default, and why. - docs/DEVELOPMENT.md: Sill.app's Settings › Devices, and a Pairing at home bullet on pairing a Mac used through Screen Sharing, Apple Remote Desktop or Screens (a Mac without a display): the switch, a tap on the device or the menu, turning it off afterwards, the log line, the bare app's hook. - site/support.html: one item under "If it doesn't work", between `<!-- Pairing window` markers, so a republish before the release that ships it can cut it; docs/release-checklist.md gains that cut beside the clipboard's, with the grep that proves it. - SECURITY.md: the switch among the risks that are known and by design. - docs/home-pairing-plan.md: its open risk of a code in a screen share now says the switch makes it so, by choice. - CLAUDE.md: Layout (PairingWindowSharing, the pairing-sharing check), the defaults key in Build and run, and a Current step entry with what was verified and what is left for a real Screen Sharing session. The App Store's next What's New is the device's changes alone, and this change is Sill for Mac's, so it stays out of it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main gained Saffsanity#153 (the checks' build.sh quote their paths) and Saffsanity#142 (Saved Macs keep a typed destination across a key change). Merged so this branch builds and checks against today's main; no file conflicted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hecked PairDeviceWindowController's mapping from PairingWindowSharing's answer to NSWindow.SharingType (.hidden to .none, .shown to .readOnly) is the one part of the rule that reaches Screen Sharing, and no check compiled it: swapping its two cases would have inverted the switch with CI green (the verifier's finding). The mapping moves into PairingWindowSharing.swift under #if canImport(AppKit). Tests/checks/pairing-sharing imports AppKit for that enum alone (no window, no screen) and checks both values by name, since the enum prints only its type's name: 18 checks, 2 new. Three new mutants (the two cases swapped, hidden told .readOnly, shown told .none): 11 of 11 caught. The checks' README row and CLAUDE.md's Layout say so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The footnote's "(a Mac without a display, say)" and README's "such as
one without a display" came right after "another remote desktop app",
so the example read as an app without a display (the verifier's
finding). Both now say "as you would a Mac without a display", which can
only be the Mac; README keeps its second person ("If you use your
Mac…").
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
§6.2, the pairing window's spec, still said the window is sharingType = .none with nothing else, the one stale statement of it left in the docs (the verifier's finding; the 2026-10-19 triage named this line beside docs/home-pairing-plan.md's, which the branch had updated). It now adds Settings › Devices' "Show the pairing window to screen sharing": off by default, .readOnly while on, whoever opened the window, applied at once to one already open. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…an't The bullet said that while the switch is on, anyone who sees the Mac's screen remotely, "a screen recording or a video call's shared screen included", can read a code, which implied that with it off they cannot. ScreenCaptureKit is reported to ignore .none from macOS 15 (the code's own comments say so), so a recording or a call may show the code either way (the verifier's finding). It now says what the switch changes, what Screen Sharing, Apple Remote Desktop and VNC viewers see, and that a screen recording or a call's shared screen may show the code either way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main gained Saffsanity#110 (Shift-Return and Shift-Tab), Saffsanity#136 (apps in folders, the installed-apps check), Saffsanity#87 (the virtual display's BOOL encoding, the virtual-display-api check) and Saffsanity#88 (a universal Sill.app on request). Merged so this branch builds and checks against today's main. Only ci.yml's one-line mutants matrix conflicted: both sides kept, sorted (46 checks: main's installed-apps and virtual-display-api beside this branch's pairing-sharing; ledger has no mutants). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…d on main CLAUDE.md's entry for the switch says what the verifier found after fa57c2f and how each finding was fixed (the window's sharing type in the pure type and its check, 18 checks and 11 of 11 mutants; the Mac's example in the footnote and README; the remote access plan's §6.2; DEVELOPMENT.md on what the switch changes and what it can't), and what was run again once main was merged in at 4d990e9: a clean release build, all 47 pure checks, the previews against main's and the window server's sharing state on a loopback test host. Tests/checks/README.md names pairing-sharing among the checks whose counts came with their pull request, as main's last merges did for theirs, and DEVELOPMENT.md's list of what the pure checks decide names the pairing window and screen sharing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…affsanity#93, Saffsanity#151) into fix/remote-desktop-qr-code Main brought Send Audio as a beta (Saffsanity#94), Reverse Scrolling (Saffsanity#159), SILL_TEST_NO_SCREEN (Saffsanity#227), the Close timeout and the interrupted picture (Saffsanity#93) and a sleeping display woken for a device (Saffsanity#151). Six files conflicted, each resolved keeping both sides: - .github/workflows/ci.yml: the mutants matrix is main's 51 checks with pairing-sharing, sorted: 52, exactly the folders of Tests/checks with a mutants.py. - Sources/SillMenuBar/HostSettings.swift: Saffsanity#94's sendAudio beside showPairingWindowToScreenSharing in Key and in the registered defaults (sendAudio first, a HostConfig knob next to shareClipboard), as Noah's comment of 2026-10-19 settled the overlap: whichever lands second keeps both. HostConfig's sendAudio, read and saved, and this switch's property merged by themselves. - Sources/SillMenuBar/DebugHooks.swift: -SillSetAfter's list names both keys (sendAudio, and showPairingWindowToScreenSharing=0|1; shareClipboard, which both sides had added, once); both cases of apply merged by themselves. - Tests/checks/README.md: main's rewrapped introduction with this PR's "whether screen sharing shows the pairing window"; the pairing-sharing row beside main's phone-portrait and pointer-control rows; the history's list of checks and their pull requests ends with pairing-sharing and Saffsanity#157. - docs/release-checklist.md: the marker paragraph names every marker once (Clipboard, Keyframe, Display wake, Sound, Reverse Scrolling, Pairing window), the screen sharing grep among those that print nothing, and all six perl lines. - CLAUDE.md: both Current step entries, this PR's first, then main's three; Build and run's defaults list with sendAudio and showPairingWindowToScreenSharing; this PR's entry names this merge, and its verification is this merge's (Tests/checks/run-all.sh, all 52; the previews against main's 74d099c, the Devices pane's eight differ, two new, the other 136 byte for byte). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main gained the pairing window shown to screen sharing (Saffsanity#157, issue Saffsanity#108: Settings › Devices' Show the pairing window to screen sharing, PairingWindowSharing and the pairing-sharing check, README, SECURITY.md, DEVELOPMENT.md and the plans, support.html's item marked <!-- Pairing window). Where it met the connect screen's tiles and groups (Saffsanity#117): - .github/workflows/ci.yml: the mutants matrix is the sorted union, connection-list and pairing-sharing both in (53 checks, exactly the folders with a mutants.py). - Tests/checks/README.md: both rows (connection-list after compatibility, pairing-sharing after pairing-address); the history's first sentence names connection-list with Saffsanity#117 (Saffsanity#116), then Reverse Scrolling's with Saffsanity#159 and pairing-sharing with Saffsanity#157 (Saffsanity#108), and main's re-wrapped audio sentences follow unchanged. Main's lede phrase merged by itself (this branch has none). - docs/release-checklist.md: the marked-words paragraph names Clipboard, Keyframe, Display wake, Sound, Tiles (PR Saffsanity#117), Reverse Scrolling and Pairing window (PR Saffsanity#157) once each, with every grep ('tiles|group name' and 'screen sharing' among them), one to a line; the perl lines merged by themselves, Tiles' and Pairing window's among them, seven in all. On a copy of the merged site/, the seven lines leave no marker and the greps print what the paragraph says. Merged by themselves, each differing from the other parent by exactly that side's hunks: CLAUDE.md (main's Current step entry for Saffsanity#157 at the top, this branch having none; Layout's PairingWindowSharing beside the tiles' paragraph; the checks list with connection-list and pairing-sharing; Build and run's defaults list with showPairingWindowToScreenSharing beside the tiles' -SillSettingsScript disconnect step) and docs/DEVELOPMENT.md (main's Through Screen Sharing item and Settings › Devices sentence, the tiles' paragraphs and harness lines). site/privacy.html (the Tiles items) and site/support.html (the Pairing window item) changed on one side each, inside their own markers; the project file on this branch's side only (plutil OK, each id once). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both sides kept where they met. CLAUDE.md's Current step has Saffsanity#147's entry, then Saffsanity#157's (the pairing window to screen sharing), then the sound's; Saffsanity#147's header now says main at 76bc100. Layout, the checks list and the defaults list merged by themselves: PairingWindowSharing, pairing-sharing and showPairingWindowToScreenSharing beside TextComposition and text-composition. CI's mutants matrix is the union, sorted: 53 checks, pairing-sharing from main and text-composition from here, exactly the folders with a mutants.py. The checks README's lede names what an input method or dictation sends the Mac and whether screen sharing shows the pairing window; both rows and both history clauses merged by themselves. docs/DEVELOPMENT.md's pure-checks paragraph names both too; its "Through Screen Sharing" bullet and "Other languages and dictation" merged by themselves. docs/release-checklist.md's paragraph on marked words names every marker once (Clipboard, Keyframe, Display wake, Other languages, Sound, Reverse Scrolling, Pairing window) with every grep, the dictation one and the screen sharing one among them; the perl lines, one per marker, merged by themselves. The cuts tried on a copy of site/ leave the greps only privacy.html's clipboard log line. README.md's How it works and Pairing, and site/support.html's Other languages paragraph and Pairing window item, each inside its own markers, merged by themselves. The next What's New is unchanged at 1,571 characters (Saffsanity#157 is the Mac's alone); privacy.html was changed by neither side. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Only CI's mutants matrix conflicted: both new checks kept, in order (capture-scale, pairing-sharing). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ring) into goodbye-sleep Main moved on while the review's fixes were made. Only CLAUDE.md conflicted: both Current step entries kept whole, this branch's first, then Saffsanity#157's. DebugHooks.swift merged by itself, this branch's -SillWakeAfter in the timed hooks beside Saffsanity#157's showPairingWindowToScreenSharing; Tests/checks/README.md, DEVELOPMENT.md and ci.yml's matrix (pairing-sharing added, still sorted) likewise. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main moved from 74d099c (where this branch began) to 48a4ef3: #157, the pairing window shown to screen sharing (Settings › Devices' switch, PairingWindowSharing and its check), then #147, Chinese input and dictation (TextComposition, the input view as a UITextInput, its check). Neither touched DiscoveryPolicy.swift, StreamClient+Home.swift, any other file the checks that compile DiscoveryPolicy.swift compile, or those checks (home-device, home-model and policy with records.swift, which this branch changed, and remote-rules). Where they met: - CLAUDE.md: both sides put an entry at the top of Current step. All three kept, this branch's first (a Mac's row keeps its TXT record when the cable goes), then main's two in main's order (#147, then #157). Both sides' Layout lines (DiscoveryPolicy's rowRecords; TextComposition, PairingWindowSharing and the two new checks) and main's key in the defaults list (showPairingWindowToScreenSharing) merged by themselves. - Tests/checks/README.md: main re-wrapped the history paragraph's last sentence (after adding "pairing-sharing with #157"), where this branch had appended its own sentence (home-device, home-model and policy grew with cable-unplug-row): main's wrap, then this branch's sentence. The table's rows (this branch's home-device, home-model and policy; main's pairing-sharing and text-composition), main's lede and its text-composition paragraph merged by themselves. - Merged by themselves: iOSClient/StreamClient.swift (this branch's recomputeMacs reading rows through networkRecords and nearbyRecords and record(of:) in place of tag(of:) and door(of:); main's onCaretMayMove and sendAfterInput, apart from it; no new caller of the two functions this branch removed) and docs/DEVELOPMENT.md (this branch's bullet in Pairing at home, beside #157's sentences there). - One side's alone: ci.yml's mutants matrix is main's (53, exactly the folders with a mutants.py, sorted: pairing-sharing and text-composition added; this branch added no folder); docs/release-checklist.md and site/ are main's (each marker once, with its perl line and grep). Verified on this merge (Xcode 27.1): Tests/checks/run-all.sh, all 53; the mutants of the four checks that compile DiscoveryPolicy.swift, every one caught: home-device 203 checks and 129 of 129, home-model 73 and 24 of 24, policy 458 and 163 of 163 (this branch's T1-T5 on rowRecords among them), remote-rules 144 and 76 of 76; swift build -c release, only the CaptureProbe warning; the iOS app, Debug for the generic simulator as CI builds it (CODE_SIGNING_ALLOWED=NO ARCHS=arm64), only the known StreamClient capture warning (StreamClient.swift:3899). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Main moved again, from 48a4ef3 (merged in just before) to b4caa91: #233, capture at the display's own pixel scale (CaptureScale and its check; HostConfig, InputInjector, StreamCoordinator and WindowCapture). Host only, as this branch is device only: no Swift file on both sides (Sources/ and Package.swift are main's, iOSClient/ this branch's). Where they met: - CLAUDE.md: both sides put an entry at the top of Current step again. This branch's first, then #233's, then #147's and #157's as before. #233's Layout lines merged by themselves beside this branch's. - Tests/checks/README.md: #233's lede words and its capture-scale row merged by themselves beside this branch's rows (home-device, home-model, policy) and its history sentence. - docs/DEVELOPMENT.md merged by itself (#233's words on the capture's scale; this branch's bullet in Pairing at home). - ci.yml's mutants matrix is main's: 54, capture-scale added, exactly the folders with a mutants.py, sorted (this branch added none). docs/release-checklist.md and site/ are main's, untouched by #233. Verified on this merge (Xcode 27.1): Tests/checks/run-all.sh, all 54 (capture-scale 79 among them; home-device 203, home-model 73, policy 458, remote-rules 144); swift build -c release from a git archive of this tree, only the CaptureProbe warning; the iOS app, Debug for the generic simulator as CI builds it (CODE_SIGNING_ALLOWED=NO ARCHS=arm64), only the known StreamClient capture warning (StreamClient.swift:3899). #233 changed no file that the four checks compiling DiscoveryPolicy.swift compile, so their mutants on the merge before this one (392, every one caught) stand. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thank you @scripsweave, your opt-in switch is in the Mac nightly: Settings › Devices › "Show the pairing window to screen sharing" lets a headless Mac pair through Screen Sharing or Apple Remote Desktop, and I've let the folks on #108 and #137 know. Sill for iPhone and iPad 0.5.5 (5) is on TestFlight too; nightlies build as 0.5.5 with rising build numbers and Sill's update check never offers them, and 0.5.1 stays the supported release. Please tell me here how it goes on your setup! |
Fixes #108.
The pairing window always sets
NSWindow.sharingTypeto.none, so Apple Remote Desktop hides the QR code and typed pairing code. Add Display QR code on remote desktops in Settings → Devices. It is off by default and persists across launches. Enabling it sets the pairing window to.readOnly; changing it updates an existing window immediately, without restarting the host or regenerating its code.The setting explains that viewers of a shared screen can use the pairing code. Sill's own desktop streams and window catalog continue excluding Sill's windows. Other dialogs keep their existing behavior.
Validation:
swift test: both new regression tests pass (default, persistence, existing/new window sharing, and no host reconfiguration). Uses isolated preference suites and never starts the host.swift build -c release: passes; existing CaptureProbe pointer warning only.Scripts/make-app.sh: builds and verifies a locally signed app bundle.Tests/checks/run-all.sh: all existing check suites pass.An Apple Remote Desktop connection has not been tested end to end; the regression test verifies the actual AppKit window's sharing mode.