// CaptureCoordinator.swift // GreenshotMac // // Drives the capture flows, the macOS counterpart of Greenshot's CaptureHelper. import AppKit import UniformTypeIdentifiers @MainActor final class CaptureCoordinator { static let shared = CaptureCoordinator() private var overlay: CaptureOverlayController? private var isCapturing = false private var lastRegion: CGRect? private init() {} var hasLastRegion: Bool { lastRegion != nil } // MARK: - Entry points func captureRegion() { beginOverlayCapture(mode: .region) } func captureWindow() { beginOverlayCapture(mode: .window) } func captureFullscreen() { guard !isCapturing, CaptureEngine.ensurePermission() else { return requestPermission() } isCapturing = true Task { defer { isCapturing = false } do { try await delayIfNeeded() let capture = try await CaptureEngine.captureFullscreen() finish(capture) } catch { report(error) } } } func captureLastRegion() { guard let lastRegion else { return captureRegion() } guard !isCapturing, CaptureEngine.ensurePermission() else { return requestPermission() } isCapturing = true Task { defer { isCapturing = false } do { try await delayIfNeeded() let snapshots = try await CaptureEngine.snapshotAllDisplays() guard let snapshot = snapshots.first(where: { $0.cgFrame.intersects(lastRegion) }) else { throw CaptureError.noContent } let capture = try CaptureEngine.crop(snapshot: snapshot, cgRect: lastRegion, mode: .lastRegion) finish(capture) } catch { report(error) } } } /// Opens an image file in the editor, the equivalent of Greenshot's "Open image from file". func openImageFile() { let panel = NSOpenPanel() panel.allowedContentTypes = [.image] panel.allowsMultipleSelection = false NSApp.activate(ignoringOtherApps: true) guard panel.runModal() == .OK, let url = panel.url, let image = NSImage(contentsOf: url), let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else { return } 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)) } // MARK: - Overlay flow private func beginOverlayCapture(mode: OverlayMode) { guard !isCapturing else { return } guard CaptureEngine.ensurePermission() else { return requestPermission() } isCapturing = true Task { do { try await delayIfNeeded() let content = try await CaptureEngine.shareableContent() let snapshots = try await CaptureEngine.snapshotAllDisplays() let windows = CaptureEngine.selectableWindows(from: content) let controller = CaptureOverlayController(snapshots: snapshots, windows: windows, mode: mode) overlay = controller controller.present { result in self.overlay = nil self.isCapturing = false self.handle(result) } } catch { isCapturing = false report(error) } } } private func handle(_ result: OverlayResult) { switch result { case .cancelled: return case .region(let snapshot, let rect): lastRegion = rect do { let capture = try CaptureEngine.crop(snapshot: snapshot, cgRect: rect, mode: .region) finish(capture) } catch { report(error) } case .window(let info): Task { do { let capture = try await CaptureEngine.captureWindow(info) finish(capture) } catch { report(error) } } } } // MARK: - Shared private func finish(_ capture: Capture) { CaptureEngine.playFeedback(on: capture.details.screenBounds) Destinations.export(capture, to: Configuration.shared.defaultDestination) } private func delayIfNeeded() async throws { let millis = Configuration.shared.captureDelayMillis guard millis > 0 else { return } try await Task.sleep(nanoseconds: UInt64(millis) * 1_000_000) } private func report(_ error: Error) { if let captureError = error as? CaptureError, case .cancelled = captureError { return } Destinations.presentError(error) } private func requestPermission() { let alert = NSAlert() alert.messageText = "Screen Recording permission needed" alert.informativeText = """ macOS requires explicit permission before an app can capture the screen. Open System Settings › Privacy & Security › Screen Recording, enable GreenshotMac, then try again. """ alert.addButton(withTitle: "Open System Settings") alert.addButton(withTitle: "Cancel") NSApp.activate(ignoringOtherApps: true) if alert.runModal() == .alertFirstButtonReturn, let url = URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_ScreenCapture") { NSWorkspace.shared.open(url) } } }