Merge pull request 'clan_vm_manager: --debug enables debug mode in clan_cli too' (#840) from Qubasa-add_spinner into main
This commit is contained in:
@@ -1,9 +1,9 @@
|
|||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
from os import path
|
from collections.abc import Generator
|
||||||
|
from contextlib import contextmanager
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from tempfile import NamedTemporaryFile
|
from tempfile import NamedTemporaryFile
|
||||||
from time import sleep
|
|
||||||
|
|
||||||
from clan_cli.dirs import vm_state_dir
|
from clan_cli.dirs import vm_state_dir
|
||||||
from qemu.qmp import QEMUMonitorProtocol
|
from qemu.qmp import QEMUMonitorProtocol
|
||||||
@@ -16,6 +16,27 @@ from ..ssh import Host, parse_deployment_address
|
|||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class VMAttr:
|
||||||
|
def __init__(self, state_dir: Path) -> None:
|
||||||
|
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]:
|
||||||
|
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")
|
||||||
|
self._qmp = QEMUMonitorProtocol(str(rpath))
|
||||||
|
self._qmp.connect()
|
||||||
|
try:
|
||||||
|
yield self._qmp
|
||||||
|
finally:
|
||||||
|
self._qmp.close()
|
||||||
|
|
||||||
|
|
||||||
class Machine:
|
class Machine:
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@@ -36,14 +57,10 @@ class Machine:
|
|||||||
self.build_cache: dict[str, Path] = {}
|
self.build_cache: dict[str, Path] = {}
|
||||||
|
|
||||||
self._deployment_info: None | dict[str, str] = deployment_info
|
self._deployment_info: None | dict[str, str] = deployment_info
|
||||||
|
|
||||||
state_dir = vm_state_dir(flake_url=str(self.flake), vm_name=self.name)
|
state_dir = vm_state_dir(flake_url=str(self.flake), vm_name=self.name)
|
||||||
|
|
||||||
self.qmp_socket: Path = state_dir / "qmp.sock"
|
self.vm: VMAttr = VMAttr(state_dir)
|
||||||
self.qga_socket: Path = state_dir / "qga.sock"
|
|
||||||
|
|
||||||
log.debug(f"qmp_socket: {self.qmp_socket}")
|
|
||||||
self._qmp = QEMUMonitorProtocol(path.realpath(self.qmp_socket))
|
|
||||||
self._qmp_connected = False
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return f"Machine(name={self.name}, flake={self.flake})"
|
return f"Machine(name={self.name}, flake={self.flake})"
|
||||||
@@ -60,28 +77,6 @@ class Machine:
|
|||||||
)
|
)
|
||||||
return self._deployment_info
|
return self._deployment_info
|
||||||
|
|
||||||
def qmp_connect(self) -> None:
|
|
||||||
if not self._qmp_connected:
|
|
||||||
tries = 100
|
|
||||||
for num in range(tries):
|
|
||||||
try:
|
|
||||||
# the socket file link might be outdated, therefore re-init the qmp object
|
|
||||||
self._qmp = QEMUMonitorProtocol(path.realpath(self.qmp_socket))
|
|
||||||
self._qmp.connect()
|
|
||||||
self._qmp_connected = True
|
|
||||||
log.debug("QMP Connected")
|
|
||||||
return
|
|
||||||
except FileNotFoundError:
|
|
||||||
if num < 99:
|
|
||||||
sleep(0.1)
|
|
||||||
continue
|
|
||||||
else:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def qmp_command(self, command: str) -> dict:
|
|
||||||
self.qmp_connect()
|
|
||||||
return self._qmp.command(command)
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def target_host_address(self) -> str:
|
def target_host_address(self) -> str:
|
||||||
# deploymentAddress is deprecated.
|
# deploymentAddress is deprecated.
|
||||||
|
|||||||
@@ -154,7 +154,9 @@ def qemu_command(
|
|||||||
|
|
||||||
|
|
||||||
# TODO move this to the Machines class
|
# TODO move this to the Machines class
|
||||||
def build_vm(machine: Machine, vm: VmConfig, nix_options: list[str]) -> dict[str, str]:
|
def build_vm(
|
||||||
|
machine: Machine, vm: VmConfig, nix_options: list[str] = []
|
||||||
|
) -> dict[str, str]:
|
||||||
config = nix_config()
|
config = nix_config()
|
||||||
system = config["system"]
|
system = config["system"]
|
||||||
|
|
||||||
|
|||||||
@@ -40,17 +40,25 @@ def run_vm_in_thread(machine_name: str) -> None:
|
|||||||
# wait for qmp socket to exist
|
# wait for qmp socket to exist
|
||||||
def wait_vm_up(state_dir: Path) -> None:
|
def wait_vm_up(state_dir: Path) -> None:
|
||||||
socket_file = state_dir / "qga.sock"
|
socket_file = state_dir / "qga.sock"
|
||||||
|
timeout: float = 300
|
||||||
while True:
|
while True:
|
||||||
|
if timeout <= 0:
|
||||||
|
raise TimeoutError(f"qga socket {socket_file} not found")
|
||||||
if socket_file.exists():
|
if socket_file.exists():
|
||||||
break
|
break
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
|
timeout -= 0.1
|
||||||
|
|
||||||
|
|
||||||
# wait for vm to be down by checking if qga socket is down
|
# wait for vm to be down by checking if qga socket is down
|
||||||
def wait_vm_down(state_dir: Path) -> None:
|
def wait_vm_down(state_dir: Path) -> None:
|
||||||
socket_file = state_dir / "qga.sock"
|
socket_file = state_dir / "qga.sock"
|
||||||
|
timeout: float = 300
|
||||||
while socket_file.exists():
|
while socket_file.exists():
|
||||||
|
if timeout <= 0:
|
||||||
|
raise TimeoutError(f"qga socket {socket_file} still exists")
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
|
timeout -= 0.1
|
||||||
|
|
||||||
|
|
||||||
# wait for vm to be up then connect and return qmp instance
|
# wait for vm to be up then connect and return qmp instance
|
||||||
|
|||||||
@@ -1,88 +1,35 @@
|
|||||||
## Developing GTK3 Applications
|
## Developing GTK4 Applications
|
||||||
|
|
||||||
Here we will document on how to develop GTK3 application UI in python. First we want to setup
|
|
||||||
an example code base to look into. In this case gnome-music.
|
|
||||||
|
|
||||||
## Setup gnome-music as code reference
|
|
||||||
|
|
||||||
gnome-music does not use glade
|
|
||||||
|
|
||||||
Clone gnome-music and check out the tag v40.0
|
|
||||||
[gnome-music](https://github.com/GNOME/gnome-music/tree/40.0)
|
|
||||||
|
|
||||||
|
## Demos
|
||||||
|
Adw has a demo application showing all widgets. You can run it by executing:
|
||||||
```bash
|
```bash
|
||||||
git clone git@github.com:GNOME/gnome-music.git && cd gnome-music && git checkout 40.0
|
adwaita-1-demo
|
||||||
|
```
|
||||||
|
GTK4 has a demo application showing all widgets. You can run it by executing:
|
||||||
|
```bash
|
||||||
|
gtk4-widget-factory
|
||||||
```
|
```
|
||||||
|
|
||||||
Checkout nixpkgs version `468cb5980b56d348979488a74a9b5de638400160` for the correct gnome-music devshell then execute:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
|
|
||||||
nix develop /home/username/Projects/nixpkgs#gnome.gnome-music
|
|
||||||
```
|
|
||||||
|
|
||||||
Look into the file `gnome-music.in` which bootstraps the application.
|
|
||||||
|
|
||||||
## Setup gnu-cash as reference
|
|
||||||
|
|
||||||
Gnucash uses glade with complex UI
|
|
||||||
Setup gnucash
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone git@github.com:Gnucash/gnucash.git
|
|
||||||
git checkout ed4921271c863c7f6e0c800e206b25ac6e9ba4da
|
|
||||||
|
|
||||||
cd nixpkgs
|
|
||||||
git checkout 015739d7bffa7da4e923978040a2f7cba6af3270
|
|
||||||
nix develop /home/username/Projects/nixpkgs#gnucash
|
|
||||||
mkdir build && cd build
|
|
||||||
cmake ..
|
|
||||||
cd ..
|
|
||||||
make
|
|
||||||
```
|
|
||||||
|
|
||||||
- The use the GTK Builder instead of templates.
|
|
||||||
|
|
||||||
## Look into virt-manager it uses python + spice-gtk
|
|
||||||
|
|
||||||
Look into `virtManager/details/viewers.py` to see how spice-gtk is being used
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/virt-manager/virt-manager
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### Glade
|
|
||||||
|
|
||||||
Make sure to check the 'composit' box in glade in the GtkApplicationWindow to be able to
|
|
||||||
import the glade file through GTK template
|
|
||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
- [Adw PyGobject Reference](http://lazka.github.io/pgi-docs/index.html#Adw-1)
|
||||||
|
- [GTK4 PyGobject Reference](http://lazka.github.io/pgi-docs/index.html#Gtk-4.0)
|
||||||
|
- [Adw Widget Gallery](https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/widget-gallery.html)
|
||||||
|
- [Python + GTK3 Tutorial](https://python-gtk-3-tutorial.readthedocs.io/en/latest/textview.html)
|
||||||
|
|
||||||
- Another python glade project [syncthing-gtk](https://github.com/kozec/syncthing-gtk)
|
|
||||||
|
|
||||||
- Other python glade project [linuxcnc](https://github.com/podarok/linuxcnc/tree/master)
|
|
||||||
|
|
||||||
- Install [Glade UI Toolbuilder](https://gitlab.gnome.org/GNOME/glade)
|
|
||||||
|
|
||||||
- To understand GTK3 Components look into the [Python GTK3 Tutorial](https://python-gtk-3-tutorial.readthedocs.io/en/latest/search.html?q=ApplicationWindow&check_keywords=yes&area=default)
|
|
||||||
|
|
||||||
- https://web.archive.org/web/20100706201447/http://www.pygtk.org/pygtk2reference/ (GTK2 Reference, many methods still exist in gtk3)
|
|
||||||
-
|
|
||||||
- Also look into [PyGObject](https://pygobject.readthedocs.io/en/latest/guide/gtk_template.html) to know more about threading and async etc.
|
|
||||||
- [GI Python API](https://lazka.github.io/pgi-docs/#Gtk-3.0)
|
|
||||||
- https://developer.gnome.org/documentation/tutorials/application.html
|
|
||||||
- [GTK3 Python] https://github.com/sam-m888/python-gtk3-tutorial/tree/master
|
|
||||||
- https://gnome.pages.gitlab.gnome.org/libhandy/doc/1.8/index.html
|
|
||||||
- https://github.com/geigi/cozy
|
|
||||||
- https://github.com/lutris/lutris/blob/2e9bd115febe08694f5d42dabcf9da36a1065f1d/lutris/gui/widgets/cellrenderers.py#L92
|
|
||||||
|
|
||||||
## Debugging Style and Layout
|
## Debugging Style and Layout
|
||||||
|
|
||||||
|
You can append `--debug` flag to enable debug logging printed into the console.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Enable the debugger
|
# Enable the GTK debugger
|
||||||
gsettings set org.gtk.Settings.Debug enable-inspector-keybinding true
|
gsettings set org.gtk.Settings.Debug enable-inspector-keybinding true
|
||||||
|
|
||||||
# Start the application with the debugger attached
|
# Start the application with the debugger attached
|
||||||
GTK_DEBUG=interactive ./bin/clan-vm-manager
|
GTK_DEBUG=interactive ./bin/clan-vm-manager --debug
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ class MainApplication(Adw.Application):
|
|||||||
|
|
||||||
if "debug" in options:
|
if "debug" in options:
|
||||||
setup_logging("DEBUG", root_log_name=__name__.split(".")[0])
|
setup_logging("DEBUG", root_log_name=__name__.split(".")[0])
|
||||||
|
setup_logging("DEBUG", root_log_name="clan_cli")
|
||||||
else:
|
else:
|
||||||
setup_logging("INFO", root_log_name=__name__.split(".")[0])
|
setup_logging("INFO", root_log_name=__name__.split(".")[0])
|
||||||
log.debug("Debug logging enabled")
|
log.debug("Debug logging enabled")
|
||||||
|
|||||||
@@ -22,6 +22,11 @@ avatar {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
margin-right: 25px;
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
.group-list {
|
.group-list {
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import multiprocessing as mp
|
|||||||
import threading
|
import threading
|
||||||
|
|
||||||
from clan_cli.machines.machines import Machine
|
from clan_cli.machines.machines import Machine
|
||||||
from gi.repository import Gio, GLib, GObject
|
from gi.repository import Gio, GLib, GObject, Gtk
|
||||||
|
|
||||||
log = logging.getLogger(__name__)
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -114,10 +114,15 @@ class VM(GObject.Object):
|
|||||||
self._stop_timer_init: datetime | None = None
|
self._stop_timer_init: datetime | None = None
|
||||||
self._logs_id: int = 0
|
self._logs_id: int = 0
|
||||||
self._log_file: IO[str] | None = None
|
self._log_file: IO[str] | None = None
|
||||||
|
self.progress_bar: Gtk.ProgressBar = Gtk.ProgressBar()
|
||||||
|
self.progress_bar.hide()
|
||||||
|
self.progress_bar.set_hexpand(True) # Horizontally expand
|
||||||
|
self.prog_bar_id: int = 0
|
||||||
self.log_dir = tempfile.TemporaryDirectory(
|
self.log_dir = tempfile.TemporaryDirectory(
|
||||||
prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}"
|
prefix="clan_vm-", suffix=f"-{self.data.flake.flake_attr}"
|
||||||
)
|
)
|
||||||
self._finalizer = weakref.finalize(self, self.stop)
|
self._finalizer = weakref.finalize(self, self.stop)
|
||||||
|
self.connect("build_vm", self.build_vm)
|
||||||
|
|
||||||
uri = ClanURI.from_str(
|
uri = ClanURI.from_str(
|
||||||
url=self.data.flake.flake_url, flake_attr=self.data.flake.flake_attr
|
url=self.data.flake.flake_url, flake_attr=self.data.flake.flake_attr
|
||||||
@@ -134,14 +139,43 @@ class VM(GObject.Object):
|
|||||||
flake=url, # type: ignore
|
flake=url, # type: ignore
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _pulse_progress_bar(self) -> bool:
|
||||||
|
self.progress_bar.pulse()
|
||||||
|
return GLib.SOURCE_CONTINUE
|
||||||
|
|
||||||
|
def build_vm(self, vm: "VM", _vm: "VM", building: bool) -> None:
|
||||||
|
if building:
|
||||||
|
log.info("Building VM")
|
||||||
|
self.progress_bar.show()
|
||||||
|
self.prog_bar_id = GLib.timeout_add(100, self._pulse_progress_bar)
|
||||||
|
if self.prog_bar_id == 0:
|
||||||
|
raise ClanError("Couldn't spawn a progess bar task")
|
||||||
|
else:
|
||||||
|
self.progress_bar.hide()
|
||||||
|
if not GLib.Source.remove(self.prog_bar_id):
|
||||||
|
log.error("Failed to remove progress bar task")
|
||||||
|
log.info("VM built")
|
||||||
|
|
||||||
def __start(self) -> None:
|
def __start(self) -> None:
|
||||||
log.info(f"Starting VM {self.get_id()}")
|
log.info(f"Starting VM {self.get_id()}")
|
||||||
vm = vms.run.inspect_vm(self.machine)
|
vm = vms.run.inspect_vm(self.machine)
|
||||||
|
|
||||||
GLib.idle_add(self.emit, "build_vm", self, True)
|
GLib.idle_add(self.emit, "build_vm", self, True)
|
||||||
vms.run.build_vm(self.machine, vm, [])
|
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
|
||||||
|
|
||||||
self.process = spawn(
|
self.process = spawn(
|
||||||
on_except=None,
|
on_except=None,
|
||||||
log_dir=Path(str(self.log_dir.name)),
|
log_dir=Path(str(self.log_dir.name)),
|
||||||
@@ -160,8 +194,6 @@ class VM(GObject.Object):
|
|||||||
if self._watcher_id == 0:
|
if self._watcher_id == 0:
|
||||||
raise ClanError("Failed to add watcher")
|
raise ClanError("Failed to add watcher")
|
||||||
|
|
||||||
self.machine.qmp_connect()
|
|
||||||
|
|
||||||
def start(self) -> None:
|
def start(self) -> None:
|
||||||
if self.is_running():
|
if self.is_running():
|
||||||
log.warn("VM is already running")
|
log.warn("VM is already running")
|
||||||
@@ -220,7 +252,12 @@ class VM(GObject.Object):
|
|||||||
def __stop(self) -> None:
|
def __stop(self) -> None:
|
||||||
log.info(f"Stopping VM {self.get_id()}")
|
log.info(f"Stopping VM {self.get_id()}")
|
||||||
|
|
||||||
self.machine.qmp_command("system_powerdown")
|
try:
|
||||||
|
with self.machine.vm.qmp() as qmp:
|
||||||
|
qmp.command("system_powerdown")
|
||||||
|
except ClanError as e:
|
||||||
|
log.debug(e)
|
||||||
|
|
||||||
self._stop_timer_init = datetime.now()
|
self._stop_timer_init = datetime.now()
|
||||||
self._stop_watcher_id = GLib.timeout_add(100, self.__shutdown_watchdog)
|
self._stop_watcher_id = GLib.timeout_add(100, self.__shutdown_watchdog)
|
||||||
if self._stop_watcher_id == 0:
|
if self._stop_watcher_id == 0:
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class ClanList(Gtk.Box):
|
|||||||
groups = Clans.use()
|
groups = Clans.use()
|
||||||
join = Join.use()
|
join = Join.use()
|
||||||
|
|
||||||
|
self.log_label: Gtk.Label = Gtk.Label()
|
||||||
self.__init_machines = history.add.list_history()
|
self.__init_machines = history.add.list_history()
|
||||||
self.join_boxed_list = create_boxed_list(
|
self.join_boxed_list = create_boxed_list(
|
||||||
model=join.list_store, render_row=self.render_join_row
|
model=join.list_store, render_row=self.render_join_row
|
||||||
@@ -126,25 +127,13 @@ class ClanList(Gtk.Box):
|
|||||||
self.group_list.add_css_class("no-shadow")
|
self.group_list.add_css_class("no-shadow")
|
||||||
|
|
||||||
def render_vm_row(self, boxed_list: Gtk.ListBox, vm: VM) -> Gtk.Widget:
|
def render_vm_row(self, boxed_list: Gtk.ListBox, vm: VM) -> Gtk.Widget:
|
||||||
|
# Remove no-shadow class if attached
|
||||||
if boxed_list.has_css_class("no-shadow"):
|
if boxed_list.has_css_class("no-shadow"):
|
||||||
boxed_list.remove_css_class("no-shadow")
|
boxed_list.remove_css_class("no-shadow")
|
||||||
flake = vm.data.flake
|
flake = vm.data.flake
|
||||||
row = Adw.ActionRow()
|
row = Adw.ActionRow()
|
||||||
|
|
||||||
# Title
|
# ====== Display Avatar ======
|
||||||
row.set_title(flake.flake_attr)
|
|
||||||
|
|
||||||
row.set_title_lines(1)
|
|
||||||
row.set_title_selectable(True)
|
|
||||||
|
|
||||||
# Subtitle
|
|
||||||
if flake.vm.machine_description:
|
|
||||||
row.set_subtitle(flake.vm.machine_description)
|
|
||||||
else:
|
|
||||||
row.set_subtitle(flake.clan_name)
|
|
||||||
row.set_subtitle_lines(1)
|
|
||||||
|
|
||||||
# Avatar
|
|
||||||
avatar = Adw.Avatar()
|
avatar = Adw.Avatar()
|
||||||
|
|
||||||
machine_icon = flake.vm.machine_icon
|
machine_icon = flake.vm.machine_icon
|
||||||
@@ -159,7 +148,26 @@ class ClanList(Gtk.Box):
|
|||||||
avatar.set_size(50)
|
avatar.set_size(50)
|
||||||
row.add_prefix(avatar)
|
row.add_prefix(avatar)
|
||||||
|
|
||||||
# Switch
|
# ====== Display Name And Url =====
|
||||||
|
row.set_title(flake.flake_attr)
|
||||||
|
|
||||||
|
row.set_title_lines(1)
|
||||||
|
row.set_title_selectable(True)
|
||||||
|
|
||||||
|
if flake.vm.machine_description:
|
||||||
|
row.set_subtitle(flake.vm.machine_description)
|
||||||
|
else:
|
||||||
|
row.set_subtitle(flake.clan_name)
|
||||||
|
row.set_subtitle_lines(1)
|
||||||
|
|
||||||
|
# ==== Display build progress bar ====
|
||||||
|
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
|
box.set_valign(Gtk.Align.CENTER)
|
||||||
|
box.append(vm.progress_bar)
|
||||||
|
box.set_homogeneous(False)
|
||||||
|
row.add_suffix(box) # This allows children to have different sizes
|
||||||
|
|
||||||
|
# ==== Action buttons ====
|
||||||
switch = Gtk.Switch()
|
switch = Gtk.Switch()
|
||||||
|
|
||||||
switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
switch_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
|
||||||
@@ -169,10 +177,6 @@ class ClanList(Gtk.Box):
|
|||||||
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=5)
|
||||||
box.set_valign(Gtk.Align.CENTER)
|
box.set_valign(Gtk.Align.CENTER)
|
||||||
|
|
||||||
# suffix_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
|
|
||||||
# suffix.set_halign(Gtk.Align.CENTER)
|
|
||||||
# suffix_box.append(switch)
|
|
||||||
|
|
||||||
open_action = Gio.SimpleAction.new("edit", GLib.VariantType.new("s"))
|
open_action = Gio.SimpleAction.new("edit", GLib.VariantType.new("s"))
|
||||||
open_action.connect("activate", self.on_edit)
|
open_action.connect("activate", self.on_edit)
|
||||||
|
|
||||||
@@ -190,7 +194,6 @@ class ClanList(Gtk.Box):
|
|||||||
|
|
||||||
switch.connect("notify::active", partial(self.on_row_toggle, vm))
|
switch.connect("notify::active", partial(self.on_row_toggle, vm))
|
||||||
vm.connect("vm_status_changed", partial(self.vm_status_changed, switch))
|
vm.connect("vm_status_changed", partial(self.vm_status_changed, switch))
|
||||||
vm.connect("build_vm", self.build_vm)
|
|
||||||
|
|
||||||
# suffix.append(box)
|
# suffix.append(box)
|
||||||
row.add_suffix(box)
|
row.add_suffix(box)
|
||||||
@@ -251,9 +254,6 @@ class ClanList(Gtk.Box):
|
|||||||
def show_error_dialog(self, error: str) -> None:
|
def show_error_dialog(self, error: str) -> None:
|
||||||
p = Views.use().main_window
|
p = Views.use().main_window
|
||||||
|
|
||||||
# app = Gio.Application.get_default()
|
|
||||||
# p = Gtk.Application.get_active_window(app)
|
|
||||||
|
|
||||||
dialog = Adw.MessageDialog(heading="Error")
|
dialog = Adw.MessageDialog(heading="Error")
|
||||||
dialog.add_response("ok", "ok")
|
dialog.add_response("ok", "ok")
|
||||||
dialog.set_body(error)
|
dialog.set_body(error)
|
||||||
@@ -294,12 +294,6 @@ class ClanList(Gtk.Box):
|
|||||||
row.set_state(True)
|
row.set_state(True)
|
||||||
vm.stop()
|
vm.stop()
|
||||||
|
|
||||||
def build_vm(self, vm: VM, _vm: VM, building: bool) -> None:
|
|
||||||
if building:
|
|
||||||
log.info("Building VM")
|
|
||||||
else:
|
|
||||||
log.info("VM built")
|
|
||||||
|
|
||||||
def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None:
|
def vm_status_changed(self, switch: Gtk.Switch, vm: VM, _vm: VM) -> None:
|
||||||
switch.set_active(vm.is_running())
|
switch.set_active(vm.is_running())
|
||||||
switch.set_state(vm.is_running())
|
switch.set_state(vm.is_running())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
{ lib, stdenv, clan-vm-manager, libadwaita, clan-cli, mkShell, ruff, desktop-file-utils, xdg-utils, mypy, python3Packages }:
|
{ lib, stdenv, clan-vm-manager, gtk4, libadwaita, clan-cli, mkShell, ruff, desktop-file-utils, xdg-utils, mypy, python3Packages }:
|
||||||
mkShell {
|
mkShell {
|
||||||
inherit (clan-vm-manager) propagatedBuildInputs buildInputs;
|
inherit (clan-vm-manager) propagatedBuildInputs buildInputs;
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ mkShell {
|
|||||||
desktop-file-utils
|
desktop-file-utils
|
||||||
mypy
|
mypy
|
||||||
python3Packages.ipdb
|
python3Packages.ipdb
|
||||||
|
gtk4.dev
|
||||||
libadwaita.devdoc # has the demo called 'adwaita-1-demo'
|
libadwaita.devdoc # has the demo called 'adwaita-1-demo'
|
||||||
] ++ clan-vm-manager.nativeBuildInputs;
|
] ++ clan-vm-manager.nativeBuildInputs;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user