mirror of
https://github.com/acamarata/curtain.git
synced 2026-06-30 18:54: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.
53 lines
2.6 KiB
Swift
53 lines
2.6 KiB
Swift
import Foundation
|
|
import UserNotifications
|
|
|
|
/// Purpose: Thin wrapper over UNUserNotificationCenter for the app's banners. Replaces
|
|
/// the deprecated NSUserNotification path with the modern UserNotifications API.
|
|
/// Inputs: title/body strings; optional throttleKey + window to suppress repeats.
|
|
/// Outputs: an immediate (nil-trigger) user notification, or nothing when throttled.
|
|
/// Constraints: @MainActor — touches shared throttle state and the notification center
|
|
/// from a single context to satisfy Swift 6 strict concurrency. Every call is
|
|
/// defensive: a missing bundle id or unavailable center must never crash the
|
|
/// agent, so failures are logged and swallowed. Authorization is best-effort.
|
|
/// SPORT: MASTER-NOTIFIER
|
|
@MainActor
|
|
enum Notifier {
|
|
|
|
/// Last post time per throttle key, used to suppress rapid repeats.
|
|
private static var lastPost: [String: Date] = [:]
|
|
|
|
/// Ask once (at launch) for permission to show banners and play sounds. The result
|
|
/// is ignored: if the user declines, post(...) simply becomes a no-op silently.
|
|
static func requestAuthorization() {
|
|
guard Bundle.main.bundleIdentifier != nil else {
|
|
NSLog("Curtain: skipping notification authorization — no bundle identifier")
|
|
return
|
|
}
|
|
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { _, error in
|
|
if let error { NSLog("Curtain: notification authorization failed: \(error.localizedDescription)") }
|
|
}
|
|
}
|
|
|
|
/// Post an immediate banner. When `throttleKey` is set and `throttleSeconds > 0`,
|
|
/// repeats inside the window are dropped. Safe to call from anywhere; hops to main.
|
|
static func post(title: String, body: String, throttleKey: String? = nil, throttleSeconds: TimeInterval = 0) {
|
|
Task { @MainActor in
|
|
guard Bundle.main.bundleIdentifier != nil else { return }
|
|
|
|
if let key = throttleKey, throttleSeconds > 0 {
|
|
let now = Date()
|
|
if let last = lastPost[key], now.timeIntervalSince(last) < throttleSeconds { return }
|
|
lastPost[key] = now
|
|
}
|
|
|
|
let content = UNMutableNotificationContent()
|
|
content.title = title
|
|
content.body = body
|
|
|
|
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: nil)
|
|
UNUserNotificationCenter.current().add(request) { error in
|
|
if let error { NSLog("Curtain: failed to post notification: \(error.localizedDescription)") }
|
|
}
|
|
}
|
|
}
|
|
}
|