From c9a807e8a00eca5619fb8c87038570c02bc86444 Mon Sep 17 00:00:00 2001 From: TZGyn Date: Wed, 12 Aug 2026 16:34:18 +0800 Subject: [PATCH] Fix secondary display captures after retina scaling change MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 1.1.2 retina fix overrode the stream width from the display's pixel dimensions, which made captures on secondary monitors come out at the wrong scale or offset. Restore requesting the display's native pixel resolution and keep the crop scale derived from the actual returned image, plus guard against out-of-bounds crop rects. 💘 Generated with Crush Assisted-by: Crush:deepseek-v4-flash-0731 --- Sources/Kapture/CaptureController.swift | 25 ++++++++++++++----------- build-app.sh | 4 ++-- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/Sources/Kapture/CaptureController.swift b/Sources/Kapture/CaptureController.swift index 08e3f79..0da1e55 100644 --- a/Sources/Kapture/CaptureController.swift +++ b/Sources/Kapture/CaptureController.swift @@ -86,10 +86,12 @@ final class ScreenCapture { @available(macOS 14.0, *) private func crop(_ image: CGImage, to rect: NSRect, display: SCDisplay) -> CGImage? { - // Scale from the actual captured image dimensions, which reflect the - // stream's pixel output (retina => 2x, non-retina => 1x). + // The captured image may be at any scale (retina 2x, secondary display + // 1x, or trimmed by ScreenCaptureKit). Derive the pixel scale from the + // image we actually got, relative to the display's point frame. 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 cgRect = CGRect( x: rect.minX, y: rect.minY, @@ -102,14 +104,18 @@ final class ScreenCapture { 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 } guard let cropCtx = CGContext( - data: nil, width: Int(pxRect.width), height: Int(pxRect.height), + data: nil, width: Int(safeRect.width), height: Int(safeRect.height), bitsPerComponent: 8, bytesPerRow: 0, space: CGColorSpaceCreateDeviceRGB(), bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue ) else { return nil } cropCtx.interpolationQuality = .high - cropCtx.draw(image, in: CGRect(x: -pxRect.minX, y: -pxRect.minY, width: CGFloat(image.width), height: CGFloat(image.height))) + cropCtx.draw(image, in: CGRect(x: -safeRect.minX, y: -safeRect.minY, width: CGFloat(image.width), height: CGFloat(image.height))) return cropCtx.makeImage() } @@ -135,13 +141,10 @@ final class ScreenCapture { let excluded = content.windows.filter { excludedSet.contains($0.windowID) } let filter = SCContentFilter(display: display, excludingWindows: excluded) let config = SCStreamConfiguration() - // width/height are in points. Use the display's backing scale so - // retina captures come out at full pixel resolution. - let backingScale = NSScreen.screens.first(where: { $0.frame.contains( - NSPoint(x: rect.midX, y: rect.midY) - ) })?.backingScaleFactor ?? 2 - config.width = Int(display.frame.width * backingScale) - config.height = Int(display.frame.height * backingScale) + // display.width/height are pixels; request the display's full + // native resolution so ScreenCaptureKit gives us every pixel. + config.width = Int(display.width) + config.height = Int(display.height) config.showsCursor = true guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else { diff --git a/build-app.sh b/build-app.sh index 8bff8a3..33c77dd 100755 --- a/build-app.sh +++ b/build-app.sh @@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST' CFBundleIconFile Icon CFBundleShortVersionString - 1.1.3 + 1.1.4 CFBundleVersion - 13 + 14 LSUIElement NSHighResolutionCapable