import AppKit import SwiftUI import ShotdeckCore // SwiftPM treats a file named main.swift as top-level code, which forbids `@main`. // App.main() is the equivalent entry point. if ProcessInfo.processInfo.environment["SHOTDECK_SNAPSHOT_DIR"] != nil { MainActor.assumeIsolated { PanelSnapshot.runIfRequested() } } if ProcessInfo.processInfo.environment["REDLINE_SELFTEST_PHASE"] == "onedrive" { MainActor.assumeIsolated { PickerSelfTest.runOneDriveOnlyIfRequested() } } if ProcessInfo.processInfo.environment["SHOTDECK_PICKER_SELFTEST"] != nil { MainActor.assumeIsolated { PickerSelfTest.runIfRequested() } } ShotdeckApp.main() struct ShotdeckApp: App { @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate var body: some Scene { MenuBarExtra { MenuBarView() .environment(appDelegate.model) } label: { let state = appDelegate.model.iconState HStack(spacing: 4) { Image(systemName: state.symbolName) if let count = state.countText { Text(count).font(.system(size: 11, weight: .semibold)) } } .accessibilityLabel("Redline") } .menuBarExtraStyle(.window) } } @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { let model: AppModel override init() { NSApplication.shared.setActivationPolicy(.accessory) model = AppDelegate.makeLaunchModel() super.init() } func applicationDidFinishLaunching(_ notification: Notification) { Task { await model.bootstrap() } } /// Builds the model exactly the way the real app launches: paths come from /// `TransportSettings.resolvedAppSupportPaths()` — transport-aware, so the watcher /// this feeds is never seeded with a stale AirDrop folder while OneDrive is the /// persisted transport (that was the BLOCKER this function used to have, when it /// called the AirDrop-only `FolderSettings.resolvedAppSupportPaths()` instead). /// `appSupportRoot` exists only so PickerSelfTest's relaunch-simulation sub-step can /// point this at a temp directory instead of the real /// ~/Library/Application Support/Shotdeck — production always calls this with no /// argument (the real root). Internal, not private, for that same reason. static func makeLaunchModel(appSupportRoot: URL? = nil) -> AppModel { do { let paths = try TransportSettings.resolvedAppSupportPaths(root: appSupportRoot) return try makeModel(paths: paths) } catch { Log.ui.critical( "AppModel init failed: \(String(describing: error), privacy: .public)" ) let tmp = FileManager.default.temporaryDirectory let fallbackRoot = tmp.appendingPathComponent("Shotdeck-fallback", isDirectory: true) // Safe: temp-dir creation for a path this process controls cannot legitimately fail. let fallback = try! AppSupportPaths(root: fallbackRoot, outbox: tmp, watchFolder: tmp) let model = try! makeModel(paths: fallback) model.setStatus("Redline could not access its storage folder. Captures will not persist.") return model } } private static func makeModel(paths: AppSupportPaths) throws -> AppModel { let ledger = try ReturnLedger(paths: paths) return AppModel( paths: paths, spool: try SpoolStore(paths: paths), composer: PDFComposer(), capturer: ScreenCapturer(), hotkeys: HotkeyCenter(), picker: RegionPickerController(), ledger: ledger, watcher: ReturnWatcher(paths: paths, ledger: ledger) ) } }