"""System-monitor quad ("Ship Systems"): the machine's vitals reskinned as a starship diagnostic panel. A small backend script (backend/sysmon.sh) reports usage + temperature for each subsystem, renamed in the ship's language: CPU -> Thrusters GPU -> Hyperdrive RAM -> Life Support Systems Storage -> Cargo Hold (one entry per mounted drive) Each is drawn as a bracketed usage meter (-<████░░░░>-) with a temperature readout (-( 54°C )-). Rows are laid out in a Gtk.Grid so every column lines up regardless of the (variable-width) nerd-font icons. The compact cell shows the four headline subsystems (first drive only); the expanded view lists every drive with its capacity. """ from __future__ import annotations import gi gi.require_version("Gtk", "4.0") from gi.repository import GLib, Gtk # noqa: E402 from lib.proc import run_text from module_base import ModuleContext, ModuleInstance, ModuleSpec from paths import BACKEND_DIR _SCRIPT = str(BACKEND_DIR / "sysmon.sh") _MONO = "Agave Nerd Font Mono" # palette (kept in sync by eye with style/_colors.css / the hologram violets) _MAGENTA = "#EB00A6" _ACCENT = "#E40046" _DIM_EMPTY = "#3B2A6E" # unfilled meter cells _DIM_TEXT = "#9385C9" # detail line _COOL = "#22D3EE" _WARM = "#F5A623" _HOT = "#E40046" _GOOD = "#2BE08A" # category -> nerd-font glyph (verified to render in Agave Nerd Font) _ICONS = { "cpu": chr(0xf135), # nf-fa-rocket "gpu": chr(0xf0e7), # nf-fa-bolt "ram": chr(0xf004), # nf-fa-heart "disk": chr(0xf0a0), # nf-fa-hdd_o } def _usage_color(pct: int) -> str: if pct >= 85: return _HOT if pct >= 60: return _WARM return _GOOD def _temp_color(temp: int) -> str: if temp >= 85: return _HOT if temp >= 65: return _WARM return _COOL def _esc(text: str) -> str: return GLib.markup_escape_text(text) class _SystemMonitor(Gtk.Box): REFRESH_MS = 2500 def __init__(self, ctx: ModuleContext, compact: bool): super().__init__(orientation=Gtk.Orientation.VERTICAL) self.ctx = ctx self.compact = compact self.add_css_class("sysmon-view") self.set_valign(Gtk.Align.CENTER) self._size = 10 if compact else 12 self._bar_w = 9 if compact else 16 self._timer: int | None = None self._grid = Gtk.Grid() self._grid.set_row_spacing(3 if compact else 7) self._grid.set_column_spacing(12) self.append(self._grid) # -- lifecycle --------------------------------------------------------- def start(self) -> None: self.refresh() if self._timer is None: self._timer = GLib.timeout_add(self.REFRESH_MS, self._tick) def stop(self) -> None: if self._timer is not None: GLib.source_remove(self._timer) self._timer = None def _tick(self) -> bool: self.refresh() return True def refresh(self) -> None: run_text([_SCRIPT], self._on_result) def _on_result(self, ok: bool, out: str, _err: str) -> None: if not ok: return rows = [] for line in out.splitlines(): parts = line.split("\t") if len(parts) < 5: continue rows.append(dict(zip(("cat", "name", "usage", "temp", "detail"), parts))) if self.compact: # headline subsystems + only the first (root) drive trimmed, seen_disk = [], False for r in rows: if r["cat"] == "disk": if seen_disk: continue seen_disk = True trimmed.append(r) rows = trimmed self._render(rows) # -- rendering --------------------------------------------------------- def _clear(self) -> None: child = self._grid.get_first_child() while child is not None: nxt = child.get_next_sibling() self._grid.remove(child) child = nxt def _span(self, markup: str, xalign: float = 0.0) -> Gtk.Label: lbl = Gtk.Label(xalign=xalign) lbl.add_css_class("sysmon-row") lbl.set_markup(f"{markup}") return lbl def _render(self, rows: list[dict]) -> None: self._clear() for i, r in enumerate(rows): try: usage = int(r["usage"]) except ValueError: usage = 0 fill_col = _usage_color(usage) filled = max(0, min(self._bar_w, round(usage / 100 * self._bar_w))) bar = (f"{'█' * filled}" f"{'░' * (self._bar_w - filled)}") meter = (f"-<{bar}" f">-") try: temp = int(r["temp"]) temp_txt, temp_col = f"{temp}°C", _temp_color(temp) except ValueError: temp_txt, temp_col = "--°C", _DIM_TEXT temp_field = (f"-( " f"{temp_txt:>5} " f")-") icon = _ICONS.get(r["cat"], "") self._grid.attach(self._span(f"{icon}"), 0, i, 1, 1) self._grid.attach(self._span(f"{_esc(r['name'])}"), 1, i, 1, 1) self._grid.attach(self._span(meter), 2, i, 1, 1) self._grid.attach(self._span(f"{usage:>3}%", 1.0), 3, i, 1, 1) self._grid.attach(self._span(temp_field), 4, i, 1, 1) if not self.compact: self._grid.attach( self._span(f"{_esc(r['detail'])}"), 5, i, 1, 1) def build(ctx: ModuleContext) -> ModuleInstance: compact = _SystemMonitor(ctx, compact=True) expanded = _SystemMonitor(ctx, compact=False) def on_show() -> None: compact.start() expanded.start() def on_hide() -> None: compact.stop() expanded.stop() return ModuleInstance(compact=compact, expanded=expanded, scroll_expanded=True, on_show=on_show, on_hide=on_hide) SPEC = ModuleSpec( id="sysmon", title="Ship Systems", icon=chr(0xf085), # nf-fa-cogs build=build, )