Capture picked windows natively with real rounded corners

Window picks now capture via ScreenCaptureKit's desktopIndependentWindow
filter, so the image carries the app's actual rounded-corner alpha
instead of a fixed 10pt mask that let the desktop show through on apps
with larger radii. Falls back to crop+mask when the window is not in the
shareable content.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
This commit is contained in:
2026-09-09 14:10:03 +08:00
parent 83b127cf44
commit 7633e81c52
2 changed files with 42 additions and 21 deletions
+40 -19
View File
@@ -14,7 +14,8 @@ final class CaptureController: NSObject {
// Selection state (driven by the event tap; AppKit global coordinates) // Selection state (driven by the event tap; AppKit global coordinates)
private var marqueeStart: NSPoint? private var marqueeStart: NSPoint?
private(set) var marqueeRect: NSRect? private(set) var marqueeRect: NSRect?
private(set) var hoverFrame: NSRect? private(set) var hoverWindow: (id: CGWindowID, frame: NSRect)?
var hoverFrame: NSRect? { hoverWindow?.frame }
private var trackedWindows: [(id: CGWindowID, frame: NSRect)] = [] private var trackedWindows: [(id: CGWindowID, frame: NSRect)] = []
private var lastWindowScan = Date.distantPast private var lastWindowScan = Date.distantPast
private var eventTap: CFMachPort? private var eventTap: CFMachPort?
@@ -102,14 +103,15 @@ final class CaptureController: NSObject {
} }
} }
/// A window was picked from hover — capture it with rounded corners. /// A window was picked from hover — capture it directly (real rounded
private func finishWindowPick(_ rect: NSRect) { /// corners come from the window's own alpha).
private func finishWindowPick(_ windowID: CGWindowID, _ rect: NSRect) {
let ids = overlayIDs let ids = overlayIDs
hideOverlays() hideOverlays()
isSelecting = false isSelecting = false
restorePreviousApp() restorePreviousApp()
DispatchQueue.main.async { [self] in DispatchQueue.main.async { [self] in
capture(rect, excludingWindowIDs: ids, roundedCorners: true) capture(rect, excludingWindowIDs: ids, windowID: windowID)
} }
} }
@@ -136,7 +138,7 @@ final class CaptureController: NSObject {
overlays.forEach { $0.orderOut(nil) } overlays.forEach { $0.orderOut(nil) }
overlays = [] overlays = []
marqueeRect = nil marqueeRect = nil
hoverFrame = nil hoverWindow = nil
marqueeStart = nil marqueeStart = nil
} }
@@ -246,10 +248,10 @@ final class CaptureController: NSObject {
return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height) return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height)
} }
private func topmostWindowContaining(_ appKitPoint: NSPoint) -> NSRect? { private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect)? {
for (_, frame) in trackedWindows { for entry in trackedWindows {
if frame.contains(appKitPoint) { if entry.frame.contains(appKitPoint) {
return frame return entry
} }
} }
return nil return nil
@@ -282,9 +284,9 @@ final class CaptureController: NSObject {
} }
let appKitPoint = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y)) let appKitPoint = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y))
if type == .mouseMoved { if type == .mouseMoved {
let frame = topmostWindowContaining(appKitPoint) let window = topmostWindowContaining(appKitPoint)
if frame != hoverFrame { if window?.frame != hoverFrame {
hoverFrame = frame hoverWindow = window
renderOverlays() renderOverlays()
} }
return event return event
@@ -298,7 +300,7 @@ final class CaptureController: NSObject {
height: abs(appKitPoint.y - start.y) height: abs(appKitPoint.y - start.y)
) )
marqueeRect = rect marqueeRect = rect
hoverFrame = nil hoverWindow = nil
renderOverlays() renderOverlays()
return nil return nil
case .leftMouseDown: case .leftMouseDown:
@@ -313,9 +315,9 @@ final class CaptureController: NSObject {
if let rect = marqueeRect, dragDistance >= 4, rect.width >= 2, rect.height >= 2 { if let rect = marqueeRect, dragDistance >= 4, rect.width >= 2, rect.height >= 2 {
CaptureController.log("tap: marquee complete \(rect)") CaptureController.log("tap: marquee complete \(rect)")
finishSelection(rect) finishSelection(rect)
} else if let hover = hoverFrame { } else if let hover = hoverWindow {
CaptureController.log("tap: window pick \(hover)") CaptureController.log("tap: window pick id=\(hover.id) \(hover.frame)")
finishWindowPick(hover) finishWindowPick(hover.id, hover.frame)
} else { } else {
CaptureController.log("tap: click with no hover/marquee — cancel") CaptureController.log("tap: click with no hover/marquee — cancel")
cancelSelection() cancelSelection()
@@ -328,8 +330,8 @@ final class CaptureController: NSObject {
// MARK: - Capture // MARK: - Capture
private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int], roundedCorners: Bool = false) { private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int], roundedCorners: Bool = false, windowID: CGWindowID? = nil) {
ScreenCapture.shared.capture(rect, excludingWindowIDs: ids, roundedCorners: roundedCorners) { [weak self] image in ScreenCapture.shared.capture(rect, excludingWindowIDs: ids, roundedCorners: roundedCorners, windowID: windowID) { [weak self] image in
DispatchQueue.main.async { DispatchQueue.main.async {
self?.isSelecting = false self?.isSelecting = false
self?.restorePreviousApp() self?.restorePreviousApp()
@@ -414,7 +416,7 @@ final class ScreenCapture {
return content.displays.first(where: { $0.displayID == cgID }) ?? content.displays.first return content.displays.first(where: { $0.displayID == cgID }) ?? content.displays.first
} }
func capture(_ rect: NSRect, excludingWindowIDs excludedIDs: [Int], roundedCorners: Bool = false, completion: @escaping (CGImage?) -> Void) { func capture(_ rect: NSRect, excludingWindowIDs excludedIDs: [Int], roundedCorners: Bool = false, windowID: CGWindowID? = nil, completion: @escaping (CGImage?) -> Void) {
if #available(macOS 14.0, *) { if #available(macOS 14.0, *) {
Task { Task {
guard let content = try? await SCShareableContent.current, guard let content = try? await SCShareableContent.current,
@@ -449,6 +451,25 @@ final class ScreenCapture {
height: rect.height 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
) {
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 { guard let image = try? await SCScreenshotManager.captureImage(contentFilter: filter, configuration: config) else {
completion(nil) completion(nil)
return return
+2 -2
View File
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
<key>CFBundleIconFile</key> <key>CFBundleIconFile</key>
<string>Icon</string> <string>Icon</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.7.4</string> <string>1.8.0</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>74</string> <string>80</string>
<key>LSUIElement</key> <key>LSUIElement</key>
<true/> <true/>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>