From 2752995ec5ce621f87e19cbb4e2e5948e54f79b5 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 5 Aug 2023 10:43:56 -0700 Subject: [PATCH] macos: Cmd-Q without any window focus works Previously, this would crash. Once the crash was fixed, it would hang because we would only show confirmation if the terminal window had focus. This introduces some medium complexity logic to work around this: 1. If we are the key window, then show the confirmation dialog. Done. 2. Otherwise, if any other window is a terminal window and is key, they're going to take it so we do nothing. 3. Otherwise, if we are the first terminal window in the application windows list, we show it even if we're not focused. --- macos/Sources/AppDelegate.swift | 9 +++++-- .../Features/Primary Window/PrimaryView.swift | 24 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) 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 })