diff --git a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift index 896b25326..6e5607c6f 100644 --- a/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift +++ b/macos/Sources/Features/QuickTerminal/QuickTerminalController.swift @@ -45,7 +45,7 @@ class QuickTerminalController: BaseTerminalController { object: nil) center.addObserver( self, - selector: #selector(onToggleFullscreen), + selector: #selector(onToggleFullscreen(notification:)), name: Ghostty.Notification.ghosttyToggleFullscreen, object: nil) center.addObserver( @@ -154,8 +154,18 @@ class QuickTerminalController: BaseTerminalController { // current space. Animate out. animateOut() } else { - // We've moved to a different space. Bring the quick terminal back - // into view. + // We've moved to a different space. + + // If we're fullscreen, we need to exit fullscreen because the visible + // bounds may have changed causing a new behavior. + if let fullscreenStyle, fullscreenStyle.isFullscreen { + fullscreenStyle.exit() + DispatchQueue.main.async { + self.onToggleFullscreen() + } + } + + // Make the window visible again on this space DispatchQueue.main.async { self.window?.makeKeyAndOrderFront(nil) } @@ -479,9 +489,23 @@ class QuickTerminalController: BaseTerminalController { @objc private func onToggleFullscreen(notification: SwiftUI.Notification) { guard let target = notification.object as? Ghostty.SurfaceView else { return } guard target == self.focusedSurface else { return } + onToggleFullscreen() + } - // We ignore the requested mode and always use non-native for the quick terminal - toggleFullscreen(mode: .nonNative) + private func onToggleFullscreen() { + // We ignore the configured fullscreen style and always use non-native + // because the way the quick terminal works doesn't support native. + // + // An additional detail is that if the is NOT frontmost, then our + // NSApp.presentationOptions will not take effect so we must always + // do the visible menu mode since we can't get rid of the menu. + let mode: FullscreenMode = if (NSApp.isFrontmost) { + .nonNative + } else { + .nonNativeVisibleMenu + } + + toggleFullscreen(mode: mode) } @objc private func ghosttyConfigDidChange(_ notification: Notification) { diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index 1daf9f142..b6fb08271 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -275,7 +275,8 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // calculate this ourselves. var frame = screen.frame - if (!properties.hideMenu) { + if (!NSApp.presentationOptions.contains(.autoHideMenuBar) && + !NSApp.presentationOptions.contains(.hideMenuBar)) { // We need to subtract the menu height since we're still showing it. frame.size.height -= NSApp.mainMenu?.menuBarHeight ?? 0 @@ -358,7 +359,13 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // spaces. We do this because fullscreen spaces already hide the // menu and if we insert/remove this presentation option we get // issues (see #7075) - self.menu = CGSSpace.list(for: window.cgWindowId).allSatisfy { $0.type != .fullscreen } + let activeSpace = CGSSpace.active() + let spaces = CGSSpace.list(for: window.cgWindowId) + if spaces.contains(activeSpace) { + self.menu = activeSpace.type != .fullscreen + } else { + self.menu = spaces.allSatisfy { $0.type != .fullscreen } + } } } } diff --git a/macos/Sources/Helpers/NSApplication+Extension.swift b/macos/Sources/Helpers/NSApplication+Extension.swift index 0580cd5fc..d8e41523a 100644 --- a/macos/Sources/Helpers/NSApplication+Extension.swift +++ b/macos/Sources/Helpers/NSApplication+Extension.swift @@ -1,5 +1,7 @@ import Cocoa +// MARK: Presentation Options + extension NSApplication { private static var presentationOptionCounts: [NSApplication.PresentationOptions.Element: UInt] = [:] @@ -29,3 +31,13 @@ extension NSApplication.PresentationOptions.Element: @retroactive Hashable { hasher.combine(rawValue) } } + +// MARK: Frontmost + +extension NSApplication { + /// True if the application is frontmost. This isn't exactly the same as isActive because + /// an app can be active but not be frontmost if the window with activity is an NSPanel. + var isFrontmost: Bool { + NSWorkspace.shared.frontmostApplication?.bundleIdentifier == Bundle.main.bundleIdentifier + } +} diff --git a/macos/Sources/Helpers/Private/CGS.swift b/macos/Sources/Helpers/Private/CGS.swift index f9c20afe9..0d3b9aa4c 100644 --- a/macos/Sources/Helpers/Private/CGS.swift +++ b/macos/Sources/Helpers/Private/CGS.swift @@ -54,7 +54,7 @@ struct CGSSpace: Hashable, CustomStringConvertible { return .init(rawValue: space) } - /// List the sapces for the given window. + /// List the spaces for the given window. static func list(for windowID: CGWindowID, mask: CGSSpaceMask = .allSpaces) -> [CGSSpace] { guard let spaces = CGSCopySpacesForWindows( CGSMainConnectionID(),