import AppKit // MARK: - Overlay Window for Screenshot Selection class OverlayWindow: NSWindow { var crosshairView: CrosshairCursorView? var onCancelRequested: (() -> Void)? init(screen: NSScreen) { super.init(contentRect: screen.frame, styleMask: [.borderless], backing: .buffered, defer: false) self.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.mainMenuWindow)) + 1) self.isOpaque = false self.backgroundColor = NSColor.clear self.ignoresMouseEvents = false self.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] // Try to hide the system cursor CGDisplayHideCursor(CGMainDisplayID()) // Create an empty content view first let contentContainer = NSView(frame: screen.frame) contentContainer.wantsLayer = true self.contentView = contentContainer // Make this window visible self.makeKeyAndOrderFront(nil as Any?) self.orderFrontRegardless() // Add our custom crosshair view at the top level crosshairView = CrosshairCursorView(frame: NSRect(x: 0, y: 0, width: 24, height: 24)) if let crosshairView = crosshairView { contentContainer.addSubview(crosshairView) // Get current mouse position and update crosshair initial position let mouseLoc = NSEvent.mouseLocation let windowLoc = self.convertPoint(fromScreen: mouseLoc) crosshairView.frame = NSRect(x: windowLoc.x - 12, y: windowLoc.y - 12, width: 24, height: 24) // Start tracking timer crosshairView.startTracking() } } deinit { // Show the cursor again when window is destroyed CGDisplayShowCursor(CGMainDisplayID()) } // Forceer dat het window key events kan ontvangen, zelfs zonder title bar override var canBecomeKey: Bool { return true } override func keyDown(with event: NSEvent) { if event.keyCode == 53 { // 53 is Escape print("ESC key pressed in OverlayWindow - keyDown") // Roep alleen de callback aan onCancelRequested?() // Sluit het venster NIET HIER } else { // Voor andere toetsen, stuur door naar super super.keyDown(with: event) } } override func rightMouseDown(with event: NSEvent) { print("🖱 Right mouse down in OverlayWindow") // Roep alleen de callback aan onCancelRequested?() // Roep super niet aan, we handelen dit af. } override func close() { // Show the cursor before closing CGDisplayShowCursor(CGMainDisplayID()) super.close() } } // MARK: - Selection View for Drawing Selection Rectangle class SelectionView: NSView { var startPoint: NSPoint? var endPoint: NSPoint? var selectionLayer = CAShapeLayer() var onSelectionComplete: ((NSRect) -> Void)? weak var parentWindow: OverlayWindow? private var isSelecting = false private var hasStartedSelection = false override var acceptsFirstResponder: Bool { true } // Add mouse event monitoring private var eventMonitor: Any? override init(frame frameRect: NSRect) { super.init(frame: frameRect) self.wantsLayer = true // Listen to mouse events at the application level setupEventMonitor() } private func setupEventMonitor() { eventMonitor = NSEvent.addLocalMonitorForEvents(matching: [.leftMouseDown, .leftMouseDragged, .leftMouseUp]) { [weak self] event in guard let self = self else { return event } // Get the location in window coordinates let locationInWindow = event.locationInWindow switch event.type { case .leftMouseDown: self.handleMouseDown(at: locationInWindow) case .leftMouseDragged: self.handleMouseDragged(at: locationInWindow) case .leftMouseUp: self.handleMouseUp(at: locationInWindow) default: break } return event } } // Maak internal ipv private func removeEventMonitor() { if let eventMonitor = eventMonitor { NSEvent.removeMonitor(eventMonitor) self.eventMonitor = nil } } required init?(coder: NSCoder) { super.init(coder: coder) } override func viewDidMoveToWindow() { super.viewDidMoveToWindow() parentWindow = window as? OverlayWindow } override func removeFromSuperview() { // Remove event monitor removeEventMonitor() // Show cursor when removed CGDisplayShowCursor(CGMainDisplayID()) super.removeFromSuperview() } func handleMouseDown(at location: NSPoint) { isSelecting = true hasStartedSelection = true startPoint = location endPoint = location updateSelectionLayer() print("🖱 Mouse down at: \(location)") // Update crosshair position if let parentWindow = parentWindow, let crosshairView = parentWindow.crosshairView { crosshairView.updatePosition(location: location) } } func handleMouseDragged(at location: NSPoint) { guard isSelecting && hasStartedSelection else { return } endPoint = location updateSelectionLayer() // Update crosshair position if let parentWindow = parentWindow, let crosshairView = parentWindow.crosshairView { crosshairView.updatePosition(location: location) } } func handleMouseUp(at location: NSPoint) { guard isSelecting && hasStartedSelection else { return } isSelecting = false // Set the end point endPoint = location guard let start = startPoint, let end = endPoint else { return } // Calculate the selection rectangle in screen coordinates let rect: NSRect // If start and end are very close (within 5 pixels), assume user wants the full screen if abs(start.x - end.x) < 5 && abs(start.y - end.y) < 5 { rect = self.bounds print("📺 Full screen capture requested") } else { rect = NSRect(x: min(start.x, end.x), y: min(start.y, end.y), width: abs(start.x - end.x), height: abs(start.y - end.y)) print("✂️ Selection completed: \(rect)") } // Show cursor before completing CGDisplayShowCursor(CGMainDisplayID()) // Remove the event monitor removeEventMonitor() onSelectionComplete?(rect) removeFromSuperview() window?.orderOut(nil as Any?) } // Keep these for direct interactions with the view override func mouseDown(with event: NSEvent) { handleMouseDown(at: event.locationInWindow) } override func mouseDragged(with event: NSEvent) { handleMouseDragged(at: event.locationInWindow) } override func mouseUp(with event: NSEvent) { handleMouseUp(at: event.locationInWindow) } func updateSelectionLayer() { selectionLayer.removeFromSuperlayer() // Only create selection if we have valid start and end points guard let start = startPoint, let end = endPoint, abs(start.x - end.x) >= 1 || abs(start.y - end.y) >= 1 else { return } let rect = NSRect(x: min(start.x, end.x), y: min(start.y, end.y), width: max(1, abs(start.x - end.x)), height: max(1, abs(start.y - end.y))) let path = CGPath(rect: rect, transform: nil) selectionLayer.path = path selectionLayer.fillColor = NSColor(calibratedWhite: 1, alpha: 0.3).cgColor selectionLayer.strokeColor = NSColor.white.cgColor selectionLayer.lineWidth = 1.0 self.layer?.addSublayer(selectionLayer) } }