macOS: save/restore firstResponder on non-native fullscreen (#7279)

Fixes #6999
Supersedes #7201 

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.
pull/6915/head
Mitchell Hashimoto 2025-05-06 13:32:33 -07:00 committed by GitHub
commit e5765dfa79
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 19 additions and 11 deletions

View File

@ -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) {

View File

@ -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 }

View File

@ -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