From 78e9640dec5e842ab77895f1f58c02463fdd26e7 Mon Sep 17 00:00:00 2001 From: Julian Appel Date: Sat, 29 Aug 2026 01:02:18 +0200 Subject: [PATCH] Document the layout fix and the return of the title bar AGENTS.md: die WinAPI-Regel praeziser gefasst -- verboten bleiben Fenstermanipulation und globale Hooks, passive Layout-Abfragen sind es nicht (und die offizielle VersaGUI macht dasselbe). Dazu die neue Aufloesungsreihenfolge samt Warnung, nicht auf "Zeichen auswerten" zurueckzubauen, die Kollisionsregel bei Tastennamen, der Mehrmonitor-Fix und die Folgen der Titelleiste (welche Eigenbauten dadurch entfallen sind und warum randlos nicht ohne Ruecksprache zurueckkommt). README.md und docs/architecture.md entsprechend: Position statt Zeichen, layoutrichtige Beschriftungen inkl. der Anzeigeaenderung an bestehenden Belegungen, Taskleisten-Eintrag statt randlosem Fenster. Co-Authored-By: Claude Opus 5 --- AGENTS.md | 112 ++++++++++++++++++++++++++++++++----------- README.md | 37 +++++++------- docs/architecture.md | 64 ++++++++++++++++++------- 3 files changed, 152 insertions(+), 61 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 4971069..aab3b1b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -59,6 +59,13 @@ Nutzerorientierte Einführung: [`README.md`](README.md). `read_profile_names()` liest nur die Namen (kein Board-Zugriff, fuer Tab-Beschriftungen im Nur-Lese-Modus, siehe Installierbarkeit-Notiz). +- `versapad_keylayout.py` (seit 2026-08-29) — passive Abfragen ans aktive + Windows-Tastaturlayout: `hid_for_vk()` (Virtual-Key → Scan-Code → HID über + `MapVirtualKeyW`) und `key_name()` (Beschriftung über `GetKeyNameTextW`). + Optional: auf Nicht-Windows/ohne ctypes bleibt `AVAILABLE` False und + `versapad_data` faellt auf seine US-Naeherung zurueck. Zur Abgrenzung + gegen die WinAPI-Verbote siehe Domaenenregeln. + **UI:** - `desktop_viewer.py` — Tkinter, flaches Design, drei unabhängige Modi (Nur lesen / Live-Sync / Programmiermodus, siehe Kritische Domänenregeln), @@ -235,24 +242,47 @@ Protokoll) liegt in `docs/`, siehe „Dokumentation und Verifikation" unten. `vcomb.DEFAULT_PATH`, fällt nur bei fehlender/kaputter Datei auf `default_combined()` zurück. Bei jedem "komisches Layout"-Report hier immer ALLE 3 Profile prüfen, nicht nur das gemeldete. -- **Randloses Fenster (seit 2026-08-15):** Die Titelleiste ist per reinem Tk - `overrideredirect(True)` ausgeblendet -- **niemals** per ctypes/WinAPI - nachhelfen (kein `SetWindowLongW`/`ShowWindow` auf das eigene Fenster), - siehe Speicher-Notiz "keine Selbstversteck-Fenstertricks": genau diese - Kombination hat in einem anderen Projekt Bitdefender-Fehlalarme - ausgelöst. Konsequenzen, die mitgebaut werden müssen: kein - Taskleisten-Eintrag (Rückweg nur über das Tray-Icon), kein Ziehen am - Rahmen (Kopfzeile ist deshalb per `` verschiebbar), keine - System-Buttons (eigene `✕`/`—` in der Kopfzeile, beide legen ins Tray) - und **keine Resize-Ränder**. -- **Der Größen-Anfasser hängt per `place()` an der Fensterecke, nicht - gepackt am Ende des Inhalts.** Gepackt verschwindet er, sobald das Fenster - kleiner als der Inhalt ist -- also exakt dann, wenn man ihn zum - Vergrößern bräuchte. `MIN_W/MIN_H` begrenzen das Verkleinern. +- **WinAPI: was verboten bleibt und was nicht.** Verboten sind + **Fenstermanipulation am eigenen Fenster** (`SetWindowLongW`, + `ShowWindow`) und **globale Eingabehooks** (`SetWindowsHookEx`) — genau + diese Kombination hat laut Speicher-Notiz „keine Selbstversteck- + Fenstertricks" in einem anderen Projekt Bitdefender-Fehlalarme ausgeloest. + Erlaubt und seit 2026-08-29 in Benutzung sind **passive Layout-Abfragen** + (`MapVirtualKeyW`, `GetKeyNameTextW` in `versapad_keylayout.py`): sie + lesen nur die Tastaturbelegung, fassen weder Fenster noch Prozesse noch + den Eingabestrom an; die offizielle VersaGUI (C#) benutzt + `GetKeyNameText()` fuer denselben Zweck. Die Grenze verlaeuft also nicht + bei „ctypes", sondern bei „greift ins System ein". +- **Normales Fenster mit Titelleiste (seit 2026-08-29).** Von 2026-08-15 + bis dahin lief das Fenster randlos (`overrideredirect(True)`) — das kostet + unter Windows zwingend den Taskleisten-Eintrag. Als der ausdruecklich + gebraucht wurde, gab es nur drei Wege: Titelleiste zurueck, + `SetWindowLongW`+WS_EX_APPWINDOW (siehe Verbot oben) oder ein + unsichtbares Proxy-Fenster. Der User hat sich fuer die Titelleiste + entschieden. Damit sind ersatzlos entfallen: ziehbare Kopfzeile + (`_start_move`/`_on_move`), Groessen-Anfasser unten rechts + (`_start_resize`/`_on_resize`, ersetzt durch `self.minsize()`), die + eigenen `✕`/`—`-Knoepfe und der ``-Handler, der Minimieren ins Tray + umgeleitet hat. **Wer das Fenster wieder randlos machen will, nimmt dem + User den Taskleisten-Eintrag weg** — nicht ohne Ruecksprache. +- Schliessen (`✕`) legt weiterhin ins Tray statt zu beenden (wie die + offizielle VersaGUI, das Programm laeuft im Hintergrund weiter), + Minimieren geht jetzt aber ganz normal in die Taskleiste. `Escape` + minimiert ebenfalls, statt wie frueher ins Tray zu legen — mit + Taskleisten-Eintrag waere „verschwindet spurlos" die unangenehmere + Ueberraschung. - Fensterhöhe muss zum Karteninhalt passen: seit die Karten eine Notizzeile 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. +- **Dialogposition wird gegen das ELTERNFENSTER begrenzt, nie gegen + `winfo_screenwidth()` (2026-08-29):** Tk meldet dort nur den + Hauptbildschirm. Die erste Fassung hat damit geklemmt — lag das + Hauptfenster auf einem zweiten Monitor, zog genau diese Begrenzung den + Dialog zurueck an den Rand des ersten. Passt der Dialog nicht ins + Elternfenster (der Makro-Schritte-Dialog ist breiter als der + Action-Dialog), wird er an dessen linker oberer Ecke ausgerichtet statt + zentriert. - **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 @@ -290,6 +320,23 @@ Protokoll) liegt in `docs/`, siehe „Dokumentation und Verifikation" unten. 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. + - **Welche Taste gemeint ist, wird ueber die PHYSISCHE POSITION + aufgeloest, nie ueber das erzeugte Zeichen** (2026-08-29, nach + Fehlermeldung aus der Praxis). HID-Keycodes *sind* Positionen: das Board + sendet eine Position, erst Windows macht daraus ein Zeichen. Die erste + Fassung ging ueber keysym/Zeichen und war auf deutschem Layout + entsprechend kaputt — Y und Z landeten vertauscht auf dem Board, ÄÖÜ + und #/+ waren gar nicht erfassbar. Reihenfolge jetzt: (1) benannte + Tasten ueber den keysym, (2) Zeichentasten ueber + `versapad_keylayout.hid_for_vk()`, (3) die alte Naeherung nur noch als + Fallback ohne WinAPI. **Nicht auf "Zeichen auswerten" zurueckbauen** — + das ist genau der Fehler, der hier behoben wurde. + - Schritt (1) ist keine Bequemlichkeit, sondern noetig: `MapVirtualKeyW` + liefert fuer die Pfeiltasten denselben Scan-Code wie fuer ihre + Numpad-Zwillinge (gemessen: VK_LEFT und VK_NUMPAD4 beide 0x4B, das + E0-Praefix von `MAPVK_VK_TO_VSC_EX` bleibt dort aus). Ueber den + Scan-Code allein waeren Pfeiltasten nicht von Numpad-Tasten zu + unterscheiden. - 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 @@ -300,19 +347,30 @@ Protokoll) liegt in `docs/`, siehe „Dokumentation und Verifikation" unten. 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). +- **Tastenbeschriftungen kommen vom aktiven Layout** (seit 2026-08-29). + `versapad_data.hid_key_name()` fragt fuer Zeichentasten + `GetKeyNameTextW` (deutsch: HID 0x1C → „Z", 0x34 → „ä"); fuer alles + andere bleiben die gepflegten deutschen Namen aus `_SPECIAL_KEYS` + („Enter", „Bild↑", „Num5") — die lesen sich besser als das, was Windows + liefert („EINGABE", „4 (ZEHNERTASTATUR)"). Ohne Layout-Abfrage + (Nicht-Windows) faellt alles auf die alte US-Naeherung zurueck. + Konsequenzen, die man kennen muss: + - Bestehende Belegungen aendern ihre **Anzeige**, nicht ihre Daten. Wer + frueher im Dropdown „Z" gewaehlt hat, bekam 0x1D — das steht so in der + Config und zeigt jetzt wahrheitsgemaess „Y", weil es auf dieser Tastatur + ein Y tippt. Das ist keine Regression, sondern der sichtbar gewordene + Altfehler. + - Namen sind Schluessel (Dropdown, `hid_key_code_for_name()`) und muessen + eindeutig bleiben. Es gibt echte Kollisionen: auf deutschem Layout heisst + HID 0x31 schlicht „#" — den Namen trug bisher HID 0x32. Der Layoutname + gewinnt, der verdraengte US-Name wird als „# (US-Layout)" gekennzeichnet + statt verworfen, damit die Taste ansprechbar bleibt. + - `hid_key_code_for_name()` akzeptiert weiterhin beide Schreibweisen, bei + Kollision gewinnt das Layout. Fuer den MCP-Server heisst das: + `set_button_key(key="Z")` trifft die Taste, die auf dieser Tastatur ein + Z tippt (0x1C), nicht mehr die US-Position 0x1D. + - Ein Layoutwechsel zur Laufzeit wird nicht bemerkt (Namen werden einmal + ermittelt und behalten). - 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 diff --git a/README.md b/README.md index bcbb275..0c84f10 100644 --- a/README.md +++ b/README.md @@ -34,10 +34,15 @@ falls gewünscht. 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 + drücken (Modifier inklusive). Erkannt wird die *physische* Taste, nicht + das Zeichen — Y/Z, ÄÖÜ, `#`, `+` und `ß` landen also richtig auf dem + Board, auch auf deutschem Layout. Läuft über das Dialogfenster, nicht + über einen System-Hook: vom System abgefangene Kombinationen (Win+L, + Strg+Alt+Entf) kommen nicht an +- **Layoutrichtige Tastennamen** — Beschriftungen kommen vom aktiven + Windows-Layout (`Strg+Z` heißt auf deutscher Tastatur auch `Strg+Z`, und + `ä`/`ö`/`ü` heißen so). Bestehende Belegungen ändern dadurch ihre + Anzeige, nicht ihre Funktion - **Makro-Editor** — bis zu 8 Schritte pro Slot, liest/schreibt die echte Makro-Tabelle vom Board. Schritte einzeln erfassen oder die ganze Folge am Stück aufnehmen („⏺ Folge aufnehmen"). Die Slot-Auswahl listet alle 32 @@ -59,14 +64,13 @@ falls gewünscht. Tool-Aufruf ändern, ohne Klicks in der GUI (siehe unten) - **Tray-Icon** — minimiert/schließt ins Tray statt in die Taskleiste, wie die offizielle VersaGUI -- **Randloses Fenster** — ohne Windows-Titelleiste, dafür kompakter Kopf - (Modus-Checkboxen direkt neben dem Titel). Verschieben durch Ziehen an - der Kopfzeile, Größe ändern am Anfasser unten rechts, `✕`/`—` legen ins - Tray. Einen Taskleisten-Eintrag gibt es dadurch nicht — das Fenster kommt - über das Tray-Icon zurück. +- **Normales Fenster mit Taskleisten-Eintrag** — Titelleiste, Alt+Tab, + Aero-Snap und Größe ändern am Rahmen funktionieren nativ. `✕` beendet + nicht, sondern legt ins Tray (wie die offizielle VersaGUI — das Programm + läuft im Hintergrund weiter); Minimieren geht normal in die Taskleiste. - **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. + Taste unter dem Mauszeiger kopieren/einfügen, `Esc` minimieren. ## Voraussetzungen @@ -123,7 +127,8 @@ automatisch zuerst nach `%TEMP%` und baut nur dort. | Datei | Zweck | |---|---| -| `versapad_data.py` | Decoding für die Anzeige: JSON laden, HID-Keycodes/Consumer-IDs/Modifier → lesbarer Text | +| `versapad_data.py` | Decoding für die Anzeige: JSON laden, HID-Keycodes/Consumer-IDs/Modifier → lesbarer Text, Tastendruck → HID-Keycode | +| `versapad_keylayout.py` | Abfragen ans aktive Windows-Tastaturlayout: physische Tastenposition und Tastenname (optional, nur Windows) | | `versapad_protocol.py` | Binäres NVM-Layout des Boards (740B Config + 512B Makros), CRC16 — pack/unpack | | `versapad_serial.py` | Serial-Client: liest/schreibt Config + Makros per 8-Byte-Paket-Protokoll | | `versapad_combined.py` | Ein-Datei-Format für alle 3 Profile + Makros + lokale Profilnamen | @@ -187,12 +192,10 @@ rechts im Fenster, oder direkt in `versapad_mcp_server.py`. - Die Tastendruck-Erkennung läuft bewusst über das Dialogfenster statt über 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 - 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 +- Ein Wechsel des Tastaturlayouts im laufenden Programm wird nicht bemerkt + (die Tastennamen werden einmal beim ersten Zugriff ermittelt) — Neustart + hilft. Unter Nicht-Windows fällt die Beschriftung auf eine + US-Layout-Näherung zurück - Unsignierte `.exe` — kann von Antivirus/Smart App Control blockiert werden; `--onedir` (statt `--onefile`) verringert das Risiko, verhindert es aber nicht diff --git a/docs/architecture.md b/docs/architecture.md index c16e23f..66424cd 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -155,27 +155,57 @@ 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). | | 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 +### Tastendruck-Erkennung: Position statt Zeichen Die Erkennung im Bearbeiten-Dialog nutzt ausschließlich Tk-Events des -fokussierten Fensters. Daraus folgt zweierlei, und beides ist bewusst so: +fokussierten Fensters — kein globaler `SetWindowsHookEx`-Hook (siehe +`AGENTS.md`). Erste Folge: **nur was das Fenster erreicht, wird erkannt.** +Win+L, Strg+Alt+Entf und andere vom Betriebssystem abgefangene +Kombinationen kommen nie an. -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. +Zweite und wichtigere Folge betrifft die *Zuordnung*. HID-Keycodes +bezeichnen **physische Tastenpositionen**: das Board sendet eine Position, +erst Windows macht daraus über das aktive Layout ein Zeichen. Wer die +Zuordnung über das *Zeichen* aufbaut, dreht diese Kette falsch herum — auf +deutschem Layout landete dadurch jedes Y auf der Z-Taste des Boards und +ÄÖÜ/#/+ waren gar nicht erfassbar. `versapad_data.tk_event_to_hid()` löst +deshalb in dieser Reihenfolge auf: -Details der Zuordnungstabellen: `versapad_data.tk_event_to_hid()` und die -`_TK_*`/`_WIN_VK_TO_HID`-Dicts darüber. +1. **Benannte Tasten über den Tk-keysym** (Enter, Escape, Pfeile, F-Tasten, + Numpad, Entf …). Layoutunabhängig eindeutig — und hier zwingend, weil + `MapVirtualKeyW` für die Pfeiltasten denselben Scan-Code liefert wie für + ihre Numpad-Zwillinge (gemessen: VK_LEFT und VK_NUMPAD4 beide 0x4B). +2. **Zeichentasten über die physische Position** — + `versapad_keylayout.hid_for_vk()`: Virtual-Key → Scan-Code + (`MapVirtualKeyW`) → HID über die layoutunabhängige Tabelle + `SCANCODE_TO_HID`. Der Virtual-Key ist unabhängig davon, ob Shift oder + AltGr mitgehalten wird. +3. **Näherung ohne WinAPI** (keysym-Zeichentabelle, dann VK-Tabelle) — nur + relevant, wenn `versapad_keylayout` nicht verfügbar ist (Nicht-Windows, + kein ctypes). Auf dieser Ebene bleibt es bei der US-Layout-Näherung + inklusive vertauschtem Y/Z. + +### Tastenbeschriftungen + +`versapad_data.hid_key_name()` fragt für Zeichentasten `GetKeyNameTextW` +und zeigt damit den Namen des aktiven Layouts (deutsch: HID 0x1C → „Z“, +0x34 → „ä“). Für alles andere bleiben die gepflegten deutschen Namen aus +`_SPECIAL_KEYS` („Enter“, „Bild↑“, „Num5“) — die lesen sich besser als das, +was Windows dafür liefert („EINGABE“, „4 (ZEHNERTASTATUR)“). + +Diese Namen sind zugleich Schlüssel (Dropdown-Einträge, +`hid_key_code_for_name()` für den MCP-Server) und müssen eindeutig bleiben. +Echte Kollisionen kommen vor: auf deutschem Layout heißt HID 0x31 schlicht +„#“, und diesen Namen trug bisher HID 0x32. Der Layoutname gewinnt, der +verdrängte US-Name wird als „# (US-Layout)“ gekennzeichnet statt verworfen. +`hid_key_code_for_name()` akzeptiert beide Schreibweisen; bei Kollision +gewinnt das Layout, damit `set_button_key(key="Z")` die Taste trifft, die +auf dieser Tastatur ein Z tippt. + +Bestehende Belegungen ändern dadurch ihre **Anzeige, nicht ihre Daten**: +eine früher über das Dropdown gesetzte „Z“ steht als 0x1D in der Config und +wird jetzt wahrheitsgemäß als „Y“ angezeigt, weil sie auf dieser Tastatur +ein Y tippt. Ein Layoutwechsel zur Laufzeit wird nicht nachgezogen. ### Kopieren/Einfügen zwischen Tasten