display: improved auto-save on close

No longer need to run a fake snapshot on startup.

Fixes #5745
This commit is contained in:
osy 2023-10-03 14:27:40 -07:00
parent 44d7776556
commit 29eb620dc8
3 changed files with 40 additions and 33 deletions

View File

@ -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()

View File

@ -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

View File

@ -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
}