diff --git a/Sources/Kapture/CaptureController.swift b/Sources/Kapture/CaptureController.swift
index 5a889f6..fa8557f 100644
--- a/Sources/Kapture/CaptureController.swift
+++ b/Sources/Kapture/CaptureController.swift
@@ -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
}
@@ -246,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
@@ -282,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
@@ -298,7 +300,7 @@ final class CaptureController: NSObject {
height: abs(appKitPoint.y - start.y)
)
marqueeRect = rect
- hoverFrame = nil
+ hoverWindow = nil
renderOverlays()
return nil
case .leftMouseDown:
@@ -313,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()
@@ -328,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()
@@ -414,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,
@@ -449,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
diff --git a/build-app.sh b/build-app.sh
index 3a38527..a108ddd 100755
--- a/build-app.sh
+++ b/build-app.sh
@@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST'
CFBundleIconFile
Icon
CFBundleShortVersionString
- 1.7.4
+ 1.8.0
CFBundleVersion
- 74
+ 80
LSUIElement
NSHighResolutionCapable