UI: Added process executor. Display vm status correctly in list. | CLI: Added get_qemu_version(), fixed virtio audio bug.
This commit is contained in:
parent
4d8c20f284
commit
ca265b0c59
|
@ -16,8 +16,9 @@ def url_ok(url: str) -> None:
|
|||
try:
|
||||
# Open the URL and get the response object
|
||||
res = urllib.request.urlopen(req)
|
||||
|
||||
# Return True if the status code is 200 (OK)
|
||||
if not res.status_code == 200:
|
||||
if not res.getcode() == 200:
|
||||
raise ClanError(f"URL has status code: {res.status_code}")
|
||||
except urllib.error.URLError as ex:
|
||||
raise ClanError(f"URL error: {ex}")
|
||||
|
|
|
@ -38,7 +38,7 @@ def inspect_flake(flake_url: str | Path, flake_attr: str) -> FlakeConfig:
|
|||
]
|
||||
)
|
||||
|
||||
proc = subprocess.run(cmd, text=True, capture_output=True)
|
||||
proc = subprocess.run(cmd, text=True, stdout=subprocess.PIPE)
|
||||
assert proc.stdout is not None
|
||||
if proc.returncode != 0:
|
||||
raise ClanError(
|
||||
|
@ -47,8 +47,6 @@ command: {shlex.join(cmd)}
|
|||
exit code: {proc.returncode}
|
||||
stdout:
|
||||
{proc.stdout}
|
||||
stderr:
|
||||
{proc.stderr}
|
||||
"""
|
||||
)
|
||||
res = proc.stdout.strip()
|
||||
|
|
|
@ -22,7 +22,7 @@ def list_machines(flake_url: Path | str) -> list[str]:
|
|||
"--json",
|
||||
]
|
||||
)
|
||||
proc = subprocess.run(cmd, text=True, capture_output=True)
|
||||
proc = subprocess.run(cmd, text=True, stdout=subprocess.PIPE)
|
||||
assert proc.stdout is not None
|
||||
if proc.returncode != 0:
|
||||
raise ClanError(
|
||||
|
@ -31,8 +31,6 @@ command: {shlex.join(cmd)}
|
|||
exit code: {proc.returncode}
|
||||
stdout:
|
||||
{proc.stdout}
|
||||
stderr:
|
||||
{proc.stderr}
|
||||
"""
|
||||
)
|
||||
res = proc.stdout.strip()
|
||||
|
|
|
@ -18,8 +18,29 @@ from .inspect import VmConfig, inspect_vm
|
|||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_qemu_version() -> list[int]:
|
||||
# Run the command and capture the output
|
||||
output = subprocess.check_output(["qemu-kvm", "--version"])
|
||||
# Decode the output from bytes to string
|
||||
output_str = output.decode("utf-8")
|
||||
# Split the output by newline and get the first line
|
||||
first_line = output_str.split("\n")[0]
|
||||
# Split the first line by space and get the third element
|
||||
version = first_line.split(" ")[3]
|
||||
|
||||
# Split the version by dot and convert each part to integer
|
||||
version_list = [int(x) for x in version.split(".")]
|
||||
# Return the version as a list of integers
|
||||
return version_list
|
||||
|
||||
|
||||
def graphics_options(vm: VmConfig) -> list[str]:
|
||||
common = ["-audio", "driver=pa,model=virtio"]
|
||||
common: list[str] = []
|
||||
|
||||
# Check if the version is greater than 8.1.3 to enable virtio audio
|
||||
if get_qemu_version() > [8, 1, 3]:
|
||||
common = ["-audio", "driver=pa,model=virtio"]
|
||||
|
||||
if vm.wayland:
|
||||
# fmt: off
|
||||
return [
|
||||
|
|
|
@ -8,17 +8,18 @@
|
|||
},
|
||||
{
|
||||
"path": "../clan-cli/tests"
|
||||
}
|
||||
},
|
||||
],
|
||||
"settings": {
|
||||
"python.linting.mypyEnabled": true,
|
||||
"files.exclude": {
|
||||
"**/.direnv": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.ruff_cache": true,
|
||||
"**/.hypothesis": true,
|
||||
"**/__pycache__": true,
|
||||
"**/.reports": true
|
||||
"**/.direnv": true,
|
||||
"**/.hypothesis": true,
|
||||
"**/.mypy_cache": true,
|
||||
"**/.reports": true,
|
||||
"**/.ruff_cache": true,
|
||||
"**/result": true
|
||||
},
|
||||
"search.exclude": {
|
||||
"**/.direnv": true,
|
||||
|
@ -29,4 +30,4 @@
|
|||
"**/.reports": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import gi
|
||||
from clan_cli import vms
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
from clan_cli.clan_uri import ClanURI
|
||||
from gi.repository import Gio, Gtk
|
||||
|
||||
from .constants import constants
|
||||
from .executor import ProcessManager, spawn
|
||||
from .interfaces import Callbacks, InitialJoinValues
|
||||
from .windows.join import JoinWindow
|
||||
from .windows.overview import OverviewWindow
|
||||
|
@ -33,8 +37,15 @@ class Application(Gtk.Application):
|
|||
)
|
||||
self.init_style()
|
||||
self.windows = windows
|
||||
self.proc_manager = ProcessManager()
|
||||
initial = windows.__dict__[config.initial_window]
|
||||
self.cbs = Callbacks(show_list=self.show_list, show_join=self.show_join)
|
||||
self.cbs = Callbacks(
|
||||
show_list=self.show_list,
|
||||
show_join=self.show_join,
|
||||
spawn_vm=self.spawn_vm,
|
||||
stop_vm=self.stop_vm,
|
||||
running_vms=self.running_vms,
|
||||
)
|
||||
if issubclass(initial, JoinWindow):
|
||||
# see JoinWindow constructor
|
||||
self.window = initial(
|
||||
|
@ -46,6 +57,37 @@ class Application(Gtk.Application):
|
|||
# see OverviewWindow constructor
|
||||
self.window = initial(cbs=self.cbs)
|
||||
|
||||
# Connect to the shutdown signal
|
||||
self.connect("shutdown", self.on_shutdown)
|
||||
|
||||
def on_shutdown(self, app: Gtk.Application) -> None:
|
||||
print("Shutting down")
|
||||
self.proc_manager.kill_all()
|
||||
|
||||
def spawn_vm(self, url: str, attr: str) -> None:
|
||||
print(f"spawn_vm {url}")
|
||||
|
||||
# TODO: We should use VMConfig from the history file
|
||||
vm = vms.run.inspect_vm(flake_url=url, flake_attr=attr)
|
||||
log_path = Path(".")
|
||||
|
||||
# TODO: We only use the url as the ident. This is not unique as the flake_attr is missing.
|
||||
# when we migrate everything to use the ClanURI class we can use the full url as the ident
|
||||
self.proc_manager.spawn(
|
||||
ident=url,
|
||||
wait_stdin_con=False,
|
||||
log_path=log_path,
|
||||
func=vms.run.run_vm,
|
||||
vm=vm,
|
||||
)
|
||||
|
||||
def stop_vm(self, url: str, attr: str) -> None:
|
||||
print(f"stop_vm {url}")
|
||||
self.proc_manager.kill(url)
|
||||
|
||||
def running_vms(self) -> list[str]:
|
||||
return list(self.proc_manager.procs.keys())
|
||||
|
||||
def show_list(self) -> None:
|
||||
prev = self.window
|
||||
self.window = self.windows.__dict__["overview"](cbs=self.cbs)
|
||||
|
@ -125,10 +167,6 @@ def dummy_f(msg: str) -> None:
|
|||
|
||||
|
||||
def show_run_vm(parser: argparse.ArgumentParser) -> None:
|
||||
from pathlib import Path
|
||||
|
||||
from .executor import spawn
|
||||
|
||||
log_path = Path(".").resolve()
|
||||
proc = spawn(wait_stdin_con=True, log_path=log_path, func=dummy_f, msg="Hello")
|
||||
input("Press enter to kill process: ")
|
||||
|
|
|
@ -2,34 +2,44 @@ import os
|
|||
import signal
|
||||
import sys
|
||||
import traceback
|
||||
import weakref
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import gi
|
||||
from clan_cli.errors import ClanError
|
||||
|
||||
gi.require_version("GdkPixbuf", "2.0")
|
||||
|
||||
import dataclasses
|
||||
import multiprocessing as mp
|
||||
from collections.abc import Callable
|
||||
|
||||
OUT_FILE: Path | None = None
|
||||
IN_FILE: Path | None = None
|
||||
|
||||
# Kill the new process and all its children by sending a SIGTERM signal to the process group
|
||||
def _kill_group(proc: mp.Process) -> None:
|
||||
pid = proc.pid
|
||||
assert pid is not None
|
||||
if proc.is_alive():
|
||||
print(
|
||||
f"Killing process group pid={pid}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
os.killpg(pid, signal.SIGTERM)
|
||||
else:
|
||||
print(f"Process {proc.name} with pid {pid} is already dead", file=sys.stderr)
|
||||
|
||||
|
||||
@dataclasses.dataclass(frozen=True)
|
||||
class MPProcess:
|
||||
def __init__(
|
||||
self, *, name: str, proc: mp.Process, out_file: Path, in_file: Path
|
||||
) -> None:
|
||||
self.name = name
|
||||
self.proc = proc
|
||||
self.out_file = out_file
|
||||
self.in_file = in_file
|
||||
name: str
|
||||
proc: mp.Process
|
||||
out_file: Path
|
||||
in_file: Path
|
||||
|
||||
# Kill the new process and all its children by sending a SIGTERM signal to the process group
|
||||
def kill_group(self) -> None:
|
||||
pid = self.proc.pid
|
||||
assert pid is not None
|
||||
os.killpg(pid, signal.SIGTERM)
|
||||
_kill_group(proc=self.proc)
|
||||
|
||||
|
||||
def _set_proc_name(name: str) -> None:
|
||||
|
@ -51,23 +61,6 @@ def _set_proc_name(name: str) -> None:
|
|||
prctl(15, name.encode(), 0, 0, 0)
|
||||
|
||||
|
||||
def _signal_handler(signum: int, frame: Any) -> None:
|
||||
signame = signal.strsignal(signum)
|
||||
print("Signal received:", signame)
|
||||
|
||||
# Delete files
|
||||
if OUT_FILE is not None:
|
||||
OUT_FILE.unlink()
|
||||
if IN_FILE is not None:
|
||||
IN_FILE.unlink()
|
||||
|
||||
# Restore the default handler
|
||||
signal.signal(signal.SIGTERM, signal.SIG_DFL)
|
||||
|
||||
# Re-raise the signal
|
||||
os.kill(os.getpid(), signum)
|
||||
|
||||
|
||||
def _init_proc(
|
||||
func: Callable,
|
||||
out_file: Path,
|
||||
|
@ -76,39 +69,29 @@ def _init_proc(
|
|||
proc_name: str,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
# Set the global variables
|
||||
global OUT_FILE, IN_FILE
|
||||
OUT_FILE = out_file
|
||||
IN_FILE = in_file
|
||||
|
||||
# Create a new process group
|
||||
os.setsid()
|
||||
|
||||
# Open stdout and stderr
|
||||
out_fd = os.open(str(out_file), flags=os.O_RDWR | os.O_CREAT | os.O_TRUNC)
|
||||
os.dup2(out_fd, sys.stdout.fileno())
|
||||
os.dup2(out_fd, sys.stderr.fileno())
|
||||
with open(out_file, "w") as out_fd:
|
||||
os.dup2(out_fd.fileno(), sys.stdout.fileno())
|
||||
os.dup2(out_fd.fileno(), sys.stderr.fileno())
|
||||
|
||||
# Print some information
|
||||
pid = os.getpid()
|
||||
gpid = os.getpgid(pid=pid)
|
||||
print(f"Started new process pid={pid} gpid={gpid}")
|
||||
|
||||
# Register the signal handler for SIGINT
|
||||
signal.signal(signal.SIGTERM, _signal_handler)
|
||||
print(f"Started new process pid={pid} gpid={gpid}", file=sys.stderr)
|
||||
|
||||
# Set the process name
|
||||
_set_proc_name(proc_name)
|
||||
|
||||
# Open stdin
|
||||
flags = None
|
||||
if wait_stdin_connect:
|
||||
print(f"Waiting for stdin connection on file {in_file}", file=sys.stderr)
|
||||
flags = os.O_RDONLY
|
||||
with open(in_file) as in_fd:
|
||||
os.dup2(in_fd.fileno(), sys.stdin.fileno())
|
||||
else:
|
||||
flags = os.O_RDONLY | os.O_NONBLOCK
|
||||
in_fd = os.open(str(in_file), flags=flags)
|
||||
os.dup2(in_fd, sys.stdin.fileno())
|
||||
sys.stdin.close()
|
||||
|
||||
# Execute the main function
|
||||
print(f"Executing function {func.__name__} now", file=sys.stderr)
|
||||
|
@ -116,9 +99,10 @@ def _init_proc(
|
|||
func(**kwargs)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
finally:
|
||||
pid = os.getpid()
|
||||
gpid = os.getpgid(pid=pid)
|
||||
print(f"Killing process group pid={pid} gpid={gpid}")
|
||||
print(f"Killing process group pid={pid} gpid={gpid}", file=sys.stderr)
|
||||
os.killpg(gpid, signal.SIGTERM)
|
||||
|
||||
|
||||
|
@ -127,7 +111,13 @@ def spawn(
|
|||
) -> MPProcess:
|
||||
# Decouple the process from the parent
|
||||
if mp.get_start_method(allow_none=True) is None:
|
||||
mp.set_start_method(method="spawn")
|
||||
mp.set_start_method(method="forkserver")
|
||||
print("Set mp start method to forkserver", file=sys.stderr)
|
||||
|
||||
if not log_path.is_dir():
|
||||
raise ClanError(f"Log path {log_path} is not a directory")
|
||||
if not log_path.exists():
|
||||
log_path.mkdir(parents=True)
|
||||
|
||||
# Set names
|
||||
proc_name = f"MPExec:{func.__name__}"
|
||||
|
@ -152,6 +142,7 @@ def spawn(
|
|||
assert proc.pid is not None
|
||||
print(f"Started process '{proc_name}'")
|
||||
print(f"Arguments: {kwargs}")
|
||||
|
||||
if wait_stdin_con:
|
||||
cmd = f"cat - > {in_file}"
|
||||
print(f"Connect to stdin with : {cmd}")
|
||||
|
@ -165,4 +156,41 @@ def spawn(
|
|||
out_file=out_file,
|
||||
in_file=in_file,
|
||||
)
|
||||
|
||||
return mp_proc
|
||||
|
||||
|
||||
# Processes are killed when the ProcessManager is garbage collected
|
||||
class ProcessManager:
|
||||
def __init__(self) -> None:
|
||||
self.procs: dict[str, MPProcess] = dict()
|
||||
self._finalizer = weakref.finalize(self, self.kill_all)
|
||||
|
||||
def spawn(
|
||||
self,
|
||||
*,
|
||||
ident: str,
|
||||
wait_stdin_con: bool,
|
||||
log_path: Path,
|
||||
func: Callable,
|
||||
**kwargs: Any,
|
||||
) -> MPProcess:
|
||||
proc = spawn(
|
||||
wait_stdin_con=wait_stdin_con, log_path=log_path, func=func, **kwargs
|
||||
)
|
||||
if ident in self.procs:
|
||||
raise ClanError(f"Process with id {ident} already exists")
|
||||
self.procs[ident] = proc
|
||||
return proc
|
||||
|
||||
def kill_all(self) -> None:
|
||||
print("Killing all processes", file=sys.stderr)
|
||||
for proc in self.procs.values():
|
||||
proc.kill_group()
|
||||
|
||||
def kill(self, ident: str) -> None:
|
||||
if ident not in self.procs:
|
||||
raise ClanError(f"Process with id {ident} does not exist")
|
||||
proc = self.procs[ident]
|
||||
proc.kill_group()
|
||||
del self.procs[ident]
|
||||
|
|
|
@ -13,3 +13,6 @@ class InitialJoinValues:
|
|||
class Callbacks:
|
||||
show_list: Callable[[], None]
|
||||
show_join: Callable[[], None]
|
||||
spawn_vm: Callable[[str, str], None]
|
||||
stop_vm: Callable[[str, str], None]
|
||||
running_vms: Callable[[], list[str]]
|
||||
|
|
|
@ -19,6 +19,7 @@ class VMBase:
|
|||
name: str
|
||||
url: str
|
||||
status: bool
|
||||
_flake_attr: str
|
||||
|
||||
@staticmethod
|
||||
def name_to_type_map() -> OrderedDict[str, type]:
|
||||
|
@ -28,6 +29,7 @@ class VMBase:
|
|||
"Name": str,
|
||||
"URL": str,
|
||||
"Online": bool,
|
||||
"_FlakeAttr": str,
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -42,13 +44,10 @@ class VMBase:
|
|||
"Name": self.name,
|
||||
"URL": self.url,
|
||||
"Online": self.status,
|
||||
"_FlakeAttr": self._flake_attr,
|
||||
}
|
||||
)
|
||||
|
||||
def run(self) -> None:
|
||||
print(f"Running VM {self.name}")
|
||||
# vm = vms.run.inspect_vm(flake_url=self.url, flake_attr="defaultVM")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class VM:
|
||||
|
@ -60,7 +59,9 @@ class VM:
|
|||
|
||||
|
||||
# start/end indexes can be used optionally for pagination
|
||||
def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
||||
def get_initial_vms(
|
||||
running_vms: list[str], start: int = 0, end: int | None = None
|
||||
) -> list[VM]:
|
||||
vm_list = []
|
||||
|
||||
# Execute `clan flakes add <path>` to democlan for this to work
|
||||
|
@ -69,11 +70,16 @@ def get_initial_vms(start: int = 0, end: int | None = None) -> list[VM]:
|
|||
if entry.flake.icon is not None:
|
||||
icon = entry.flake.icon
|
||||
|
||||
status = False
|
||||
if entry.flake.flake_url in running_vms:
|
||||
status = True
|
||||
|
||||
base = VMBase(
|
||||
icon=icon,
|
||||
name=entry.flake.clan_name,
|
||||
url=entry.flake.flake_url,
|
||||
status=False,
|
||||
status=status,
|
||||
_flake_attr=entry.flake.flake_attr,
|
||||
)
|
||||
vm_list.append(VM(base=base))
|
||||
|
||||
|
|
|
@ -2,7 +2,8 @@ from collections.abc import Callable
|
|||
|
||||
from gi.repository import GdkPixbuf, Gtk
|
||||
|
||||
from ..models import VMBase, get_initial_vms
|
||||
from ..interfaces import Callbacks
|
||||
from ..models import VMBase
|
||||
|
||||
|
||||
class ClanEditForm(Gtk.ListBox):
|
||||
|
@ -55,11 +56,7 @@ class ClanEdit(Gtk.Box):
|
|||
self.show_list = remount_list
|
||||
self.selected = selected_vm
|
||||
|
||||
button_hooks = {
|
||||
"on_save_clicked": self.on_save,
|
||||
}
|
||||
|
||||
self.toolbar = ClanEditToolbar(**button_hooks)
|
||||
self.toolbar = ClanEditToolbar(on_save_clicked=self.on_save)
|
||||
self.add(self.toolbar)
|
||||
self.add(ClanEditForm(selected=self.selected))
|
||||
|
||||
|
@ -88,8 +85,9 @@ class ClanList(Gtk.Box):
|
|||
remount_list: Callable[[], None],
|
||||
remount_edit: Callable[[], None],
|
||||
set_selected: Callable[[VMBase | None], None],
|
||||
show_join: Callable[[], None],
|
||||
cbs: Callbacks,
|
||||
selected_vm: VMBase | None,
|
||||
vms: list[VMBase],
|
||||
show_toolbar: bool = True,
|
||||
) -> None:
|
||||
super().__init__(orientation=Gtk.Orientation.VERTICAL, expand=True)
|
||||
|
@ -98,35 +96,45 @@ class ClanList(Gtk.Box):
|
|||
self.remount_list_view = remount_list
|
||||
self.set_selected = set_selected
|
||||
self.show_toolbar = show_toolbar
|
||||
self.show_join = show_join
|
||||
self.cbs = cbs
|
||||
|
||||
self.selected_vm: VMBase | None = selected_vm
|
||||
|
||||
button_hooks = {
|
||||
"on_start_clicked": self.on_start_clicked,
|
||||
"on_stop_clicked": self.on_stop_clicked,
|
||||
"on_edit_clicked": self.on_edit_clicked,
|
||||
"on_join_clicked": self.on_join_clicked,
|
||||
}
|
||||
if show_toolbar:
|
||||
self.toolbar = ClanListToolbar(**button_hooks)
|
||||
self.toolbar = ClanListToolbar(
|
||||
on_start_clicked=self.on_start_clicked,
|
||||
on_stop_clicked=self.on_stop_clicked,
|
||||
on_edit_clicked=self.on_edit_clicked,
|
||||
on_join_clicked=self.on_join_clicked,
|
||||
)
|
||||
self.toolbar.set_is_selected(self.selected_vm is not None)
|
||||
self.add(self.toolbar)
|
||||
|
||||
self.list_hooks = {
|
||||
"on_select_row": self.on_select_vm,
|
||||
}
|
||||
self.add(ClanListView(**self.list_hooks, selected_vm=selected_vm))
|
||||
self.add(
|
||||
ClanListView(
|
||||
vms=vms,
|
||||
on_select_row=self.on_select_vm,
|
||||
selected_vm=selected_vm,
|
||||
on_double_click=self.on_double_click,
|
||||
)
|
||||
)
|
||||
|
||||
def on_double_click(self, vm: VMBase) -> None:
|
||||
print(f"on_double_click: {vm.name}")
|
||||
self.on_start_clicked(self)
|
||||
|
||||
def on_start_clicked(self, widget: Gtk.Widget) -> None:
|
||||
print("Start clicked")
|
||||
if self.selected_vm:
|
||||
self.selected_vm.run()
|
||||
self.cbs.spawn_vm(self.selected_vm.url, self.selected_vm._flake_attr)
|
||||
# Call this to reload
|
||||
self.remount_list_view()
|
||||
|
||||
def on_stop_clicked(self, widget: Gtk.Widget) -> None:
|
||||
print("Stop clicked")
|
||||
if self.selected_vm:
|
||||
self.cbs.stop_vm(self.selected_vm.url, self.selected_vm._flake_attr)
|
||||
self.remount_list_view()
|
||||
|
||||
def on_join_clicked(self, widget: Gtk.Widget) -> None:
|
||||
print("Join clicked")
|
||||
|
@ -208,10 +216,13 @@ class ClanListView(Gtk.Box):
|
|||
*,
|
||||
on_select_row: Callable[[VMBase], None],
|
||||
selected_vm: VMBase | None,
|
||||
vms: list[VMBase],
|
||||
on_double_click: Callable[[VMBase], None],
|
||||
) -> None:
|
||||
super().__init__(expand=True)
|
||||
self.vms: list[VMBase] = [vm.base for vm in get_initial_vms()]
|
||||
self.vms: list[VMBase] = vms
|
||||
self.on_select_row = on_select_row
|
||||
self.on_double_click = on_double_click
|
||||
store_types = VMBase.name_to_type_map().values()
|
||||
|
||||
self.list_store = Gtk.ListStore(*store_types)
|
||||
|
@ -264,7 +275,7 @@ class ClanListView(Gtk.Box):
|
|||
model, row = selection.get_selected()
|
||||
if row is not None:
|
||||
vm = VMBase(*model[row])
|
||||
vm.run()
|
||||
self.on_double_click(vm)
|
||||
|
||||
|
||||
def setColRenderers(tree_view: Gtk.TreeView) -> None:
|
||||
|
|
|
@ -2,7 +2,7 @@ from typing import Any
|
|||
|
||||
import gi
|
||||
|
||||
from ..models import VMBase
|
||||
from ..models import VMBase, get_initial_vms
|
||||
|
||||
gi.require_version("Gtk", "3.0")
|
||||
|
||||
|
@ -19,18 +19,20 @@ class OverviewWindow(Gtk.ApplicationWindow):
|
|||
self.set_title("cLAN Manager")
|
||||
self.connect("delete-event", self.on_quit)
|
||||
self.set_default_size(800, 600)
|
||||
self.cbs = cbs
|
||||
|
||||
vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True)
|
||||
self.add(vbox)
|
||||
self.stack = Gtk.Stack()
|
||||
|
||||
self.list_hooks = {
|
||||
"remount_list": self.remount_list_view,
|
||||
"remount_edit": self.remount_edit_view,
|
||||
"set_selected": self.set_selected,
|
||||
"show_join": cbs.show_join,
|
||||
}
|
||||
clan_list = ClanList(**self.list_hooks, selected_vm=None) # type: ignore
|
||||
clan_list = ClanList(
|
||||
vms=[vm.base for vm in get_initial_vms(self.cbs.running_vms())],
|
||||
cbs=self.cbs,
|
||||
remount_list=self.remount_list_view,
|
||||
remount_edit=self.remount_edit_view,
|
||||
set_selected=self.set_selected,
|
||||
selected_vm=None,
|
||||
)
|
||||
# Add named stacks
|
||||
self.stack.add_titled(clan_list, "list", "List")
|
||||
self.stack.add_titled(
|
||||
|
@ -59,7 +61,14 @@ class OverviewWindow(Gtk.ApplicationWindow):
|
|||
if widget:
|
||||
widget.destroy()
|
||||
|
||||
clan_list = ClanList(**self.list_hooks, selected_vm=self.selected_vm) # type: ignore
|
||||
clan_list = ClanList(
|
||||
vms=[vm.base for vm in get_initial_vms(self.cbs.running_vms())],
|
||||
cbs=self.cbs,
|
||||
remount_list=self.remount_list_view,
|
||||
remount_edit=self.remount_edit_view,
|
||||
set_selected=self.set_selected,
|
||||
selected_vm=self.selected_vm,
|
||||
)
|
||||
self.stack.add_titled(clan_list, "list", "List")
|
||||
self.show_all()
|
||||
self.stack.set_visible_child_name("list")
|
||||
|
|
Loading…
Reference in New Issue
Block a user