diff --git a/app/sovran_systemsos_hub/application.py b/app/sovran_systemsos_hub/application.py index 8d43dd1..42e04a2 100644 --- a/app/sovran_systemsos_hub/application.py +++ b/app/sovran_systemsos_hub/application.py @@ -16,13 +16,18 @@ from .service_tile import ServiceTile APP_ID = "com.sovransystems.hub" +# Initialize libadwaita BEFORE any widget creation +Adw.init() + class SovranHubWindow(Adw.ApplicationWindow): def __init__(self, app, config): super().__init__( - application=app, title="Sovran_SystemsOS Hub", - default_width=680, default_height=700, + application=app, + title="Sovran_SystemsOS Hub", + default_width=680, + default_height=700, ) self._config = config self._tiles = [] @@ -37,22 +42,33 @@ class SovranHubWindow(Adw.ApplicationWindow): ) header = Adw.HeaderBar() - refresh_btn = Gtk.Button(icon_name="view-refresh-symbolic", tooltip_text="Refresh now") + refresh_btn = Gtk.Button( + icon_name="view-refresh-symbolic", + tooltip_text="Refresh now", + ) refresh_btn.connect("clicked", lambda _b: self._refresh_all()) header.pack_end(refresh_btn) self._flowbox = Gtk.FlowBox( - max_children_per_line=4, min_children_per_line=2, - selection_mode=Gtk.SelectionMode.NONE, homogeneous=True, - row_spacing=12, column_spacing=12, - margin_top=16, margin_bottom=16, margin_start=16, margin_end=16, - halign=Gtk.Align.CENTER, valign=Gtk.Align.START, + max_children_per_line=4, + min_children_per_line=2, + selection_mode=Gtk.SelectionMode.NONE, + homogeneous=True, + row_spacing=12, + column_spacing=12, + margin_top=16, + margin_bottom=16, + margin_start=16, + margin_end=16, + halign=Gtk.Align.CENTER, + valign=Gtk.Align.START, ) scrolled = Gtk.ScrolledWindow( hscrollbar_policy=Gtk.PolicyType.NEVER, vscrollbar_policy=Gtk.PolicyType.AUTOMATIC, - vexpand=True, child=self._flowbox, + vexpand=True, + child=self._flowbox, ) toolbar_view = Adw.ToolbarView() @@ -61,25 +77,31 @@ class SovranHubWindow(Adw.ApplicationWindow): self.set_content(toolbar_view) self._build_tiles() + interval = config.get("refresh_interval", 5) if interval and interval > 0: GLib.timeout_add_seconds(interval, self._auto_refresh) def _build_tiles(self): + method = self._config.get("command_method", "systemctl") for entry in self._config.get("services", []): tile = ServiceTile( name=entry.get("name", entry["unit"]), unit=entry["unit"], scope=entry.get("type", "system"), - method=self._config.get("command_method", "systemctl"), + method=method, icon_name=entry.get("icon", ""), ) self._flowbox.append(tile) self._tiles.append(tile) + # Defer first status poll so the window renders immediately + GLib.idle_add(self._refresh_all) + def _refresh_all(self): for t in self._tiles: t.refresh() + return False def _auto_refresh(self): self._refresh_all() @@ -89,7 +111,10 @@ class SovranHubWindow(Adw.ApplicationWindow): class SovranHubApp(Adw.Application): def __init__(self): - super().__init__(application_id=APP_ID, flags=Gio.ApplicationFlags.DEFAULT_FLAGS) + super().__init__( + application_id=APP_ID, + flags=Gio.ApplicationFlags.DEFAULT_FLAGS, + ) self._config = load_config() def do_activate(self): diff --git a/app/sovran_systemsos_hub/service_tile.py b/app/sovran_systemsos_hub/service_tile.py index 33376f7..5966f03 100644 --- a/app/sovran_systemsos_hub/service_tile.py +++ b/app/sovran_systemsos_hub/service_tile.py @@ -10,7 +10,7 @@ gi.require_version("Gtk", "4.0") gi.require_version("Adw", "1") gi.require_version("Gdk", "4.0") -from gi.repository import Adw, Gdk, GdkPixbuf, GLib, Gtk +from gi.repository import Gdk, GdkPixbuf, GLib, Gtk from . import systemctl @@ -47,7 +47,11 @@ class ServiceTile(Gtk.Box): halign=Gtk.Align.CENTER, ellipsize=3, max_width_chars=14, )) - self._status_label = Gtk.Label(css_classes=["caption"], halign=Gtk.Align.CENTER) + self._status_label = Gtk.Label( + label="● …", + css_classes=["caption", "dim-label"], + halign=Gtk.Align.CENTER, + ) self.append(self._status_label) controls = Gtk.Box( @@ -66,22 +70,24 @@ class ServiceTile(Gtk.Box): controls.append(restart_btn) self.append(controls) - self.refresh() + # No self.refresh() here — the application calls it via GLib.idle_add def _set_logo(self, icon_name): if icon_name and ICON_DIR: for ext in ICON_EXTENSIONS: path = os.path.join(ICON_DIR, f"{icon_name}{ext}") if os.path.isfile(path): - pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, 48, 48, True) - texture = Gdk.Texture.new_for_pixbuf(pixbuf) - self._logo.set_from_paintable(texture) - return + try: + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(path, 48, 48, True) + texture = Gdk.Texture.new_for_pixbuf(pixbuf) + self._logo.set_from_paintable(texture) + return + except Exception: + break self._logo.set_from_icon_name("system-run-symbolic") def refresh(self): active = systemctl.is_active(self._unit, self._scope) - enabled = systemctl.is_enabled(self._unit, self._scope) is_on = active == "active" is_loading = active in LOADING_STATES is_failed = active == "failed"