import AppKit final class OverlayWindow: NSPanel { init(screen: NSScreen, onComplete: @escaping (NSRect) -> Void, onCancel: @escaping () -> Void, onWindowPick: @escaping (NSRect) -> Void) { let content = OverlayView(onComplete: onComplete, onCancel: onCancel, onWindowPick: onWindowPick) super.init( contentRect: screen.frame, styleMask: [.borderless, .nonactivatingPanel], backing: .buffered, defer: false ) contentView = content level = .screenSaver collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .stationary, .transient] backgroundColor = .clear isOpaque = false hasShadow = false isReleasedWhenClosed = false acceptsMouseMovedEvents = true } override var canBecomeKey: Bool { true } } final class OverlayView: NSView { private let onComplete: (NSRect) -> Void private let onCancel: () -> Void private let onWindowPick: (NSRect) -> Void private var startPoint: NSPoint? private(set) var selection: NSRect? private var isDragging = false private var dragDistance: CGFloat = 0 // Window detection (frames stored in AppKit global space) private var trackedWindows: [(id: CGWindowID, frame: NSRect)] = [] private var hoveredWindowFrame: NSRect? init(onComplete: @escaping (NSRect) -> Void, onCancel: @escaping () -> Void, onWindowPick: @escaping (NSRect) -> Void) { self.onComplete = onComplete self.onCancel = onCancel self.onWindowPick = onWindowPick super.init(frame: .zero) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override var acceptsFirstResponder: Bool { true } override func acceptsFirstMouse(for event: NSEvent?) -> Bool { true } override func resetCursorRects() { addCursorRect(bounds, cursor: .crosshair) } // MARK: - Window detection /// Rebuild the list of capturable windows intersecting this screen. /// Called lazily on mouse moves; cheap enough for interactive hover. private func refreshTrackedWindows() { guard let window = window else { return } let screenFrame = window.convertToScreen(NSRect(origin: .zero, size: bounds.size)) // Exclude this overlay's own window (the other overlays don't intersect // this screen, so the on-screen filter handles them). let ownID = window.windowNumber guard let list = CGWindowListCopyWindowInfo( [ .optionOnScreenOnly, .excludeDesktopElements ], kCGNullWindowID ) as? [[String: Any]] else { trackedWindows = []; return } var found: [(CGWindowID, NSRect)] = [] 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, layer == 0 else { continue } // Skip our own overlays if Int(cid) == ownID { continue } let cgRect = NSRect( x: bounds["X"] ?? 0, y: bounds["Y"] ?? 0, width: bounds["Width"] ?? 0, height: bounds["Height"] ?? 0 ) guard cgRect.width >= 80, cgRect.height >= 60 else { continue } // Convert to AppKit space FIRST — CG and AppKit Y axes disagree on // multi-desktop layouts, so intersect tests must use one space. let rect = Self.cgToAppKit(rect: cgRect) // Skip windows filling the whole screen (fullscreen apps) guard rect.width < screenFrame.width || rect.height < screenFrame.height else { continue } // Only windows intersecting this screen guard rect.intersects(screenFrame) else { continue } found.append((cid, rect)) } trackedWindows = found } private func windowFrame(at point: NSPoint) -> NSRect? { guard let window = window else { return nil } // convertToScreen gives AppKit-global coordinates; tracked frames are // already in that space. let screenPoint = window.convertToScreen(NSRect(origin: point, size: .zero)).origin for (_, frame) in trackedWindows { if frame.contains(screenPoint) { return frame } } return nil } /// Convert a CGWindowList (top-left origin, CG global space) frame into /// AppKit screen coordinates (bottom-left origin) for capture. private static func cgToAppKit(rect: NSRect) -> NSRect { guard let mainScreen = NSScreen.screens.first else { return rect } let y = mainScreen.frame.height - rect.origin.y - rect.height return NSRect(x: rect.origin.x, y: y, width: rect.width, height: rect.height) } // MARK: - Events override func mouseMoved(with event: NSEvent) { guard !isDragging else { return } refreshTrackedWindows() let p = convert(event.locationInWindow, from: nil) let frame = windowFrame(at: p) if frame != hoveredWindowFrame { hoveredWindowFrame = frame needsDisplay = true } } override func mouseDown(with event: NSEvent) { let p = convert(event.locationInWindow, from: nil) startPoint = p dragDistance = 0 selection = NSRect(origin: p, size: .zero) needsDisplay = true } override func mouseDragged(with event: NSEvent) { guard let start = startPoint else { return } let p = convert(event.locationInWindow, from: nil) dragDistance = hypot(p.x - start.x, p.y - start.y) // Only become a marquee after real movement; micro-jitter from a // click must not cancel window-pick mode. guard dragDistance >= 4 else { return } isDragging = true hoveredWindowFrame = nil selection = NSRect( x: min(start.x, p.x), y: min(start.y, p.y), width: abs(p.x - start.x), height: abs(p.y - start.y) ) needsDisplay = true } override func mouseUp(with event: NSEvent) { defer { isDragging = false dragDistance = 0 } if isDragging { guard let sel = selection else { return } if sel.width >= 2, sel.height >= 2 { onComplete(window!.convertToScreen(sel)) } else { onCancel() } return } // It was a click. If a window is hovered, capture it with rounded // corners; otherwise treat as an empty marquee (cancel). if let frame = hoveredWindowFrame { onWindowPick(frame) } else { onCancel() } } override func keyDown(with event: NSEvent) { if event.keyCode == 53 { onCancel() } else { super.keyDown(with: event) } } override func draw(_ dirtyRect: NSRect) { NSColor.black.withAlphaComponent(0.35).setFill() NSBezierPath(rect: bounds).fill() // Highlight the hovered window (rounded, like the real window corners) if let frame = hoveredWindowFrame { // Tracked frames are AppKit-global; view space = global - window origin. guard let window = window else { return } let screenFrame = window.convertToScreen(NSRect(origin: .zero, size: bounds.size)) let viewFrame = NSRect( x: frame.origin.x - screenFrame.origin.x, y: frame.origin.y - screenFrame.origin.y, width: frame.width, height: frame.height ) NSGraphicsContext.current?.saveGraphicsState() NSGraphicsContext.current?.compositingOperation = .clear NSColor.black.setFill() NSBezierPath(roundedRect: viewFrame, xRadius: 10, yRadius: 10).fill() NSGraphicsContext.current?.restoreGraphicsState() let border = NSBezierPath(roundedRect: viewFrame, xRadius: 10, yRadius: 10) border.lineWidth = 2 NSColor.controlAccentColor.setStroke() border.stroke() return } guard let sel = selection, sel.width > 0, sel.height > 0 else { return } NSGraphicsContext.current?.saveGraphicsState() NSGraphicsContext.current?.compositingOperation = .clear NSColor.black.setFill() NSBezierPath(rect: sel).fill() NSGraphicsContext.current?.restoreGraphicsState() let border = NSBezierPath(rect: sel) border.lineWidth = 1.5 NSColor.controlAccentColor.setStroke() border.stroke() let label = "\(Int(sel.width)) × \(Int(sel.height))" let attrs: [NSAttributedString.Key: Any] = [ .font: NSFont.monospacedDigitSystemFont(ofSize: 12, weight: .medium), .foregroundColor: NSColor.white, ] let labelSize = label.size(withAttributes: attrs) let labelRect = NSRect( x: sel.maxX - labelSize.width - 8, y: sel.minY - labelSize.height - 10, width: labelSize.width + 8, height: labelSize.height + 6 ) NSColor.black.withAlphaComponent(0.6).setFill() NSBezierPath(roundedRect: labelRect, xRadius: 4, yRadius: 4).fill() label.draw( at: NSPoint(x: labelRect.minX + 4, y: labelRect.minY + 3), withAttributes: attrs ) } }