feat: initial project scaffold — server, client, Android APK
- FastAPI server with audio mixer (Windows + Voicemeeter), media tracking, notifications (progress + alerts), telemetry, and system tray .exe build - Test suite covering notifications, audio mixer, media, telemetry, and API - E-Ink web client (vanilla JS, DOM API, block meters, swipe gestures) - Android WebView APK for BOOX Go 7 Color Gen II (Android 13, Kotlin) - Design decision records in .pi/docs/design/ - PyInstaller build script for server .exe
This commit is contained in:
@@ -0,0 +1,104 @@
|
||||
"""System tray wrapper for PaperDash.
|
||||
|
||||
Runs the FastAPI server in a background thread and displays a tray icon
|
||||
with a quit option. This is the entry point for the compiled .exe.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import multiprocessing
|
||||
import signal
|
||||
import time
|
||||
|
||||
import pystray
|
||||
from PIL import Image, ImageDraw
|
||||
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format="%(asctime)s [%(name)s] %(levelname)s: %(message)s",
|
||||
)
|
||||
logger = logging.getLogger("paperdash.tray")
|
||||
|
||||
|
||||
def create_icon() -> Image.Image:
|
||||
"""Create a simple 16x16 tray icon."""
|
||||
img = Image.new("RGB", (16, 16), color=(0, 0, 0))
|
||||
draw = ImageDraw.Draw(img)
|
||||
# Dashboard frame
|
||||
draw.rectangle([1, 1, 15, 15], outline=(255, 255, 255), width=1)
|
||||
# Screen lines
|
||||
draw.line([3, 3, 13, 3], fill=(255, 255, 255), width=1)
|
||||
draw.line([3, 5, 13, 5], fill=(255, 255, 255), width=1)
|
||||
draw.line([3, 7, 8, 7], fill=(255, 255, 255), width=1)
|
||||
# Stand
|
||||
draw.line([6, 15, 10, 15], fill=(255, 255, 255), width=1)
|
||||
return img
|
||||
|
||||
|
||||
def _run_server() -> None:
|
||||
"""Run the FastAPI server in this process (for multiprocessing)."""
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
host="0.0.0.0",
|
||||
port=8921,
|
||||
log_level="info",
|
||||
reload=False,
|
||||
)
|
||||
|
||||
|
||||
def _on_quit(icon: pystray.Icon, item: pystray.MenuItem) -> None:
|
||||
"""Handle quit from tray menu."""
|
||||
logger.info("Quit requested from tray")
|
||||
icon.stop()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Entry point for the compiled .exe."""
|
||||
logger.info("PaperDash starting...")
|
||||
|
||||
# Start server in a subprocess
|
||||
server_process = multiprocessing.Process(target=_run_server, daemon=True)
|
||||
server_process.start()
|
||||
logger.info(f"Server process started (PID {server_process.pid})")
|
||||
|
||||
# Wait briefly for server to be ready
|
||||
time.sleep(2)
|
||||
|
||||
# Create and run tray icon
|
||||
icon_image = create_icon()
|
||||
menu = pystray.Menu(
|
||||
pystray.MenuItem("PaperDash", pystray.MenuItem.default, enabled=False),
|
||||
pystray.Menu.Separator(),
|
||||
pystray.MenuItem("Quit", _on_quit),
|
||||
)
|
||||
|
||||
icon = pystray.Icon(
|
||||
name="PaperDash",
|
||||
image=icon_image,
|
||||
title="PaperDash",
|
||||
menu=menu,
|
||||
)
|
||||
|
||||
# Handle OS exit signals
|
||||
def _signal_handler(signum, frame):
|
||||
logger.info(f"Received signal {signum}, shutting down")
|
||||
icon.stop()
|
||||
server_process.terminate()
|
||||
|
||||
signal.signal(signal.SIGINT, _signal_handler)
|
||||
signal.signal(signal.SIGTERM, _signal_handler)
|
||||
|
||||
logger.info("PaperDash running in system tray. Quit from notification area.")
|
||||
icon.run()
|
||||
|
||||
# Cleanup
|
||||
logger.info("Shutting down server process")
|
||||
server_process.terminate()
|
||||
server_process.join(timeout=5)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user