diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 74b73ea00..f29e19384 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -1061,22 +1061,6 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr } } - // Set the initial window position. This must happen after the window - // is fully set up (content view, toolbar, default size) so that - // decorations added by subclass awakeFromNib (e.g. toolbar for tabs - // style) don't change the frame after the position is restored. - if let terminalWindow = window as? TerminalWindow { - terminalWindow.setInitialWindowPosition( - x: derivedConfig.windowPositionX, - y: derivedConfig.windowPositionY, - ) - } - - LastWindowPosition.shared.restore( - window, - origin: derivedConfig.windowPositionX == nil && derivedConfig.windowPositionY == nil, - size: defaultSize == nil, - ) // Store our initial frame so we can know our default later. This MUST // be after the defaultSize call above so that we don't re-apply our frame. @@ -1110,6 +1094,34 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr syncAppearance(.init(config)) } + /// Setup correct window frame before showing the window + override func showWindow(_ sender: Any?) { + guard let terminalWindow = window as? TerminalWindow else { return } + + // Set the initial window position. This must happen after the window + // is fully set up (content view, toolbar, default size) so that + // decorations added by subclass awakeFromNib (e.g. toolbar for tabs + // style) don't change the frame after the position is restored. + let originChanged = terminalWindow.setInitialWindowPosition( + x: derivedConfig.windowPositionX, + y: derivedConfig.windowPositionY, + ) + let restored = LastWindowPosition.shared.restore( + terminalWindow, + origin: !originChanged, + size: defaultSize == nil, + ) + + // If nothing is changed for the frame, + // we should center the window + if !originChanged, !restored { + // This doesn't work in `windowDidLoad` somehow + terminalWindow.center() + } + + super.showWindow(sender) + } + // Shows the "+" button in the tab bar, responds to that click. override func newWindowForTab(_ sender: Any?) { // Trigger the ghostty core event logic for a new tab. diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index b9ca1ecc4..e19d6711f 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -536,17 +536,15 @@ class TerminalWindow: NSWindow { terminalController?.updateColorSchemeForSurfaceTree() } - func setInitialWindowPosition(x: Int16?, y: Int16?) { + func setInitialWindowPosition(x: Int16?, y: Int16?) -> Bool { // If we don't have an X/Y then we try to use the previously saved window pos. guard let x = x, let y = y else { - center() - return + return false } // Prefer the screen our window is being placed on otherwise our primary screen. guard let screen = screen ?? NSScreen.screens.first else { - center() - return + return false } // Convert top-left coordinates to bottom-left origin using our utility extension @@ -562,6 +560,7 @@ class TerminalWindow: NSWindow { safeOrigin.y = min(max(safeOrigin.y, vf.minY), vf.maxY - frame.height) setFrameOrigin(safeOrigin) + return true } private func hideWindowButtons() { diff --git a/macos/Sources/Helpers/LastWindowPosition.swift b/macos/Sources/Helpers/LastWindowPosition.swift index 298367c74..c7989b6fa 100644 --- a/macos/Sources/Helpers/LastWindowPosition.swift +++ b/macos/Sources/Helpers/LastWindowPosition.swift @@ -50,7 +50,13 @@ class LastWindowPosition { newFrame.size.height = min(values[3], visibleFrame.height) } - if restoreOrigin, !visibleFrame.contains(newFrame.origin) { + // If the new frame is not constrained to the visible screen, + // we need to shift it a little bit before AppKit does this for us, + // so that we can save the correct size beforehand. + // This fixes restoration while running UI tests, + // where config is modified without switching apps, + // which will not trigger `windowDidBecomeMain`. + if restoreOrigin, !visibleFrame.contains(newFrame) { newFrame.origin.x = max(visibleFrame.minX, min(visibleFrame.maxX - newFrame.width, newFrame.origin.x)) newFrame.origin.y = max(visibleFrame.minY, min(visibleFrame.maxY - newFrame.height, newFrame.origin.y)) }