// AppDelegate.swift // GreenshotMac import AppKit @MainActor final class AppDelegate: NSObject, NSApplicationDelegate { private var statusMenu: StatusMenuController? func applicationDidFinishLaunching(_ notification: Notification) { // A second copy would install its own menu bar item and register the same global // hotkeys, which the first copy already owns. Hand over to the running instance. if let other = AppDelegate.otherRunningInstance() { NSLog("GreenshotMac: another instance is already running (pid \(other.processIdentifier)); exiting.") other.activate() Notifier.show(title: "GreenshotMac is already running", body: "Use the camera icon in the menu bar.") NSApp.terminate(nil) return } NSApp.setActivationPolicy(.accessory) Theme.apply() NotificationCenter.default.addObserver(forName: .appearanceChanged, object: nil, queue: .main) { _ in MainActor.assumeIsolated { Theme.apply() } } statusMenu = StatusMenuController() MainMenuBuilder.install() HotkeyBindings.shared.registerAll() if !CGPreflightScreenCaptureAccess() { // Triggers the system prompt on first launch so the first capture just works. CGRequestScreenCaptureAccess() } } /// Another process running the same bundle identifier, if there is one. static func otherRunningInstance() -> NSRunningApplication? { guard let bundleID = Bundle.main.bundleIdentifier else { return nil } let current = NSRunningApplication.current return NSRunningApplication.runningApplications(withBundleIdentifier: bundleID) .first { $0.processIdentifier != current.processIdentifier && !$0.isTerminated } } func applicationWillTerminate(_ notification: Notification) { HotkeyManager.shared.unregisterAll() } func applicationSupportsSecureRestorableState(_ app: NSApplication) -> Bool { true } func application(_ application: NSApplication, open urls: [URL]) { for url in urls { guard let image = NSImage(contentsOf: url), let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { continue } var details = CaptureDetails() details.captureMode = .importedFile details.title = url.deletingPathExtension().lastPathComponent details.scaleFactor = CGFloat(cgImage.width) / max(1, image.size.width) EditorWindowController.open(with: Capture(image: cgImage, details: details)) } } }