diff --git a/src-python/controller.py b/src-python/controller.py index e6350127..c2674851 100644 --- a/src-python/controller.py +++ b/src-python/controller.py @@ -49,12 +49,18 @@ class Controller: def setRun(self, run:Callable[[int, str, Any], None]) -> None: self.run = run - def shutdown(self) -> None: - """Shutdown controller and model (including telemetry).""" + def shutdown(self, *args, **kwargs) -> dict: + """Shutdown controller and model (including telemetry). + + Returns: + dict with status 200 and result True on success. + """ try: model.shutdown() + return {"status": 200, "result": True} except Exception: errorLogging() + return {"status": 500, "result": False} # response functions def connectedNetwork(self) -> None: diff --git a/src-python/mainloop.py b/src-python/mainloop.py index cf92b7aa..6f7fc1b8 100644 --- a/src-python/mainloop.py +++ b/src-python/mainloop.py @@ -129,6 +129,8 @@ mapping = { "/run/send_text_overlay": {"status": True, "variable":controller.sendTextOverlay}, + "/run/shutdown": {"status": True, "variable":controller.shutdown}, + "/run/swap_your_language_and_target_language": {"status": True, "variable":controller.swapYourLanguageAndTargetLanguage}, "/run/update_software": {"status": True, "variable":controller.updateSoftware}, @@ -460,20 +462,14 @@ class Main: """Read lines from stdin, parse JSON and enqueue requests. Uses blocking readline but honors stop via _stop_event checked between reads. - EOF on stdin indicates the frontend has closed; trigger app_closed event. """ while not self._stop_event.is_set(): try: line = sys.stdin.readline() if not line: - # EOF reached - frontend has closed connection - # Trigger telemetry shutdown to send app_closed event - printLog("Frontend disconnected (stdin EOF)", {"event": "frontend_closed"}) - try: - self.controller.shutdown() - except Exception: - errorLogging() - break + # EOF reached; sleep briefly and re-check stop event + time.sleep(0.1) + continue received_data = json.loads(line.strip()) if received_data: diff --git a/src-ui/logics/common/useWindow.js b/src-ui/logics/common/useWindow.js index 3e7f4059..b027cd9f 100644 --- a/src-ui/logics/common/useWindow.js +++ b/src-ui/logics/common/useWindow.js @@ -152,7 +152,18 @@ export const useWindow = () => { const asyncMinimizeApp = async () => { await appWindow.minimize(); }; + const asyncCloseApp = async () => { + // Send shutdown signal to backend before closing the app + // This ensures telemetry app_closed event is sent and flushed + // Note: Don't await this call, let it run in background (fire-and-forget) + asyncStdoutToPython("/run/shutdown"); + + // Give backend time to process shutdown and flush telemetry + // Tauri sidecar will be terminated when UI closes, so timing is critical + await new Promise(resolve => setTimeout(resolve, 2000)); + + // Now close the UI window (this will also terminate the backend sidecar) await appWindow.close(); };