fix: tray main-thread message loop, numpy <2.0 pin, installer config copy
Lint & Test / test (push) Successful in 9s
Release / create-release (push) Successful in 1s
Release / build-windows (push) Failing after 30s
Release / build-linux (push) Successful in 35s

- Rewrite tray to run on main thread (pystray owns message loop, uvicorn
  in background thread) — fixes unresponsive confirmation dialogs
- Use native Windows MessageBoxW instead of tkinter (embedded Python
  has no tkinter)
- Pin numpy <2.0 to fix soundcard's numpy.fromstring (removed in 2.0)
- Strip transitive numpy 2.x wheels in build script
- Installer copies config.example.yaml as config.yaml on fresh install
- Suppress noisy screen_brightness_control warnings
This commit is contained in:
2026-03-24 15:05:36 +03:00
parent d7e10b1005
commit 402183765c
5 changed files with 127 additions and 77 deletions
+36 -9
View File
@@ -51,6 +51,9 @@ def setup_logging():
handlers=[handler],
)
# Suppress noisy third-party loggers
logging.getLogger("screen_brightness_control").setLevel(logging.ERROR)
@asynccontextmanager
async def lifespan(app: FastAPI):
@@ -240,23 +243,47 @@ def main():
print("\nAuthentication is DISABLED (no tokens configured)")
return
# Start system tray icon (unless disabled)
tray_icon = None
if not args.no_tray:
from .tray import start_tray
from .tray import PYSTRAY_AVAILABLE, TrayManager
tray_icon = start_tray(args.host, args.port)
use_tray = PYSTRAY_AVAILABLE and not args.no_tray
try:
if use_tray:
import asyncio
import threading
# Run uvicorn in a background thread so tray owns the main thread message loop
uv_config = uvicorn.Config(
"media_server.main:app",
host=args.host,
port=args.port,
log_level=settings.log_level.lower(),
)
server = uvicorn.Server(uv_config)
def run_server():
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(server.serve())
server_thread = threading.Thread(target=run_server, daemon=True)
server_thread.start()
# Tray on main thread (blocking)
tray = TrayManager(
port=args.port,
on_exit=lambda: setattr(server, "should_exit", True),
)
tray.run()
# Tray exited — wait for server to finish graceful shutdown
server_thread.join(timeout=10)
else:
uvicorn.run(
"media_server.main:app",
host=args.host,
port=args.port,
reload=False,
)
finally:
if tray_icon is not None:
tray_icon.stop()
if __name__ == "__main__":