From b764055c3393d26f6c5f1ec373b53c438bcad939 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 9790063d7..08bdac2ad 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -531,7 +531,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 } @@ -549,7 +549,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. @@ -1362,12 +1385,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) { @@ -1375,6 +1402,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 16fcf227f..c33073a45 100644 --- a/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift +++ b/macos/Sources/Features/Terminal/Window Styles/TerminalWindow.swift @@ -84,8 +84,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 { @@ -463,7 +462,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)) { @@ -479,19 +478,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() {