diff options
| author | emmett1 <me@emmett1.my> | 2026-06-15 23:44:02 +0800 |
|---|---|---|
| committer | emmett1 <me@emmett1.my> | 2026-06-15 23:44:02 +0800 |
| commit | b3b3b11d0abc12845ff7999bd7d61b51049eae51 (patch) | |
| tree | fb7e9e30a431b764ac29edc3c3970b123d8e280b /qemu-tui.py | |
| parent | 6651c16aaad7aa06d84138124797aec9d1ea0542 (diff) | |
| download | qemu-tui-master.tar.gz qemu-tui-master.zip | |
Diffstat (limited to 'qemu-tui.py')
| -rwxr-xr-x | qemu-tui.py | 51 |
1 files changed, 25 insertions, 26 deletions
diff --git a/qemu-tui.py b/qemu-tui.py index 3cd5d4e..c929938 100755 --- a/qemu-tui.py +++ b/qemu-tui.py @@ -61,6 +61,7 @@ class VMConfig: memory: int = 1024 cpus: int = 2 disk: str = "" + disk_fmt: str = "qcow2" cdrom: str = "" arch: str = "x86_64" network: str = "user" @@ -189,7 +190,7 @@ class VMManager: vm = self.vms.get(name) if not vm: return "Not found" - if vm.status == "running": + if vm.status in ("running", "paused"): return "Stop VM first" del self.vms[name] self._save() @@ -199,7 +200,7 @@ class VMManager: vm = self.vms.get(name) if not vm: return "Not found" - if vm.status == "running": + if vm.status in ("running", "paused"): return "Stop VM first" vm.config = cfg self._save() @@ -221,7 +222,7 @@ class VMManager: cmd += ["-drive", f"if=pflash,format=raw,readonly=on,file={ovmf}"] if cfg.disk: - cmd += ["-drive", f"file={cfg.disk},format=qcow2,if=virtio"] + cmd += ["-drive", f"file={cfg.disk},format={cfg.disk_fmt},if=virtio"] if cfg.cdrom: cmd += ["-cdrom", cfg.cdrom] @@ -285,8 +286,8 @@ class VMManager: vm = self.vms.get(name) if not vm: return "Not found" - if vm.status == "running": - return "Already running" + if vm.status in ("running", "paused"): + return "Already running or paused" cmd, sock = self.build_cmd(vm.config) try: Path(sock).unlink(missing_ok=True) @@ -610,7 +611,6 @@ class VMManager: # strip ANSI escape sequences (e.g. cursor movement, colour codes) import re as _re text = _re.sub(r'\x1b\[[0-9;]*[A-Za-z]', '', text) - text = _re.sub(r'\x1b\[[0-9;]*m', '', text) text = text.replace("(qemu)", "").strip() return text or "(ok)" except Exception as e: @@ -627,6 +627,7 @@ class VMManager: vm = self.vms.get(name) if vm: vm.status = "paused" + self._save_runtime() return "" def monitor_resume(self, name: str) -> str: @@ -636,6 +637,7 @@ class VMManager: vm = self.vms.get(name) if vm: vm.status = "running" + self._save_runtime() return "" def monitor_reset(self, name: str) -> str: @@ -713,18 +715,13 @@ class VMManager: # probe with qemu-img info info = self.disk_info(disk_path) - # build config — use detected format in extra_args if not qcow2 fmt = info.get("format", "qcow2") if "error" not in info else "qcow2" cfg = VMConfig( - name = vm_name, - disk = disk_path, - extra_args = f"-drive file={disk_path},format={fmt},if=virtio" if fmt != "qcow2" else "", + name = vm_name, + disk = disk_path, + disk_fmt = fmt, ) - # if format is not qcow2, use raw drive and clear the standard disk field - if fmt != "qcow2": - cfg.disk = "" - cfg.extra_args = f"-drive file={disk_path},format={fmt},if=virtio" self.vms[vm_name] = VMState(config=cfg) self._save() @@ -1066,6 +1063,7 @@ def vm_form_modal(stdscr, cfg: Optional[VMConfig] = None) -> Optional[VMConfig]: memory = int(values["memory"] or 1024), cpus = int(values["cpus"] or 2), disk = values["disk"].strip(), + disk_fmt = cfg.disk_fmt, cdrom = values["cdrom"].strip(), arch = values["arch"], network = values["network"], @@ -1382,7 +1380,7 @@ def disk_mgmt_modal(stdscr, mgr: VMManager, vm_state) -> str: else: if not cfg.disk: cfg.disk = path_raw - mgr.update(cfg.name, cfg) + mgr._save() msg = f"Created {path_raw} ({gb} GiB)" info = _load_info() @@ -1664,7 +1662,8 @@ def portfwd_modal(stdscr, mgr: VMManager, vm_state) -> str: def _save(): cfg.portfwds = [dict(r) for r in rules] - mgr.update(cfg.name, cfg) + err = mgr.update(cfg.name, cfg) + return err while True: win.erase() @@ -1734,8 +1733,10 @@ def portfwd_modal(stdscr, mgr: VMManager, vm_state) -> str: if _is_esc(ch): _flush_esc(win) - _save() + err = _save() _close_modal(win, stdscr) + if err: + return f"Error: {err}" return f"Saved {len(rules)} rule(s)." if rules else "No rules." elif ch == curses.KEY_UP: @@ -2214,7 +2215,7 @@ class TUI: # tabs: 0=Info 1=Command 2=Console 3=Disk 4=Snapshots 5=Monitor self.tab = 0 self.log_off = 0 - self.msg = "Ready | n=new Tab=switch tab q=quit" + self.msg = "Ready" self.msg_ok = True self.last_poll = 0.0 @@ -2254,7 +2255,8 @@ class TUI: ("s","start"), ("k","stop"), ("F","force kill"), ("g","ACPI"), ("z","pause"), ("~","monitor ~"), ("d","disk"), ("p","snaps"), ("f","portfwd"), - ("x","eject"), ("q","quit"), + ("x","eject"), + ("Tab","switch tab"), ("q","quit"), ] hy = sh - len(hints) - 2 for key, act in hints: @@ -2379,7 +2381,7 @@ class TUI: elif base < y0 + h and cfg.network == "user": try: scr.addstr(base, x0, f"{'Port Fwds':<16}", curses.color_pair(6)) - scr.addstr(base, x0 + 16, "(none — press F to add)", curses.color_pair(6)) + scr.addstr(base, x0 + 16, "(none — press f to add)", curses.color_pair(6)) except curses.error: pass @@ -2445,7 +2447,7 @@ class TUI: try: scr.addstr(row, x0, "No disk configured.", curses.color_pair(6)) row += 1 - scr.addstr(row, x0, "Press 'm' to open Disk Management and create one.", + scr.addstr(row, x0, "Press 'd' to open Disk Management and create one.", curses.color_pair(8)) except curses.error: pass @@ -2456,7 +2458,7 @@ class TUI: if not p.exists(): addrow("Status", "FILE NOT FOUND", curses.color_pair(5)) try: - scr.addstr(row+1, x0, "Press 'm' > Create to make the disk image.", + scr.addstr(row+1, x0, "Press 'd' > Create to make the disk image.", curses.color_pair(8)) except curses.error: pass @@ -2481,7 +2483,7 @@ class TUI: try: scr.addstr(row, x0, "-" * min(w-1, 50), curses.color_pair(6)) row += 1 - scr.addstr(row, x0, "Press 'm' to manage: create / resize / convert / delete", + scr.addstr(row, x0, "Press 'd' to manage: create / resize / convert / delete", curses.color_pair(8)) except curses.error: pass @@ -2615,9 +2617,6 @@ class TUI: if ch in (ord("q"), ord("Q")): return False - if _is_esc(ch): - return False - elif ch == curses.KEY_DOWN: self.sel = clamp(self.sel + 1, 0, max(0, len(names)-1)) self.log_off = 0 |