diff --git a/Sources/Fluid/Services/NotchOverlayManager.swift b/Sources/Fluid/Services/NotchOverlayManager.swift index 939cf5f1..c631eb7c 100644 --- a/Sources/Fluid/Services/NotchOverlayManager.swift +++ b/Sources/Fluid/Services/NotchOverlayManager.swift @@ -160,6 +160,7 @@ final class NotchOverlayManager { // Start monitoring active app changes (updates icon in real-time) ActiveAppMonitor.shared.startMonitoring() + let targetScreen = OverlayScreenResolver.screenForCurrentPointer() // Route to bottom overlay if user preference is set if SettingsStore.shared.overlayPosition == .bottom { @@ -168,7 +169,7 @@ final class NotchOverlayManager { } // Otherwise show notch overlay (original behavior) - self.showNotchOverlay(audioLevelPublisher: audioLevelPublisher, mode: mode) + self.showNotchOverlay(audioLevelPublisher: audioLevelPublisher, mode: mode, screen: targetScreen) } /// Show bottom overlay (alternative to notch) @@ -186,7 +187,7 @@ final class NotchOverlayManager { } /// Show notch overlay (original behavior) - private func showNotchOverlay(audioLevelPublisher: AnyPublisher, mode: OverlayMode) { + private func showNotchOverlay(audioLevelPublisher: AnyPublisher, mode: OverlayMode, screen: NSScreen?) { // Hide bottom overlay if it was visible if self.isBottomOverlayVisible { BottomOverlayWindowController.shared.hide() @@ -221,7 +222,11 @@ final class NotchOverlayManager { // Show in expanded state Task { [weak self] in - await newNotch.expand() + if let screen { + await newNotch.expand(on: screen) + } else { + await newNotch.expand() + } // Only update state if we're still the active generation guard let self = self, self.generation == currentGeneration else { return } self.state = .visible @@ -403,7 +408,11 @@ final class NotchOverlayManager { self.commandOutputNotch = newNotch - await newNotch.expand() + if let screen = OverlayScreenResolver.screenForCurrentPointer() { + await newNotch.expand(on: screen) + } else { + await newNotch.expand() + } guard self.commandOutputGeneration == currentGeneration else { return } self.commandOutputState = .visible diff --git a/Sources/Fluid/Services/OverlayScreenResolver.swift b/Sources/Fluid/Services/OverlayScreenResolver.swift new file mode 100644 index 00000000..68212ede --- /dev/null +++ b/Sources/Fluid/Services/OverlayScreenResolver.swift @@ -0,0 +1,15 @@ +// +// OverlayScreenResolver.swift +// Fluid +// + +import AppKit + +enum OverlayScreenResolver { + static func screenForCurrentPointer() -> NSScreen? { + let location = NSEvent.mouseLocation + return NSScreen.screens.first { screen in + screen.frame.contains(location) + } ?? NSScreen.main ?? NSScreen.screens.first + } +} diff --git a/Sources/Fluid/Views/BottomOverlayView.swift b/Sources/Fluid/Views/BottomOverlayView.swift index f61dd05f..52187d9a 100644 --- a/Sources/Fluid/Views/BottomOverlayView.swift +++ b/Sources/Fluid/Views/BottomOverlayView.swift @@ -33,6 +33,7 @@ final class BottomOverlayWindowController { private var pendingResizeWorkItem: DispatchWorkItem? private var localMouseDownMonitor: Any? private var globalMouseDownMonitor: Any? + private var targetScreen: NSScreen? private init() { NotificationCenter.default.addObserver(forName: NSNotification.Name("OverlayOffsetChanged"), object: nil, queue: .main) { [weak self] _ in @@ -78,7 +79,7 @@ final class BottomOverlayWindowController { self.createWindow() } - // Position at bottom center of main screen + self.targetScreen = OverlayScreenResolver.screenForCurrentPointer() self.positionWindow() // Show with animation @@ -98,6 +99,7 @@ final class BottomOverlayWindowController { self.audioSubscription = nil self.pendingResizeWorkItem?.cancel() self.pendingResizeWorkItem = nil + self.targetScreen = nil self.removeMouseDownMonitors() BottomOverlayPromptMenuController.shared.hide() BottomOverlayModeMenuController.shared.hide() @@ -247,8 +249,7 @@ final class BottomOverlayWindowController { // Safe check for window and screen availability guard let window = window else { return } - // Use the screen that contains the window, or fallback to the main screen - let screen = window.screen ?? NSScreen.main + let screen = self.targetScreen ?? window.screen ?? OverlayScreenResolver.screenForCurrentPointer() guard let screen = screen else { return } let fullFrame = screen.frame