display: improved auto-save on close
No longer need to run a fake snapshot on startup. Fixes #5745
This commit is contained in:
parent
44d7776556
commit
29eb620dc8
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue