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/
Consumer-Usage/Modifier → lesbarer Text, Grid-Geometrie
(`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
(`versapad_combined.DEFAULT_PATH`) — bei der `.exe` der Installations-
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),
Tray-Icon statt Taskleisten-Minimierung, Info-Button mit MCP-Doku.
- `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:**
- `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
Encoder-Bereich und Fußzeile unterhalb des Rands. Wer `CARD_H` ändert,
muss `geometry()` mit anpassen.
- HID-Tasten-Auswahl im Programmiermodus ist ein Dropdown, kein
Tastendruck-Capture (bewusst — kein WinAPI-Hook, um keinen AV-Fehlalarm
wie bei den Fensterverstecktricks in anderen Projekten zu riskieren).
- **Dialoge öffnen über dem Hauptfenster, nicht in der Bildschirmecke
(2026-08-28):** `_ModalDialog` baut sich `withdraw()`n auf, positioniert
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
aktive Windows-Tastaturlayout (die echte VersaGUI löst das über
`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
Fremdthread (Serial-Thread, pystray-Thread) — hat in einer früheren
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
- Profilnamen aufs Board schreiben — technisch unmöglich (kein Platz im
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
aktuell synchron im UI-Thread (kurzzeitiges Einfrieren möglich)
- Vorgefertigte `.exe` im Repo/als Release-Asset — bewusst nicht committet
@ -363,6 +422,17 @@ Prüfungen vor einem Commit an Binärformat/Protokoll:
```bash
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
(read → unpack → pack → Bytevergleich, siehe Existing-Codebase-Regel) —
es gibt keine automatisierten Unit-Tests dafür, die Verifikation läuft