Detection: netstat lives at /usr/sbin/netstat, not /usr/bin — the hardcoded wrong path silently killed the ESTABLISHED-TCP activator (root cause of the failed live test). Fixed and live-verified. Added peered-UDP activator (5900-5902) for High-Performance sessions, per-signal transition logging, unconditional error logging for dead probe helpers, and probe v2 with full CGSession dictionary diffing. 7 new parser tests (32 total). Fixes from a full audit + adversarial review: idle source setting honored (default now Remote session activity), cover scope reduced to a coherent two-mode model with legacy migration (per-display toggle was inverted in onlyMarked and dead in all), curtain test no longer schedules a teardown over a live session, specific-display password box placement gets a real picker, refuse-to-arm enforced, activation notification posts a real banner, menu password gate bypassed when the event tap is dead, shared single-decoder aerial player with stale-task guard and async playability check, password buffer zeroed on successful unlock and Esc, XPC interruption/invalidation handlers, modern Accessibility settings URL, launchPath modernized, codesign failures now abort release.sh, monotonic CFBundleVersion, install.sh temp cleanup, dead armDisarmHotkey setting removed. Refactor: Curtain.swift and PreferencesWindow.swift split into focused files (largest now 479 lines). Wiki, README, and contributing docs updated to match. Build clean at 0 warnings, 32/32 tests pass.
4 KiB
Installation
Prerequisites
- macOS 13 (Ventura) or later. Apple Silicon recommended.
- Screen Sharing enabled: System Settings → General → Sharing → Screen Sharing.
Install
- Download
Curtain-1.0.0.dmgfrom the GitHub Releases page. - Open the DMG.
- Drag
Curtain.appinto theApplicationsfolder. - Launch Curtain from
/Applications.
On first launch an onboarding flow walks you through setup: Welcome → grant Accessibility → optional disconnect helper → optional password → finish. When it completes, the curtains icon appears in the menu bar.
First launch: Gatekeeper
Curtain is currently ad-hoc signed, not yet notarized. On a clean download macOS Gatekeeper blocks the first launch and reports the app is damaged or from an unidentified developer. This is expected for now.
Clear the quarantine flag once, then open the app normally:
xattr -dr com.apple.quarantine /Applications/Curtain.app
Then double-click Curtain.app. Right-clicking and choosing Open is no longer enough on recent macOS, so use the command above. Once a notarized build ships, this step goes away and Curtain opens straight from the DMG.
Grant Accessibility
Curtain needs Accessibility permission to block the desk keyboard and mouse. Without it, Curtain refuses to show the cover at all and posts a notification instead. This prevents putting up a screen that cannot be unlocked. The emergency hotkey Control + Option + Command + U always works regardless.
The onboarding flow deep-links you straight to the right pane. You can also open it yourself:
- Open System Settings → Privacy & Security → Accessibility.
- Find Curtain in the list and turn it on.
- Relaunch Curtain so the new permission takes effect.
If Curtain does not appear in the Accessibility list, launch it once from /Applications, then check again.
After every rebuild of a local ad-hoc build, re-grant Accessibility. Rebuilding produces a new code signature and macOS does not carry over the old grant automatically.
Open at login
Curtain manages login startup itself with SMAppService. Turn on Open at login in the settings window. macOS tracks this under System Settings → General → Login Items, where you can also toggle it off. There is no LaunchAgent and no plist to manage by hand.
Set a password
Open the settings window (click the menu-bar icon) and type a password in the Security section. This is what someone at the desk types to get past the curtain.
If you never set a password, the default is curtain. The password is stored as a salted PBKDF2-HMAC-SHA256 hash in UserDefaults. The plaintext is never saved.
Disconnect helper (optional)
The optional "disconnect the remote session" feature is off by default. When you enable it (in settings or during onboarding), Curtain registers a privileged helper through SMAppService and asks for one approval in System Settings. There is no sudoers rule.
Under the current ad-hoc build, this helper may fail to register. The privileged-helper path needs a notarized or Developer ID signed build to install cleanly. Until then, leave the feature off or expect the registration to be rejected.
Mark DisplayLink monitors (if you have them)
If any external monitor is DisplayLink, open Settings → Displays and mark it as DisplayLink. This tells Curtain to use a capturable cover mode for that display.
Displays are identified by a stable UUID, so the marking survives reboots and reconnects. Detection works with both classic and high-performance Screen Sharing.
See How It Works for why this matters.
Confirm Curtain is running
pgrep -fl Curtain
You can also open Activity Monitor and search for Curtain.
Uninstall
Quit Curtain, then drag Curtain.app from /Applications to the Trash.
If you had an older script-based install on this machine, Scripts/uninstall.sh in the repo cleans up any legacy LaunchAgent, helper binary, or sudoers rule left behind.