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.
This commit is contained in:
Mitchell Hashimoto
2023-08-05 10:43:56 -07:00
parent b6d5848f13
commit 2752995ec5
2 changed files with 30 additions and 3 deletions

View File

@ -48,8 +48,13 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject {
if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow } if (windows.allSatisfy { !$0.isVisible }) { return .terminateNow }
// If the user is shutting down, restarting, or logging out, we don't confirm quit. // If the user is shutting down, restarting, or logging out, we don't confirm quit.
if let event = NSAppleEventManager.shared().currentAppleEvent { why: if let event = NSAppleEventManager.shared().currentAppleEvent {
if let why = event.attributeDescriptor(forKeyword: AEKeyword("why?")!) { // 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) { switch (why.typeCodeValue) {
case kAEShutDown: case kAEShutDown:
fallthrough fallthrough

View File

@ -24,6 +24,28 @@ struct PrimaryView: View {
@FocusedValue(\.ghosttySurfaceView) private var focusedSurface @FocusedValue(\.ghosttySurfaceView) private var focusedSurface
@FocusedValue(\.ghosttySurfaceTitle) private var surfaceTitle @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 { var body: some View {
switch ghostty.readiness { switch ghostty.readiness {
case .loading: case .loading:
@ -44,7 +66,7 @@ struct PrimaryView: View {
let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen) let toggleFullscreen = center.publisher(for: Ghostty.Notification.ghosttyToggleFullscreen)
let confirmQuitting = Binding<Bool>(get: { let confirmQuitting = Binding<Bool>(get: {
self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false) self.appDelegate.confirmQuit && self.ownsQuitConfirmation
}, set: { }, set: {
self.appDelegate.confirmQuit = $0 self.appDelegate.confirmQuit = $0
}) })