diff --git a/server/src/wled_controller/tray.py b/server/src/wled_controller/tray.py index 257e804..a60adb5 100644 --- a/server/src/wled_controller/tray.py +++ b/server/src/wled_controller/tray.py @@ -4,9 +4,32 @@ import os import sys import webbrowser from pathlib import Path -from tkinter import messagebox from typing import Callable + +def _confirm(message: str, title: str = "LED Grab") -> bool: + """Show a Yes/No confirmation dialog using the Win32 API. + + Uses ``ctypes`` instead of ``tkinter.messagebox`` because the packaged + embedded Python distribution does not include the tcl/tk runtime, which + causes ``tkinter`` to fail with ``Can't find a usable init.tcl``. + """ + if sys.platform != "win32": + # Non-Windows: no tray in practice, but fall back to auto-confirm. + return True + try: + import ctypes + + # MB_YESNO = 0x04, MB_ICONQUESTION = 0x20, MB_SETFOREGROUND = 0x10000 + # IDYES = 6 + flags = 0x04 | 0x20 | 0x10000 + result = ctypes.windll.user32.MessageBoxW(0, message, title, flags) + return result == 6 + except Exception: + # If the dialog cannot be shown for any reason, proceed with the action. + return True + + try: import pystray from PIL import Image @@ -60,9 +83,10 @@ class TrayManager: webbrowser.open(f"http://localhost:{self._port}") def _restart(self, icon: "pystray.Icon", item: "pystray.MenuItem") -> None: - if not messagebox.askyesno("LED Grab", "Restart the server?"): + if not _confirm("Restart the server?"): return from wled_controller.server_ref import _broadcast_restarting + _broadcast_restarting() self._icon.stop() self._on_exit() @@ -70,7 +94,7 @@ class TrayManager: os.execv(sys.executable, [sys.executable, "-m", "wled_controller"]) def _shutdown(self, icon: "pystray.Icon", item: "pystray.MenuItem") -> None: - if not messagebox.askyesno("LED Grab", "Shut down the server?"): + if not _confirm("Shut down the server?"): return self._on_exit() self._icon.stop()