macos: fix window size/position restoration on Cmd+W close
This fixes two overlapping issues regarding window positioning and Cmd+W window closures on macOS: 1. `window-position-x` and `window-position-y` coordinates were being ignored on initial launch because `TerminalWindow.setInitialWindowPosition` depended on the `TerminalController`, which isn't fully attached during `awakeFromNib`. This logic was moved so explicit coordinates are correctly enforced. 2. When closing a window via Cmd+W (leaving the app active), reopening the window would continuously cascade down and to the right rather than restoring to the previous position. It now checks if there are other windows open before cascading. 3. `LastWindowPosition` was updated to save both the frame origin and size (width/height), ensuring that restoring a closed window correctly mimics native AppKit State Restoration size behaviors while honoring explicit configurations.pull/11070/head
parent
71cb9debb9
commit
0db32ab9a8
|
|
@ -253,7 +253,14 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
// Only cascade if we aren't fullscreen.
|
||||
if let window = c.window {
|
||||
if !window.styleMask.contains(.fullScreen) {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
let hasFixedPos = c.derivedConfig.windowPositionX != nil && c.derivedConfig.windowPositionY != nil
|
||||
let shouldCascade = !hasFixedPos && TerminalController.all.count > 1
|
||||
|
||||
if shouldCascade {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
} else if !hasFixedPos {
|
||||
Self.lastCascadePoint = NSPoint(x: window.frame.minX, y: window.frame.maxY)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -323,7 +330,14 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
window.setFrameTopLeftPoint(position)
|
||||
window.constrainToScreen()
|
||||
} else {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
let hasFixedPos = c.derivedConfig.windowPositionX != nil && c.derivedConfig.windowPositionY != nil
|
||||
let shouldCascade = !hasFixedPos && TerminalController.all.count > 1
|
||||
|
||||
if shouldCascade {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
} else if !hasFixedPos {
|
||||
Self.lastCascadePoint = NSPoint(x: window.frame.minX, y: window.frame.maxY)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -429,7 +443,14 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
// Only cascade if we aren't fullscreen and are alone in the tab group.
|
||||
if !window.styleMask.contains(.fullScreen) &&
|
||||
window.tabGroup?.windows.count ?? 1 == 1 {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
let hasFixedPos = controller.derivedConfig.windowPositionX != nil && controller.derivedConfig.windowPositionY != nil
|
||||
let shouldCascade = !hasFixedPos && TerminalController.all.count > 1
|
||||
|
||||
if shouldCascade {
|
||||
Self.lastCascadePoint = window.cascadeTopLeft(from: Self.lastCascadePoint)
|
||||
} else if !hasFixedPos {
|
||||
Self.lastCascadePoint = NSPoint(x: window.frame.minX, y: window.frame.maxY)
|
||||
}
|
||||
}
|
||||
|
||||
controller.showWindow(self)
|
||||
|
|
@ -1165,6 +1186,15 @@ class TerminalController: BaseTerminalController, TabGroupCloseCoordinator.Contr
|
|||
}
|
||||
}
|
||||
|
||||
override func windowDidResize(_ notification: Notification) {
|
||||
super.windowDidResize(notification)
|
||||
|
||||
// Whenever we resize save our last position and size for the next start.
|
||||
if let window {
|
||||
LastWindowPosition.shared.save(window)
|
||||
}
|
||||
}
|
||||
|
||||
func windowDidBecomeMain(_ notification: Notification) {
|
||||
// Whenever we get focused, use that as our last window position for
|
||||
// restart. This differs from Terminal.app but matches iTerm2 behavior
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ class TerminalWindow: NSWindow {
|
|||
|
||||
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 x != nil, y != nil else {
|
||||
guard let x = x, let y = y else {
|
||||
if !LastWindowPosition.shared.restore(self) {
|
||||
center()
|
||||
}
|
||||
|
|
@ -552,14 +552,19 @@ class TerminalWindow: NSWindow {
|
|||
return
|
||||
}
|
||||
|
||||
// We have an X/Y, use our controller function to set it up.
|
||||
guard let terminalController else {
|
||||
center()
|
||||
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)
|
||||
|
||||
let frame = terminalController.adjustForWindowPosition(frame: frame, on: screen)
|
||||
setFrameOrigin(frame.origin)
|
||||
// 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)
|
||||
}
|
||||
|
||||
private func hideWindowButtons() {
|
||||
|
|
|
|||
|
|
@ -7,22 +7,28 @@ class LastWindowPosition {
|
|||
private let positionKey = "NSWindowLastPosition"
|
||||
|
||||
func save(_ window: NSWindow) {
|
||||
let origin = window.frame.origin
|
||||
let point = [origin.x, origin.y]
|
||||
UserDefaults.standard.set(point, forKey: positionKey)
|
||||
let frame = window.frame
|
||||
let rect = [frame.origin.x, frame.origin.y, frame.size.width, frame.size.height]
|
||||
UserDefaults.standard.set(rect, forKey: positionKey)
|
||||
}
|
||||
|
||||
func restore(_ window: NSWindow) -> Bool {
|
||||
guard let points = UserDefaults.standard.array(forKey: positionKey) as? [Double],
|
||||
points.count == 2 else { return false }
|
||||
guard let values = UserDefaults.standard.array(forKey: positionKey) as? [Double],
|
||||
values.count >= 2 else { return false }
|
||||
|
||||
let lastPosition = CGPoint(x: points[0], y: points[1])
|
||||
let lastPosition = CGPoint(x: values[0], y: values[1])
|
||||
|
||||
guard let screen = window.screen ?? NSScreen.main else { return false }
|
||||
let visibleFrame = screen.visibleFrame
|
||||
|
||||
var newFrame = window.frame
|
||||
newFrame.origin = lastPosition
|
||||
|
||||
if values.count >= 4 {
|
||||
newFrame.size.width = min(values[2], visibleFrame.width)
|
||||
newFrame.size.height = min(values[3], visibleFrame.height)
|
||||
}
|
||||
|
||||
if !visibleFrame.contains(newFrame.origin) {
|
||||
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))
|
||||
|
|
|
|||
Loading…
Reference in New Issue