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:
parent
21f9cbc2ed
commit
8a00b3ae36
3 changed files with 152 additions and 12 deletions
82
AGENTS.md
82
AGENTS.md
|
|
@ -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
|
||||||
|
|
|
||||||
36
README.md
36
README.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue