import AppKit import CoreGraphics import ScreenCaptureKit import ApplicationServices final class CaptureController: NSObject { static let shared = CaptureController() private var overlays: [OverlayWindow] = [] private(set) var isSelecting = false private var previousApp: NSRunningApplication? private var overlayIDs: [Int] = [] // Selection state (driven by the event tap; AppKit global coordinates) private var marqueeStart: NSPoint? private(set) var marqueeRect: NSRect? private(set) var hoverWindow: (id: CGWindowID, frame: NSRect, pid: Int)? var hoverFrame: NSRect? { hoverWindow?.frame } private var trackedWindows: [(id: CGWindowID, frame: NSRect, pid: Int)] = [] private var lastWindowScan = Date.distantPast private var eventTap: CFMachPort? /// Session event log for debugging (dumped via Copy Diagnostics). /// Also appended to /tmp/kapture-debug.log so it survives relaunches. static var sessionLog: [String] = [] static func log(_ line: String) { let stamped = String(format: "%@ %@", DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium), line) sessionLog.append(stamped) if sessionLog.count > 200 { sessionLog.removeFirst() } NSLog("[Kapture] \(line)") let path = "/tmp/kapture-debug.log" if let handle = FileHandle(forWritingAtPath: path) { handle.seekToEndOfFile() handle.write((stamped + "\n").data(using: .utf8)!) handle.closeFile() } else { FileManager.default.createFile(atPath: path, contents: (stamped + "\n").data(using: .utf8)) } } /// Diagnostic snapshot for "Copy Diagnostics" menu item. func diagnostics() -> String { var lines: [String] = [] lines.append("Kapture diagnostics — version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] ?? "?")") lines.append("accessibility=\(AXIsProcessTrusted()) eventTap=\(eventTap != nil)") lines.append("--- session log (newest last) ---") lines.append(contentsOf: Self.sessionLog) lines.append("--- screens ---") for (i, s) in NSScreen.screens.enumerated() { let scale = s.backingScaleFactor let cgID = (s.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0 lines.append("screen[\(i)]: frame=\(s.frame) scale=\(scale) backingSize=\(s.frame.width*scale)x\(s.frame.height*scale) cgID=\(cgID)") } if #available(macOS 14.0, *) { Task { if let content = try? await SCShareableContent.current { for (i, d) in content.displays.enumerated() { lines.append("SCDisplay[\(i)]: frame=\(d.frame) width=\(d.width)x\(d.height) id=\(d.displayID)") } } else { lines.append("SCShareableContent: unavailable (no Screen Recording permission?)") } let text = lines.joined(separator: "\n") let pb = NSPasteboard.general pb.clearContents() pb.setString(text, forType: .string) } } else { let text = lines.joined(separator: "\n") let pb = NSPasteboard.general pb.clearContents() pb.setString(text, forType: .string) } return lines.joined(separator: "\n") } // MARK: - Session lifecycle /// Frozen mode: the screen is snapshotted at hotkey press, so the pointer /// and any hover state (tooltips, open menus) end up in the capture. private(set) var isFrozenMode = false /// Recording mode: the selection flow ends in a recording, not a still. private(set) var isRecordingMode = false private var frozenSnapshots: [ScreenCapture.DisplaySnapshot] = [] func begin(frozen: Bool = false, recording: Bool = false) { guard !isSelecting else { return } // No new captures while a recording is running (the selection // overlays would end up in the video). guard !RecordingController.shared.state.isRecording else { return } guard PermissionManager.isGranted else { PermissionManager.request() return } isSelecting = true isFrozenMode = frozen && !recording isRecordingMode = recording previousApp = NSWorkspace.shared.frontmostApplication // With the event tap driving input we must NOT activate: stealing // focus turns the target app into background mode (dimmed chrome), // and that is what would end up in the screenshot. Activate only on // the fallback path, where panel events are the only input. let usesTap = AXIsProcessTrusted() if !usesTap { NSApp.activate(ignoringOtherApps: true) } CaptureController.log("begin: frozen=\(isFrozenMode) recording=\(recording) usesTap=\(usesTap) active=\(NSApp.isActive)") if frozen { ScreenCapture.shared.snapshotAllDisplays(includeCursor: true) { [weak self] snapshots in DispatchQueue.main.async { guard let self, self.isSelecting else { return } self.frozenSnapshots = snapshots CaptureController.log("frozen: captured \(snapshots.count) displays") self.showOverlays() } } } else { showOverlays() } } private func showOverlays() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in guard let self, self.isSelecting else { return } self.overlays = NSScreen.screens.map { screen in let panel = OverlayWindow(screen: screen, controller: self) if let view = panel.contentView as? OverlayView { view.controller = self view.frozenImage = self.frozenSnapshots.first(where: { $0.frame == screen.frame })?.image } return panel } // orderFrontRegardless: show without taking key/focus. self.overlays.forEach { $0.orderFrontRegardless() } self.overlayIDs = self.overlays.map { $0.windowNumber } CaptureController.log("begin: \(self.overlays.count) overlays, ids=\(self.overlayIDs) — prior app stays focused") self.installEventTap() } } /// A window was picked from hover — capture it directly (real rounded /// corners come from the window's own alpha). private func finishWindowPick(_ windowID: CGWindowID, _ rect: NSRect, ownedRisk: Bool) { let ids = overlayIDs let recording = isRecordingMode hideOverlays() isSelecting = false restorePreviousApp() if recording { // Owned-popup risk: record the screen region instead of the // window filter, which could composite the parent into the video. RecordingController.shared.begin( region: rect, windowID: ownedRisk ? nil : windowID, excludingWindowIDs: ids ) return } DispatchQueue.main.async { [self] in if ownedRisk { capture(rect, excludingWindowIDs: ids, roundedCorners: true) } else { capture(rect, excludingWindowIDs: ids, windowID: windowID) } } } private func finishSelection(_ rect: NSRect) { let ids = overlayIDs let frozen = frozenSnapshots let wasFrozen = isFrozenMode let recording = isRecordingMode hideOverlays() guard rect.width >= 2, rect.height >= 2 else { cancelSelection() return } if wasFrozen { let midpoint = NSPoint(x: rect.midX, y: rect.midY) let snapshot = frozen.first(where: { $0.frame.contains(midpoint) }) ?? frozen.first isSelecting = false restorePreviousApp() DispatchQueue.main.async { [self] in if let snapshot, let image = ScreenCapture.cropFrozen(snapshot, to: rect) { CaptureController.log("frozen: cropped \(Int(rect.width))x\(Int(rect.height)) -> \(image.width)x\(image.height)") ResultWindow.shared.show(image: image) } else { ResultWindow.shared.show(image: ResultWindow.failureImage("Could not crop the frozen screen.")) } } return } if recording { isSelecting = false restorePreviousApp() RecordingController.shared.begin(region: rect, windowID: nil, excludingWindowIDs: ids) return } DispatchQueue.main.async { [self] in capture(rect, excludingWindowIDs: ids) } } private func cancelSelection() { hideOverlays() isSelecting = false restorePreviousApp() } private func hideOverlays() { removeEventTap() overlays.forEach { $0.orderOut(nil) } overlays = [] marqueeRect = nil hoverWindow = nil marqueeStart = nil frozenSnapshots = [] isFrozenMode = false isRecordingMode = false } private func restorePreviousApp() { previousApp?.activate(options: [.activateIgnoringOtherApps]) previousApp = nil } private func renderOverlays() { for overlay in overlays { (overlay.contentView as? OverlayView)?.needsDisplay = true } } // MARK: - Event tap private func installEventTap() { guard eventTap == nil else { return } guard AXIsProcessTrusted() else { CaptureController.log("tap: accessibility not granted — falling back to panel events") PermissionManager.requestAccessibility() return } let mask: CGEventMask = (1 << CGEventType.mouseMoved.rawValue) | (1 << CGEventType.leftMouseDown.rawValue) | (1 << CGEventType.leftMouseDragged.rawValue) | (1 << CGEventType.leftMouseUp.rawValue) | (1 << CGEventType.keyDown.rawValue) guard let tap = CGEvent.tapCreate( tap: .cghidEventTap, place: .headInsertEventTap, options: .defaultTap, eventsOfInterest: CGEventMask(mask), callback: Self.tapCallback, userInfo: Unmanaged.passUnretained(self).toOpaque() ) else { CaptureController.log("tap: creation FAILED — falling back to panel events") return } eventTap = tap if let source = CFMachPortCreateRunLoopSource(nil, tap, 0) { CFRunLoopAddSource(CFRunLoopGetMain(), source, .commonModes) } CGEvent.tapEnable(tap: tap, enable: true) CaptureController.log("tap: installed") } private func removeEventTap() { guard let tap = eventTap else { return } CGEvent.tapEnable(tap: tap, enable: false) if let source = CFMachPortCreateRunLoopSource(nil, tap, 0) { CFRunLoopRemoveSource(CFRunLoopGetMain(), source, .commonModes) } CFMachPortInvalidate(tap) eventTap = nil CaptureController.log("tap: removed") } private static let tapCallback: CGEventTapCallBack = { _, type, event, refcon in guard let refcon else { return Unmanaged.passUnretained(event) } let controller = Unmanaged.fromOpaque(refcon).takeUnretainedValue() if let result = controller.handleTap(type, event) { return Unmanaged.passUnretained(result) } return nil } /// Convert a CG global point (top-left origin) to AppKit global. private func cgPointToAppKit(_ p: NSPoint) -> NSPoint { guard let mainScreen = NSScreen.screens.first else { return p } return NSPoint(x: p.x, y: mainScreen.frame.height - p.y) } /// Scan on-screen windows (AppKit global frames), front to back. private func refreshTrackedWindows() { guard let list = CGWindowListCopyWindowInfo( [ .optionOnScreenOnly, .excludeDesktopElements ], kCGNullWindowID ) as? [[String: Any]] else { trackedWindows = []; return } let ownIDs = Set(overlayIDs) var found: [(id: CGWindowID, frame: NSRect, pid: Int)] = [] for info in list { guard let cid = info[kCGWindowNumber as String] as? CGWindowID, let bounds = info[kCGWindowBounds as String] as? [String: CGFloat], let layer = info[kCGWindowLayer as String] as? Int, let pid = info[kCGWindowOwnerPID as String] as? Int, layer == 0 else { continue } if ownIDs.contains(Int(cid)) { continue } let cgRect = NSRect( x: bounds["X"] ?? 0, y: bounds["Y"] ?? 0, width: bounds["Width"] ?? 0, height: bounds["Height"] ?? 0 ) guard cgRect.width >= 80, cgRect.height >= 60 else { continue } let rect = Self.cgToAppKit(rect: cgRect) found.append((id: cid, frame: rect, pid: pid)) } trackedWindows = found } /// Convert a CGWindowList (top-left origin) frame into AppKit screen /// coordinates (bottom-left origin). private static func cgToAppKit(rect: NSRect) -> NSRect { guard let mainScreen = NSScreen.screens.first else { return rect } let y = mainScreen.frame.height - rect.origin.y - rect.height return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height) } private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect, pid: Int)? { for entry in trackedWindows { if entry.frame.contains(appKitPoint) { return entry } } return nil } /// System dialogs (permission prompts, System Settings) must always /// receive their clicks even while we're selecting. private func isSystemDialogFrontmost() -> Bool { guard let front = NSWorkspace.shared.frontmostApplication else { return false } let id = front.bundleIdentifier ?? "" return id.hasPrefix("com.apple.security") || id.hasPrefix("com.apple.SystemSettings") || id == "com.apple.systempreferences" } /// Returns the event to pass through, or nil to consume it. func handleTap(_ type: CGEventType, _ event: CGEvent) -> CGEvent? { guard isSelecting else { return event } if isSystemDialogFrontmost() { CaptureController.log("tap: system dialog frontmost — passing through \(type)") return event } switch type { case .tapDisabledByUserInput, .tapDisabledByTimeout: // A timed-out tap must be re-enabled; guard against a torn-down tap. if let tap = eventTap { CGEvent.tapEnable(tap: tap, enable: true) CaptureController.log("tap: re-enabled after \(type)") } return event case .keyDown: if event.getIntegerValueField(.keyboardEventKeycode) == 53 { CaptureController.log("tap: Esc — cancel") cancelSelection() return nil } return event case .mouseMoved, .leftMouseDragged: // Throttle window scans while moving if Date().timeIntervalSince(lastWindowScan) > 0.15 { lastWindowScan = Date() refreshTrackedWindows() } let appKitPoint = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y)) if type == .mouseMoved { // Frozen mode is marquee-only: no window hover detection. if isFrozenMode { return event } let window = topmostWindowContaining(appKitPoint) if window?.frame != hoverFrame { hoverWindow = window if let w = window { CaptureController.log("hover: picked id=\(w.id) frame=\(w.frame) (tracked=\(trackedWindows.count))") } else { CaptureController.log("hover: none (tracked=\(trackedWindows.count))") } renderOverlays() } return event } // Drag: grow the marquee — but only after real movement, so a // click's micro-jitter keeps the hovered window eligible. guard let start = marqueeStart else { return nil } let distance = hypot(appKitPoint.x - start.x, appKitPoint.y - start.y) guard distance >= 4 else { return nil } let rect = NSRect( x: min(start.x, appKitPoint.x), y: min(start.y, appKitPoint.y), width: abs(appKitPoint.x - start.x), height: abs(appKitPoint.y - start.y) ) marqueeRect = rect hoverWindow = nil renderOverlays() return nil case .leftMouseDown: marqueeStart = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y)) marqueeRect = nil CaptureController.log("tap: mouseDown at \(marqueeStart.map { "\($0)" } ?? "?")") return nil case .leftMouseUp: defer { marqueeStart = nil; marqueeRect = nil } let appKitPoint = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y)) let dragDistance = marqueeStart.map { hypot($0.x - appKitPoint.x, $0.y - appKitPoint.y) } ?? 0 if let rect = marqueeRect, dragDistance >= 4, rect.width >= 2, rect.height >= 2 { CaptureController.log("tap: marquee complete \(rect)") finishSelection(rect) } else if isFrozenMode { CaptureController.log("tap: frozen click without drag — cancel") cancelSelection() } else if let hover = hoverWindow { // An overlapping window from the same app means this is an // owned popup — desktopIndependentWindow would composite the // parent too, so capture the screen crop instead. // Owned popups composite their parent even when the windows // don't overlap, so ANY other same-app window on screen means // crop (correct region) instead of the group composite. let ownedRisk = trackedWindows.contains { $0.id != hover.id && $0.pid == hover.pid } CaptureController.log("tap: window pick id=\(hover.id) pid=\(hover.pid) ownedRisk=\(ownedRisk) \(hover.frame)") finishWindowPick(hover.id, hover.frame, ownedRisk: ownedRisk) } else { CaptureController.log("tap: click with no hover/marquee — cancel") cancelSelection() } return nil default: return event } } // MARK: - Capture private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int], roundedCorners: Bool = false, windowID: CGWindowID? = nil) { ScreenCapture.shared.capture(rect, excludingWindowIDs: ids, roundedCorners: roundedCorners, windowID: windowID) { [weak self] image in DispatchQueue.main.async { self?.isSelecting = false self?.restorePreviousApp() Task { @MainActor in guard let image else { ResultWindow.shared.show(image: ResultWindow.failureImage("Capture failed. Check Screen Recording permission.")) return } CaptureController.log("capture done: \(image.width)x\(image.height) — showing result window") ResultWindow.shared.show(image: image) } } } } } final class ScreenCapture { static let shared = ScreenCapture() private init() {} /// Estimate the window's corner radius (in image pixels) by scanning the /// top-left corner: the crop's corner pixels are the desktop background; /// app content starts where the color departs from it. static func detectCornerRadius(of image: CGImage) -> CGFloat? { let w = image.width, h = image.height guard w > 30, h > 30 else { return nil } var px = [UInt8](repeating: 0, count: w * h * 4) guard let c = CGContext(data: &px, width: w, height: h, bitsPerComponent: 8, bytesPerRow: w * 4, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } c.draw(image, in: CGRect(x: 0, y: 0, width: w, height: h)) func pixel(x: Int, yTop: Int) -> (r: Int, g: Int, b: Int) { let row = yTop * w * 4 + x * 4 return (Int(px[row]), Int(px[row + 1]), Int(px[row + 2])) } let bg = pixel(x: 0, yTop: 0) func diffFromBg(_ p: (r: Int, g: Int, b: Int)) -> Int { abs(p.r - bg.r) + abs(p.g - bg.g) + abs(p.b - bg.b) } var radii: [CGFloat] = [] for (limit, axis) in [(h / 3, 0), (w / 3, 1)] { for i in 0.. 24 { radii.append(CGFloat(i)) break } } } guard radii.count == 2 else { return nil } let r = (radii[0] + radii[1]) / 2 return (r >= 3 && r <= 40) ? r : nil } @available(macOS 14.0, *) private func crop(_ image: CGImage, to cgRect: CGRect, display: SCDisplay, roundedCorners: Bool = false) -> CGImage? { // cgRect is in SCDisplay space (bottom-left origin, Y flipped vs AppKit). // The captured image has a TOP-LEFT origin. So within the display, // we must flip Y: pixelY = displayHeight - (cgY - displayMinY) - cgHeight. let scaleX = CGFloat(image.width) / display.frame.width let scaleY = CGFloat(image.height) / display.frame.height guard scaleX > 0, scaleY > 0 else { return nil } let localMinX = cgRect.minX - display.frame.minX let localMaxY = cgRect.maxY - display.frame.minY let flippedY = display.frame.height - localMaxY let pxRect = CGRect( x: localMinX * scaleX, y: flippedY * scaleY, width: cgRect.width * scaleX, height: cgRect.height * scaleY ) // Guard against negative offsets when the selection reaches beyond the // display's frame (e.g. secondary monitor coordinate quirks). let safeRect = pxRect.intersection(CGRect(x: 0, y: 0, width: CGFloat(image.width), height: CGFloat(image.height))) guard safeRect.width >= 1, safeRect.height >= 1 else { return nil } // Use a pixel-exact subimage crop instead of redrawing through a // CGContext, which resamples and loses quality on retina captures. let rounded = safeRect.integral guard rounded.width >= 1, rounded.height >= 1 else { return nil } guard var result = image.cropping(to: rounded) else { return nil } // Apply a rounded-corner alpha mask for window captures, matching // macOS window chrome (≈10pt radius, scaled to image pixels). if roundedCorners { // The screen crop contains the app's real rounded corners against // the desktop — measure the radius from the corner pixels, then // mask with it (supersampled for smooth edges). let detected = Self.detectCornerRadius(of: result) let radius = min(max((detected.map { $0 * 1.23 + 2.6 }) ?? (10 * scaleX), 4), 40) CaptureController.log("corners: detected=\(detected.map { String(format: "%.1f", $0) } ?? "nil") using=\(Int(radius))px") // Anti-aliased gray mask: supersample 3x, downsample, then draw // the image through it ONCE at 1x (no content resampling blur). let ss = 3 let W = result.width * ss, H = result.height * ss guard let hiMask = CGContext( data: nil, width: W, height: H, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue ) else { return result } hiMask.setFillColor(CGColor(gray: 1, alpha: 1)) hiMask.fill(CGRect(x: 0, y: 0, width: W, height: H)) hiMask.setFillColor(CGColor(gray: 0, alpha: 1)) let cornerPath = CGMutablePath() cornerPath.addRect(CGRect(x: 0, y: 0, width: W, height: H)) cornerPath.addRoundedRect(in: CGRect(x: 0, y: 0, width: W, height: H), cornerWidth: radius * CGFloat(ss), cornerHeight: radius * CGFloat(ss), transform: .identity) hiMask.addPath(cornerPath) hiMask.fillPath(using: .evenOdd) guard let hiMaskImg = hiMask.makeImage(), let downMask = CGContext( data: nil, width: result.width, height: result.height, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceGray(), bitmapInfo: CGImageAlphaInfo.none.rawValue ) else { return result } downMask.interpolationQuality = .high downMask.draw(hiMaskImg, in: CGRect(x: 0, y: 0, width: result.width, height: result.height)) guard let mask1x = downMask.makeImage(), let ctx = CGContext( data: nil, width: result.width, height: result.height, bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue ) else { return result } ctx.clip(to: CGRect(x: 0, y: 0, width: result.width, height: result.height), mask: mask1x) ctx.draw(result, in: CGRect(x: 0, y: 0, width: result.width, height: result.height)) if let masked = ctx.makeImage() { result = masked } } return result } /// Screen frame (AppKit global points) + full-resolution image per display. struct DisplaySnapshot { let frame: NSRect let image: CGImage } /// Snapshot every display right now. Used by frozen mode: the images are /// taken before the overlay appears, so the pointer and hover states /// (tooltips, menus) are baked in. func snapshotAllDisplays(includeCursor: Bool, completion: @escaping ([DisplaySnapshot]) -> Void) { if #available(macOS 14.0, *) { Task { guard let content = try? await SCShareableContent.current else { completion([]) return } var out: [DisplaySnapshot] = [] for display in content.displays { guard let screen = NSScreen.screens.first(where: { (($0.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0) == display.displayID }) else { continue } let config = SCStreamConfiguration() let scale = screen.backingScaleFactor config.width = Int(CGFloat(display.width) * scale) config.height = Int(CGFloat(display.height) * scale) config.showsCursor = includeCursor let filter = SCContentFilter(display: display, excludingWindows: []) if let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) { out.append(DisplaySnapshot(frame: screen.frame, image: image)) } } completion(out) } } else { var out: [DisplaySnapshot] = [] for screen in NSScreen.screens { let cgRect = CGRect( x: screen.frame.minX, y: (NSScreen.screens.first(where: { $0.frame.origin == .zero })?.frame.height ?? screen.frame.height) - screen.frame.maxY, width: screen.frame.width, height: screen.frame.height ) if let image = CGWindowListCreateImage(cgRect, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution, .boundsIgnoreFraming]) { out.append(DisplaySnapshot(frame: screen.frame, image: image)) } } completion(out) } } /// Crop a selection out of a frozen snapshot (top-left pixel space). static func cropFrozen(_ snapshot: DisplaySnapshot, to rect: NSRect) -> CGImage? { let scaleX = CGFloat(snapshot.image.width) / snapshot.frame.width let scaleY = CGFloat(snapshot.image.height) / snapshot.frame.height guard scaleX > 0, scaleY > 0 else { return nil } let localX = rect.minX - snapshot.frame.minX let localTopY = snapshot.frame.maxY - rect.maxY let px = CGRect( x: localX * scaleX, y: localTopY * scaleY, width: rect.width * scaleX, height: rect.height * scaleY ) let safe = px.intersection(CGRect(x: 0, y: 0, width: CGFloat(snapshot.image.width), height: CGFloat(snapshot.image.height))).integral guard safe.width >= 1, safe.height >= 1 else { return nil } return snapshot.image.cropping(to: safe) } @available(macOS 14.0, *) private func displayContaining(_ rect: NSRect, in content: SCShareableContent) -> SCDisplay? { let mid = NSPoint(x: rect.midX, y: rect.midY) guard let screen = NSScreen.screens.first(where: { $0.frame.contains(mid) }) ?? NSScreen.screens.first else { return nil } let cgID = (screen.deviceDescription[NSDeviceDescriptionKey("NSScreenNumber")] as? NSNumber)?.uint32Value ?? 0 return content.displays.first(where: { $0.displayID == cgID }) ?? content.displays.first } func capture(_ rect: NSRect, excludingWindowIDs excludedIDs: [Int], roundedCorners: Bool = false, windowID: CGWindowID? = nil, completion: @escaping (CGImage?) -> Void) { if #available(macOS 14.0, *) { Task { guard let content = try? await SCShareableContent.current, let display = displayContaining(rect, in: content) else { completion(nil) return } let excludedSet = Set(excludedIDs.map { CGWindowID($0) }) let excluded = content.windows.filter { excludedSet.contains($0.windowID) } let filter = SCContentFilter(display: display, excludingWindows: excluded) let config = SCStreamConfiguration() // SCDisplay.width/height are in POINTS; SCStreamConfiguration // width/height are output PIXELS. Multiply by the backing scale // of the screen containing the selection so retina displays are // captured at full resolution and 1x displays stay 1x. guard let screen = NSScreen.screens.first(where: { $0.frame.contains(NSPoint(x: rect.midX, y: rect.midY)) }) else { completion(nil) return } let backingScale = screen.backingScaleFactor config.width = Int(CGFloat(display.width) * backingScale) config.height = Int(CGFloat(display.height) * backingScale) config.showsCursor = false // ScreenCaptureKit uses a different origin than AppKit for // screens stacked around the main display: per-display, // SCDisplay y = appKitY - NSScreen.maxY + SCDisplay.maxY. let scRect = CGRect( x: rect.minX, y: rect.minY - screen.frame.maxY + display.frame.maxY, width: rect.width, height: rect.height ) // Window pick: capture the window DIRECTLY — the image comes // with the app's real rounded-corner alpha (no mask guessing). if let windowID, let scWindow = content.windows.first(where: { $0.windowID == windowID }) { var windowConfig = SCStreamConfiguration() windowConfig.width = Int(scWindow.frame.width * backingScale) windowConfig.height = Int(scWindow.frame.height * backingScale) windowConfig.showsCursor = false if let windowImage = try? await SCScreenshotManager.captureImage( contentFilter: SCContentFilter(desktopIndependentWindow: scWindow), configuration: windowConfig ) { let expectedW = CGFloat(scWindow.frame.width) * backingScale let expectedH = CGFloat(scWindow.frame.height) * backingScale let overflow = max( CGFloat(windowImage.width) / max(expectedW, 1), CGFloat(windowImage.height) / max(expectedH, 1) ) if overflow > 1.35 { // SCKit composited extra windows (owned popups etc.) // — reject and use the screen crop instead. CaptureController.log("capture: WINDOW id=\(windowID) overflow=\(overflow) (\(windowImage.width)x\(windowImage.height) vs \(Int(expectedW))x\(Int(expectedH))) — rejecting, using crop+mask") } else { CaptureController.log("capture: WINDOW id=\(windowID) image=\(windowImage.width)x\(windowImage.height) (native alpha corners)") completion(windowImage) return } } CaptureController.log("capture: window filter failed for id=\(windowID) — falling back to crop+mask") } guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else { completion(nil) return } CaptureController.log("capture: rect=\(rect) screen=\(screen.frame) display=\(display.frame) backing=\(backingScale) image=\(image.width)x\(image.height) scRect=\(scRect)") completion(self.crop(image, to: scRect, display: display, roundedCorners: roundedCorners)) } } else { guard let primary = NSScreen.screens.first(where: { $0.frame.origin == .zero }) ?? NSScreen.screens.first else { completion(nil) return } let cgRect = CGRect( x: rect.minX, y: primary.frame.height - rect.maxY, width: rect.width, height: rect.height ) completion(CGWindowListCreateImage(cgRect, .optionOnScreenOnly, kCGNullWindowID, [.bestResolution, .boundsIgnoreFraming])) } } }