From 87cd101eb3c55d0ad7e8f2142fcce90865da7d39 Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Sun, 13 Sep 2026 11:22:40 +0200 Subject: feat(vm-manager): offer to discard a saved state that will not restore A VM saved rather than shut down restores its memory image on the next start. When that image cannot be restored the start fails every time with "unable to execute QEMU command 'migrate-incoming'", and the panel showed an ordinary "shut off" with a Start button that could never work. Detection is virsh dominfo grepped for "Managed save: yes", polled per VM on every list refresh the way the agent rows are. domstats does not report it: the shut-off reason reads "failed", from the failed start, not from the save. virsh list --all --managed-save does print "saved" in the state column, but not together with --name, which is the form the panel lists with. The button appears only when a saved image exists, since managedsave-remove fails without one and would be noise on every other VM. It takes a confirmation click but not a typed name: it deletes the memory image and leaves the disk alone, so the cost of a misclick is a cold boot. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Lo1FG4qTr1inavmhqhfobe --- vm-manager/VmPanel.qml | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) (limited to 'vm-manager/VmPanel.qml') diff --git a/vm-manager/VmPanel.qml b/vm-manager/VmPanel.qml index cf224d6..5960a1a 100644 --- a/vm-manager/VmPanel.qml +++ b/vm-manager/VmPanel.qml @@ -62,16 +62,21 @@ Scope { // Which verbs make sense in the current state, mirroring the states the // old rofi script switched on. - function actionsFor(s) { + function actionsFor(s, saved) { if (s === "running") return [["shutdown", "Shutdown"], ["reboot", "Reboot"], ["suspend", "Suspend"], ["reset", "Reset"], ["destroy", "Force stop"]]; if (s === "paused" || s === "suspended") return [["resume", "Resume"], ["shutdown", "Shutdown"], ["destroy", "Force stop"]]; + // Only worth offering when a saved image actually exists: without one + // managedsave-remove fails, and the button would be noise on every + // other VM. + if (saved) + return [["start", "Start"], ["discardsave", "Discard saved state"]]; return [["start", "Start"]]; } - function isDestructive(a) { return a === "reset" || a === "destroy"; } + function isDestructive(a) { return a === "reset" || a === "destroy" || a === "discardsave"; } function run(vm, action) { if (isDestructive(action)) root.confirming = { kind: action, vm: vm, snap: "" }; @@ -265,6 +270,7 @@ Scope { sourceComponent: detail property string vmName: modelData property string vmState: vm.state ?? "" + property bool vmSaved: vm.saved ?? false } } } @@ -282,6 +288,7 @@ Scope { readonly property string vmName: parent.vmName readonly property string vmState: parent.vmState + readonly property bool vmSaved: parent.vmSaved Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.08) } @@ -299,7 +306,7 @@ Scope { spacing: 8 Repeater { - model: root.actionsFor(vmState) + model: root.actionsFor(vmState, vmSaved) Button { required property var modelData text: modelData[1] @@ -391,6 +398,7 @@ Scope { if (c.kind === "revert") return `Revert ${c.vm} to "${c.snap}"? Changes since that snapshot are lost.`; if (c.kind === "snapdelete") return `Delete snapshot "${c.snap}"?`; if (c.kind === "destroy") return `Force stop ${c.vm}? This is a power cut, not a shutdown.`; + if (c.kind === "discardsave") return `Discard the saved state of ${c.vm}? Its memory image is deleted and the next start boots cold. The disk is untouched.`; if (c.kind === "reset") return `Reset ${c.vm}? This is a hard reset, not a reboot.`; return ""; } -- cgit v1.2.3