import AppKit import ScreenCaptureKit import SwiftUI import UniformTypeIdentifiers import ObjectiveC // MARK: - Window Capture Manager @available(macOS 12.3, *) class WindowCaptureManager: NSObject, ObservableObject { // MARK: - Properties weak var screenshotApp: ScreenshotApp? var determinedMainScreen: NSScreen! // Stores the true main screen for the current selection session // Available content for capture @Published var availableWindows: [SCWindow] = [] @Published var availableDisplays: [SCDisplay] = [] @Published var isCapturing = false @Published var captureError: String? // Window selection state @Published var isWindowSelectionActive = false private var windowSelectionOverlay: WindowSelectionOverlay? private var globalMouseMonitor: Any? // MARK: - Initialization override init() { super.init() } convenience init(screenshotApp: ScreenshotApp) { self.init() self.screenshotApp = screenshotApp } // MARK: - Content Discovery func refreshAvailableContent() async { print("πŸ”„ Refreshing available content for window capture...") do { let shareableContent = try await SCShareableContent.current await updateAvailableContent(shareableContent) print("βœ… Content refreshed successfully") } catch { await handleContentRefreshError(error) } } // MARK: - Private Helper Methods for Content Discovery private func updateAvailableContent(_ shareableContent: SCShareableContent) async { let filteredWindows = filterShareableWindows(shareableContent.windows) let validDisplays = shareableContent.displays.filter { $0.width > 0 && $0.height > 0 } await MainActor.run { self.availableWindows = filteredWindows self.availableDisplays = validDisplays self.captureError = nil } logContentSummary(windows: filteredWindows, displays: validDisplays) } private func filterShareableWindows(_ windows: [SCWindow]) -> [SCWindow] { return windows.filter { window in // Filter criteria for capturable windows guard window.isOnScreen, window.frame.width > 50, window.frame.height > 50, let app = window.owningApplication, app.applicationName != "WindowServer", app.applicationName != "Dock" else { return false } // Exclude our own app's windows if let ourBundleID = Bundle.main.bundleIdentifier, app.bundleIdentifier == ourBundleID { return false } return true } } private func logContentSummary(windows: [SCWindow], displays: [SCDisplay]) { print("πŸ“Š Content Summary:") print(" Windows: \(windows.count) capturable windows found") print(" Displays: \(displays.count) displays found") if windows.count > 0 { print("πŸͺŸ Sample windows:") for (index, window) in windows.prefix(5).enumerated() { let app = window.owningApplication?.applicationName ?? "Unknown" let title = window.title?.isEmpty == false ? window.title! : "No Title" print(" \(index + 1). \(title) (\(app)) - Layer: \(window.windowLayer)") } if windows.count > 5 { print(" ... and \(windows.count - 5) more windows") } } } private func handleContentRefreshError(_ error: Error) async { await MainActor.run { self.captureError = "Failed to get shareable content: \(error.localizedDescription)" print("❌ WindowCaptureManager Error: \(error)") } } // MARK: - Window Selection Mode func activateWindowSelectionMode() { print("🎬 ACTIVATE: Starting window selection mode...") print("πŸͺŸ Activating window selection mode") // Determine the actual main screen (with origin 0,0) for consistent coordinate calculations self.determinedMainScreen = NSScreen.screens.first { $0.frame.origin == .zero } if self.determinedMainScreen == nil { print("⚠️ CRITICAL: Could not find screen with origin (0,0). Falling back to default NSScreen.main.") self.determinedMainScreen = NSScreen.main! // Fallback, though this might be the unreliable one } print("✨ Using determined main screen for this session: \(determinedMainScreen.customLocalizedName) with frame \(determinedMainScreen.frame)") print("🎬 ACTIVATE: Starting async task to refresh content and show overlay...") Task { print("🎬 TASK: Inside async task, refreshing content...") await refreshAvailableContent() await MainActor.run { print("🎬 TASK: Content refreshed, setting flags and showing overlay...") self.isWindowSelectionActive = true self.showWindowSelectionOverlay() print("🎬 TASK: Window selection overlay should now be shown") } } } // MARK: - Window Selection Mode Deactivation func deactivateWindowSelectionMode() { print("πŸͺŸ Deactivating window selection mode") isWindowSelectionActive = false hideWindowSelectionOverlay() // Remove any mouse monitors if let monitor = globalMouseMonitor { NSEvent.removeMonitor(monitor) globalMouseMonitor = nil } } private func showWindowSelectionOverlay() { // Create overlay window that shows available windows let overlay = WindowSelectionOverlay(windowCaptureManager: self) overlay.show() windowSelectionOverlay = overlay } private func hideWindowSelectionOverlay() { windowSelectionOverlay?.hide() windowSelectionOverlay = nil } private func setupWindowSelectionMonitoring() { globalMouseMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown]) { [weak self] event in guard let self = self, self.isWindowSelectionActive else { return } let location = NSEvent.mouseLocation self.handleWindowSelection(at: location) } } private func removeWindowSelectionMonitoring() { if let monitor = globalMouseMonitor { NSEvent.removeMonitor(monitor) globalMouseMonitor = nil } } private func handleWindowSelection(at location: NSPoint) { // HERSTEL: Zoek naar een window if let window = findWindowAt(location: location) { let windowTitleForPrint = window.title ?? "Untitled" print("🎯 Window selected: \(windowTitleForPrint) at \(location)") deactivateWindowSelectionMode() Task { await captureWindow(window) // Gebruik de captureWindow methode } } else { print(" No window found at \(location), canceling") deactivateWindowSelectionMode() } } // MARK: - Window Detection func findWindowAt(location: NSPoint) -> SCWindow? { NSLog("🎯 Finding window at global location: %@", NSStringFromPoint(location)) // Step 1: Validate main screen reference guard let mainScreenRef = validateMainScreenReference() else { return nil } // Step 2: Find the screen containing the mouse location guard let mouseScreen = findScreenContaining(location: location) else { return nil } // Step 3: Search for window at location return searchWindowsAt(location: location, mouseScreen: mouseScreen, mainScreenRef: mainScreenRef) } // MARK: - Private Helper Methods for Window Detection private func validateMainScreenReference() -> NSScreen? { guard let mainScreenRef = self.determinedMainScreen else { NSLog("❌ No determined main screen available for findWindowAt") return nil } return mainScreenRef } private func findScreenContaining(location: NSPoint) -> NSScreen? { for screen in NSScreen.screens { if screen.frame.contains(location) { NSLog("🎯 Mouse is on NSScreen: '%@' (Frame: %@)", screen.customLocalizedName, NSStringFromRect(screen.frame)) return screen } } NSLog("❌ Mouse location not on any NSScreen: %@", NSStringFromPoint(location)) return nil } private func searchWindowsAt(location: NSPoint, mouseScreen: NSScreen, mainScreenRef: NSScreen) -> SCWindow? { // Sort windows by layer, so frontmost windows are checked first let sortedWindows = availableWindows.sorted { $0.windowLayer > $1.windowLayer } NSLog("🎯 Checking \(sortedWindows.count) sorted windows.") for window in sortedWindows { if let foundWindow = checkWindowAtLocation(window: window, location: location, mouseScreen: mouseScreen, mainScreenRef: mainScreenRef) { return foundWindow } } NSLog(" ❌ No window found at global location: %@ on screen %@", NSStringFromPoint(location), mouseScreen.customLocalizedName) return nil } private func checkWindowAtLocation(window: SCWindow, location: NSPoint, mouseScreen: NSScreen, mainScreenRef: NSScreen) -> SCWindow? { let scWinFrame = window.frame let globalWindowFrame = self.convertSCWindowFrameToGlobal(scWinFrame, mainScreen: mainScreenRef) // Check 1: Does the window intersect with the screen the mouse is on? guard mouseScreen.frame.intersects(globalWindowFrame) else { return nil } // Check 2: Does the global window frame contain the mouse location? guard globalWindowFrame.contains(location) else { return nil } NSLog(" πŸŽ‰ FOUND window by global coords: '%@' (App: %@) on screen '%@'", window.title ?? "Untitled", window.owningApplication?.applicationName ?? "Unknown", mouseScreen.customLocalizedName) return window } // MARK: - Window Capture Methods /// Capture a specific window by SCWindow object func captureWindow(_ window: SCWindow) async { print("πŸ“Έ WindowCaptureManager: Capturing window: \(window.title ?? "Untitled") using ScreenCaptureKitProvider") // Step 1: Initialize capture state await initializeCaptureState() // Step 2: Validate screen capture provider guard let provider = validateScreenCaptureProvider() else { await handleCaptureError("ScreenCaptureProvider not available.", code: 3) return } // Step 3: Perform capture await performWindowCapture(window: window, provider: provider) } // MARK: - Private Helper Methods for Window Capture private func initializeCaptureState() async { await MainActor.run { isCapturing = true captureError = nil } } private func validateScreenCaptureProvider() -> ScreenCaptureKitProvider? { return self.screenshotApp?.screenCaptureProvider } private func performWindowCapture(window: SCWindow, provider: ScreenCaptureKitProvider) async { if let image = await provider.captureWindow(window: window) { await handleSuccessfulCapture(image: image) } else { await handleCaptureError("Failed to capture window with ScreenCaptureKitProvider.", code: 2) } } private func handleSuccessfulCapture(image: NSImage) async { await MainActor.run { self.isCapturing = false self.screenshotApp?.processCapture(image: image) self.deactivateWindowSelectionMode() print("βœ… Window captured successfully and selection mode deactivated.") } } private func handleCaptureError(_ description: String, code: Int) async { await MainActor.run { self.isCapturing = false self.captureError = NSError(domain: "WindowCaptureError", code: code, userInfo: [NSLocalizedDescriptionKey: description]).localizedDescription self.deactivateWindowSelectionMode() print("Error: \(description)") } } // MARK: - Core Graphics Capture Method (Old - Replaced by ScreenCaptureKitProvider) /* private func captureWindowWithCoreGraphics(_ window: SCWindow) async { let windowID = window.windowID let imageRef = CGWindowListCreateImage(.null, .optionIncludingWindow, windowID, .bestResolution) guard let cgImage = imageRef else { await MainActor.run { self.captureError = "Failed to capture window" self.isCapturing = false } return } let nsImage = NSImage(cgImage: cgImage, size: NSSize(width: cgImage.width, height: cgImage.height)) await MainActor.run { self.isCapturing = false // Process the captured image through the main app self.screenshotApp?.processWindowCapture(image: nsImage, windowTitle: window.title) } } */ /// Capture window by application name func captureWindowByApp(appName: String) async { await refreshAvailableContent() guard let window = availableWindows.first(where: { $0.owningApplication?.applicationName == appName }) else { await MainActor.run { self.captureError = "No window found for app: \(appName)" } return } await captureWindow(window) } /// Capture frontmost window func captureFrontmostWindow() async { await refreshAvailableContent() // Get the frontmost window (highest window level) guard let frontmostWindow = availableWindows.max(by: { $0.windowLayer < $1.windowLayer }) else { await MainActor.run { self.captureError = "No frontmost window found" } return } await captureWindow(frontmostWindow) } /// Capture window containing a specific point func captureWindowAt(point: NSPoint) async { await refreshAvailableContent() guard let window = findWindowAt(location: point) else { await MainActor.run { self.captureError = "No window found at specified location" } return } await captureWindow(window) } // MARK: - Settings Integration func getWindowCaptureSettings() -> WindowCaptureSettings { return WindowCaptureSettings( includeCursor: UserDefaults.standard.bool(forKey: "windowCaptureIncludeCursor"), showSelectionUI: UserDefaults.standard.bool(forKey: "windowCaptureShowSelectionUI") ) } func saveWindowCaptureSettings(_ settings: WindowCaptureSettings) { UserDefaults.standard.set(settings.includeCursor, forKey: "windowCaptureIncludeCursor") UserDefaults.standard.set(settings.showSelectionUI, forKey: "windowCaptureShowSelectionUI") } // MARK: - Coordinate Conversion Helper // Converts an SCWindow.frame (origin top-left of main screen, Y-down) to Global coordinates (origin bottom-left of main screen, Y-up) func convertSCWindowFrameToGlobal(_ scWindowFrame: CGRect, mainScreen: NSScreen) -> CGRect { let globalX = scWindowFrame.origin.x // Y of top edge of scWindow in Global Y-up (from main screen bottom) let globalY_topEdge = mainScreen.frame.height - scWindowFrame.origin.y // Y of bottom edge of scWindow in Global Y-up (this becomes the origin for the CGRect) let globalY_bottomEdge_forOrigin = globalY_topEdge - scWindowFrame.height return CGRect(x: globalX, y: globalY_bottomEdge_forOrigin, width: scWindowFrame.width, height: scWindowFrame.height) } // MARK: - Window Detection (Wordt Display Detection) func findDisplayAt(location: NSPoint) -> SCDisplay? { NSLog("🎯 Finding display at global location: %@", NSStringFromPoint(location)) // We gebruiken de beschikbare SCDisplay objecten direct. // Hun frames zijn in pixels, oorsprong linksboven van *dat specifieke display*. // Om te checken of de muis (globale punten, oorsprong linksonder hoofdmenu) binnen een display valt, // moeten we de SCDisplay.frame converteren naar globale punten, of de muislocatie naar de coΓΆrdinaten van elk display. // Makkelijker: gebruik NSScreen.screens, vind de NSScreen waar de muis op is, en zoek dan de SCDisplay met dezelfde displayID. var currentMouseNSScreen: NSScreen? for s in NSScreen.screens { if s.frame.contains(location) { currentMouseNSScreen = s break } } guard let mouseNSScreen = currentMouseNSScreen else { NSLog("❌ Mouse location %@ not on any NSScreen.", NSStringFromPoint(location)) return nil } NSLog("🎯 Mouse is on NSScreen: '%@' (Frame: %@), DisplayID: \(mouseNSScreen.displayID)", mouseNSScreen.customLocalizedName, NSStringFromRect(mouseNSScreen.frame)) // Zoek de SCDisplay die overeenkomt met deze NSScreen if let matchedDisplay = availableDisplays.first(where: { $0.displayID == mouseNSScreen.displayID }) { NSLog(" πŸŽ‰ FOUND display by ID match: DisplayID \(matchedDisplay.displayID)") return matchedDisplay } NSLog(" ❌ No SCDisplay found matching NSScreen with DisplayID \(mouseNSScreen.displayID) at global location: %@", NSStringFromPoint(location)) return nil } // MARK: - Window Capture Methods (Wordt Display Capture Methods) /// Capture a specific display by SCDisplay object func captureDisplay(_ display: SCDisplay) async { print("πŸ–₯️ WindowCaptureManager: Capturing display ID \(display.displayID) using ScreenCaptureKitProvider") // Converteer SCDisplay naar NSScreen guard let targetNSScreen = NSScreen.screens.first(where: { $0.displayID == display.displayID }) else { await MainActor.run { self.isCapturing = false self.captureError = NSError(domain: "DisplayCaptureError", code: 1, userInfo: [NSLocalizedDescriptionKey: "Could not find NSScreen matching SCDisplay ID \(display.displayID)."]).localizedDescription self.deactivateWindowSelectionMode() print("Error: Could not find NSScreen for SCDisplay ID \(display.displayID).") } return } print("πŸ–₯️ Corresponderende NSScreen gevonden: \(targetNSScreen.customLocalizedName)") await MainActor.run { isCapturing = true captureError = nil } if let provider = self.screenshotApp?.screenCaptureProvider { // ScreenCaptureKitProvider.captureScreen verwacht een NSScreen // De excludingWindows parameter is optioneel en wordt hier nil gelaten, // omdat we het hele scherm capturen en desktop icons apart worden behandeld in de provider. if let image = await provider.captureScreen(screen: targetNSScreen, excludingWindows: nil) { await MainActor.run { self.isCapturing = false self.screenshotApp?.processCapture(image: image) self.deactivateWindowSelectionMode() print("βœ… Display captured successfully and selection mode deactivated.") } } else { await MainActor.run { self.isCapturing = false self.captureError = NSError(domain: "DisplayCaptureError", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to capture display with ScreenCaptureKitProvider."]).localizedDescription self.deactivateWindowSelectionMode() print("Error: Failed to capture display ID \(display.displayID) with ScreenCaptureKitProvider.") } } } else { await MainActor.run { self.isCapturing = false self.captureError = NSError(domain: "DisplayCaptureError", code: 3, userInfo: [NSLocalizedDescriptionKey: "ScreenCaptureProvider not available."]).localizedDescription self.deactivateWindowSelectionMode() print("Error: ScreenCaptureProvider not available for display capture.") } } } } // MARK: - Window Capture Settings struct WindowCaptureSettings { var includeCursor: Bool = true var showSelectionUI: Bool = true } // MARK: - Window Selection Overlay @available(macOS 12.3, *) class WindowSelectionOverlay: NSObject { private var windowCaptureManager: WindowCaptureManager private var overlayWindows: [NSWindow] = [] // Dit zijn de semi-transparante overlay windows per scherm private var globalMouseMonitor: Any? init(windowCaptureManager: WindowCaptureManager) { self.windowCaptureManager = windowCaptureManager super.init() } func show() { print("🎬 OVERLAY SHOW: Starting to create overlays...") hide() // Clean up any existing overlays print("πŸͺŸ WindowCaptureManager: Starting window selection on \(NSScreen.screens.count) screens") for (index, screen) in NSScreen.screens.enumerated() { print(" Screen \(index): \(screen.customLocalizedName) - Frame: \(screen.frame)") } // Create a separate overlay window for each screen for (index, screen) in NSScreen.screens.enumerated() { print("🎬 OVERLAY SHOW: Creating overlay for screen \(index): \(screen.customLocalizedName)") let overlayWindow = createOverlayWindow(for: screen) overlayWindows.append(overlayWindow) print("🎬 OVERLAY SHOW: Window created, ordering front...") // IMPROVED: Proper window ordering and activation overlayWindow.orderFrontRegardless() overlayWindow.makeKeyAndOrderFront(nil) print("🎬 OVERLAY SHOW: Window ordered front, checking content view...") // Force the content view to display immediately if let contentView = overlayWindow.contentView { print("🎬 OVERLAY SHOW: Content view found, forcing display...") contentView.needsDisplay = true contentView.displayIfNeeded() print("🎨 FORCED DISPLAY for screen \(screen.customLocalizedName)") } else { print("❌ OVERLAY SHOW: NO CONTENT VIEW found for screen \(screen.customLocalizedName)") } } print("🎬 OVERLAY SHOW: All overlays created, setting up mouse monitoring...") // Setup global mouse monitoring for all screens setupGlobalMouseMonitoring() // Force app activation NSApp.activate(ignoringOtherApps: true) print("πŸͺŸ Window selection overlay shown on \(overlayWindows.count) screens") // ADDITIONAL: Force a refresh after a short delay DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { print("🎬 DELAYED REFRESH: Starting...") for (index, window) in self.overlayWindows.enumerated() { if let contentView = window.contentView { contentView.needsDisplay = true print("🎨 DELAYED REFRESH for window \(index)") } else { print("❌ DELAYED REFRESH: NO CONTENT VIEW for window \(index)") } } } // Debug: List available windows with their positions print("πŸͺŸ Available windows for capture:") for (index, window) in windowCaptureManager.availableWindows.enumerated() { let frame = window.frame let app = window.owningApplication?.applicationName ?? "Unknown" print(" \(index): \(window.title ?? "Untitled") (\(app)) - Frame: \(frame)") } } func hide() { // Remove all overlay windows for window in overlayWindows { window.orderOut(nil) } overlayWindows.removeAll() // Remove global mouse monitor if let monitor = globalMouseMonitor { NSEvent.removeMonitor(monitor) globalMouseMonitor = nil } print("πŸͺŸ Window selection overlay hidden") } private func createOverlayWindow(for screen: NSScreen) -> NSWindow { let window = NSWindow( contentRect: screen.frame, styleMask: [.borderless, .fullSizeContentView], backing: .buffered, defer: false ) // FIXED: Window configuration for proper drawing and event handling window.backgroundColor = NSColor.clear // Clear background so we can draw our own window.isOpaque = false window.level = NSWindow.Level.floating // Lower level but still above normal windows window.ignoresMouseEvents = false window.acceptsMouseMovedEvents = true window.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary, .ignoresCycle] // CRITICAL: Ensure window can receive events and display properly window.displaysWhenScreenProfileChanges = true window.animationBehavior = .none window.hidesOnDeactivate = false // IMPORTANT: Enable automatic invalidation for proper drawing window.invalidateShadow() window.viewsNeedDisplay = true // Create content view for this screen let overlayView = WindowSelectionOverlayView( windowCaptureManager: windowCaptureManager, screen: screen ) // CRITICAL: Properly configure the view for drawing overlayView.wantsLayer = false // Use direct drawing for better control overlayView.needsDisplay = true window.contentView = overlayView // Position window exactly on this screen window.setFrame(screen.frame, display: true) // Force display update print("πŸͺŸ Created overlay window for screen: \(screen.customLocalizedName) at level \(window.level.rawValue)") print("πŸͺŸ Window frame: \(window.frame)") print("πŸͺŸ Content view frame: \(overlayView.frame)") print("πŸͺŸ Content view needsDisplay: \(overlayView.needsDisplay)") return window } private func setupGlobalMouseMonitoring() { globalMouseMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown, .keyDown]) { [weak self] event in guard let self = self else { return } if event.type == .leftMouseDown { let location = NSEvent.mouseLocation print("🎯 Global mouse click at: \(location) for window capture") print("πŸ”₯ DEBUG: Available windows: \(self.windowCaptureManager.availableWindows.count)") // πŸ”₯ FIXED: In window capture mode, any click should capture the window print("πŸͺŸ Click detected in window capture mode - capturing window") self.handleMouseClick(at: location) } else if event.type == .keyDown && event.keyCode == 53 { // ESC print("⌨️ ESC key pressed, canceling window/display capture") self.windowCaptureManager.deactivateWindowSelectionMode() } } print("🎯 Global mouse monitoring setup for window capture") } private func handleMouseClick(at location: NSPoint) { // HERSTEL: Zoek naar een window if let window = windowCaptureManager.findWindowAt(location: location) { let selectedWindowTitle = window.title ?? "Untitled" print("🎯 Window selected: \(selectedWindowTitle) at \(location)") windowCaptureManager.deactivateWindowSelectionMode() Task { await windowCaptureManager.captureWindow(window) // Gebruik de captureWindow methode } } else { print(" No window found at \(location), canceling") windowCaptureManager.deactivateWindowSelectionMode() } } } // MARK: - Window Selection Overlay View @available(macOS 12.3, *) class WindowSelectionOverlayView: NSView { private var windowCaptureManager: WindowCaptureManager private var screen: NSScreen // Het NSScreen object waar deze view op getekend wordt private var hoveredWindow: SCWindow? // HERSTEL: terug naar SCWindow private var trackingArea: NSTrackingArea? init(windowCaptureManager: WindowCaptureManager, screen: NSScreen) { self.windowCaptureManager = windowCaptureManager self.screen = screen super.init(frame: NSRect(origin: .zero, size: screen.frame.size)) self.wantsLayer = false self.layerContentsRedrawPolicy = .onSetNeedsDisplay self.autoresizingMask = [.width, .height] self.translatesAutoresizingMaskIntoConstraints = true NSLog("πŸŽ¨βœ… VIEW INIT for screen: %@, Frame: %@, Bounds: %@", screen.customLocalizedName, NSStringFromRect(self.frame), NSStringFromRect(self.bounds)) setupTrackingArea() self.needsDisplay = true DispatchQueue.main.async { NSLog("πŸŽ¨βœ… VIEW INIT DELAYED DISPLAY for screen: %@", self.screen.customLocalizedName) self.needsDisplay = true self.displayIfNeeded() // Force display after setup } } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupTrackingArea() { // Remove existing tracking area if let existingArea = trackingArea { removeTrackingArea(existingArea) } // Create new tracking area that covers this view trackingArea = NSTrackingArea( rect: bounds, options: [.activeAlways, .mouseMoved, .inVisibleRect], owner: self, userInfo: nil ) if let trackingArea = trackingArea { addTrackingArea(trackingArea) } } override func updateTrackingAreas() { super.updateTrackingAreas() setupTrackingArea() } override func draw(_ dirtyRect: NSRect) { // NSLog("πŸŽ¨βœ… DRAWING ENTRY POINT for view on screen: %@ with dirtyRect: %@", self.window?.screen?.customLocalizedName ?? "UnknownScreen", NSStringFromRect(dirtyRect)) super.draw(dirtyRect) NSColor.black.withAlphaComponent(0.05).setFill() dirtyRect.fill() // Lichte overlay over het hele scherm // HERSTEL: Teken highlight voor hoveredWindow guard let currentHoveredWindow = self.hoveredWindow, let currentViewNSScreen = self.window?.screen, // Het NSScreen waar deze view op is let mainScreenForConversion = self.windowCaptureManager.determinedMainScreen else { // Als er geen window gehovered is, teken dan alleen de instructietekst op het hoofdscherm if self.window?.screen == self.windowCaptureManager.determinedMainScreen { // Gebruik determinedMainScreen voor consistentie drawInstructionText(on: self.window!.screen!, viewSize: self.bounds.size) } return } let scWindowFrame = currentHoveredWindow.frame // Converteer SCWindow.frame naar Globale AppKit coΓΆrdinaten (Y-omhoog, oorsprong linksonder hoofd NSScreen) let globalHoveredWindowFrame = self.windowCaptureManager.convertSCWindowFrameToGlobal(scWindowFrame, mainScreen: mainScreenForConversion) // Converteer de globale frame naar coΓΆrdinaten lokaal aan *deze* view. // De (0,0) van deze view is de linksonderhoek van het currentViewNSScreen. let localHighlightX = globalHoveredWindowFrame.origin.x - currentViewNSScreen.frame.origin.x let localHighlightY = globalHoveredWindowFrame.origin.y - currentViewNSScreen.frame.origin.y let rectToHighlightInViewCoordinates = NSRect(x: localHighlightX, y: localHighlightY, width: globalHoveredWindowFrame.width, height: globalHoveredWindowFrame.height) // NSLog("🎨 DRAW on view for NSScreen \'%@\': Highlighting window \'%@\'", currentViewNSScreen.customLocalizedName, currentHoveredWindow.title ?? "N/A") // NSLog("🎨 SCWindow Frame: %@ (Layer: \(currentHoveredWindow.windowLayer))", NSStringFromRect(scWindowFrame)) // NSLog("🎨 Global Hovered Frame: %@", NSStringFromRect(globalHoveredWindowFrame)) // NSLog("🎨 Calculated Highlight Rect (local to view on screen \'%@\'): %@", currentViewNSScreen.customLocalizedName, NSStringFromRect(rectToHighlightInViewCoordinates)) // Teken alleen als de highlight rect daadwerkelijk de bounds van deze view snijdt. // Dit is belangrijk omdat een venster over meerdere schermen kan spannen. if self.bounds.intersects(rectToHighlightInViewCoordinates) { NSColor.blue.withAlphaComponent(0.3).setFill() // We clippen de highlight path naar de intersectie met de view bounds voor het geval het venster groter is dan dit schermsegment. let drawingRect = self.bounds.intersection(rectToHighlightInViewCoordinates) let highlightPath = NSBezierPath(rect: drawingRect) highlightPath.fill() NSColor.blue.withAlphaComponent(0.8).setStroke() highlightPath.lineWidth = 2 // Standaard lijndikte voor venster highlightPath.stroke() // NSLog("🎨 Window highlight drawn (intersected rect: %@).", NSStringFromRect(drawingRect)) } else { // NSLog("🎨 Highlight rect %@ does NOT intersect view bounds %@. Not drawing highlight on this screen segment.", NSStringFromRect(rectToHighlightInViewCoordinates), NSStringFromRect(self.bounds)) } // Teken instructietekst (alleen op de primary overlay) if currentViewNSScreen == self.windowCaptureManager.determinedMainScreen { // Gebruik determinedMainScreen drawInstructionText(on: currentViewNSScreen, viewSize: self.bounds.size) } } private func drawInstructionText(on screen: NSScreen, viewSize: NSSize) { // HERSTEL: Tekst voor window selectie let instructionText = "Hover to select a window, Click to capture, ESC to cancel" let attributes: [NSAttributedString.Key: Any] = [ .font: NSFont.systemFont(ofSize: 18, weight: .medium), .foregroundColor: NSColor.white.withAlphaComponent(0.9), .strokeColor: NSColor.black.withAlphaComponent(0.5), .strokeWidth: -2.0, .paragraphStyle: { let style = NSMutableParagraphStyle() style.alignment = .center return style }() ] let attributedString = NSAttributedString(string: instructionText, attributes: attributes) let textSize = attributedString.size() let textRect = NSRect(x: (viewSize.width - textSize.width) / 2, y: viewSize.height - textSize.height - 30, width: textSize.width, height: textSize.height) let backgroundPadding: CGFloat = 10 let backgroundRect = NSRect( x: textRect.origin.x - backgroundPadding, y: textRect.origin.y - backgroundPadding, width: textRect.width + (2 * backgroundPadding), height: textRect.height + (2 * backgroundPadding) ) let BORDER_RADIUS: CGFloat = 10 let textBackgroundPath = NSBezierPath(roundedRect: backgroundRect, xRadius: BORDER_RADIUS, yRadius: BORDER_RADIUS) NSColor.black.withAlphaComponent(0.4).setFill() textBackgroundPath.fill() attributedString.draw(in: textRect) // NSLog("🎨 Instruction text drawn on main screen.") // Keep this one less verbose for now } override func mouseMoved(with event: NSEvent) { let globalLocation = NSEvent.mouseLocation var currentMouseNSScreen: NSScreen? for s in NSScreen.screens { if s.frame.contains(globalLocation) { currentMouseNSScreen = s break } } // Als de muis niet op het scherm van DEZE specifieke overlay view is, reset hoveredWindow voor DEZE view. // Dit zorgt ervoor dat een venster dat over meerdere schermen spant, alleen gehighlight wordt op het scherm waar de muis is. guard let mouseNSScreen = currentMouseNSScreen, mouseNSScreen.displayID == self.screen.displayID else { if hoveredWindow != nil { hoveredWindow = nil needsDisplay = true } return } // Muis is op het scherm van deze view. Kijk welk SCWindow (indien aanwezig) gehovered wordt. let foundWindow = windowCaptureManager.findWindowAt(location: globalLocation) if hoveredWindow != foundWindow { // Vergelijk SCWindow objecten direct hoveredWindow = foundWindow needsDisplay = true // if foundWindow != nil { // NSLog("πŸ–±οΈ ✨ Hovered window SET to: \'%@\' (ID: \(foundWindow!.windowID), Layer: \(foundWindow!.windowLayer)) on screen %@", foundWindow!.title ?? "N/A", self.screen.customLocalizedName) // } else { // NSLog("πŸ–±οΈ πŸ’¨ No window hovered on screen %@ at %@", self.screen.customLocalizedName, NSStringFromPoint(globalLocation)) // } } } // πŸ”₯ NIEUW: Handle mouse clicks directly in the overlay view override func mouseDown(with event: NSEvent) { let globalLocation = NSEvent.mouseLocation print("πŸ”₯ OVERLAY: Mouse click detected at \(globalLocation)") // Find window at click location if let window = windowCaptureManager.findWindowAt(location: globalLocation) { let selectedWindowTitle = window.title ?? "Untitled" print("🎯 βœ… OVERLAY: Window selected: \(selectedWindowTitle) at \(globalLocation)") // Deactivate window selection mode first windowCaptureManager.deactivateWindowSelectionMode() // Capture the window Task { print("πŸ”₯ OVERLAY: Starting window capture task for \(selectedWindowTitle)") await windowCaptureManager.captureWindow(window) print("πŸ”₯ OVERLAY: Window capture task completed for \(selectedWindowTitle)") } } else { print("❌ OVERLAY: No window found at \(globalLocation)") print("πŸ”₯ DEBUG: Available windows count: \(windowCaptureManager.availableWindows.count)") // Extra debug: List all windows and their positions for (index, win) in windowCaptureManager.availableWindows.enumerated() { let frame = win.frame let isPointInFrame = NSPointInRect(globalLocation, frame) print("πŸ”₯ DEBUG: Window \(index): \(win.title ?? "Untitled") - Frame: \(frame) - Contains point: \(isPointInFrame)") } windowCaptureManager.deactivateWindowSelectionMode() } } override func keyDown(with event: NSEvent) { if event.keyCode == 53 { // ESC windowCaptureManager.deactivateWindowSelectionMode() } } override var acceptsFirstResponder: Bool { return true } } // MARK: - ScreenshotApp Extension for Window Capture Integration extension ScreenshotApp { // Add window capture manager property to ScreenshotApp private static var windowCaptureManagerKey: UInt8 = 0 var windowCaptureManager: WindowCaptureManager? { get { return objc_getAssociatedObject(self, &ScreenshotApp.windowCaptureManagerKey) as? WindowCaptureManager } set { objc_setAssociatedObject(self, &ScreenshotApp.windowCaptureManagerKey, newValue, .OBJC_ASSOCIATION_RETAIN) } } func initializeWindowCaptureManager() { if #available(macOS 12.3, *) { windowCaptureManager = WindowCaptureManager(screenshotApp: self) print("βœ… WindowCaptureManager initialized") } else { print("⚠️ WindowCaptureManager requires macOS 12.3 or later") } } func processWindowCapture(image: NSImage, windowTitle: String?) { // Process window capture similar to regular screenshot capture print("πŸͺŸ Processing window capture: \(windowTitle ?? "Untitled Window")") let tempURL = createTempUrl() setTempFileURL(tempURL) guard let tiffData = image.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffData), let pngData = bitmap.representation(using: .png, properties: [:]) else { print("❌ Failed to convert window capture to PNG") return } do { try pngData.write(to: tempURL) print("πŸ’Ύ Window capture saved temporarily: \(tempURL.path)") // Play sound if enabled if SettingsManager.shared.playSoundOnCapture { NSSound(named: "Glass")?.play() } // Show preview with window title context showPreview(image: image, windowTitle: windowTitle) } catch { print("❌ Failed to save window capture: \(error)") } } private func showPreview(image: NSImage, windowTitle: String?) { // Use existing preview logic but potentially add window title to UI showPreview(image: image) // You could extend the preview UI to show window title if let title = windowTitle { print("πŸ“Έ Window captured: \(title)") } } }