From 4f7f34f9b45162f2afe6d6269a284ebe3e9889d4 Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 16 Feb 2024 12:25:06 +0700 Subject: [PATCH 1/2] clan-vm-manager: Added clan icon to trayicon --- pkgs/clan-cli/clan_cli/machines/machines.py | 8 ++- pkgs/clan-cli/tests/test_vms_cli.py | 4 +- pkgs/clan-vm-manager/README.md | 4 ++ pkgs/clan-vm-manager/clan_vm_manager/app.py | 23 ++++++- .../assets/clan_white_notext.png | Bin 0 -> 3203 bytes .../clan_vm_manager/models/use_vms.py | 2 +- .../clan_vm_manager/trayicon.py | 64 +++++------------- 7 files changed, 49 insertions(+), 56 deletions(-) create mode 100644 pkgs/clan-vm-manager/clan_vm_manager/assets/clan_white_notext.png diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 8c1d7d55..5744b1f5 100644 --- a/pkgs/clan-cli/clan_cli/machines/machines.py +++ b/pkgs/clan-cli/clan_cli/machines/machines.py @@ -18,17 +18,21 @@ log = logging.getLogger(__name__) class VMAttr: def __init__(self, state_dir: Path) -> None: + # These sockets here are just symlinks to the real sockets which + # are created by the run.py file. The reason being that we run into + # file path length issues on Linux. If no qemu process is running + # the symlink will be dangling. self._qmp_socket: Path = state_dir / "qmp.sock" self._qga_socket: Path = state_dir / "qga.sock" self._qmp: QEMUMonitorProtocol | None = None @contextmanager - def qmp(self) -> Generator[QEMUMonitorProtocol, None, None]: + def qmp_ctx(self) -> Generator[QEMUMonitorProtocol, None, None]: if self._qmp is None: log.debug(f"qmp_socket: {self._qmp_socket}") rpath = self._qmp_socket.resolve() if not rpath.exists(): - raise ClanError(f"qmp socket {rpath} does not exist") + raise ClanError(f"qmp socket {rpath} does not exist. Is the VM running?") self._qmp = QEMUMonitorProtocol(str(rpath)) self._qmp.connect() try: diff --git a/pkgs/clan-cli/tests/test_vms_cli.py b/pkgs/clan-cli/tests/test_vms_cli.py index 6e272098..c96fd191 100644 --- a/pkgs/clan-cli/tests/test_vms_cli.py +++ b/pkgs/clan-cli/tests/test_vms_cli.py @@ -43,7 +43,7 @@ def wait_vm_up(state_dir: Path) -> None: timeout: float = 300 while True: if timeout <= 0: - raise TimeoutError(f"qga socket {socket_file} not found") + raise TimeoutError(f"qga socket {socket_file} not found. Is the VM running?") if socket_file.exists(): break sleep(0.1) @@ -56,7 +56,7 @@ def wait_vm_down(state_dir: Path) -> None: timeout: float = 300 while socket_file.exists(): if timeout <= 0: - raise TimeoutError(f"qga socket {socket_file} still exists") + raise TimeoutError(f"qga socket {socket_file} still exists. Is the VM down?") sleep(0.1) timeout -= 0.1 diff --git a/pkgs/clan-vm-manager/README.md b/pkgs/clan-vm-manager/README.md index bc8e04b0..fc966750 100644 --- a/pkgs/clan-vm-manager/README.md +++ b/pkgs/clan-vm-manager/README.md @@ -11,6 +11,10 @@ GTK4 has a demo application showing all widgets. You can run it by executing: gtk4-widget-factory ``` +To find available icons execute: +```bash +gtk4-icon-browser +``` diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index fe1ba4f3..57fbdd99 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -72,6 +72,24 @@ class MainApplication(Adw.Application): return 0 + def get_application_icon_path(self) -> None: + self.icon_name = "lol.clan.vm.manager" + if not self.icon_name: + return None + + icon_theme = Gtk.IconTheme.get_for_display( + self.get_active_window().get_display() + ) + # Use the correct method to look up an icon + icon_lookup_flags = 16 + icon = icon_theme.lookup_icon( + self.icon_name, 128, 1.0, Gtk.TextDirection.NONE, icon_lookup_flags + ) + + if icon: + return icon.get_file().get_path() + return None + def on_shutdown(self, app: Gtk.Application) -> None: log.debug("Shutting down") @@ -84,9 +102,8 @@ class MainApplication(Adw.Application): assert self.window is not None if self.window.is_visible(): self.window.hide() - return - - self.window.present() + else: + self.window.present() def dummy_menu_entry(self) -> None: log.info("Dummy menu entry called") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/assets/clan_white_notext.png b/pkgs/clan-vm-manager/clan_vm_manager/assets/clan_white_notext.png new file mode 100644 index 0000000000000000000000000000000000000000..45361f1a1a42bb8afa2b58b22af4b54834f07e5b GIT binary patch literal 3203 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 literal 0 HcmV?d00001 diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py index 4f5f216c..098324d5 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py @@ -253,7 +253,7 @@ class VM(GObject.Object): log.info(f"Stopping VM {self.get_id()}") try: - with self.machine.vm.qmp() as qmp: + with self.machine.vm.qmp_ctx() as qmp: qmp.command("system_powerdown") except ClanError as e: log.debug(e) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/trayicon.py b/pkgs/clan-vm-manager/clan_vm_manager/trayicon.py index f827a608..ceb4fc31 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/trayicon.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/trayicon.py @@ -172,7 +172,7 @@ class BaseImplementation: self.application = application self.menu_items: dict[int, Any] = {} self.menu_item_id: int = 1 - self.activate_callback: Callable = application.on_window_hide_unhide + self.activate_callback: Callable = lambda a, b: self.update_window_visibility self.is_visible: bool = True self.create_menu() @@ -213,16 +213,12 @@ class BaseImplementation: def create_menu(self) -> None: self.show_hide_item = self.create_item( - "default", self.application.dummy_menu_entry + "default", self.application.on_window_hide_unhide ) - self.connect_disconnect_item = self.create_item( - "default", self.application.dummy_menu_entry - ) + # self.create_item() - self.create_item() - - self.create_item("_Quit", self.application.dummy_menu_entry) + # self.create_item("_Quit", self.application.on_shutdown) def update_window_visibility(self) -> None: if self.application.window is None: @@ -237,14 +233,6 @@ class BaseImplementation: self.update_menu() def update_user_status(self) -> None: - sensitive = core.users.login_status != slskmessages.UserStatus.OFFLINE - label = "_Disconnect" if sensitive else "_Connect" - - # self.set_item_sensitive(self.away_item, sensitive) - - self.set_item_text(self.connect_disconnect_item, label) - # self.set_item_toggled(self.away_item, core.users.login_status == slskmessages.UserStatus.AWAY) - self.update_icon() self.update_menu() @@ -266,9 +254,9 @@ class BaseImplementation: # icon_name = "disconnect" # icon_name = f"{pynicotine.__application_id__}-{icon_name}" - # self.set_icon_name(icon_name) + # self.set_icon(icon_name) - def set_icon_name(self, icon_name: str) -> None: + def set_icon(self, icon_name: str) -> None: # Implemented in subclasses pass @@ -410,6 +398,7 @@ class StatusNotifierImplementation(BaseImplementation): ): method = self.methods[method_name] result = method.callback(*parameters.unpack()) + out_arg_types = "".join(method.out_args) return_value = None @@ -570,6 +559,11 @@ class StatusNotifierImplementation(BaseImplementation): ) self.tray_icon.register() + from .assets import loc + + icon_path = str(loc / "clan_white_notext.png") + self.set_icon(icon_path) + self.bus.call_sync( bus_name="org.kde.StatusNotifierWatcher", object_path="/StatusNotifierWatcher", @@ -617,38 +611,12 @@ class StatusNotifierImplementation(BaseImplementation): """Returns an icon path to use for tray icons, or None to fall back to system-wide icons.""" - self.custom_icons = False - custom_icon_path = os.path.join(config.data_folder_path, ".nicotine-icon-theme") - - if hasattr(sys, "real_prefix") or sys.base_prefix != sys.prefix: - # Virtual environment - local_icon_path = os.path.join( - sys.prefix, "share", "icons", "hicolor", "scalable", "apps" - ) - else: - # Git folder - local_icon_path = os.path.join( - GTK_GUI_FOLDER_PATH, "icons", "hicolor", "scalable", "apps" - ) - - for icon_name in ("away", "connect", "disconnect", "msg"): - # Check if custom icons exist - if self.check_icon_path(icon_name, custom_icon_path): - self.custom_icons = True - return custom_icon_path - - # Check if local icons exist - if self.check_icon_path(icon_name, local_icon_path): - return local_icon_path + # icon_path = self.application.get_application_icon_path() return "" - def set_icon_name(self, icon_name): - if self.custom_icons: - # Use alternative icon names to enforce custom icons, since system-wide icons take precedence - icon_name = icon_name.replace(pynicotine.__application_id__, "nplus-tray") - - self.tray_icon.properties["IconName"].value = icon_name + def set_icon(self, icon_path) -> None: + self.tray_icon.properties["IconName"].value = icon_path self.tray_icon.emit_signal("NewIcon") if not self.is_visible: @@ -1064,7 +1032,7 @@ class Win32Implementation(BaseImplementation): self._menu, item_id, False, byref(item_info) ) - def set_icon_name(self, icon_name): + def set_icon(self, icon_name): self._update_notify_icon(icon_name=icon_name) def show_notification(self, title, message): From 280bee0861397e1139e893c2fa414603e559531d Mon Sep 17 00:00:00 2001 From: Qubasa Date: Fri, 16 Feb 2024 16:10:49 +0700 Subject: [PATCH 2/2] clan-vm-manager: Fixing vm starting. --- pkgs/clan-cli/clan_cli/machines/machines.py | 4 +- pkgs/clan-cli/clan_cli/vms/run.py | 2 +- pkgs/clan-cli/tests/test_vms_cli.py | 8 ++- pkgs/clan-vm-manager/clan_vm_manager/app.py | 28 ++------ .../clan_vm_manager/models/executor.py | 10 +-- .../clan_vm_manager/models/use_join.py | 4 +- .../clan_vm_manager/models/use_vms.py | 68 +++++++++---------- .../clan_vm_manager/views/list.py | 14 ++-- 8 files changed, 59 insertions(+), 79 deletions(-) diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 5744b1f5..bab07ea3 100644 --- a/pkgs/clan-cli/clan_cli/machines/machines.py +++ b/pkgs/clan-cli/clan_cli/machines/machines.py @@ -32,7 +32,9 @@ class VMAttr: log.debug(f"qmp_socket: {self._qmp_socket}") rpath = self._qmp_socket.resolve() if not rpath.exists(): - raise ClanError(f"qmp socket {rpath} does not exist. Is the VM running?") + raise ClanError( + f"qmp socket {rpath} does not exist. Is the VM running?" + ) self._qmp = QEMUMonitorProtocol(str(rpath)) self._qmp.connect() try: diff --git a/pkgs/clan-cli/clan_cli/vms/run.py b/pkgs/clan-cli/clan_cli/vms/run.py index cac5ab9a..abeb28f9 100644 --- a/pkgs/clan-cli/clan_cli/vms/run.py +++ b/pkgs/clan-cli/clan_cli/vms/run.py @@ -37,7 +37,7 @@ def facts_to_nixos_config(facts: dict[str, dict[str, bytes]]) -> dict: # TODO move this to the Machines class def build_vm( - machine: Machine, vm: VmConfig, tmpdir: Path, nix_options: list[str] + machine: Machine, vm: VmConfig, tmpdir: Path, nix_options: list[str] = [] ) -> dict[str, str]: secrets_dir = get_secrets(machine, tmpdir) diff --git a/pkgs/clan-cli/tests/test_vms_cli.py b/pkgs/clan-cli/tests/test_vms_cli.py index c96fd191..9c2700ad 100644 --- a/pkgs/clan-cli/tests/test_vms_cli.py +++ b/pkgs/clan-cli/tests/test_vms_cli.py @@ -43,7 +43,9 @@ def wait_vm_up(state_dir: Path) -> None: timeout: float = 300 while True: if timeout <= 0: - raise TimeoutError(f"qga socket {socket_file} not found. Is the VM running?") + raise TimeoutError( + f"qga socket {socket_file} not found. Is the VM running?" + ) if socket_file.exists(): break sleep(0.1) @@ -56,7 +58,9 @@ def wait_vm_down(state_dir: Path) -> None: timeout: float = 300 while socket_file.exists(): if timeout <= 0: - raise TimeoutError(f"qga socket {socket_file} still exists. Is the VM down?") + raise TimeoutError( + f"qga socket {socket_file} still exists. Is the VM down?" + ) sleep(0.1) timeout -= 0.1 diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 57fbdd99..48c0fb14 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -14,7 +14,7 @@ from gi.repository import Adw, Gdk, Gio, Gtk from clan_vm_manager.models.interfaces import ClanConfig from clan_vm_manager.models.use_join import GLib, GObject -from clan_vm_manager.models.use_vms import VMS +from clan_vm_manager.models.use_vms import VMs from .trayicon import TrayIcon from .windows.main_window import MainWindow @@ -44,7 +44,8 @@ class MainApplication(Adw.Application): "enable debug mode", None, ) - + self.vms = VMs.use() + log.debug(f"VMS object: {self.vms}") self.window: Adw.ApplicationWindow | None = None self.connect("shutdown", self.on_shutdown) self.connect("activate", self.show_window) @@ -69,35 +70,16 @@ class MainApplication(Adw.Application): log.debug(f"Join request: {args[1]}") uri = args[1] self.emit("join_request", uri) - return 0 - def get_application_icon_path(self) -> None: - self.icon_name = "lol.clan.vm.manager" - if not self.icon_name: - return None - - icon_theme = Gtk.IconTheme.get_for_display( - self.get_active_window().get_display() - ) - # Use the correct method to look up an icon - icon_lookup_flags = 16 - icon = icon_theme.lookup_icon( - self.icon_name, 128, 1.0, Gtk.TextDirection.NONE, icon_lookup_flags - ) - - if icon: - return icon.get_file().get_path() - return None - def on_shutdown(self, app: Gtk.Application) -> None: log.debug("Shutting down") + self.vms.kill_all() + if self.tray_icon is not None: self.tray_icon.destroy() - VMS.use().kill_all() - def on_window_hide_unhide(self, *_args: Any) -> None: assert self.window is not None if self.window.is_visible(): diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py b/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py index dfc56832..5987a9a1 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/executor.py @@ -7,7 +7,6 @@ from pathlib import Path from typing import Any import gi -from clan_cli.errors import ClanError gi.require_version("GdkPixbuf", "2.0") @@ -24,7 +23,7 @@ def _kill_group(proc: mp.Process) -> None: if proc.is_alive() and pid: os.killpg(pid, signal.SIGTERM) else: - log.warning(f"Process {proc.name} with pid {pid} is already dead") + log.warning(f"Process '{proc.name}' with pid '{pid}' is already dead") @dataclasses.dataclass(frozen=True) @@ -102,7 +101,7 @@ def _init_proc( def spawn( *, - log_dir: Path, + out_file: Path, on_except: Callable[[Exception, mp.process.BaseProcess], None] | None, func: Callable, **kwargs: Any, @@ -111,13 +110,8 @@ def spawn( if mp.get_start_method(allow_none=True) is None: mp.set_start_method(method="forkserver") - if not log_dir.is_dir(): - raise ClanError(f"Log path {log_dir} is not a directory") - log_dir.mkdir(parents=True, exist_ok=True) - # Set names proc_name = f"MPExec:{func.__name__}" - out_file = log_dir / "out.log" # Start the process proc = mp.Process( diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py index cba131e7..88215b1e 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_join.py @@ -9,7 +9,7 @@ from clan_cli.clan_uri import ClanURI from clan_cli.history.add import add_history from clan_vm_manager.errors.show_error import show_error_dialog -from clan_vm_manager.models.use_vms import VMS, Clans +from clan_vm_manager.models.use_vms import Clans gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") @@ -76,7 +76,7 @@ class Join: def after_join(item: JoinValue, _: Any) -> None: self.discard(item) Clans.use().refresh() - VMS.use().refresh() + # VMS.use().refresh() print("Refreshed list after join") on_join(item) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py index 098324d5..fbefec06 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models/use_vms.py @@ -107,6 +107,7 @@ class VM(GObject.Object): data: HistoryEntry, ) -> None: super().__init__() + self.KILL_TIMEOUT = 6 # seconds self.data = data self.process = MPProcess("dummy", mp.Process(), Path("./dummy")) self._watcher_id: int = 0 @@ -121,9 +122,8 @@ class VM(GObject.Object): self.log_dir = tempfile.TemporaryDirectory( prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}" ) - self._finalizer = weakref.finalize(self, self.stop) + self._finalizer = weakref.finalize(self, self.kill) self.connect("build_vm", self.build_vm) - uri = ClanURI.from_str( url=self.data.flake.flake_url, flake_attr=self.data.flake.flake_attr ) @@ -160,25 +160,25 @@ class VM(GObject.Object): log.info(f"Starting VM {self.get_id()}") vm = vms.run.inspect_vm(self.machine) - GLib.idle_add(self.emit, "build_vm", self, True) - self.process = spawn( - on_except=None, - log_dir=Path(str(self.log_dir.name)), - func=vms.run.build_vm, - machine=self.machine, - vm=vm, - ) - self.process.proc.join() + # GLib.idle_add(self.emit, "build_vm", self, True) + # self.process = spawn( + # on_except=None, + # log_dir=Path(str(self.log_dir.name)), + # func=vms.run.build_vm, + # machine=self.machine, + # vm=vm, + # ) + # self.process.proc.join() - GLib.idle_add(self.emit, "build_vm", self, False) + # GLib.idle_add(self.emit, "build_vm", self, False) - if self.process.proc.exitcode != 0: - log.error(f"Failed to build VM {self.get_id()}") - return + # if self.process.proc.exitcode != 0: + # log.error(f"Failed to build VM {self.get_id()}") + # return self.process = spawn( on_except=None, - log_dir=Path(str(self.log_dir.name)), + out_file=Path(str(self.log_dir.name)) / "vm.log", func=vms.run.run_vm, vm=vm, ) @@ -241,7 +241,7 @@ class VM(GObject.Object): if self.is_running(): assert self._stop_timer_init is not None diff = datetime.now() - self._stop_timer_init - if diff.seconds > 10: + if diff.seconds > self.KILL_TIMEOUT: log.error(f"VM {self.get_id()} has not stopped. Killing it") self.process.kill_group() return GLib.SOURCE_CONTINUE @@ -263,12 +263,19 @@ class VM(GObject.Object): if self._stop_watcher_id == 0: raise ClanError("Failed to add stop watcher") - def stop(self) -> None: + def shutdown(self) -> None: if not self.is_running(): return log.info(f"Stopping VM {self.get_id()}") threading.Thread(target=self.__stop).start() + def kill(self) -> None: + if not self.is_running(): + log.warning(f"Tried to kill VM {self.get_id()} is not running") + return + log.info(f"Killing VM {self.get_id()} now") + self.process.kill_group() + def read_whole_log(self) -> str: if not self.process.out_file.exists(): log.error(f"Log file {self.process.out_file} does not exist") @@ -276,28 +283,16 @@ class VM(GObject.Object): return self.process.out_file.read_text() -class VMS: - """ - This is a singleton. - It is initialized with the first call of use() - - Usage: - - VMS.use().get_running_vms() - - VMS.use() can also be called before the data is needed. e.g. to eliminate/reduce waiting time. - - """ - +class VMs: list_store: Gio.ListStore - _instance: "None | VMS" = None + _instance: "None | VMs" = None # Make sure the VMS class is used as a singleton def __init__(self) -> None: raise RuntimeError("Call use() instead") @classmethod - def use(cls: Any) -> "VMS": + def use(cls: Any) -> "VMs": if cls._instance is None: cls._instance = cls.__new__(cls) cls.list_store = Gio.ListStore.new(VM) @@ -327,10 +322,13 @@ class VMS: return list(filter(lambda vm: vm.is_running(), self.list_store)) def kill_all(self) -> None: + log.debug(f"Running vms: {self.get_running_vms()}") for vm in self.get_running_vms(): - vm.stop() + vm.kill() def refresh(self) -> None: + log.error("NEVER FUCKING DO THIS") + return self.list_store.remove_all() for vm in get_saved_vms(): self.list_store.append(vm) @@ -338,7 +336,7 @@ class VMS: def get_saved_vms() -> list[VM]: vm_list = [] - + log.info("=====CREATING NEW VM OBJ====") try: # Execute `clan flakes add ` to democlan for this to work for entry in list_history(): diff --git a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py index b28f14ce..ed529675 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/views/list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/views/list.py @@ -14,7 +14,7 @@ from clan_vm_manager.models.use_views import Views gi.require_version("Adw", "1") from gi.repository import Adw, Gdk, Gio, GLib, GObject, Gtk -from clan_vm_manager.models.use_vms import VM, VMS, ClanGroup, Clans +from clan_vm_manager.models.use_vms import VM, ClanGroup, Clans log = logging.getLogger(__name__) @@ -48,8 +48,8 @@ class ClanList(Gtk.Box): def __init__(self, config: ClanConfig) -> None: super().__init__(orientation=Gtk.Orientation.VERTICAL) - app = Gio.Application.get_default() - app.connect("join_request", self.on_join_request) + self.app = Gio.Application.get_default() + self.app.connect("join_request", self.on_join_request) groups = Clans.use() join = Join.use() @@ -123,7 +123,7 @@ class ClanList(Gtk.Box): def on_search_changed(self, entry: Gtk.SearchEntry) -> None: Clans.use().filter_by_name(entry.get_text()) # Disable the shadow if the list is empty - if not VMS.use().list_store.get_n_items(): + if not self.app.vms.list_store.get_n_items(): self.group_list.add_css_class("no-shadow") def render_vm_row(self, boxed_list: Gtk.ListBox, vm: VM) -> Gtk.Widget: @@ -202,7 +202,7 @@ class ClanList(Gtk.Box): def on_edit(self, action: Any, parameter: Any) -> None: target = parameter.get_string() - vm = VMS.use().get_by_id(target) + vm = self.app.vms.get_by_id(target) if not vm: raise ClanError("Something went wrong. Please restart the app.") @@ -220,7 +220,7 @@ class ClanList(Gtk.Box): row.add_css_class("trust") # TODO: figure out how to detect that - exist = VMS.use().get_by_id(item.url.get_id()) + exist = self.app.vms.use().get_by_id(item.url.get_id()) if exist: sub = row.get_subtitle() row.set_subtitle( @@ -292,7 +292,7 @@ class ClanList(Gtk.Box): if not row.get_active(): row.set_state(True) - vm.stop() + vm.shutdown() def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None: switch.set_active(vm.is_running())