Use screen crop for owned popup windows

desktopIndependentWindow composites owned popups together with their
parent window and scales the group to the popup's size. Detect the case
at pick time — another window from the same app overlapping the picked
one — and capture the screen crop with a rounded mask instead, which
contains only the popup.

💘 Generated with Crush

Assisted-by: Crush:deepseek-v4-flash-0731
This commit is contained in:
2026-09-09 15:05:34 +08:00
parent 265571d7e2
commit 3078713f8f
2 changed files with 22 additions and 11 deletions
+20 -9
View File
@@ -14,9 +14,9 @@ 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 hoverWindow: (id: CGWindowID, frame: NSRect)? private(set) var hoverWindow: (id: CGWindowID, frame: NSRect, pid: Int)?
var hoverFrame: NSRect? { hoverWindow?.frame } 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 lastWindowScan = Date.distantPast
private var eventTap: CFMachPort? private var eventTap: CFMachPort?
@@ -105,13 +105,17 @@ final class CaptureController: NSObject {
/// A window was picked from hover — capture it directly (real rounded /// A window was picked from hover — capture it directly (real rounded
/// corners come from the window's own alpha). /// 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 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, 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 } ) as? [[String: Any]] else { trackedWindows = []; return }
let ownIDs = Set(overlayIDs) let ownIDs = Set(overlayIDs)
var found: [(id: CGWindowID, frame: NSRect)] = [] var found: [(id: CGWindowID, frame: NSRect, pid: Int)] = []
for info in list { for info in list {
guard let cid = info[kCGWindowNumber as String] as? CGWindowID, guard let cid = info[kCGWindowNumber as String] as? CGWindowID,
let bounds = info[kCGWindowBounds as String] as? [String: CGFloat], let bounds = info[kCGWindowBounds as String] as? [String: CGFloat],
let layer = info[kCGWindowLayer as String] as? Int, let layer = info[kCGWindowLayer as String] as? Int,
let pid = info[kCGWindowOwnerPID as String] as? Int,
layer == 0 layer == 0
else { continue } else { continue }
if ownIDs.contains(Int(cid)) { continue } if ownIDs.contains(Int(cid)) { continue }
@@ -235,7 +240,7 @@ final class CaptureController: NSObject {
) )
guard cgRect.width >= 80, cgRect.height >= 60 else { continue } guard cgRect.width >= 80, cgRect.height >= 60 else { continue }
let rect = Self.cgToAppKit(rect: cgRect) let rect = Self.cgToAppKit(rect: cgRect)
found.append((cid, rect)) found.append((id: cid, frame: rect, pid: pid))
} }
trackedWindows = found trackedWindows = found
} }
@@ -248,7 +253,7 @@ 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) -> (id: CGWindowID, frame: NSRect)? { private func topmostWindowContaining(_ appKitPoint: NSPoint) -> (id: CGWindowID, frame: NSRect, pid: Int)? {
for entry in trackedWindows { for entry in trackedWindows {
if entry.frame.contains(appKitPoint) { if entry.frame.contains(appKitPoint) {
return entry return entry
@@ -321,8 +326,14 @@ final class CaptureController: NSObject {
CaptureController.log("tap: marquee complete \(rect)") CaptureController.log("tap: marquee complete \(rect)")
finishSelection(rect) finishSelection(rect)
} else if let hover = hoverWindow { } else if let hover = hoverWindow {
CaptureController.log("tap: window pick id=\(hover.id) \(hover.frame)") // An overlapping window from the same app means this is an
finishWindowPick(hover.id, hover.frame) // 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 { } else {
CaptureController.log("tap: click with no hover/marquee — cancel") CaptureController.log("tap: click with no hover/marquee — cancel")
cancelSelection() cancelSelection()
+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.8.1</string> <string>1.8.2</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>
<string>81</string> <string>82</string>
<key>LSUIElement</key> <key>LSUIElement</key>
<true/> <true/>
<key>NSHighResolutionCapable</key> <key>NSHighResolutionCapable</key>