(self)
| 321 | self._live_job = self.base.after(4000, self._live_refresh) |
| 322 | |
| 323 | def _stop_live(self) -> None: |
| 324 | if self._live_job is not None: |
| 325 | self.base.after_cancel(self._live_job) |
| 326 | self._live_job = None |
| 327 | engine = self._live_engine |
| 328 | # Clear before _poll_thread so any _live_refresh firing during base.update() |
| 329 | # sees None and returns without rescheduling. |
| 330 | self._live_engine = None |
| 331 | if engine: |
| 332 | iface = engine._iface |
| 333 | self._spin_start("Running covert check") |
| 334 | t, _ = self._run_in_thread(engine.stop) |
| 335 | try: |
| 336 | self._poll_thread(t) |
| 337 | finally: |
| 338 | self._spin_stop(f"✓ {len(memory.packet_db)} sessions captured") |
| 339 | import time |
| 340 | self._open_store().save_session(f"live_{iface}_{int(time.time())}") |
| 341 | |
| 342 | self.live_button.config(text="▶ Start Live") |
| 343 | self._live_status.config(text="📡 Captured — click Visualize! for snapshot", fg="#81c784") |
| 344 | import interactive_gui |
| 345 | interactive_gui.refresh_live() |
| 346 | interactive_gui.set_panel_title("Network Graph (stopped) | Click Visualize! to generate static snapshot") |
| 347 | self._enable_file_controls() |
| 348 | self._populate_filter_menus() |
| 349 | self.trigger['state'] = 'normal' |
| 350 | self.base.after(100, self._force_focus) |
| 351 | |
| 352 | def _live_refresh(self) -> None: |
| 353 | if self._live_engine is None or not self._live_engine.is_running(): |
no test coverage detected