from datetime import datetime import sys import gi import os gi.require_version("Gtk", "4.0") gi.require_version("Gdk", "4.0") from gi.repository import Gdk, GLib, Gtk class UIController: def __init__( self, ui_file="window.ui", css_file="style.css", status_provider_callback=None, power_action_callback=None ): self.builder = Gtk.Builder.new_from_file(ui_file) self.window = self.builder.get_object("main_window") self._load_styles(css_file) self._setup_clock() self._initialist_power_options() self.power_action_callback = power_action_callback if status_provider_callback: self.setup_status_poller(status_provider_callback) def setup_status_poller(self, status_provider_callback, interval_seconds=20): """ Accepts any function that returns a dictionary of statuses, currently: {'net':True, 'ziti':True} """ def poll(): try: statuses = status_provider_callback() print(f"Status result {statuses}") # UPDATE UI self._update_label( "net_status", "Net: Connected" if statuses.get("net") else "Net: Disconnected", statuses.get("net"), ) self._update_label( "ziti_status", "Ziti: Connected" if statuses.get("ziti") else "Ziti: Disconnected", statuses.get("ziti") ) except Exception as e: print(f"Error polling status: {e}") import traceback traceback.print_exc() return True poll() GLib.timeout_add_seconds(interval_seconds, poll) def _update_label(self, widget_id, text, is_healthy): label = self.builder.get_object(widget_id) if label: label.set_text(text) if is_healthy: label.remove_css_class("error-label") label.add_css_class("ok-label") else: label.remove_css_class("ok-label") label.add_css_class("error-label") def _load_styles(self, css_file): css_provider = Gtk.CssProvider() css_provider.load_from_path(css_file) Gtk.StyleContext.add_provider_for_display( Gdk.Display.get_default(), css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION, ) def _setup_clock(self): clock_label = self.builder.get_object("clock_label") date_label = self.builder.get_object("date_label") def update(): now = datetime.now() clock_label.set_text(now.strftime("%H:%M")) date_label.set_text(now.strftime("%A %d/%m/%Y")) return True update() GLib.timeout_add_seconds(10, update) def _initialist_power_options(self): btn_reboot = self.builder.get_object("btn_reboot") btn_shutdown = self.builder.get_object("btn_shutdown") if btn_reboot: btn_reboot.connect( "clicked", lambda _: self.power_action_callback("power", "reboot") ) if btn_shutdown: btn_shutdown.connect( "clicked", lambda _: self.power_action_callback("power", "shutdown") ) def set_app_title(self, title): title_label = self.builder.get_object("app_title_label") title_label.set_text(title) def add_card(self, title_text, desc_text, bg_path, on_click_callback): container = self.builder.get_object("cards_flow") button = Gtk.Button() button.add_css_class("image-card") # Switched to our new image-card class button.connect("clicked", lambda *_: on_click_callback()) # Main overlay container overlay = Gtk.Overlay() # 1. Background Image Layer if bg_path and os.path.exists(bg_path): picture = Gtk.Picture.new_for_filename(bg_path) picture.set_content_fit(Gtk.ContentFit.COVER) else: picture = Gtk.Box() picture.add_css_class("card-fallback-bg") overlay.set_child(picture) # 2. Dark Gradient Tint Layer for legibility tint_box = Gtk.Box() tint_box.add_css_class("card-tint-overlay") overlay.add_overlay(tint_box) # 3. Text Content Layer (anchored to the bottom) box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4) box.add_css_class("card-content-box") box.set_valign(Gtk.Align.END) title = Gtk.Label(label=title_text) title.set_xalign(0.0) title.add_css_class("card-title-shadowed") desc = Gtk.Label(label=desc_text) desc.set_xalign(0.0) desc.set_wrap(True) desc.add_css_class("card-desc-shadowed") box.append(title) box.append(desc) overlay.add_overlay(box) button.set_child(overlay) container.append(button) def setup_exit_shortcut(self, quit_callback): controller = Gtk.EventControllerKey.new() def on_key_pressed(_, keyval, __, ___): if keyval == Gdk.KEY_Escape: quit_callback() return True return False controller.connect("key-pressed", on_key_pressed) self.window.add_controller(controller) def present(self, app): self.window.set_application(app) self.window.present() # Allow direct execution for isolated UI testing if __name__ == "__main__": class TestApp(Gtk.Application): def __init__(self): super().__init__(application_id="com.local.UITest") self.ui = None def do_activate(self): self.ui = UIController() self.ui.set_app_title("UI Test Mode") # Add mock test cards self.ui.add_card( "Test Card 1", "Verifying layout rendering", lambda: print("Clicked Test 1"), ) self.ui.add_card( "Test Card 2", "Checking flowbox constraints", lambda: print("Clicked Test 2"), ) self.ui.setup_exit_shortcut(self.quit) self.ui.present(self) app = TestApp() sys.exit(app.run(sys.argv))