From b9df743e04338482b73dba0b27c3227a63238029 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Oct 2025 16:14:28 -0700 Subject: [PATCH] macos: window-position-x/y works with window-width/height (#9313) Fixes #9132 We were processing our window size defaults separate from our window position and the result was that you'd get some incorrect behavior. Unify the logic more to fix the positioning. Note there is room to improve this further, I think that all initial positioning could go into the controller completely. But I wanted to minimize the diff for a backport. --- .../Terminal/TerminalController.swift | 33 +++++++++++++++++-- .../Window Styles/TerminalWindow.swift | 24 +++++--------- 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 779c13d9c..2e6021f56 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -527,7 +527,7 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth)) frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight)) - return frame + return adjustForWindowPosition(frame: frame, on: screen) } guard let initialFrame else { return nil } @@ -545,7 +545,30 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr frame.origin.x = max(screen.frame.origin.x, min(frame.origin.x, screen.frame.maxX - newWidth)) frame.origin.y = max(screen.frame.origin.y, min(frame.origin.y, screen.frame.maxY - newHeight)) - return frame + return adjustForWindowPosition(frame: frame, on: screen) + } + + /// Adjusts the given frame for the configured window position. + func adjustForWindowPosition(frame: NSRect, on screen: NSScreen) -> NSRect { + guard let x = derivedConfig.windowPositionX else { return frame } + guard let y = derivedConfig.windowPositionY else { return frame } + + // Convert top-left coordinates to bottom-left origin using our utility extension + let origin = screen.origin( + fromTopLeftOffsetX: CGFloat(x), + offsetY: CGFloat(y), + windowSize: frame.size) + + // Clamp the origin to ensure the window stays fully visible on screen + var safeOrigin = origin + let vf = screen.visibleFrame + safeOrigin.x = min(max(safeOrigin.x, vf.minX), vf.maxX - frame.width) + safeOrigin.y = min(max(safeOrigin.y, vf.minY), vf.maxY - frame.height) + + // Return our new origin + var result = frame + result.origin = safeOrigin + return result } /// This is called anytime a node in the surface tree is being removed. @@ -1358,12 +1381,16 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr let macosWindowButtons: Ghostty.MacOSWindowButtons let macosTitlebarStyle: String let maximize: Bool + let windowPositionX: Int16? + let windowPositionY: Int16? init() { self.backgroundColor = Color(NSColor.windowBackgroundColor) self.macosWindowButtons = .visible self.macosTitlebarStyle = "system" self.maximize = false + self.windowPositionX = nil + self.windowPositionY = nil } init(_ config: Ghostty.Config) { @@ -1371,6 +1398,8 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr self.macosWindowButtons = config.macosWindowButtons self.macosTitlebarStyle = config.macosTitlebarStyle self.maximize = config.maximize + self.windowPositionX = config.windowPositionX + self.windowPositionY = config.windowPositionY } } } diff --git a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift index 5bb8d2f10..ce9f5f4cf 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -65,8 +65,7 @@ class TerminalWindow: NSWindow { // fallback to original centering behavior setInitialWindowPosition( x: config.windowPositionX, - y: config.windowPositionY, - windowDecorations: config.windowDecorations) + y: config.windowPositionY) // If our traffic buttons should be hidden, then hide them if config.macosWindowButtons == .hidden { @@ -425,7 +424,7 @@ class TerminalWindow: NSWindow { return derivedConfig.backgroundColor.withAlphaComponent(alpha) } - private func setInitialWindowPosition(x: Int16?, y: Int16?, windowDecorations: Bool) { + private func setInitialWindowPosition(x: Int16?, y: Int16?) { // If we don't have an X/Y then we try to use the previously saved window pos. guard let x, let y else { if (!LastWindowPosition.shared.restore(self)) { @@ -441,19 +440,14 @@ class TerminalWindow: NSWindow { return } - // Convert top-left coordinates to bottom-left origin using our utility extension - let origin = screen.origin( - fromTopLeftOffsetX: CGFloat(x), - offsetY: CGFloat(y), - windowSize: frame.size) + // We have an X/Y, use our controller function to set it up. + guard let terminalController else { + center() + return + } - // Clamp the origin to ensure the window stays fully visible on screen - var safeOrigin = origin - let vf = screen.visibleFrame - safeOrigin.x = min(max(safeOrigin.x, vf.minX), vf.maxX - frame.width) - safeOrigin.y = min(max(safeOrigin.y, vf.minY), vf.maxY - frame.height) - - setFrameOrigin(safeOrigin) + let frame = terminalController.adjustForWindowPosition(frame: frame, on: screen) + setFrameOrigin(frame.origin) } private func hideWindowButtons() {