Self-heal off-screen main window instead of requiring quit/relaunch
macOS's built-in window-restore (Resume) can place the window at coordinates for a display that's no longer connected, leaving it visible-but-invisible with no way to recover short of quitting. Now re-centers automatically on app activate/Dock reopen, plus a manual "Reset Window Position" menu command as a guaranteed fallback.
This commit is contained in:
@@ -53,6 +53,41 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
return shouldTerminate ? .terminateNow : .terminateCancel
|
||||
}
|
||||
|
||||
// macOS's built-in window-restoration (Resume) can place the main window at coordinates
|
||||
// that no longer correspond to any connected display (e.g. it was last positioned on an
|
||||
// external monitor that's now disconnected). The window is still technically "visible"
|
||||
// (isVisible == true), so clicking the Dock icon or Cmd+Tab-ing to the app doesn't trigger
|
||||
// any of the usual recovery paths — the process runs fine, there's just nothing on screen.
|
||||
// Self-heal by re-centering any main-sized window that doesn't intersect a current screen,
|
||||
// both whenever the app becomes active and when the Dock icon is clicked with the app
|
||||
// already running.
|
||||
func applicationDidBecomeActive(_ notification: Notification) {
|
||||
AppDelegate.repositionOffscreenWindows()
|
||||
}
|
||||
|
||||
func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
|
||||
AppDelegate.repositionOffscreenWindows()
|
||||
return true
|
||||
}
|
||||
|
||||
/// Pure geometry check, kept separate from AppKit so it's directly testable.
|
||||
nonisolated static func isFrameOffscreen(_ frame: CGRect, screenFrames: [CGRect]) -> Bool {
|
||||
!screenFrames.contains { $0.intersects(frame) }
|
||||
}
|
||||
|
||||
/// Re-centers the main window if it's off-screen, or unconditionally when `force` is true
|
||||
/// (used by the manual "Reset Window Position" menu command).
|
||||
static func repositionOffscreenWindows(force: Bool = false) {
|
||||
let screenFrames = NSScreen.screens.map(\.frame)
|
||||
for window in NSApp.windows where window.isVisible && !(window is NSPanel) && window.frame.width > 200 && window.frame.height > 200 {
|
||||
if force || isFrameOffscreen(window.frame, screenFrames: screenFrames) {
|
||||
window.center()
|
||||
}
|
||||
window.makeKeyAndOrderFront(nil)
|
||||
}
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
|
||||
// `chatViewModel` is wired from `ContentView.onAppear`, not from `oAIApp.init()` — reading
|
||||
// the `@State private var chatViewModel` there returns a throwaway instance distinct from
|
||||
// the one SwiftUI actually renders (confirmed via ObjectIdentifier logging: two different
|
||||
@@ -217,6 +252,16 @@ struct oAIApp: App {
|
||||
chatViewModel.onlineMode.toggle()
|
||||
}
|
||||
.keyboardShortcut("o", modifiers: [.command, .shift])
|
||||
|
||||
Divider()
|
||||
|
||||
// Fallback for when the window ends up off-screen (stale macOS window-restore
|
||||
// coordinates, e.g. after an external monitor is disconnected) — see
|
||||
// AppDelegate.repositionOffscreenWindows. No keyboard shortcut assigned yet;
|
||||
// add one once it's been live-verified against macOS's reserved bindings.
|
||||
Button("Reset Window Position") {
|
||||
AppDelegate.repositionOffscreenWindows(force: true)
|
||||
}
|
||||
}
|
||||
|
||||
// ── Help menu ─────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user