mirror of
https://github.com/acamarata/curtain.git
synced 2026-07-01 03:04:25 +00:00
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.
38 lines
1.6 KiB
Swift
38 lines
1.6 KiB
Swift
import Cocoa
|
|
|
|
// Curtain — a privacy curtain for macOS Screen Sharing.
|
|
// Normally launches as a background menu-bar agent (no Dock icon).
|
|
//
|
|
// Hidden build helper: `Curtain --render-icon <dir>` writes an .iconset of PNGs so
|
|
// install.sh can build AppIcon.icns without shipping any image assets.
|
|
|
|
if CommandLine.arguments.contains("--render-icon"),
|
|
let dirIndex = CommandLine.arguments.firstIndex(of: "--render-icon"),
|
|
CommandLine.arguments.indices.contains(dirIndex + 1) {
|
|
CurtainIcon.exportIconset(to: CommandLine.arguments[dirIndex + 1]) // offscreen bitmap render
|
|
exit(0)
|
|
}
|
|
|
|
// The AppKit bootstrap is main-actor work; top-level code is nonisolated under Swift 6,
|
|
// so run it inside an assumeIsolated block (process start is already on the main thread).
|
|
// app.run() blocks here and never returns, so `delegate` and the signal source stay
|
|
// retained on this stack. NSApplication.delegate is weak, so `delegate` must be held.
|
|
MainActor.assumeIsolated {
|
|
let app = NSApplication.shared
|
|
let delegate = AppDelegate()
|
|
app.delegate = delegate
|
|
app.setActivationPolicy(.accessory) // background agent; settings window still shows
|
|
|
|
// SIGTERM (launchd stop / `kill`): a C signal handler can't safely touch AppKit, so
|
|
// ignore the default action and route the signal through a DispatchSource on the main
|
|
// queue, where it can run cleanup before exiting.
|
|
signal(SIGTERM, SIG_IGN)
|
|
let sigtermSource = DispatchSource.makeSignalSource(signal: SIGTERM, queue: .main)
|
|
sigtermSource.setEventHandler {
|
|
MainActor.assumeIsolated { delegate.cleanup() }
|
|
exit(0)
|
|
}
|
|
sigtermSource.resume()
|
|
|
|
app.run()
|
|
}
|