diff --git a/Sources/Kapture/CaptureController.swift b/Sources/Kapture/CaptureController.swift index 7c5d81f..b828a87 100644 --- a/Sources/Kapture/CaptureController.swift +++ b/Sources/Kapture/CaptureController.swift @@ -14,9 +14,9 @@ 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 hoverWindow: (id: CGWindowID, frame: NSRect)? + private(set) var hoverWindow: (id: CGWindowID, frame: NSRect, pid: Int)? var hoverFrame: NSRect? { hoverWindow?.frame } - private var trackedWindows: [(id: CGWindowID, frame: NSRect)] = [] + private var trackedWindows: [(id: CGWindowID, frame: NSRect, pid: Int)] = [] private var lastWindowScan = Date.distantPast private var eventTap: CFMachPort? @@ -105,13 +105,17 @@ final class CaptureController: NSObject { /// 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) { + private func finishWindowPick(_ windowID: CGWindowID, _ rect: NSRect, ownedRisk: Bool) { let ids = overlayIDs hideOverlays() isSelecting = false restorePreviousApp() DispatchQueue.main.async { [self] in - capture(rect, excludingWindowIDs: ids, windowID: windowID) + if ownedRisk { + capture(rect, excludingWindowIDs: ids, roundedCorners: true) + } else { + capture(rect, excludingWindowIDs: ids, windowID: windowID) + } } } @@ -219,11 +223,12 @@ final class CaptureController: NSObject { ) as? [[String: Any]] else { trackedWindows = []; return } let ownIDs = Set(overlayIDs) - var found: [(id: CGWindowID, frame: NSRect)] = [] + var found: [(id: CGWindowID, frame: NSRect, pid: Int)] = [] for info in list { guard let cid = info[kCGWindowNumber as String] as? CGWindowID, let bounds = info[kCGWindowBounds as String] as? [String: CGFloat], let layer = info[kCGWindowLayer as String] as? Int, + let pid = info[kCGWindowOwnerPID as String] as? Int, layer == 0 else { continue } if ownIDs.contains(Int(cid)) { continue } @@ -235,7 +240,7 @@ final class CaptureController: NSObject { ) guard cgRect.width >= 80, cgRect.height >= 60 else { continue } let rect = Self.cgToAppKit(rect: cgRect) - found.append((cid, rect)) + found.append((id: cid, frame: rect, pid: pid)) } trackedWindows = found } @@ -248,7 +253,7 @@ final class CaptureController: NSObject { return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height) } - private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect)? { + private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect, pid: Int)? { for entry in trackedWindows { if entry.frame.contains(appKitPoint) { return entry @@ -321,8 +326,14 @@ final class CaptureController: NSObject { CaptureController.log("tap: marquee complete \(rect)") finishSelection(rect) } else if let hover = hoverWindow { - CaptureController.log("tap: window pick id=\(hover.id) \(hover.frame)") - finishWindowPick(hover.id, hover.frame) + // An overlapping window from the same app means this is an + // owned popup — desktopIndependentWindow would composite the + // parent too, so capture the screen crop instead. + let ownedRisk = trackedWindows.contains { + $0.id != hover.id && $0.pid == hover.pid && $0.frame.intersects(hover.frame) + } + CaptureController.log("tap: window pick id=\(hover.id) pid=\(hover.pid) ownedRisk=\(ownedRisk) \(hover.frame)") + finishWindowPick(hover.id, hover.frame, ownedRisk: ownedRisk) } else { CaptureController.log("tap: click with no hover/marquee — cancel") cancelSelection() diff --git a/build-app.sh b/build-app.sh index ff2c134..af2e2d5 100755 --- a/build-app.sh +++ b/build-app.sh @@ -108,9 +108,9 @@ cat > "$APP/Contents/Info.plist" <<'PLIST' CFBundleIconFile Icon CFBundleShortVersionString - 1.8.1 + 1.8.2 CFBundleVersion - 81 + 82 LSUIElement NSHighResolutionCapable