diff --git a/pkgs/clan-cli/clan_cli/machines/machines.py b/pkgs/clan-cli/clan_cli/machines/machines.py index 8c1d7d55c..5744b1f5a 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 6e2720986..c96fd1919 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 bc8e04b00..fc966750f 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 fe1ba4f37..57fbdd99a 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 000000000..45361f1a1 Binary files /dev/null and b/pkgs/clan-vm-manager/clan_vm_manager/assets/clan_white_notext.png differ 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 4f5f216ca..098324d51 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 f827a608b..ceb4fc31f 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):