Compare commits

...
2 Commits
Author SHA1 Message Date
TZGyn 7633e81c52 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
2026-09-09 14:10:03 +08:00
TZGyn 83b127cf44 Detect maximized windows on secondary displays
A leftover heuristic skipped any window matching its screen's dimensions,
which made window detection silently fail on secondary monitors where the
working window is typically maximized.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
2026-09-09 14:03:52 +08:00
2 changed files with 42 additions and 26 deletions
+40 -24
View File
@@ -14,7 +14,8 @@ final class CaptureController: NSObject {
// Selection state (driven by the event tap; AppKit global coordinates)
private var marqueeStart: NSPoint?
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 lastWindowScan = Date.distantPast
private var eventTap: CFMachPort?
@@ -102,14 +103,15 @@ final class CaptureController: NSObject {
}
}
/// A window was picked from hover — capture it with rounded corners.
private func finishWindowPick(_ rect: NSRect) {
/// 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) {
let ids = overlayIDs
hideOverlays()
isSelecting = false
restorePreviousApp()
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 = []
marqueeRect = nil
hoverFrame = nil
hoverWindow = nil
marqueeStart = nil
}
@@ -233,11 +235,6 @@ final class CaptureController: NSObject {
)
guard cgRect.width >= 80, cgRect.height >= 60 else { continue }
let rect = Self.cgToAppKit(rect: cgRect)
// Skip windows filling an entire screen (fullscreen apps)
let coversWholeScreen = NSScreen.screens.contains { screen in
abs(screen.frame.width - rect.width) < 2 && abs(screen.frame.height - rect.height) < 2
}
guard !coversWholeScreen else { continue }
found.append((cid, rect))
}
trackedWindows = found
@@ -251,10 +248,10 @@ final class CaptureController: NSObject {
return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height)
}
private func topmostWindowContaining(_ appKitPoint: NSPoint) -> NSRect? {
for (_, frame) in trackedWindows {
if frame.contains(appKitPoint) {
return frame
private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect)? {
for entry in trackedWindows {
if entry.frame.contains(appKitPoint) {
return entry
}
}
return nil
@@ -287,9 +284,9 @@ final class CaptureController: NSObject {
}
let appKitPoint = cgPointToAppKit(NSPoint(x: event.location.x, y: event.location.y))
if type == .mouseMoved {
let frame = topmostWindowContaining(appKitPoint)
if frame != hoverFrame {
hoverFrame = frame
let window = topmostWindowContaining(appKitPoint)
if window?.frame != hoverFrame {
hoverWindow = window
renderOverlays()
}
return event
@@ -303,7 +300,7 @@ final class CaptureController: NSObject {
height: abs(appKitPoint.y - start.y)
)
marqueeRect = rect
hoverFrame = nil
hoverWindow = nil
renderOverlays()
return nil
case .leftMouseDown:
@@ -318,9 +315,9 @@ final class CaptureController: NSObject {
if let rect = marqueeRect, dragDistance >= 4, rect.width >= 2, rect.height >= 2 {
CaptureController.log("tap: marquee complete \(rect)")
finishSelection(rect)
} else if let hover = hoverFrame {
CaptureController.log("tap: window pick \(hover)")
finishWindowPick(hover)
} else if let hover = hoverWindow {
CaptureController.log("tap: window pick id=\(hover.id) \(hover.frame)")
finishWindowPick(hover.id, hover.frame)
} else {
CaptureController.log("tap: click with no hover/marquee — cancel")
cancelSelection()
@@ -333,8 +330,8 @@ final class CaptureController: NSObject {
// MARK: - Capture
private func capture(_ rect: NSRect, excludingWindowIDs ids: [Int], roundedCorners: Bool = false) {
ScreenCapture.shared.capture(rect, excludingWindowIDs: ids, roundedCorners: roundedCorners) { [weak self] image in
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()
@@ -419,7 +416,7 @@ final class ScreenCapture {
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, *) {
Task {
guard let content = try? await SCShareableContent.current,
@@ -454,6 +451,25 @@ final class ScreenCapture {
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 {
completion(nil)
return
+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.7.3</string>
<string>1.8.0</string>
<key>CFBundleVersion</key>
<string>73</string>
<string>80</string>
<key>LSUIElement</key>
<true/>
<key>NSHighResolutionCapable</key>