diff options
Diffstat (limited to 'desktop/modules/vm/README.md')
| -rw-r--r-- | desktop/modules/vm/README.md | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/desktop/modules/vm/README.md b/desktop/modules/vm/README.md new file mode 100644 index 0000000..bcf12ac --- /dev/null +++ b/desktop/modules/vm/README.md @@ -0,0 +1,125 @@ +# vm-manager + +A drawer for libvirt virtual machines: state, live statistics, the actions +that make sense in the current state, and the snapshot list. It replaces the +menu half of `rofi-qemu.sh`, which could only ever show a list of strings. + + ┌─ buildsystem ───────────────── ● running ─┐ + │ CPU 4% RAM 0.7/16 GB Disk 60/130 GB │ + │ 172.16.34.20 │ + │ [Shutdown] [Reboot] [Suspend] [Reset] │ + │ snapshots │ + │ working 2026-03-06 10:27 │ + └───────────────────────────────────────────┘ + +## Running it + + qs -p . + +`SUPER+v` toggles it, through an IPC call, so the shell has to be running +first. Both are wired in the Hyprland config: + + -- autostart.lua + hl.exec_cmd("qs -p ~/Programming/GIT/quickshell/vm-manager") + + -- keybindings.lua + hl.bind(mainMod .. " + v", hl.dsp.exec_cmd( + "qs -p ~/Programming/GIT/quickshell/vm-manager ipc call panel toggle")) + +Write those two paths out in full in the real config: `exec_cmd` runs the +command directly, with no shell to expand `~`. + +The drawer opens on the monitor named by the `monitor` property at the top of +`VmPanel.qml`, defaulting to `DP-3`, and falls back to the first screen when +that one is not connected. It dims the rest of that monitor, because the +secondary screen usually has a real window on it, and takes keyboard focus so +Escape closes it and a delete can be confirmed by typing. + +## Where the numbers come from + +Everything shells out to `virsh`, the same commands the rofi script used. No +libvirt bindings, no new dependency. + +| Row | Source | +| ---------- | ------------------------------------------------- | +| state | `virsh domstats --state` | +| CPU | `cpu.time` sampled twice, divided by vCPU count | +| RAM | `virsh dommemstat`: `actual - usable` | +| Disk | guest agent `guest-get-fsinfo`, the `/` mount | +| address | `virsh domifaddr --source agent` | +| snapshots | `virsh snapshot-list` | + +Three of those need **qemu-guest-agent running inside the guest**. It is not +up for the first few seconds after boot, and another VM might not have it at +all, so those rows show `—` and the header says `agent starting` rather than +substituting a host-side figure. That distinction matters: libvirt's own +`balloon.current` is memory *allocated* to the VM, which on a VM that has +booted reads as 100% forever, and `block.0.allocation` is qcow2 growth on the +host, not usage inside the guest. Showing either in place of the real number +would be quietly wrong, so they are not used as a fallback. + +## Live without polling + +`virsh event --all --loop` streams lifecycle changes, and that process runs +for the whole session, panel open or not. Starting a VM from `virsh` or +virt-manager updates the drawer, and opening it shows current state rather +than whatever was true last time. + +Statistics do need sampling, on a 2 second timer, but only while the drawer +is open: `Virsh.sampling` follows the panel's `open`. A closed panel costs one +idle process waiting on an event socket. + +## Two things that bite + +**A config with no visible window exits.** The drawer is closed most of the +time, so the shell holds itself open with a 1x1 transparent window with an +empty mask, which is click-through and draws nothing. Without it the shell +loads, reports no error and quits, and since `SUPER+v` reaches it over IPC, +the key then has nothing to talk to and silently does nothing. + +**Key events reach an item, not a window.** Setting `keyboardFocus` on the +layer shell is necessary but not sufficient: `Keys.onEscapePressed` on the +PanelWindow itself never fires, because no item inside holds focus. A filled +`Item` with `focus: true` catches it, and takes focus back when the TextInput +in a delete confirmation gives it up. + +## Destructive actions + +`Discard saved state` appears on a shut-off VM only when one actually exists, +and runs `virsh managedsave-remove`. A VM that was saved rather than shut down +restores that memory image on the next `start`, and when the image cannot be +restored the start fails every time with a QEMU `migrate-incoming` error while +the panel shows an ordinary `shut off`. Discarding it deletes the memory image +and nothing else, so the next start is a cold boot and the disk is untouched. +That is why it takes a confirmation but not a typed name. + +Detection is `virsh dominfo`, grepped for `Managed save: yes`, once per VM on +every list refresh. `domstats` does not carry it and `virsh list --name` drops +the column that would. + +`Reset`, `Force stop`, snapshot `Revert` and snapshot `Delete` each take one +confirmation click. `Delete VM` requires the machine's name to be typed, +because it runs `virsh undefine --remove-all-storage`, which erases the disk +image with no undo. The rofi script ran exactly that from a single menu +selection with no confirmation at all. + +Failures are reported with `notify-send`, since a `virsh` error otherwise has +nowhere to go: the process output is not attached to a terminal. + +## Theme + +`Theme.qml` carries only fallbacks. The palette comes from +`~/.cache/wal/udt-palette.qml`, generated by `udt-accent` from +unified-desktop-theme's `palette.rasi`, and is watched: editing the palette +there recolours a running drawer without restarting it. State dots use the +green, yellow and red from that palette. + +The frosting is Hyprland's, matched on this window's namespace: + + hl.layer_rule({ + name = "blur-vm-manager", + match = { namespace = "^(quickshell-vm-manager)$" }, + blur = true, + xray = false, + ignore_alpha = 0.1, + }) |
