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 sys
import webbrowser import webbrowser
from pathlib import Path from pathlib import Path
from tkinter import messagebox
from typing import Callable 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: try:
import pystray import pystray
from PIL import Image from PIL import Image
@@ -60,9 +83,10 @@ class TrayManager:
webbrowser.open(f"http://localhost:{self._port}") webbrowser.open(f"http://localhost:{self._port}")
def _restart(self, icon: "pystray.Icon", item: "pystray.MenuItem") -> None: 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 return
from wled_controller.server_ref import _broadcast_restarting from wled_controller.server_ref import _broadcast_restarting
_broadcast_restarting() _broadcast_restarting()
self._icon.stop() self._icon.stop()
self._on_exit() self._on_exit()
@@ -70,7 +94,7 @@ class TrayManager:
os.execv(sys.executable, [sys.executable, "-m", "wled_controller"]) os.execv(sys.executable, [sys.executable, "-m", "wled_controller"])
def _shutdown(self, icon: "pystray.Icon", item: "pystray.MenuItem") -> None: 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 return
self._on_exit() self._on_exit()
self._icon.stop() self._icon.stop()