// CaptureEngine.swift // GreenshotMac // // ScreenCaptureKit replacement for Greenshot's WindowCapture / GDI capture code. import AppKit import ScreenCaptureKit /// A frozen snapshot of one display, used as the backdrop of the selection overlay. struct DisplaySnapshot { let screen: NSScreen let display: SCDisplay let image: CGImage /// Display bounds in CoreGraphics global coordinates (top-left origin, points). let cgFrame: CGRect var scale: CGFloat { CGFloat(image.width) / cgFrame.width } } /// A window that can be picked in window capture mode. struct WindowInfo { let window: SCWindow /// Window frame in CoreGraphics global coordinates (top-left origin, points). var cgFrame: CGRect { window.frame } var title: String { window.title ?? "" } var applicationName: String { window.owningApplication?.applicationName ?? "" } var bundleID: String? { window.owningApplication?.bundleIdentifier } } enum CaptureEngine { // MARK: - Permission @discardableResult static func ensurePermission() -> Bool { if CGPreflightScreenCaptureAccess() { return true } CGRequestScreenCaptureAccess() return CGPreflightScreenCaptureAccess() } // MARK: - Shareable content static func shareableContent() async throws -> SCShareableContent { do { return try await SCShareableContent.excludingDesktopWindows(true, onScreenWindowsOnly: true) } catch { throw CaptureError.permissionDenied } } /// Windows worth offering to the user: on screen, reasonably sized, not our own overlay. static func selectableWindows(from content: SCShareableContent) -> [WindowInfo] { let ownPID = ProcessInfo.processInfo.processIdentifier return content.windows .filter { window in window.isOnScreen && window.frame.width > 40 && window.frame.height > 40 && window.owningApplication?.processID != ownPID } .map(WindowInfo.init) } // MARK: - Snapshots /// Grabs every display so the selection overlay can show a frozen desktop. static func snapshotAllDisplays() async throws -> [DisplaySnapshot] { let content = try await shareableContent() guard !content.displays.isEmpty else { throw CaptureError.noContent } var snapshots: [DisplaySnapshot] = [] for display in content.displays { guard let screen = ScreenGeometry.screen(for: display.displayID) else { continue } let image = try await capture(display: display, content: content, sourceRect: nil, screen: screen) snapshots.append(DisplaySnapshot(screen: screen, display: display, image: image, cgFrame: CGRect(x: CGFloat(display.frame.origin.x), y: CGFloat(display.frame.origin.y), width: CGFloat(display.width), height: CGFloat(display.height)))) } guard !snapshots.isEmpty else { throw CaptureError.noContent } return snapshots } private static func capture(display: SCDisplay, content: SCShareableContent, sourceRect: CGRect?, screen: NSScreen) async throws -> CGImage { let configuration = SCStreamConfiguration() let scale = screen.backingScaleFactor let rect = sourceRect ?? CGRect(x: 0, y: 0, width: display.width, height: display.height) configuration.sourceRect = rect configuration.width = Int((rect.width * scale).rounded()) configuration.height = Int((rect.height * scale).rounded()) configuration.scalesToFit = false configuration.showsCursor = Configuration.shared.captureMousepointer configuration.captureResolution = .best if #available(macOS 14.0, *) { configuration.ignoreShadowsSingleWindow = true } let ownWindows = content.windows.filter { $0.owningApplication?.processID == ProcessInfo.processInfo.processIdentifier } let filter = SCContentFilter(display: display, excludingWindows: ownWindows) do { return try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: configuration) } catch { throw CaptureError.captureFailed(error.localizedDescription) } } // MARK: - Public capture entry points /// Full screen capture of the display the mouse is on, or of a given screen. static func captureFullscreen(screen requested: NSScreen? = nil) async throws -> Capture { let content = try await shareableContent() let mouseLocation = NSEvent.mouseLocation let screen = requested ?? ScreenGeometry.screen(containing: mouseLocation) ?? NSScreen.main guard let screen, let displayID = ScreenGeometry.displayID(for: screen), let display = content.displays.first(where: { $0.displayID == displayID }) else { throw CaptureError.noContent } let image = try await capture(display: display, content: content, sourceRect: nil, screen: screen) var details = CaptureDetails() details.captureMode = .fullscreen details.title = screen.localizedName details.scaleFactor = screen.backingScaleFactor details.screenBounds = CGRect(x: CGFloat(display.frame.origin.x), y: CGFloat(display.frame.origin.y), width: CGFloat(display.width), height: CGFloat(display.height)) return Capture(image: image, details: details) } /// Captures one window on its own, without whatever overlaps it. static func captureWindow(_ info: WindowInfo) async throws -> Capture { let configuration = SCStreamConfiguration() let screen = ScreenGeometry.screen(containing: ScreenGeometry.appKitRect(fromCG: info.cgFrame).center) ?? NSScreen.main let scale = screen?.backingScaleFactor ?? 2 configuration.width = Int((info.cgFrame.width * scale).rounded()) configuration.height = Int((info.cgFrame.height * scale).rounded()) configuration.showsCursor = false configuration.scalesToFit = false configuration.captureResolution = .best if #available(macOS 14.0, *) { configuration.ignoreShadowsSingleWindow = Configuration.shared.windowCaptureMode != .windowWithShadow } let filter = SCContentFilter(desktopIndependentWindow: info.window) let image: CGImage do { image = try await SCScreenshotManager.captureImage(contentFilter: filter, configuration: configuration) } catch { throw CaptureError.captureFailed(error.localizedDescription) } var details = CaptureDetails() details.captureMode = .window details.title = info.title.isEmpty ? info.applicationName : info.title details.applicationName = info.applicationName details.applicationBundleID = info.bundleID details.scaleFactor = scale details.screenBounds = info.cgFrame return Capture(image: image, details: details) } /// Crops a region out of an already frozen display snapshot. static func crop(snapshot: DisplaySnapshot, cgRect: CGRect, mode: CaptureMode) throws -> Capture { let local = CGRect(x: cgRect.origin.x - snapshot.cgFrame.origin.x, y: cgRect.origin.y - snapshot.cgFrame.origin.y, width: cgRect.width, height: cgRect.height) guard let cropped = ScreenGeometry.crop(snapshot.image, toPointRect: local, scale: snapshot.scale) else { throw CaptureError.captureFailed("The selected region was empty.") } var details = CaptureDetails() details.captureMode = mode details.title = snapshot.screen.localizedName details.scaleFactor = snapshot.scale details.screenBounds = cgRect return Capture(image: cropped, details: details) } // MARK: - Feedback @MainActor static func playFeedback(on rect: CGRect?) { if Configuration.shared.playCameraSound { NSSound(named: NSSound.Name("Grab"))?.play() } if Configuration.shared.showFlashAnimation, let rect { FlashOverlay.flash(appKitRect: ScreenGeometry.appKitRect(fromCG: rect)) } } } extension CGRect { var center: CGPoint { CGPoint(x: midX, y: midY) } } /// Short white flash over the captured area, the equivalent of Greenshot's capture flash. @MainActor final class FlashOverlay { private static var windows: [NSWindow] = [] static func flash(appKitRect rect: CGRect) { guard rect.width > 1, rect.height > 1 else { return } let window = NSWindow(contentRect: rect, styleMask: .borderless, backing: .buffered, defer: false) window.isOpaque = false window.backgroundColor = .white window.alphaValue = 0.7 window.level = .screenSaver window.ignoresMouseEvents = true window.collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary] window.orderFrontRegardless() windows.append(window) NSAnimationContext.runAnimationGroup { context in context.duration = 0.18 window.animator().alphaValue = 0 } completionHandler: { // Run on the main thread by contract, so main actor isolation can be assumed. MainActor.assumeIsolated { window.orderOut(nil) FlashOverlay.windows.removeAll { $0 === window } } } } }