From e2a0f439c6332884577eabb2491a343afde22c42 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 6 May 2025 12:57:53 -0700 Subject: [PATCH] macOS: save/restore firstResponder on non-native fullscreen Fixes #6999 It appears that at some point one of the operations causes focus to move away for non-native fullscreen. We previously relied on the delegate method to restore this but a better approach appears to handle this directly in the fullscreen implementations. This fixes the linked issue. I still think long term all the `Ghostty.moveFocus` stuff is a code smell and we should be auditing all that code to see if we can eliminate it. But this is a step in the right direction, and removes one of those uses. --- .../Terminal/BaseTerminalController.swift | 8 -------- .../Features/Terminal/TerminalController.swift | 4 +--- macos/Sources/Helpers/Fullscreen.swift | 18 ++++++++++++++++++ 3 files changed, 19 insertions(+), 11 deletions(-) diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index b502e56e0..36dc5f93c 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -361,14 +361,6 @@ class BaseTerminalController: NSWindowController, } } - func fullscreenDidChange() { - // For some reason focus can get lost when we change fullscreen. Regardless of - // mode above we just move it back. - if let focusedSurface { - Ghostty.moveFocus(to: focusedSurface) - } - } - // MARK: Clipboard Confirmation @objc private func onConfirmClipboardRequest(notification: SwiftUI.Notification) { diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index f384b97ed..cf2dd3348 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -121,9 +121,7 @@ class TerminalController: BaseTerminalController { } - override func fullscreenDidChange() { - super.fullscreenDidChange() - + func fullscreenDidChange() { // When our fullscreen state changes, we resync our appearance because some // properties change when fullscreen or not. guard let focusedSurface else { return } diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index 5233af62d..6094bf844 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -171,6 +171,13 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { guard let savedState = SavedState(window) else { return } self.savedState = savedState + // Get our current first responder on this window. For non-native fullscreen + // we have to restore this because for some reason the operations below + // lose it (see: https://github.com/ghostty-org/ghostty/issues/6999). + // I don't know the root cause here so if we can figure that out there may + // be a nicer way than this. + let firstResponder = window.firstResponder + // We hide the dock if the window is on a screen with the dock. // We must hide the dock FIRST then hide the menu: // If you specify autoHideMenuBar, it must be accompanied by either hideDock or autoHideDock. @@ -207,6 +214,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // https://github.com/ghostty-org/ghostty/issues/1996 DispatchQueue.main.async { self.window.setFrame(self.fullscreenFrame(screen), display: true) + if let firstResponder { + self.window.makeFirstResponder(firstResponder) + } + self.delegate?.fullscreenDidChange() } } @@ -220,6 +231,9 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { let center = NotificationCenter.default center.removeObserver(self, name: NSWindow.didChangeScreenNotification, object: window) + // See enter where we do the same thing to understand why. + let firstResponder = window.firstResponder + // Unhide our elements if savedState.dock { unhideDock() @@ -258,6 +272,10 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { } } + if let firstResponder { + window.makeFirstResponder(firstResponder) + } + // Unset our saved state, we're restored! self.savedState = nil