From 92870e4e60f19f4280e2ba99cb0f69b4e92bdd40 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 27 Mar 2023 09:41:00 -0700 Subject: [PATCH] macos: confirm on quit --- macos/Ghostty.xcodeproj/project.pbxproj | 4 +++ macos/Sources/ContentView.swift | 48 +++++++++++++++++++++++++ macos/Sources/GhosttyApp.swift | 19 +++++----- 3 files changed, 61 insertions(+), 10 deletions(-) create mode 100644 macos/Sources/ContentView.swift diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 7e919e969..77e95db04 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -20,6 +20,7 @@ A5CEAFDE29B8058B00646FDA /* SplitView.Divider.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */; }; A5CEAFFF29C2410700646FDA /* Backport.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5CEAFFE29C2410700646FDA /* Backport.swift */; }; A5D495A2299BEC7E00DD1313 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; }; + A5FECBD729D1FC3900022361 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FECBD629D1FC3900022361 /* ContentView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ @@ -38,6 +39,7 @@ A5CEAFDD29B8058B00646FDA /* SplitView.Divider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SplitView.Divider.swift; sourceTree = ""; }; A5CEAFFE29C2410700646FDA /* Backport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Backport.swift; sourceTree = ""; }; A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcframework; path = GhosttyKit.xcframework; sourceTree = ""; }; + A5FECBD629D1FC3900022361 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -63,6 +65,7 @@ A55685DF29A03A9F004303CE /* AppError.swift */, A59444F629A2ED5200725BBA /* SettingsView.swift */, A5CEAFFE29C2410700646FDA /* Backport.swift */, + A5FECBD629D1FC3900022361 /* ContentView.swift */, ); path = Sources; sourceTree = ""; @@ -192,6 +195,7 @@ files = ( A55B7BBC29B6FC330055DE60 /* SurfaceView.swift in Sources */, A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */, + A5FECBD729D1FC3900022361 /* ContentView.swift in Sources */, A55B7BB829B6F53A0055DE60 /* Package.swift in Sources */, A55B7BBE29B701360055DE60 /* Ghostty.SplitView.swift in Sources */, A55B7BB629B6F47F0055DE60 /* AppState.swift in Sources */, diff --git a/macos/Sources/ContentView.swift b/macos/Sources/ContentView.swift new file mode 100644 index 000000000..387ab948d --- /dev/null +++ b/macos/Sources/ContentView.swift @@ -0,0 +1,48 @@ +import SwiftUI +import GhosttyKit + +struct ContentView: View { + let ghostty: Ghostty.AppState + + @EnvironmentObject private var appDelegate: AppDelegate + + var body: some View { + switch ghostty.readiness { + case .loading: + Text("Loading") + .onChange(of: appDelegate.confirmQuit) { value in + guard value else { return } + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } + case .error: + ErrorView() + .onChange(of: appDelegate.confirmQuit) { value in + guard value else { return } + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } + case .ready: + Ghostty.TerminalSplit(onClose: Self.closeWindow) + .ghosttyApp(ghostty.app!) + .confirmationDialog( + "Quit Ghostty?", + isPresented: $appDelegate.confirmQuit) { + Button("Close Ghostty", role: .destructive) { + NSApplication.shared.reply(toApplicationShouldTerminate: true) + } + .keyboardShortcut(.defaultAction) + + Button("Cancel", role: .cancel) { + NSApplication.shared.reply(toApplicationShouldTerminate: false) + } + .keyboardShortcut(.cancelAction) + } message: { + Text("All terminal sessions will be terminated.") + } + } + } + + static func closeWindow() { + guard let currentWindow = NSApp.keyWindow else { return } + currentWindow.close() + } +} diff --git a/macos/Sources/GhosttyApp.swift b/macos/Sources/GhosttyApp.swift index 79f5c3521..5a34f5df6 100644 --- a/macos/Sources/GhosttyApp.swift +++ b/macos/Sources/GhosttyApp.swift @@ -18,15 +18,7 @@ struct GhosttyApp: App { var body: some Scene { WindowGroup { - switch ghostty.readiness { - case .loading: - Text("Loading") - case .error: - ErrorView() - case .ready: - Ghostty.TerminalSplit(onClose: Self.closeWindow) - .ghosttyApp(ghostty.app!) - } + ContentView(ghostty: ghostty) } .backport.defaultSize(width: 800, height: 600) .commands { @@ -110,7 +102,9 @@ struct GhosttyApp: App { } } -class AppDelegate: NSObject, NSApplicationDelegate { +class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { + @Published var confirmQuit: Bool = false + // See CursedMenuManager for more information. private var menuManager: CursedMenuManager? @@ -124,6 +118,11 @@ class AppDelegate: NSObject, NSApplicationDelegate { // we can't create from SwiftUI. menuManager = CursedMenuManager() } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + confirmQuit = true + return .terminateLater + } } /// SwiftUI as of macOS 13.x provides no way to manage the default menu items that are created