From 9de0153854510c64db453a700fa361c4138f1081 Mon Sep 17 00:00:00 2001 From: Jeremy Shen Date: Wed, 10 Jun 2026 10:58:29 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(gui):=20fix=20config=20save?= =?UTF-8?q?=20and=20UI=20freeze=20on=20startup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Config save fix: - Add _sync_config_from_ui() to sync UI values to Config object - Store _ip_string_var reference for IP field sync - Call _sync_config_from_ui() before every save operation - Fix _on_close to sync before saving UI freeze fix: - Defer _check_vitis() and _refresh_ports_initial() to _deferred_init() - Use self.after(100, ...) to defer until window is displayed - Window now appears immediately, blocking ops run in background --- src/gui/main_window.py | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/src/gui/main_window.py b/src/gui/main_window.py index 901d41b..1b44d06 100644 --- a/src/gui/main_window.py +++ b/src/gui/main_window.py @@ -80,15 +80,24 @@ class MainWindow(ctk.CTk): self._zynq_jtag_present: bool = False self._zynq_jtag_info = None + # StringVar references for config sync + self._ip_string_var: ctk.StringVar | None = None + self._load_config() self._build_ui() - self._check_vitis() - self._refresh_ports_initial() self._update_step_dependencies() # Handle window close self.protocol("WM_DELETE_WINDOW", self._on_close) + # Defer blocking operations until after the window is displayed + self.after(100, self._deferred_init) + + def _deferred_init(self) -> None: + """Run blocking operations after the window is shown.""" + self._check_vitis() + self._refresh_ports_initial() + # ── Config ───────────────────────────────────────────────── def _find_config(self) -> Path | None: @@ -126,8 +135,29 @@ class MainWindow(ctk.CTk): else: self._config = Config.from_default() + def _sync_config_from_ui(self) -> None: + """Read current UI values and update the Config object. + + This must be called before _save_config() to ensure + the Config object has the latest UI values. + """ + if not self._config: + return + # Sync IP address + if self._ip_string_var: + self._config.zynq_ip = self._ip_string_var.get() + # Sync serial port + self._config.serial_port = self._port_var.get() + # Sync file paths from FileSelectors + files = self._get_selected_files() + self._config.bootloader_bit_path = files["bootloader_bit_path"] + self._config.bootloader_elf_path = files["bootloader_elf_path"] + self._config.bootloader_bin_path = files["bootloader_bin_path"] + self._config.firmware_bin_path = files["firmware_bin_path"] + def _save_config(self) -> None: """Save current configuration to file.""" + self._sync_config_from_ui() # Sync UI values first if self._config and self._config.config_path: self._config.save() @@ -335,7 +365,8 @@ class MainWindow(ctk.CTk): ip_frame.grid_columnconfigure(1, weight=1) ctk.CTkLabel(ip_frame, text="Zynq IP:", font=FONT_BODY).grid(row=0, column=0, sticky="w") - self._ip_entry = ctk.CTkEntry(ip_frame, textvariable=ctk.StringVar(value=self._config.zynq_ip)) + self._ip_string_var = ctk.StringVar(value=self._config.zynq_ip) + self._ip_entry = ctk.CTkEntry(ip_frame, textvariable=self._ip_string_var) self._ip_entry.grid(row=0, column=1, sticky="nsew", padx=PADDING_SMALL) # BIT path (Bootloader) @@ -946,6 +977,7 @@ class MainWindow(ctk.CTk): def _on_close(self) -> None: """Handle window close event: save config and cleanup.""" if self._config: + self._sync_config_from_ui() # Sync UI values first try: self._config.save() except Exception: