From 9262cc57049a8eb20fcbabad799242afd8f4a638 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 2 Nov 2024 10:14:08 -0700 Subject: [PATCH] macos: restore window frame on cascadeTopLeft since macOS 15 moves it Fixes #2565 This appears to be a bug in macOS 15. Specifically on macOS 15 when the new native window snapping feature is used, `cascadeTopLeft(from: zero)` will move the window frame back to its prior unsnapped position. The docs for `cascadeTopLeft(from:)` explicitly say: > When NSZeroPoint, the window is not moved, except as needed to constrain > to the visible screen This is not the behavior we are seeing on macOS 15. The window is on the visible screen, we're using NSZeroPoint, and yet the window is still being moved. This does not happen on macOS 14 (but its hard to say exactly because macOS 14 didn't have window snapping). This commit works around the issue by saving the window frame before calling `cascadeTopLeft(from: zero)` and then restoring it afterwards if it has changed. I've also filed a radar with Apple for this issue. --- .../Sources/Features/Terminal/TerminalManager.swift | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/macos/Sources/Features/Terminal/TerminalManager.swift b/macos/Sources/Features/Terminal/TerminalManager.swift index f71e198ee..0766f33b0 100644 --- a/macos/Sources/Features/Terminal/TerminalManager.swift +++ b/macos/Sources/Features/Terminal/TerminalManager.swift @@ -227,7 +227,19 @@ class TerminalManager { // are closing a tabbed window, we want to set the cascade point to be // the next cascade point from this window. if focusedWindow != controller.window { + // The cascadeTopLeft call below should NOT move the window. Starting with + // macOS 15, we found that specifically when used with the new window snapping + // features of macOS 15, this WOULD move the frame. So we keep track of the + // old frame and restore it if necessary. Issue: + // https://github.com/ghostty-org/ghostty/issues/2565 + let oldFrame = focusedWindow.frame + Self.lastCascadePoint = focusedWindow.cascadeTopLeft(from: NSZeroPoint) + + if focusedWindow.frame != oldFrame { + focusedWindow.setFrame(oldFrame, display: true) + } + return }