Compare commits

...
1 Commits
Author SHA1 Message Date
TZGyn 3078713f8f 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
2026-09-09 15:05:34 +08:00
2 changed files with 22 additions and 11 deletions
+19 -8
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,15 +105,19 @@ 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
if ownedRisk {
capture(rect, excludingWindowIDs: ids, roundedCorners: true)
} else {
capture(rect, excludingWindowIDs: ids, windowID: windowID) capture(rect, excludingWindowIDs: ids, windowID: windowID)
} }
} }
}
private func finishSelection(_ rect: NSRect) { private func finishSelection(_ rect: NSRect) {
let ids = overlayIDs let ids = overlayIDs
@@ -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>