Compare commits

..
1 Commits
Author SHA1 Message Date
TZGyn c9a807e8a0 Fix secondary display captures after retina scaling change
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
2026-08-12 16:34:18 +08:00
2 changed files with 16 additions and 13 deletions
+14 -11
View File
@@ -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 {
+2 -2
View File
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
<key>CFBundleIconFile</key>
<string>Icon</string>
<key>CFBundleShortVersionString</key>
<string>1.1.3</string>
<string>1.1.4</string>
<key>CFBundleVersion</key>
<string>13</string>
<string>14</string>
<key>LSUIElement</key>
<true/>
<key>NSHighResolutionCapable</key>