clan-vm-manager: Added clan icon to trayicon
This commit is contained in:
parent
7fe38a9a80
commit
4f7f34f9b4
@ -18,17 +18,21 @@ log = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class VMAttr:
|
class VMAttr:
|
||||||
def __init__(self, state_dir: Path) -> None:
|
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._qmp_socket: Path = state_dir / "qmp.sock"
|
||||||
self._qga_socket: Path = state_dir / "qga.sock"
|
self._qga_socket: Path = state_dir / "qga.sock"
|
||||||
self._qmp: QEMUMonitorProtocol | None = None
|
self._qmp: QEMUMonitorProtocol | None = None
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def qmp(self) -> Generator[QEMUMonitorProtocol, None, None]:
|
def qmp_ctx(self) -> Generator[QEMUMonitorProtocol, None, None]:
|
||||||
if self._qmp is None:
|
if self._qmp is None:
|
||||||
log.debug(f"qmp_socket: {self._qmp_socket}")
|
log.debug(f"qmp_socket: {self._qmp_socket}")
|
||||||
rpath = self._qmp_socket.resolve()
|
rpath = self._qmp_socket.resolve()
|
||||||
if not rpath.exists():
|
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 = QEMUMonitorProtocol(str(rpath))
|
||||||
self._qmp.connect()
|
self._qmp.connect()
|
||||||
try:
|
try:
|
||||||
|
@ -43,7 +43,7 @@ def wait_vm_up(state_dir: Path) -> None:
|
|||||||
timeout: float = 300
|
timeout: float = 300
|
||||||
while True:
|
while True:
|
||||||
if timeout <= 0:
|
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():
|
if socket_file.exists():
|
||||||
break
|
break
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
@ -56,7 +56,7 @@ def wait_vm_down(state_dir: Path) -> None:
|
|||||||
timeout: float = 300
|
timeout: float = 300
|
||||||
while socket_file.exists():
|
while socket_file.exists():
|
||||||
if timeout <= 0:
|
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)
|
sleep(0.1)
|
||||||
timeout -= 0.1
|
timeout -= 0.1
|
||||||
|
|
||||||
|
@ -11,6 +11,10 @@ GTK4 has a demo application showing all widgets. You can run it by executing:
|
|||||||
gtk4-widget-factory
|
gtk4-widget-factory
|
||||||
```
|
```
|
||||||
|
|
||||||
|
To find available icons execute:
|
||||||
|
```bash
|
||||||
|
gtk4-icon-browser
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,6 +72,24 @@ class MainApplication(Adw.Application):
|
|||||||
|
|
||||||
return 0
|
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:
|
def on_shutdown(self, app: Gtk.Application) -> None:
|
||||||
log.debug("Shutting down")
|
log.debug("Shutting down")
|
||||||
|
|
||||||
@ -84,9 +102,8 @@ class MainApplication(Adw.Application):
|
|||||||
assert self.window is not None
|
assert self.window is not None
|
||||||
if self.window.is_visible():
|
if self.window.is_visible():
|
||||||
self.window.hide()
|
self.window.hide()
|
||||||
return
|
else:
|
||||||
|
self.window.present()
|
||||||
self.window.present()
|
|
||||||
|
|
||||||
def dummy_menu_entry(self) -> None:
|
def dummy_menu_entry(self) -> None:
|
||||||
log.info("Dummy menu entry called")
|
log.info("Dummy menu entry called")
|
||||||
|
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
@ -253,7 +253,7 @@ class VM(GObject.Object):
|
|||||||
log.info(f"Stopping VM {self.get_id()}")
|
log.info(f"Stopping VM {self.get_id()}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
with self.machine.vm.qmp() as qmp:
|
with self.machine.vm.qmp_ctx() as qmp:
|
||||||
qmp.command("system_powerdown")
|
qmp.command("system_powerdown")
|
||||||
except ClanError as e:
|
except ClanError as e:
|
||||||
log.debug(e)
|
log.debug(e)
|
||||||
|
@ -172,7 +172,7 @@ class BaseImplementation:
|
|||||||
self.application = application
|
self.application = application
|
||||||
self.menu_items: dict[int, Any] = {}
|
self.menu_items: dict[int, Any] = {}
|
||||||
self.menu_item_id: int = 1
|
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.is_visible: bool = True
|
||||||
|
|
||||||
self.create_menu()
|
self.create_menu()
|
||||||
@ -213,16 +213,12 @@ class BaseImplementation:
|
|||||||
|
|
||||||
def create_menu(self) -> None:
|
def create_menu(self) -> None:
|
||||||
self.show_hide_item = self.create_item(
|
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(
|
# self.create_item()
|
||||||
"default", self.application.dummy_menu_entry
|
|
||||||
)
|
|
||||||
|
|
||||||
self.create_item()
|
# self.create_item("_Quit", self.application.on_shutdown)
|
||||||
|
|
||||||
self.create_item("_Quit", self.application.dummy_menu_entry)
|
|
||||||
|
|
||||||
def update_window_visibility(self) -> None:
|
def update_window_visibility(self) -> None:
|
||||||
if self.application.window is None:
|
if self.application.window is None:
|
||||||
@ -237,14 +233,6 @@ class BaseImplementation:
|
|||||||
self.update_menu()
|
self.update_menu()
|
||||||
|
|
||||||
def update_user_status(self) -> None:
|
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_icon()
|
||||||
self.update_menu()
|
self.update_menu()
|
||||||
|
|
||||||
@ -266,9 +254,9 @@ class BaseImplementation:
|
|||||||
# icon_name = "disconnect"
|
# icon_name = "disconnect"
|
||||||
|
|
||||||
# icon_name = f"{pynicotine.__application_id__}-{icon_name}"
|
# 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
|
# Implemented in subclasses
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -410,6 +398,7 @@ class StatusNotifierImplementation(BaseImplementation):
|
|||||||
):
|
):
|
||||||
method = self.methods[method_name]
|
method = self.methods[method_name]
|
||||||
result = method.callback(*parameters.unpack())
|
result = method.callback(*parameters.unpack())
|
||||||
|
|
||||||
out_arg_types = "".join(method.out_args)
|
out_arg_types = "".join(method.out_args)
|
||||||
return_value = None
|
return_value = None
|
||||||
|
|
||||||
@ -570,6 +559,11 @@ class StatusNotifierImplementation(BaseImplementation):
|
|||||||
)
|
)
|
||||||
self.tray_icon.register()
|
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(
|
self.bus.call_sync(
|
||||||
bus_name="org.kde.StatusNotifierWatcher",
|
bus_name="org.kde.StatusNotifierWatcher",
|
||||||
object_path="/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
|
"""Returns an icon path to use for tray icons, or None to fall back to
|
||||||
system-wide icons."""
|
system-wide icons."""
|
||||||
|
|
||||||
self.custom_icons = False
|
# icon_path = self.application.get_application_icon_path()
|
||||||
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
|
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
def set_icon_name(self, icon_name):
|
def set_icon(self, icon_path) -> None:
|
||||||
if self.custom_icons:
|
self.tray_icon.properties["IconName"].value = icon_path
|
||||||
# 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
|
|
||||||
self.tray_icon.emit_signal("NewIcon")
|
self.tray_icon.emit_signal("NewIcon")
|
||||||
|
|
||||||
if not self.is_visible:
|
if not self.is_visible:
|
||||||
@ -1064,7 +1032,7 @@ class Win32Implementation(BaseImplementation):
|
|||||||
self._menu, item_id, False, byref(item_info)
|
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)
|
self._update_notify_icon(icon_name=icon_name)
|
||||||
|
|
||||||
def show_notification(self, title, message):
|
def show_notification(self, title, message):
|
||||||
|
Loading…
Reference in New Issue
Block a user