Root cause: RegionPickerView lacked acceptsFirstMouse — as an LSUIElement accessory app Shotdeck is never active when the hotkey fires, so the user's first click on the overlay was refused and the drag never started. Also plumbs the monitored event's location through the controller (hardware-cursor reads made the chain untestable). Adds PickerSelfTest (SHOTDECK_PICKER_SELFTEST): posts synthetic mouse events through the app's own queue, asserts the exact CaptureRegion, saves a mid-drag overlay bitmap. Coordinator ran it: PICKER-SELFTEST PASS rect=(200.0, 729.0, 400.0, 300.0); overlay bitmap shows dim+punch+chip. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
82 lines
2.8 KiB
Swift
82 lines
2.8 KiB
Swift
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["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("Shotdeck")
|
|
}
|
|
.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() }
|
|
}
|
|
|
|
private static func makeLaunchModel() -> AppModel {
|
|
do {
|
|
let paths = try FolderSettings.resolvedAppSupportPaths()
|
|
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("Shotdeck 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)
|
|
)
|
|
}
|
|
}
|