Add custom icon and tray-minimize behavior (like VersaGUI's TrayApp)

Minimizing or closing hides the window instead of leaving a taskbar
entry; only "Beenden" in the tray right-click menu really exits. Tray
callbacks route through a queue instead of touching Tk directly from
pystray's thread (same pattern as the earlier serial-thread fix).

Also fixes the build script: PyInstaller failed on the network share
while copying Tcl/Tk tzdata (path-length issue), so building now
happens in a local temp copy instead of directly on Z:.

Adds an info button that explains the MCP server's available tools.
This commit is contained in:
cjjohn 2026-08-05 08:30:50 +02:00
parent e456af7c19
commit 4ac29a3e5c
6 changed files with 208 additions and 25 deletions

View file

@ -116,30 +116,63 @@ zuverlässig zur echten Python313-Installation auf.
## Gepackte .exe (PyInstaller) — kein Terminal-Flash, kein Python-Setup nötig
`build_and_deploy.ps1` baut `desktop_viewer.py` mit PyInstaller
(`--windowed --onedir`) und kopiert das Ergebnis lokal nach
`%LOCALAPPDATA%\VersaPadViewer\VersaPadViewer.exe`. Desktop-Verknüpfung
"VersaPad Viewer (Fenster)" zeigt dorthin.
`build_and_deploy.ps1` kopiert den Quellcode zuerst nach `%TEMP%`, baut dort
mit PyInstaller (`--windowed --onedir --icon icon.ico`), und kopiert das
Ergebnis nach `%LOCALAPPDATA%\VersaPadViewer\VersaPadViewer.exe`.
Desktop-Verknüpfung "VersaPad Viewer (Fenster)" zeigt dorthin (inkl. eigenem
Icon, `IconLocation` zeigt auf die `.exe` selbst statt auf `pythonw.exe`).
**`--onedir`, nicht `--onefile`** — Single-File-PyInstaller-Bundles lösen
öfter AV-Fehlalarme aus (sehen strukturell wie ein Packer aus); `--onedir`
(Ordner mit `.exe` + `_internal`) ist unauffälliger. Trotzdem unsigniert,
also grundsätzlich nie über Smart App Control/AV in Stein gemeißelt sicher.
**Kritischer Stolperstein:** Die gebaute `.exe` läuft NICHT vom Netzlaufwerk
aus (`Z:\Git\...`, SMB-Share auf cj-ki) — Windows blockiert das Nachladen
der `_internal`-DLLs von einem Netzwerkpfad, der Prozess startet dann
einfach nie, ganz ohne Fehlermeldung/Crash-Log/AV-Benachrichtigung (sah
zuerst wie ein AV-Block aus, war aber keiner — sauber verifiziert indem
dieselbe `.exe` 1:1 lokal kopiert sofort lief). Bauen darf auf Z: passieren,
das fertige Bundle muss vor dem Start nach C: kopiert werden — genau das
macht `build_and_deploy.ps1`.
**Zwei Netzlaufwerk-Stolpersteine, deshalb baut UND deployt alles lokal:**
1. Laufzeit: die gebaute `.exe` läuft NICHT vom Netzlaufwerk aus (`Z:\Git\...`,
SMB-Share auf cj-ki) — Windows blockiert das Nachladen der
`_internal`-DLLs von einem Netzwerkpfad, der Prozess startet dann einfach
nie, ganz ohne Fehlermeldung/Crash-Log/AV-Benachrichtigung (sah zuerst wie
ein AV-Block aus, war aber keiner — verifiziert indem dieselbe `.exe` 1:1
lokal kopiert sofort lief).
2. Buildzeit: PyInstaller scheitert beim Kopieren der Tcl/Tk-`tzdata`
(viele tief verschachtelte, lang benannte Zeitzonen-Ordner) auf dem
SMB-Share mit `FileNotFoundError: [WinError 3]` — Pfadlängen-Problem,
kombiniert mit dem eh schon langen Projektpfad (`Z:\Git\Versa Board\
versapad-viewer\dist\...`).
Deshalb kopiert `build_and_deploy.ps1` den Quellcode zuerst nach `%TEMP%`
und baut NUR dort — Z: wird nur zum Lesen der `.py`-Dateien angefasst.
Bei erneutem Bauen: `.\build_and_deploy.ps1` in PowerShell ausführen
(lokal, nicht über die sandboxed Bash-Tool-Umgebung — die hat eine eigene
Dateisystem-Sicht, die nicht zuverlässig mit dem echten Windows-Dateisystem
übereinstimmt; Kopieroperationen für Dinge, die der User/andere Prozesse
sehen sollen, gehören in PowerShell, nicht in Bash).
übereinstimmt; siehe Sessionlog: ein `cp -r` über Bash landete an einem Ort,
den PowerShell/der reale Explorer nicht sah. Kopieroperationen für Dinge,
die der User/andere Prozesse sehen sollen, gehören in PowerShell, nicht in
Bash).
## Icon + Tray (kein Taskleisten-Eintrag beim Minimieren, wie VersaGUI)
- `icon.png`/`icon.ico` — generiert via Pillow (dunkles abgerundetes Quadrat,
blauer Block, 2x2-Punktraster; flaches Design, keine Farbverläufe/
Anti-Aliasing-Details, siehe Speicher-Notiz zu Tkinter-Flat-Design).
`.ico` fürs Fenster-/Taskleisten-/Exe-Icon, `.png` fürs Tray-Icon (pystray
braucht ein PIL-Image, kein Multi-Res-`.ico`).
- `resource_path()` in `desktop_viewer.py` findet Assets sowohl im
Quellordner als auch im PyInstaller-Bundle (`sys._MEIPASS`).
- Minimieren (`<Unmap>` mit `state()=='iconic'`) ruft `withdraw()` statt
normal zu minimieren → kein Taskleisten-Eintrag, nur noch Tray-Icon.
Schließen (X) macht dasselbe (`_hide_to_tray`, nicht `_quit`) — echtes
Beenden nur über "Beenden" im Tray-Rechtsklickmenü (mirrort VersaGUIs
`TrayApp`-Menü: Öffnen/Beenden). Tray-Icon läuft über `pystray.Icon.
run_detached()` in eigenem Thread — Klicks im Tray-Menü legen nur ein
Ereignis in `self._tray_queue`, das der Main-Thread per `after()`-Polling
abholt (dieselbe Queue-statt-Direktzugriff-Regel wie beim Serial-Thread,
siehe oben — Tk-Aufrufe aus einem Fremdthread haben schon mal einen
stillen Absturz verursacht).
- Neue Abhängigkeiten: `pystray`, `pillow` (zusätzlich zu `pyserial`).
- Info-Button (ⓘ oben rechts im Header) öffnet einen Dialog mit der
MCP-Server-Doku (`MCP_INFO_TEXT`-Konstante) — rein informativ, keine Logik.
## Dateien/Pfade (hardcoded, hal9001-spezifisch)