Document key capture, dialog placement and copy/paste

AGENTS.md: die Regel "Auswahl ist ein Dropdown, kein Tastendruck-Capture"
ist ueberholt -- Capture gibt es jetzt, aber weiterhin ohne WinAPI-Hook, und
genau diese Grenze muss bleiben. Dazu die Layout-Naeherung (Y/Z vertauscht
auf deutschem Layout, minus-Kollision bewusst zugunsten der US-Position
aufgeloest), die neuen UI-Regeln (Dialogposition, Grab-Rueckgabe bei
verschachtelten Dialogen, feste Panel-Hoehe, In-Memory-Ablage fuers
Kopieren) und ein Verifikationsrezept fuer UI-Aenderungen inkl. der
DPI-Falle beim Screenshot-Vergleich. Der Deferred-Work-Eintrag zum Capture
faellt weg.

README.md: neue Bedienung in den Features, Tastenkuerzel-Uebersicht, und die
Einschraenkung praezisiert (nicht mehr "Dropdown statt Capture", sondern
was die Fenster-basierte Erkennung nicht sehen kann).

docs/architecture.md: _ModalDialog/_KeyCapture in der UI-Schicht, plus zwei
neue Abschnitte zu den Grenzen der Erkennung und zur Kopier-Ablage.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Julian Appel 2026-08-28 15:33:38 +02:00
parent 21f9cbc2ed
commit 8a00b3ae36
3 changed files with 152 additions and 12 deletions

View file

