Sfoglia il codice sorgente

display: improved auto-save on close

No longer need to run a fake snapshot on startup.

Fixes #5745
osy 1 anno fa
parent
commit
29eb620dc8

+ 37 - 22
Platform/macOS/AppDelegate.swift

@@ -15,17 +15,21 @@
 //
 
 @MainActor class AppDelegate: NSObject, NSApplicationDelegate {
+    private enum TerminateError: Error {
+        case wrapped(originalError: any Error, window: NSWindow?)
+    }
+
     var data: UTMData?
     
     @Setting("KeepRunningAfterLastWindowClosed") private var isKeepRunningAfterLastWindowClosed: Bool = false
     @Setting("HideDockIcon") private var isDockIconHidden: Bool = false
     @Setting("NoQuitConfirmation") private var isNoQuitConfirmation: Bool = false
     
-    private var hasRunningVirtualMachines: Bool {
+    private var runningVirtualMachines: [VMData] {
         guard let vmList = data?.vmWindows.keys else {
-            return false
+            return []
         }
-        return vmList.contains(where: { $0.wrapped?.state == .started || ($0.wrapped?.state == .paused && !$0.hasSuspendState) })
+        return vmList.filter({ $0.wrapped?.state == .started || ($0.wrapped?.state == .paused && !$0.hasSuspendState) })
     }
     
     @MainActor
@@ -50,7 +54,7 @@
     }
     
     func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
-        !isKeepRunningAfterLastWindowClosed && !hasRunningVirtualMachines
+        !isKeepRunningAfterLastWindowClosed && runningVirtualMachines.isEmpty
     }
     
     func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply {
@@ -62,12 +66,9 @@
         }
 
         let vmList = data.vmWindows.keys
-        if hasRunningVirtualMachines { // There is at least 1 running VM
-            DispatchQueue.main.async { [self] in
-                if !handleTerminateAfterSaving(allVMs: vmList, sender: sender) {
-                    handleTerminateAfterConfirmation(sender)
-                }
-            }
+        let runningList = runningVirtualMachines
+        if !runningList.isEmpty { // There is at least 1 running VM
+            handleTerminateAfterSaving(candidates: runningList, sender: sender)
             return .terminateLater
         } else if vmList.allSatisfy({ !$0.isLoaded || $0.wrapped?.state == .stopped }) { // All VMs are stopped or suspended
             return .terminateNow
@@ -76,38 +77,52 @@
         }
     }
     
-    private func handleTerminateAfterSaving(allVMs: some Sequence<VMData>, sender: NSApplication) -> Bool {
-        let candidates = allVMs.filter({ $0.wrapped?.state == .started || ($0.wrapped?.state == .paused && !$0.hasSuspendState) })
-        guard !candidates.contains(where: { $0.wrapped?.snapshotUnsupportedError != nil }) else {
-            return false
-        }
+    private func handleTerminateAfterSaving(candidates: some Sequence<VMData>, sender: NSApplication) {
         Task {
             do {
                 try await withThrowingTaskGroup(of: Void.self) { group in
                     for vm in candidates {
                         group.addTask {
+                            let vc = await self.data?.vmWindows[vm] as? VMDisplayWindowController
+                            let window = await vc?.window
                             guard let vm = await vm.wrapped else {
                                 throw UTMVirtualMachineError.notImplemented
                             }
-                            try await vm.pause()
-                            try await vm.saveSnapshot(name: nil)
+                            do {
+                                try await vm.saveSnapshot(name: nil)
+                                vm.delegate = nil
+                                await vc?.enterSuspended(isBusy: false)
+                                if let window = window {
+                                    await window.close()
+                                }
+                            } catch {
+                                throw TerminateError.wrapped(originalError: error, window: window)
+                            }
                         }
                     }
                     try await group.waitForAll()
                 }
                 NSApplication.shared.reply(toApplicationShouldTerminate: true)
+            } catch TerminateError.wrapped(let originalError, let window) {
+                handleTerminateAfterConfirmation(sender, window: window, error: originalError)
             } catch {
-                handleTerminateAfterConfirmation(sender)
+                handleTerminateAfterConfirmation(sender, error: error)
             }
         }
-        return true
     }
     
-    private func handleTerminateAfterConfirmation(_ sender: NSApplication) {
+    private func handleTerminateAfterConfirmation(_ sender: NSApplication, window: NSWindow? = nil, error: Error? = nil) {
         let alert = NSAlert()
         alert.alertStyle = .informational
-        alert.messageText = NSLocalizedString("Confirmation", comment: "VMDisplayWindowController")
+        if error == nil {
+            alert.messageText = NSLocalizedString("Confirmation", comment: "AppDelegate")
+        } else {
+            alert.messageText = NSLocalizedString("Failed to save suspend state", comment: "AppDelegate")
+        }
         alert.informativeText = NSLocalizedString("Quitting UTM will kill all running VMs.", comment: "VMQemuDisplayMetalWindowController")
+        if let error = error {
+            alert.informativeText = error.localizedDescription + "\n" + alert.informativeText
+        }
         alert.addButton(withTitle: NSLocalizedString("OK", comment: "VMDisplayWindowController"))
         alert.addButton(withTitle: NSLocalizedString("Cancel", comment: "VMDisplayWindowController"))
         alert.showsSuppressionButton = true
@@ -122,7 +137,7 @@
                 NSApplication.shared.reply(toApplicationShouldTerminate: false)
             }
         }
-        if let window = sender.keyWindow {
+        if let window = window {
             alert.beginSheetModal(for: window, completionHandler: confirm)
         } else {
             let response = alert.runModal()

+ 3 - 2
Platform/macOS/Display/VMDisplayWindowController.swift

@@ -335,12 +335,13 @@ extension VMDisplayWindowController: NSWindowDelegate {
     private func windowWillCloseAfterSaving(_ sender: NSWindow) -> Bool {
         Task {
             do {
-                try await vm.pause()
                 try await vm.saveSnapshot(name: nil)
+                vm.delegate = nil
+                self.enterSuspended(isBusy: false)
                 sender.close()
             } catch {
-                showErrorAlert(error.localizedDescription)
                 hasSaveSnapshotFailed = true
+                _ = windowWillCloseAfterConfirmation(sender, error: error)
             }
         }
         return false

+ 0 - 9
Services/UTMQemuVirtualMachine.swift

@@ -251,15 +251,6 @@ extension UTMQemuVirtualMachine {
                 return UTMQemuVirtualMachineError.qemuError(NSLocalizedString("Suspend is not supported when an emulated NVMe device is active.", comment: "UTMQemuVirtualMachine"))
             }
         }
-        do {
-            // FIXME: some race condition in QEMU causes some VMs to not work when we immedately call save/delete
-            // for now we workaround this with a 1 second delay
-            try await Task.sleep(nanoseconds: kProbeSuspendDelay)
-            try await _saveSnapshot(name: "tmp-ss-support-probe")
-            try? await _deleteSnapshot(name: "tmp-ss-support-probe")
-        } catch {
-            return error
-        }
         return nil
     }