fix(tray): replace tkinter messagebox with Win32 MessageBoxW
Lint & Test / test (push) Successful in 2m3s

The packaged embedded Python distribution does not ship the tcl/tk
runtime, so tkinter.messagebox.askyesno crashed with 'Can't find a
usable init.tcl' when the user clicked Shutdown or Restart in the
tray menu. Use ctypes + user32.MessageBoxW instead — no tcl/tk,
no extra dependencies.
This commit is contained in:
2026-04-08 12:16:32 +03:00
parent fc8ee34369
commit d037a2e929
+27 -3
View File
@@ -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()