@ -16,6 +16,11 @@ Nutzerorientierte Einführung: [`README.md`](README.md).
- `versapad_data.py` — Decoding für Anzeige: JSON laden, HID-Keycode/ - `versapad_data.py` — Decoding für Anzeige: JSON laden, HID-Keycode/
Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie
(`index = spalte*5+reihe`), `hid_key_choices()`/`consumer_choices()`. (`index = spalte*5+reihe`), `hid_key_choices()`/`consumer_choices()`.
Seit 2026-08-28 zusätzlich `tk_event_to_hid()` (Tk-Tastendruck →
HID-Keycode+Modifier, siehe Tastendruck-Erkennung unten) und
`macro_slot_choices()`/`macro_slot_from_choice()` für die Slot-Auswahl.
`action_label()`/`annotate_profile()` nehmen die Makrotabelle optional
entgegen und zeigen dann statt „Makro (Slot 7)" die echte Tastenfolge.
`app_dir()` liefert das Verzeichnis für die eigene Config `app_dir()` liefert das Verzeichnis für die eigene Config
(`versapad_combined.DEFAULT_PATH`) — bei der `.exe` der Installations- (`versapad_combined.DEFAULT_PATH`) — bei der `.exe` der Installations-
ordner, sonst der Projektordner, siehe Installierbarkeit-Notiz unten. ordner, sonst der Projektordner, siehe Installierbarkeit-Notiz unten.
@ -59,7 +64,9 @@ Nutzerorientierte Einführung: [`README.md`](README.md).
(Nur lesen / Live-Sync / Programmiermodus, siehe Kritische Domänenregeln), (Nur lesen / Live-Sync / Programmiermodus, siehe Kritische Domänenregeln),
Tray-Icon statt Taskleisten-Minimierung, Info-Button mit MCP-Doku. Tray-Icon statt Taskleisten-Minimierung, Info-Button mit MCP-Doku.
- `action_dialog.py` — Bearbeiten-Dialoge (`ActionEditDialog`, - `action_dialog.py` — Bearbeiten-Dialoge (`ActionEditDialog`,
`MacroStepsDialog`) für den Programmiermodus. `MacroStepsDialog`) für den Programmiermodus. Gemeinsame Basis
`_ModalDialog` (Positionierung über dem Elternfenster, Enter/Escape,
Verteilung der Tastenevents) und `_KeyCapture` (Tastendruck-Erkennung).
**MCP-Server:** **MCP-Server:**
- `versapad_mcp_server.py` — registriert als projektgebundener MCP-Server - `versapad_mcp_server.py` — registriert als projektgebundener MCP-Server
@ -246,12 +253,66 @@ Protokoll) liegt in `docs/`, siehe „Dokumentation und Verifikation" unten.
haben (`CARD_H` 84 → 114) braucht das Fenster ~960px, sonst liegen haben (`CARD_H` 84 → 114) braucht das Fenster ~960px, sonst liegen
Encoder-Bereich und Fußzeile unterhalb des Rands. Wer `CARD_H` ändert, Encoder-Bereich und Fußzeile unterhalb des Rands. Wer `CARD_H` ändert,
muss `geometry()` mit anpassen. muss `geometry()` mit anpassen.
- HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein - **Dialoge öffnen über dem Hauptfenster, nicht in der Bildschirmecke
Tastendruck-Capture (bewusst — kein WinAPI-Hook, um keinen AV-Fehlalarm (2026-08-28):** `_ModalDialog` baut sich `withdraw()`n auf, positioniert
wie bei den Fensterverstecktricks in anderen Projekten zu riskieren). sich in `run()` per `_center_on_parent()` und wird erst dann
`deiconify()`t. Ohne das legt Tk jeden Toplevel bei `+0+0` an — bei 20
Tasten hintereinander wandert der Blick jedes Mal in die linke obere Ecke.
Das `withdraw()` gehört zwingend dazu, sonst blitzt der Dialog dort auf,
bevor er springt.
- **Verschachtelte Dialoge geben den Grab zurück:** `MacroStepsDialog` läuft
im `ActionEditDialog`. `_finish()` ruft `grab_release()`, was den Grab des
*aufrufenden* Dialogs mit wegnimmt — `run()` setzt ihn deshalb am Ende
wieder, wenn der Parent ein `_ModalDialog` ist.
- **Panel-Höhe im `ActionEditDialog` ist fix** (`grid_propagate(False)`):
sonst springt die Fenstergröße bei jedem Typwechsel (Keine/Taste/Makro/…)
und OK/Abbrechen wandern unter dem Mauszeiger weg.
- **Kopieren/Einfügen zwischen Tasten** (Rechtsklickmenü bzw. Strg+C/Strg+V
auf der Karte unter dem Mauszeiger) benutzt eine reine In-Memory-Ablage
(`self._clip`), **nicht** die System-Zwischenablage — dort liegen
Action-Dicts, kein Text. Immer `copy.deepcopy()`, sonst teilen sich zwei
Tasten dasselbe Dict und eine spätere Bearbeitung ändert beide. „Leeren"
behält die Notiz (gleiche Regel wie beim Typwechsel im Dialog, siehe
Notizen-Bullet oben). Eine kopierte LED-Farbe auf einen Encoder
einzufügen ist wirkungslos (Encoder haben keine eigene LED) — das ist
Absicht, kein Fehler.
- Die Toolbar des Programmiermodus ist bei der Standardbreite (790px) fast
voll — zusätzliche Hinweistexte dort kurz halten, sonst werden sie rechts
abgeschnitten.
- **Tastendruck-Erkennung (seit 2026-08-28, auf expliziten User-Wunsch):**
Tasten lassen sich im Bearbeiten-Dialog per „⌨ Taste drücken" erfassen,
Makro-Schritte zusätzlich als Folge am Stück („⏺ Folge aufnehmen").
Umgesetzt **ausschließlich über Tk-Fenster-Events** (`<KeyPress>`/
`<KeyRelease>` auf dem Dialog-Toplevel, ausgewertet in
`versapad_data.tk_event_to_hid()`) — **weiterhin kein WinAPI-Hook**
(`SetWindowsHookEx` o.ä.), aus demselben AV-Fehlalarm-Grund wie bei den
Fensterverstecktricks. Wer hier auf einen globalen Hook „aufrüstet",
baut genau dieses Risiko ein. Konsequenzen, die so bleiben müssen:
- Erkannt wird nur, was das fokussierte Fenster erreicht — Win+L,
Strg+Alt+Entf und andere vom System abgefangene Kombinationen nicht.
- Das Dropdown bleibt daneben stehen (Korrekturmöglichkeit), es ersetzt
die Erkennung nicht und wird von ihr nicht ersetzt.
- Modifier werden doppelt ermittelt (selbst mitgeführte Press/Release-Bits
*plus* `event.state`), weil ein KeyRelease bei Fokuswechsel verloren
gehen kann.
- `_KeyCapture` hängt an einem `tk.Label`, nicht an einem `tk.Button`:
Buttons reagieren per Klassen-Binding selbst auf Leertaste/Enter und
würden die laufende Aufnahme mit ihrem eigenen Klick beantworten.
- Makro-Schritte filtern das Win-Bit weg (`allow_win=False`), passend zur
Firmware-Regel oben.
- Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich - Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich
aktive Windows-Tastaturlayout (die echte VersaGUI löst das über aktive Windows-Tastaturlayout (die echte VersaGUI löst das über
`GetKeyNameText()`, das bilden wir ohne WinAPI-Call nicht nach). `GetKeyNameText()`, das bilden wir ohne WinAPI-Call nicht nach).
**Dieselbe Näherung gilt für die Tastendruck-Erkennung:** HID-Keycodes
sind physische US-Tastenpositionen, Tk liefert aber nur keysym/VK-Code des
aktiven Layouts — die Position (Scan-Code) wäre dafür nötig und ist ohne
WinAPI nicht zu bekommen. Auf deutschem Layout landen Y und Z deshalb
vertauscht auf dem Board, und die einzige echte Keysym-Kollision (`minus`:
US-Position 0x2D vs. deutsche Position 0x38) ist bewusst zugunsten der
US-Position aufgelöst, damit Erkennung und Dropdown-Beschriftung dasselbe
sagen. Bei gehaltenem Shift zählt zuerst der VK-Code, weil der keysym dann
das verschobene Zeichen ist (deutsch: Shift+7 → `slash`, was sonst
fälschlich auf Taste 0x38 zeigen würde).
- Tk-Aufrufe (`self.after()`, Widget-Konfiguration) NIE direkt aus einem - Tk-Aufrufe (`self.after()`, Widget-Konfiguration) NIE direkt aus einem
Fremdthread (Serial-Thread, pystray-Thread) — hat in einer früheren Fremdthread (Serial-Thread, pystray-Thread) — hat in einer früheren
Version einen stillen Absturz verursacht. Threads legen Ergebnisse nur in Version einen stillen Absturz verursacht. Threads legen Ergebnisse nur in
@ -334,8 +395,6 @@ selbst vorgegeben (`SAction.data`), dort beibehalten statt umzubenennen.
explizit vom User abgelehnt ("lass uns weg"), Live-Sync bleibt read-only explizit vom User abgelehnt ("lass uns weg"), Live-Sync bleibt read-only
- Profilnamen aufs Board schreiben — technisch unmöglich (kein Platz im - Profilnamen aufs Board schreiben — technisch unmöglich (kein Platz im
Firmware-Struct), bleibt lokal Firmware-Struct), bleibt lokal
- Tastendruck-Capture statt Dropdown im Programmiermodus — bewusst
vermieden (WinAPI-Hook-Risiko)
- Hintergrund-Thread für "Vom Board laden"/"Zum Board übertragen" — laufen - Hintergrund-Thread für "Vom Board laden"/"Zum Board übertragen" — laufen
aktuell synchron im UI-Thread (kurzzeitiges Einfrieren möglich) aktuell synchron im UI-Thread (kurzzeitiges Einfrieren möglich)
- Vorgefertigte `.exe` im Repo/als Release-Asset — bewusst nicht committet - Vorgefertigte `.exe` im Repo/als Release-Asset — bewusst nicht committet
@ -363,6 +422,17 @@ Prüfungen vor einem Commit an Binärformat/Protokoll:
```bash ```bash
python -m py_compile *.py python -m py_compile *.py
``` ```
Für UI-Änderungen (Dialoge, Tastendruck-Erkennung, Kopieren/Einfügen) hat
sich zusätzlich bewährt, ein Wegwerf-Skript im Scratchpad zu fahren, das die
Dialoge ohne Board aufbaut, Tk-Events als kleine Fake-Event-Objekte
(`keysym`/`keycode`/`state`) durchreicht und das Ergebnis-Dict prüft — die
komplette Capture- und Copy/Paste-Logik ist so ohne Klicken verifizierbar.
Wichtig dabei: `versapad_combined.DEFAULT_PATH` vorher auf eine Temp-Datei
umbiegen, sonst schreibt `_autosave_combined()` in die echte Nutzer-Config.
Für Screenshots gilt: Tk rechnet in logischen Pixeln, `ImageGrab` liefert
physische — bei aktiver Windows-Skalierung (hier 125%) sonst ein zu kleiner
Ausschnitt, der wie ein Layout-Fehler aussieht.
Danach ein Live-Testskript gegen ein angeschlossenes Board laufen lassen Danach ein Live-Testskript gegen ein angeschlossenes Board laufen lassen
(read → unpack → pack → Bytevergleich, siehe Existing-Codebase-Regel) — (read → unpack → pack → Bytevergleich, siehe Existing-Codebase-Regel) —
es gibt keine automatisierten Unit-Tests dafür, die Verifikation läuft es gibt keine automatisierten Unit-Tests dafür, die Verifikation läuft

View file

@ -30,9 +30,27 @@ falls gewünscht.
und schaltet die Ansicht automatisch mit und schaltet die Ansicht automatisch mit
- **Programmiermodus** — Zellen anklicken und bearbeiten (Taste, Medientaste, - **Programmiermodus** — Zellen anklicken und bearbeiten (Taste, Medientaste,
Makro, Profilwechsel, LED-Farbe/Animation), direkt aufs Board schreiben Makro, Profilwechsel, LED-Farbe/Animation), direkt aufs Board schreiben
oder als Datei speichern oder als Datei speichern. Der Dialog öffnet über dem Hauptfenster,
Enter bestätigt, Escape bricht ab
- **Tastendruck-Erkennung** — statt die Taste im Dropdown zu suchen,
„⌨ Taste drücken" klicken und die gewünschte Kombination einfach
drücken (Modifier inklusive). Läuft über das Dialogfenster, nicht über
einen System-Hook — vom System abgefangene Kombinationen (Win+L,
Strg+Alt+Entf) kommen deshalb nicht an, und das Dropdown bleibt zum
Nachkorrigieren daneben stehen
- **Makro-Editor** — bis zu 8 Schritte pro Slot, liest/schreibt die echte - **Makro-Editor** — bis zu 8 Schritte pro Slot, liest/schreibt die echte
Makro-Tabelle vom Board Makro-Tabelle vom Board. Schritte einzeln erfassen oder die ganze Folge
am Stück aufnehmen („⏺ Folge aufnehmen"). Die Slot-Auswahl listet alle 32
Slots samt Inhalt, statt sie einzeln durchklicken zu müssen
- **Makros im Grid lesbar** — eine Makro-Belegung zeigt die tatsächliche
Tastenfolge (`Makro 3: Strg+C → Strg+V`) statt nur der Slot-Nummer; das
gilt auch in der Browser-Ansicht und in den MCP-Antworten
- **Farb-Schnellwahl** — zwölf Grundfarben direkt in der LED-Zeile des
Dialogs, der System-Farbdialog nur noch für den Rest („mehr…")
- **Kopieren/Einfügen zwischen Tasten** — Rechtsklick auf eine Karte im
Programmiermodus: Belegung und/oder Farbe kopieren und auf andere Tasten
anwenden, oder die Belegung leeren. Strg+C/Strg+V wirken auf die Karte
unter dem Mauszeiger
- **Notizen** — freier Text pro Button/Encoder-Aktion, was sie tatsächlich - **Notizen** — freier Text pro Button/Encoder-Aktion, was sie tatsächlich
tut (z.B. "Speichern in Fusion 360"), zusätzlich zur automatischen tut (z.B. "Speichern in Fusion 360"), zusätzlich zur automatischen
Beschriftung ("Strg+S"). Rein lokal wie Profilnamen, geht nie aufs Board, Beschriftung ("Strg+S"). Rein lokal wie Profilnamen, geht nie aufs Board,
@ -46,6 +64,9 @@ falls gewünscht.
der Kopfzeile, Größe ändern am Anfasser unten rechts, `✕`/`—` legen ins der Kopfzeile, Größe ändern am Anfasser unten rechts, `✕`/`—` legen ins
Tray. Einen Taskleisten-Eintrag gibt es dadurch nicht — das Fenster kommt Tray. Einen Taskleisten-Eintrag gibt es dadurch nicht — das Fenster kommt
über das Tray-Icon zurück. über das Tray-Icon zurück.
- **Tastenkürzel im Hauptfenster**`Strg+1/2/3` Profil wechseln, `F2`
Profil umbenennen, `Strg+E` Programmiermodus an/aus, `Strg+C`/`Strg+V`
Taste unter dem Mauszeiger kopieren/einfügen, `Esc` ins Tray.
## Voraussetzungen ## Voraussetzungen
@ -163,10 +184,15 @@ rechts im Fenster, oder direkt in `versapad_mcp_server.py`.
gleichzeitig mit der offiziellen VersaGUI laufen (die läuft dauerhaft als gleichzeitig mit der offiziellen VersaGUI laufen (die läuft dauerhaft als
Tray-App weiter, auch wenn nur ihr Konfigurationsfenster geschlossen wird Tray-App weiter, auch wenn nur ihr Konfigurationsfenster geschlossen wird
— für Parallelbetrieb muss sie über ihr Tray-Menü beendet werden) — für Parallelbetrieb muss sie über ihr Tray-Menü beendet werden)
- HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein - Die Tastendruck-Erkennung läuft bewusst über das Dialogfenster statt über
Tastendruck-Capture (bewusst, um keinen WinAPI-Hook zu brauchen) einen globalen WinAPI-Hook — vom System abgefangene Kombinationen (Win+L,
Strg+Alt+Entf) erreichen das Fenster nie und lassen sich so nicht erfassen
- Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich - Zeichentasten-Labels zeigen eine US-Layout-Näherung, nicht das tatsächlich
aktive Tastatur-Layout aktive Tastatur-Layout. Das betrifft auch die Tastendruck-Erkennung:
HID-Keycodes sind physische US-Tastenpositionen, erkennbar ist ohne
WinAPI aber nur das Zeichen des aktiven Layouts — auf deutschem Layout
landen Y und Z deshalb vertauscht auf dem Board. Das Ergebnis steht immer
sichtbar im Dropdown und lässt sich dort korrigieren
- Unsignierte `.exe` — kann von Antivirus/Smart App Control blockiert - Unsignierte `.exe` — kann von Antivirus/Smart App Control blockiert
werden; `--onedir` (statt `--onefile`) verringert das Risiko, verhindert werden; `--onedir` (statt `--onefile`) verringert das Risiko, verhindert
es aber nicht es aber nicht

View file

@ -93,7 +93,17 @@ auf.
umschaltbaren Modi (siehe „Modi" unten), Tray-Icon, Info-Dialog mit umschaltbaren Modi (siehe „Modi" unten), Tray-Icon, Info-Dialog mit
MCP-Doku. MCP-Doku.
- **`action_dialog.py`** — modale Bearbeiten-Dialoge (`ActionEditDialog`, - **`action_dialog.py`** — modale Bearbeiten-Dialoge (`ActionEditDialog`,
`MacroStepsDialog`) für den Programmiermodus. `MacroStepsDialog`) für den Programmiermodus, auf gemeinsamer Basis
`_ModalDialog`:
- positioniert sich beim Öffnen mittig über dem aufrufenden Fenster
(Aufbau `withdraw()`n, `_center_on_parent()`, dann `deiconify()`);
- bindet `<KeyPress>`/`<KeyRelease>` auf dem Toplevel und verteilt sie:
entweder an eine laufende Tastendruck-Aufnahme, sonst als
Enter = OK / Escape = Abbrechen;
- `_KeyCapture` schaltet ein Label in den Aufnahmemodus und schickt jeden
Tastendruck durch `versapad_data.tk_event_to_hid()` — reine
Tk-Fenster-Events, **kein globaler Tastaturhook** (siehe „Grenzen der
Tastendruck-Erkennung" unten).
### MCP-Server ### MCP-Server
@ -145,6 +155,40 @@ Drei Checkboxen, unabhängig voneinander:
| Live-Sync | Fragt per Serial das aktuell aktive Profil ab, schaltet die Ansicht mit | Hintergrund-Thread pollt `read_active_profile()`, hält dafür den COM-Port dauerhaft offen, solange die Checkbox an ist. Schließt sich mit VersaGUI/Programmiermodus/MCP-Board-Zugriff gegenseitig aus (exklusiver Port). | | Live-Sync | Fragt per Serial das aktuell aktive Profil ab, schaltet die Ansicht mit | Hintergrund-Thread pollt `read_active_profile()`, hält dafür den COM-Port dauerhaft offen, solange die Checkbox an ist. Schließt sich mit VersaGUI/Programmiermodus/MCP-Board-Zugriff gegenseitig aus (exklusiver Port). |
| Programmiermodus | Zellen anklicken zum Bearbeiten | Lädt `self.combined` **einmalig pro Prozesslauf** beim ersten Aktivieren (bevorzugt `DEFAULT_PATH`, sonst `default_combined()`). Jede Bearbeitung speichert sofort automatisch (`_autosave_combined()`). **Achtung:** Da der Snapshot nur einmal geladen wird, sieht der Programmiermodus externe Änderungen (z.B. per MCP) erst nach einem Neustart der exe oder einem expliziten „Datei laden…“. | | Programmiermodus | Zellen anklicken zum Bearbeiten | Lädt `self.combined` **einmalig pro Prozesslauf** beim ersten Aktivieren (bevorzugt `DEFAULT_PATH`, sonst `default_combined()`). Jede Bearbeitung speichert sofort automatisch (`_autosave_combined()`). **Achtung:** Da der Snapshot nur einmal geladen wird, sieht der Programmiermodus externe Änderungen (z.B. per MCP) erst nach einem Neustart der exe oder einem expliziten „Datei laden…“. |
### Grenzen der Tastendruck-Erkennung
Die Erkennung im Bearbeiten-Dialog nutzt ausschließlich Tk-Events des
fokussierten Fensters. Daraus folgt zweierlei, und beides ist bewusst so:
1. **Nur was das Fenster erreicht, wird erkannt.** Win+L, Strg+Alt+Entf und
andere vom Betriebssystem abgefangene Kombinationen kommen nie an. Ein
globaler `SetWindowsHookEx`-Hook würde sie sehen, ist aber ausgeschlossen
(AV-Fehlalarm-Risiko, siehe `AGENTS.md`).
2. **Die Zuordnung ist eine US-Layout-Näherung.** HID-Keycodes bezeichnen
physische Tastenpositionen des US-Layouts; Tk liefert nur `keysym` und
Windows-Virtual-Key-Code, beide vom *aktiven* Layout abgeleitet. Die
physische Position (Scan-Code) wäre nötig, um das exakt aufzulösen, und
ist ohne WinAPI-Aufruf nicht verfügbar. Praktische Folge auf deutschem
Layout: Y und Z landen vertauscht auf dem Board. Das Ergebnis wird immer
ins Dropdown und in die Modifier-Checkboxen geschrieben und ist dort
korrigierbar — die Erkennung ersetzt die manuelle Auswahl nicht, sie
beschleunigt sie nur.
Details der Zuordnungstabellen: `versapad_data.tk_event_to_hid()` und die
`_TK_*`/`_WIN_VK_TO_HID`-Dicts darüber.
### Kopieren/Einfügen zwischen Tasten
Rechtsklick auf eine Karte im Programmiermodus (bzw. `Strg+C`/`Strg+V` auf
der Karte unter dem Mauszeiger) kopiert Belegung und/oder LED-Farbe auf
andere Tasten. Die Ablage ist eine reine In-Memory-Struktur in
`desktop_viewer.VersaPadViewer._clip` (`{"action": …, "led": …}`), **nicht**
die System-Zwischenablage — dort lägen nur Textrepräsentationen, hier
werden ganze Action-Dicts übertragen. Eingefügt wird immer eine
`copy.deepcopy()`, damit zwei Tasten nicht dasselbe Dict teilen. Encoder
haben keine eigene LED; eine kopierte Farbe auf einen Encoder einzufügen ist
deshalb wirkungslos.
Tk-Aufrufe passieren nie direkt aus dem Serial- oder Tray-Hintergrundthread Tk-Aufrufe passieren nie direkt aus dem Serial- oder Tray-Hintergrundthread
— Ergebnisse landen in einer `queue.Queue`, der Main-Thread holt sie per — Ergebnisse landen in einer `queue.Queue`, der Main-Thread holt sie per
`after()`-Polling ab (Absturzrisiko bei Cross-Thread-Tk-Zugriff, siehe `after()`-Polling ab (Absturzrisiko bei Cross-Thread-Tk-Zugriff, siehe