diff --git a/macos/Sources/Features/Terminal/BaseTerminalController.swift b/macos/Sources/Features/Terminal/BaseTerminalController.swift index 68c2430049..eba1466d3d 100644 --- a/macos/Sources/Features/Terminal/BaseTerminalController.swift +++ b/macos/Sources/Features/Terminal/BaseTerminalController.swift @@ -333,7 +333,7 @@ class BaseTerminalController: NSWindowController, } } - func fullscreenDidChange() { + func fullscreenDidChange(_ action: FullscreenModeAction) { // For some reason focus can get lost when we change fullscreen. Regardless of // mode above we just move it back. if let focusedSurface { diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 7fd1802dcd..c955092398 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -95,12 +95,20 @@ class TerminalController: BaseTerminalController { } - override func fullscreenDidChange() { - super.fullscreenDidChange() + override func fullscreenDidChange(_ action: FullscreenModeAction) { + super.fullscreenDidChange(action) // When our fullscreen state changes, we resync our appearance because some // properties change when fullscreen or not. guard let focusedSurface else { return } + if window != nil, + let window = window as? TerminalWindow, + action == .exit, + ghostty.config.macosTitlebarStyle == "hidden" + { + applyHiddenTitlebarStyle(window) + } + syncAppearance(focusedSurface.derivedConfig) } @@ -277,6 +285,41 @@ class TerminalController: BaseTerminalController { shouldCascadeWindows = false } + fileprivate func applyHiddenTitlebarStyle(_ window: TerminalWindow) { + window.styleMask = [ + // We need `titled` in the mask to get the normal window frame + .titled, + + // Full size content view so we can extend + // content in to the hidden titlebar's area + .fullSizeContentView, + + .resizable, + .closable, + .miniaturizable, + ] + + // Hide the title + window.titleVisibility = .hidden + window.titlebarAppearsTransparent = true + + // Hide the traffic lights (window control buttons) + window.standardWindowButton(.closeButton)?.isHidden = true + window.standardWindowButton(.miniaturizeButton)?.isHidden = true + window.standardWindowButton(.zoomButton)?.isHidden = true + + // Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar. + window.tabbingMode = .disallowed + + // Nuke it from orbit -- hide the titlebar container entirely, just in case. There are + // some operations that appear to bring back the titlebar visibility so this ensures + // it is gone forever. + if let themeFrame = window.contentView?.superview, + let titleBarContainer = themeFrame.firstDescendant(withClassName: "NSTitlebarContainerView") { + titleBarContainer.isHidden = true + } + } + override func windowDidLoad() { super.windowDidLoad() guard let window = window as? TerminalWindow else { return } @@ -368,38 +411,7 @@ class TerminalController: BaseTerminalController { // If our titlebar style is "hidden" we adjust the style appropriately if (config.macosTitlebarStyle == "hidden") { - window.styleMask = [ - // We need `titled` in the mask to get the normal window frame - .titled, - - // Full size content view so we can extend - // content in to the hidden titlebar's area - .fullSizeContentView, - - .resizable, - .closable, - .miniaturizable, - ] - - // Hide the title - window.titleVisibility = .hidden - window.titlebarAppearsTransparent = true - - // Hide the traffic lights (window control buttons) - window.standardWindowButton(.closeButton)?.isHidden = true - window.standardWindowButton(.miniaturizeButton)?.isHidden = true - window.standardWindowButton(.zoomButton)?.isHidden = true - - // Disallow tabbing if the titlebar is hidden, since that will (should) also hide the tab bar. - window.tabbingMode = .disallowed - - // Nuke it from orbit -- hide the titlebar container entirely, just in case. There are - // some operations that appear to bring back the titlebar visibility so this ensures - // it is gone forever. - if let themeFrame = window.contentView?.superview, - let titleBarContainer = themeFrame.firstDescendant(withClassName: "NSTitlebarContainerView") { - titleBarContainer.isHidden = true - } + applyHiddenTitlebarStyle(window) } // In various situations, macOS automatically tabs new windows. Ghostty handles diff --git a/macos/Sources/Helpers/Fullscreen.swift b/macos/Sources/Helpers/Fullscreen.swift index a16f329f88..fe568e5287 100644 --- a/macos/Sources/Helpers/Fullscreen.swift +++ b/macos/Sources/Helpers/Fullscreen.swift @@ -34,15 +34,20 @@ protocol FullscreenStyle { func exit() } +enum FullscreenModeAction { + case enter + case exit +} + /// Delegate that can be implemented for fullscreen implementations. protocol FullscreenDelegate: AnyObject { /// Called whenever the fullscreen state changed. You can call isFullscreen to see /// the current state. - func fullscreenDidChange() + func fullscreenDidChange(_ action: FullscreenModeAction) } extension FullscreenDelegate { - func fullscreenDidChange() {} + func fullscreenDidChange(_ action: FullscreenModeAction) {} } /// The base class for fullscreen implementations, cannot be used as a FullscreenStyle on its own. @@ -74,11 +79,11 @@ class FullscreenBase { } @objc private func didEnterFullScreenNotification(_ notification: Notification) { - delegate?.fullscreenDidChange() + delegate?.fullscreenDidChange(.enter) } @objc private func didExitFullScreenNotification(_ notification: Notification) { - delegate?.fullscreenDidChange() + delegate?.fullscreenDidChange(.exit) } } @@ -202,7 +207,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { // https://github.com/ghostty-org/ghostty/issues/1996 DispatchQueue.main.async { self.window.setFrame(self.fullscreenFrame(screen), display: true) - self.delegate?.fullscreenDidChange() + self.delegate?.fullscreenDidChange(.enter) } } @@ -258,7 +263,7 @@ class NonNativeFullscreen: FullscreenBase, FullscreenStyle { window.makeKeyAndOrderFront(nil) // Notify the delegate - self.delegate?.fullscreenDidChange() + self.delegate?.fullscreenDidChange(.exit) } private func fullscreenFrame(_ screen: NSScreen) -> NSRect {