curtain/Sources/Curtain/AppDelegate.swift
acamarata 30d5a77ffa v1.1 — settings window, modular lifecycle actions, login item, logo
Rework Curtain into a proper menu-bar app (Caffeine-style):
- SwiftUI settings window: app, on-start, on-idle, on-end, security, displays
- Modular ActionSet/ActionRunner — per-event toggles (disconnect/lock/screen-off/deactivate)
- Configurable idle timeout; open-at-login via SMAppService; optional menu bar
- Settings persisted in UserDefaults (shared with @AppStorage); salted-SHA256 password
- Drawn curtains logo (menu-bar template + offscreen-rendered app icon)
- Split into single-responsibility modules with comment blocks
2026-06-01 15:51:20 -04:00

43 lines
1.8 KiB
Swift

import Cocoa
/// Purpose: App entry orchestration. Owns the coordinator, the optional menu bar,
/// and the settings window. Keeps logic out of the UI: it just wires
/// callbacks between the pieces.
/// SPORT: MASTER-APP
final class AppDelegate: NSObject, NSApplicationDelegate {
private let coordinator = SessionCoordinator()
private lazy var menuBar = MenuBarController(coordinator: coordinator)
private lazy var prefs = PreferencesWindowController(coordinator: coordinator)
func applicationDidFinishLaunching(_ n: Notification) {
Settings.registerDefaults()
coordinator.onStateChange = { [weak self] active in self?.menuBar.reflect(active: active) }
coordinator.start()
menuBar.onOpenSettings = { [weak self] in self?.prefs.show() }
menuBar.onQuit = { [weak self] in self?.quit() }
if Settings.showInMenuBar { menuBar.show() }
prefs.onMenuBarToggle = { [weak self] on in on ? self?.menuBar.show() : self?.menuBar.hide() }
// First run: no password and no menu bar would be confusing open settings.
if !Settings.hasPassword && !Settings.showInMenuBar { prefs.show() }
if !AXIsProcessTrusted() { requestAccessibility() }
// Reconcile the login-item state with the saved preference.
LoginItem.set(Settings.launchAtLogin)
}
/// Re-opening the app from Finder shows the settings window.
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
prefs.show(); return true
}
private func quit() { coordinator.deactivateNow(); NSApp.terminate(nil) }
private func requestAccessibility() {
let opts = [kAXTrustedCheckOptionPrompt.takeUnretainedValue(): true] as CFDictionary
_ = AXIsProcessTrustedWithOptions(opts)
}
}