diff --git a/pkgs/clan-cli/clan_cli/flakes/history.py b/pkgs/clan-cli/clan_cli/flakes/history.py index 75a3e7cc..b3e84a45 100644 --- a/pkgs/clan-cli/clan_cli/flakes/history.py +++ b/pkgs/clan-cli/clan_cli/flakes/history.py @@ -35,8 +35,9 @@ def list_history() -> list[HistoryEntry]: content: str = f.read() parsed: list[dict] = json.loads(content) logs = [HistoryEntry(**p) for p in parsed] - except json.JSONDecodeError: - print("Failed to load history") + except json.JSONDecodeError as ex: + print("Failed to load history. Invalid JSON.") + print(f"{user_history_file()}: {ex}") return logs diff --git a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py index 0958196f..9fb5d531 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/__init__.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/__init__.py @@ -1,13 +1,37 @@ import argparse -from collections.abc import Callable +import sys -start_app: Callable | None = None +import gi -from .app import start_app +from .app import Application + +gi.require_version("Gtk", "3.0") + + +def join_command(args: argparse.Namespace) -> None: + print("Joining the flake") + print(args.clan_uri) + + +def register_join_parser(parser: argparse.ArgumentParser) -> None: + parser.add_argument("clan_uri", type=str, help="machine in the flake to run") + parser.set_defaults(func=join_command) + + +def start_app(args: argparse.Namespace) -> None: + app = Application(args) + return app.run(sys.argv) def main() -> None: parser = argparse.ArgumentParser(description="clan-vm-manager") + subparser = parser.add_subparsers( + title="command", + description="command to execute", + help="the command to execute", + required=True, + ) + register_join_parser(subparser.add_parser("join", help="join a clan")) parser.set_defaults(func=start_app) args = parser.parse_args() args.func(args) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/app.py b/pkgs/clan-vm-manager/clan_vm_manager/app.py index 18a17757..2d20ee0c 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/app.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/app.py @@ -6,34 +6,16 @@ from typing import Any import gi -from clan_vm_manager.models import VMBase +from .models import VMBase gi.require_version("Gtk", "3.0") from gi.repository import Gio, Gtk from .constants import constants +from .ui.clan_join_page import ClanJoinPage from .ui.clan_select_list import ClanEdit, ClanList -class ClanJoinPage(Gtk.Box): - def __init__(self, *, stack: Gtk.Stack) -> None: - super().__init__() - self.page = Gtk.Box( - orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True - ) - self.set_border_width(10) - self.stack = stack - - button = Gtk.Button(label="Back to list", margin_left=10) - button.connect("clicked", self.switch) - self.add(button) - - self.add(Gtk.Label("Join cLan")) - - def switch(self, widget: Gtk.Widget) -> None: - self.stack.set_visible_child_name("list") - - class MainWindow(Gtk.ApplicationWindow): def __init__(self, application: Gtk.Application) -> None: super().__init__(application=application) @@ -75,7 +57,9 @@ class MainWindow(Gtk.ApplicationWindow): def set_selected(self, sel: VMBase | None) -> None: self.selected_vm = sel - print(f"APP selected + {self.selected_vm}") + + if self.selected_vm: + print(f"APP selected + {self.selected_vm.name}") def remount_list_view(self) -> None: widget = self.stack.get_child_by_name("list") @@ -107,7 +91,7 @@ class MainWindow(Gtk.ApplicationWindow): class Application(Gtk.Application): - def __init__(self) -> None: + def __init__(self, args: argparse.Namespace) -> None: super().__init__( application_id=constants["APPID"], flags=Gio.ApplicationFlags.FLAGS_NONE ) @@ -132,8 +116,3 @@ class Application(Gtk.Application): # screen = Gdk.Screen.get_default() # style_context = Gtk.StyleContext() # style_context.add_provider_for_screen(screen, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) - - -def start_app(args: argparse.Namespace) -> None: - app = Application() - return app.run(sys.argv) diff --git a/pkgs/clan-vm-manager/clan_vm_manager/models.py b/pkgs/clan-vm-manager/clan_vm_manager/models.py index b586350c..b1d93c6f 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/models.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/models.py @@ -5,6 +5,9 @@ from pathlib import Path from typing import Any import clan_cli +import gi + +gi.require_version("GdkPixbuf", "2.0") from gi.repository import GdkPixbuf from clan_vm_manager import assets @@ -40,6 +43,10 @@ class VMBase: } ) + @staticmethod + def to_idx(name: str) -> int: + return list(VMBase.name_to_type_map().keys()).index(name) + def list_data(self) -> OrderedDict[str, Any]: return OrderedDict( { diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_join_page.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_join_page.py new file mode 100644 index 00000000..9b96deca --- /dev/null +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_join_page.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 + + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + + +class ClanJoinPage(Gtk.Box): + def __init__(self, *, stack: Gtk.Stack) -> None: + super().__init__() + self.page = Gtk.Box( + orientation=Gtk.Orientation.VERTICAL, spacing=6, expand=True + ) + self.set_border_width(10) + self.stack = stack + + button = Gtk.Button(label="Back to list", margin_left=10) + button.connect("clicked", self.switch) + self.add(button) + + self.add(Gtk.Label("Join cLan")) + + def switch(self, widget: Gtk.Widget) -> None: + self.stack.set_visible_child_name("list") diff --git a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py index f8bc576d..82c3a533 100644 --- a/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py +++ b/pkgs/clan-vm-manager/clan_vm_manager/ui/clan_select_list.py @@ -133,7 +133,7 @@ class ClanList(Gtk.Box): self.remount_edit_view() def on_select_vm(self, vm: VMBase) -> None: - print(f"on_select_vm: {vm}") + print(f"on_select_vm: {vm.name}") if vm is None: self.toolbar.set_is_selected(False) else: @@ -215,7 +215,7 @@ class ClanListView(Gtk.Box): def find_vm(self, vm: VMBase) -> int: for idx, row in enumerate(self.list_store): - if row[1] == vm.name: # TODO: Change to path + if row[VMBase.to_idx("Name")] == vm.name: # TODO: Change to path return idx return -1 @@ -229,16 +229,18 @@ class ClanListView(Gtk.Box): def insertVM(self, vm: VMBase) -> None: values = list(vm.list_data().values()) - values[0] = GdkPixbuf.Pixbuf.new_from_file_at_scale( - filename=values[0], width=64, height=64, preserve_aspect_ratio=True + icon_idx = VMBase.to_idx("Icon") + values[icon_idx] = GdkPixbuf.Pixbuf.new_from_file_at_scale( + filename=values[icon_idx], width=64, height=64, preserve_aspect_ratio=True ) self.list_store.append(values) def _on_select_row(self, selection: Gtk.TreeSelection) -> None: model, row = selection.get_selected() if row is not None: - print(f"Selected {model[row][1]}") - self.on_select_row(VMBase(*model[row])) + vm = VMBase(*model[row]) + print(f"Selected {vm.name}") + self.on_select_row(vm) def _on_double_click( self, tree_view: Gtk.TreeView, path: Gtk.TreePath, column: Gtk.TreeViewColumn @@ -247,24 +249,23 @@ class ClanListView(Gtk.Box): selection = tree_view.get_selection() model, row = selection.get_selected() if row is not None: - VMBase(*model[row]).run() + vm = VMBase(*model[row]) + vm.run() def setColRenderers(tree_view: Gtk.TreeView) -> None: - for idx, (key, _) in enumerate(VMBase.name_to_type_map().items()): + for idx, (key, gtype) in enumerate(VMBase.name_to_type_map().items()): col: Gtk.TreeViewColumn = None - match key: - case "Icon": + + if key.startswith("_"): + continue + match gtype: + case GdkPixbuf.Pixbuf: renderer = Gtk.CellRendererPixbuf() col = Gtk.TreeViewColumn(key, renderer, pixbuf=idx) - case "Name" | "URL": + case str: # noqa renderer = Gtk.CellRendererText() col = Gtk.TreeViewColumn(key, renderer, text=idx) - case "Status": - renderer = Gtk.CellRendererText() - col = Gtk.TreeViewColumn(key, renderer, text=idx) - case _: - continue # CommonSetup for all columns if col: diff --git a/pkgs/clan-vm-manager/default.nix b/pkgs/clan-vm-manager/default.nix index 3b870aed..b415494e 100644 --- a/pkgs/clan-vm-manager/default.nix +++ b/pkgs/clan-vm-manager/default.nix @@ -61,7 +61,6 @@ python3.pkgs.buildPythonApplication { desktopItems = [ (makeDesktopItem { name = "clan-vm-manager"; - # TODO: this subcommand is not implemented yet exec = "clan-vm-manager join %u"; desktopName = "CLan VM Manager"; startupWMClass = "clan"; diff --git a/pkgs/clan-vm-manager/pyproject.toml b/pkgs/clan-vm-manager/pyproject.toml index b73b66b1..ab773a44 100644 --- a/pkgs/clan-vm-manager/pyproject.toml +++ b/pkgs/clan-vm-manager/pyproject.toml @@ -2,13 +2,15 @@ requires = ["setuptools"] build-backend = "setuptools.build_meta" + + [project] name = "clan-vm-manager" dynamic = ["version"] scripts = { clan-vm-manager = "clan_vm_manager:main" } [tool.setuptools.package-data] -clan_vm_manager = ["*.glade"] +clan_vm_manager = ["**/assets/*"] [tool.mypy] python_version = "3.11" diff --git a/pkgs/clan-vm-manager/shell.nix b/pkgs/clan-vm-manager/shell.nix index 16669fd9..1b706895 100644 --- a/pkgs/clan-vm-manager/shell.nix +++ b/pkgs/clan-vm-manager/shell.nix @@ -12,5 +12,13 @@ mkShell { # prepend clan-cli for development export PYTHONPATH=../clan-cli:$PYTHONPATH + + set -euox + # install desktop file + cp -f ${clan-vm-manager}/share/applications/clan-vm-manager.desktop ~/.local/share/applications/clan-vm-manager.desktop + sleep 2 + sed -i "s|Exec=.*clan-vm-manager|Exec=${clan-vm-manager}/bin/clan-vm-manager|" ~/.local/share/applications/clan-vm-manager.desktop + xdg-mime default clan-vm-manager.desktop x-scheme-handler/clan + set +x ''; }