diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index 836ac7467..e6373d9b8 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -48,8 +48,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow } // If the user is shutting down, restarting, or logging out, we don't confirm quit. - if let event = NSAppleEventManager.shared().currentAppleEvent { - if let why = event.attributeDescriptor(forKeyword: AEKeyword("why?")!) { + why: if let event = NSAppleEventManager.shared().currentAppleEvent { + // If all Ghostty windows are in the background (i.e. you Cmd-Q from the Cmd-Tab + // view), then this is null. I don't know why (pun intended) but we have to + // guard against it. + guard let keyword = AEKeyword("why?") else { break why } + + if let why = event.attributeDescriptor(forKeyword: keyword) { switch (why.typeCodeValue) { case kAEShutDown: fallthrough diff --git a/macos/Sources/Features/Primary Window/PrimaryView.swift b/macos/Sources/Features/Primary Window/PrimaryView.swift index a7843f9ba..6805c34ec 100644 --- a/macos/Sources/Features/Primary Window/PrimaryView.swift +++ b/macos/Sources/Features/Primary Window/PrimaryView.swift @@ -24,6 +24,28 @@ struct PrimaryView: View { @FocusedValue(\.ghosttySurfaceView) private var focusedSurface @FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle + // This is true if this view should be the one to show the quit confirmation. + var ownsQuitConfirmation: Bool { + // We need to have a window to show a confirmation. + guard let window = self.window else { return false } + + // If we are the key window then definitely yes. + if (window.isKeyWindow) { return true } + + // If there is some other PrimaryWindow that is key, let it handle it. + let windows = NSApplication.shared.windows + if (windows.contains { + guard let primary = $0 as? PrimaryWindow else { return false } + return primary.isKeyWindow + }) { return false } + + // We aren't the key window but also there is no key PrimaryWindow. + // If we are the FIRST PrimaryWindow in the windows array, then + // we take the job. + guard let firstWindow = (windows.first { $0 is PrimaryWindow }) else { return false } + return window == firstWindow + } + var body: some View { switch ghostty.readiness { case .loading: @@ -44,7 +66,7 @@ struct PrimaryView: View { let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen) let confirmQuitting = Binding(get: { - self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false) + self.appDelegate.confirmQuit && self.ownsQuitConfirmation }, set: { self.appDelegate.confirmQuit = $0 })