Commit graph

39 commits

Author SHA1 Message Date
d9d7d90654 Merge branch 'dev/jappel' into main
Bringt die UI/UX-Ueberarbeitung des Programmiermodus nach main:
Tastendruck-Erkennung ueber die physische Tastenposition (statt ueber das
erzeugte Zeichen -- auf deutschem Layout waren Y/Z vertauscht und
AeOeUe/#/+ gar nicht erfassbar), layoutrichtige Tastennamen, Makros zeigen
ihre echte Tastenfolge, Slot-Auswahl mit Vorschau, Farb-Schnellwahl,
Kopieren/Einfuegen zwischen Tasten per Rechtsklick, Dialoge oeffnen ueber
dem Hauptfenster (auch auf einem zweiten Monitor), Enter/Escape und
Fenster-Kuerzel, sowie die Rueckkehr zur nativen Titelleiste fuer einen
Taskleisten-Eintrag.

Dazu die aeltere dev/jappel-Historie, die auf main noch fehlte
(.mcp.json, docs/*.md, Notizen pro Aktion, Config-Pfad in Roaming-AppData,
selbstinstallierendes build_and_deploy.ps1).
2026-08-29 01:09:36 +02:00
78e9640dec 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 <noreply@anthropic.com>
2026-08-29 01:02:18 +02:00
e1e3d0e466 Bring back the native title bar so the app has a taskbar entry
Das randlose Fenster (overrideredirect) kostet unter Windows zwingend den
Taskleisten-Eintrag. Nachruesten liesse er sich nur per SetWindowLongW
(WS_EX_APPWINDOW) -- genau der Aufruf, der laut AGENTS.md schon
AV-Fehlalarme ausgeloest hat. Der User hat sich deshalb fuer die native
Titelleiste entschieden.

Damit kommen Taskleiste, Alt+Tab, Aero-Snap sowie Ziehen und
Groessenaendern am Rahmen nativ zurueck, und die Eigenbau-Loesungen dafuer
entfallen ersatzlos: ziehbare Kopfzeile, Anfasser unten rechts (ersetzt
durch self.minsize()), eigene ✕/—-Knoepfe und der <Unmap>-Handler, der
Minimieren ins Tray umgeleitet hat.

Schliessen legt weiterhin ins Tray statt zu beenden (wie die offizielle
VersaGUI). Minimieren geht jetzt normal in die Taskleiste, und Escape
minimiert ebenfalls statt ins Tray zu legen -- mit Taskleisten-Eintrag
waere "verschwindet spurlos" die unangenehmere Ueberraschung.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 01:02:07 +02:00
50868ab081 Keep edit dialogs on the monitor the main window is on
_center_on_parent() hat gegen winfo_screenwidth()/screenheight() begrenzt --
Tk meldet dort aber nur den Hauptbildschirm. Lag das Hauptfenster auf einem
zweiten Monitor, zog genau diese Begrenzung den Dialog zurueck an den Rand
des ersten. Begrenzt wird jetzt gegen das Elternfenster: passt der Dialog
hinein, wird er zentriert, sonst an dessen linker oberer Ecke ausgerichtet
-- so bleibt er in jedem Fall dort, wo gearbeitet wird.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 01:02:07 +02:00
42897d553c Resolve captured keys by physical position, not by character
Die Tastendruck-Erkennung ging bisher ueber das erzeugte Zeichen (keysym
bzw. Virtual-Key). Das dreht die Kette falsch herum: HID-Keycodes SIND
physische Tastenpositionen -- das Board sendet eine Position, erst Windows
macht daraus ueber das aktive Layout ein Zeichen. Auf deutschem Layout
landete deshalb jedes Y auf der Z-Taste des Boards und umgekehrt, und
AeOeUe/#/+/ss waren gar nicht erfassbar.

versapad_keylayout.py loest das ueber MapVirtualKeyW (Virtual-Key ->
Scan-Code) und eine layoutunabhaengige Scan-Code-zu-HID-Tabelle.
GetKeyNameTextW liefert dazu den Namen, den eine Taste auf dem aktiven
Layout traegt, so dass Dropdown und Grid "Strg+Z" zeigen, wenn Strg+Z
gemeint ist. Beides sind passive Layout-Abfragen -- kein Hook, keine
Fenstermanipulation, also nicht das, was AGENTS.md verbietet (die
offizielle VersaGUI benutzt GetKeyNameText fuer denselben Zweck).

Benannte Tasten laufen weiterhin zuerst ueber den Tk-keysym, und das ist
kein Schoenheitsfehler: MapVirtualKeyW liefert fuer die Pfeiltasten
denselben Scan-Code wie fuer ihre Numpad-Zwillinge (gemessen: VK_LEFT und
VK_NUMPAD4 beide 0x4B). Ohne diesen Schritt waeren beide nicht zu
unterscheiden.

Namen sind zugleich Schluessel (Dropdown, hid_key_code_for_name), muessen
also eindeutig bleiben -- auf deutschem Layout heisst HID 0x31 "#", ein
Name den bisher HID 0x32 trug. Der Layoutname gewinnt, der verdraengte
US-Name wird gekennzeichnet statt verworfen. Bestehende Belegungen aendern
damit ihre Anzeige, nicht ihre Daten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-29 01:01:51 +02:00
8a00b3ae36 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>
2026-08-28 15:33:38 +02:00
21f9cbc2ed Copy button settings between keys; add window shortcuts
Rechtsklick auf eine Karte im Programmiermodus: Belegung und/oder LED-Farbe
kopieren und auf andere Tasten anwenden, Belegung leeren, bearbeiten. Auf
Encodern dasselbe fuer die drei Aktionen (dort ohne Farbe -- Encoder haben
keine eigene LED). Strg+C/Strg+V wirken auf die Karte unter dem Mauszeiger.

Die Ablage ist bewusst eine reine In-Memory-Struktur (self._clip), nicht die
System-Zwischenablage: uebertragen werden ganze Action-Dicts, kein Text.
Eingefuegt wird immer eine deepcopy, sonst teilen sich zwei Tasten dasselbe
Dict und eine spaetere Bearbeitung aendert beide. "Leeren" behaelt die Notiz
-- gleiche Regel wie beim Typwechsel im Dialog, die Notiz beschreibt die
Taste, nicht die konkrete Aktion.

Ausserdem: Makro-Belegungen zeigen im Grid ihre echte Tastenfolge (Makrotabelle
wird an annotate_profile() durchgereicht), und das Fenster kennt Strg+1/2/3
(Profil), F2 (umbenennen), Strg+E (Programmiermodus), Esc (ins Tray).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:33:26 +02:00
aba81463e5 Show macro key sequences in the browser view and MCP replies
Beide Ansichten reichen jetzt die Makrotabelle an action_label() durch, so
dass eine Makro-Belegung ihre echte Tastenfolge zeigt statt nur der
Slot-Nummer -- dieselbe Darstellung wie im Desktop-Fenster.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:33:15 +02:00
f6f4edbdde Rework the edit dialogs: key capture, placement, shortcuts, quick colours
Der Bearbeiten-Dialog war der langsamste Teil der Bedienung. Fuenf Punkte,
alle auf gemeinsamer Basis _ModalDialog:

- Oeffnet mittig ueber dem aufrufenden Fenster statt bei +0+0 in der
  Bildschirmecke -- bei 20 Tasten hintereinander wanderte der Blick sonst
  jedes Mal dorthin. Aufbau erfolgt withdraw()'t, damit er nicht kurz in der
  Ecke aufblitzt, bevor er springt.
- Enter = OK, Escape = Abbrechen. Die Sequenzen sind nicht einzeln
  gebunden, sondern werden aus dem <KeyPress>-Handler verteilt -- waehrend
  einer laufenden Tastendruck-Aufnahme muessen sie als normale Tasten
  erfassbar bleiben.
- "Taste druecken" erfasst Taste + Modifier per Tastendruck, Makro-Schritte
  zusaetzlich als Folge am Stueck ("Folge aufnehmen"). _KeyCapture haengt an
  einem Label, nicht an einem Button: Buttons reagieren per Klassen-Binding
  selbst auf Leertaste/Enter und wuerden die Aufnahme mit ihrem eigenen
  Klick beantworten. Das Dropdown bleibt daneben stehen, damit ein falsch
  erkanntes Zeichen (US-Layout-Naeherung) korrigierbar ist.
- Die Makro-Slot-Auswahl ist ein Dropdown mit allen 32 Slots samt Inhalt
  statt einer Spinbox, durch die man sich klicken musste.
- Zwoelf Grundfarben direkt in der LED-Zeile, der System-Farbdialog nur noch
  fuer den Rest.

Die Panel-Hoehe ist fix (grid_propagate(False)), sonst springt die
Fenstergroesse bei jedem Typwechsel und OK/Abbrechen wandern unter dem
Mauszeiger weg. run() gibt den Grab an einen aufrufenden Dialog zurueck --
grab_release() des verschachtelten MacroStepsDialog nimmt ihn sonst mit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:33:08 +02:00
73c1135655 Map Tk key events to HID codes; show real macro sequences
Datenschicht fuer zwei UI-Verbesserungen:

tk_event_to_hid() uebersetzt einen Tk-Tastendruck (keysym, Windows-VK-Code,
state) in HID-Keycode + Modifier-Bits -- Grundlage fuer die
Tastendruck-Erkennung im Bearbeiten-Dialog. Bewusst nur aus Fenster-Events
ableitbar, kein WinAPI-Hook. Bei gehaltenem Shift zaehlt zuerst der VK-Code,
weil der keysym dann das verschobene Zeichen ist (deutsch: Shift+7 ->
"slash", was sonst faelschlich auf Taste 0x38 zeigen wuerde).

action_label()/annotate_profile() nehmen die Makrotabelle optional entgegen
und zeigen dann "Makro 3: Strg+C -> Strg+V" statt "Makro (Slot 3)" -- ohne
das sagt eine Makro-Belegung im Grid nichts darueber aus, was sie tut.
macro_slot_choices()/macro_slot_from_choice() liefern dieselbe Ansicht fuer
eine Slot-Auswahl.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-28 15:32:52 +02:00
517f12c960 Enclosure Case für das Versa Board V.40
Ein Case als Fusion *.f3d Datei inkl. Halter für 3D Connexion Space Mouse. Separate Ständer im gleichen Design mit magnetischer Arretierung.
2026-08-17 19:39:30 +02:00
189ec01e68 Merge branch 'main' into dev/jappel
Reconciles two independent lines of work: main cherry-picked and then
extended dev/jappel's build-script/config-portability/rename-tab/
COM-port fixes, additionally fixing a config-loss bug (rebuild wiped
the config when it lived in the install dir -- moved to roaming
%APPDATA% instead) and adding free-text notes per action plus a
frameless resizable window. dev/jappel keeps its .mcp.json registration
and docs/ reference tree, which main deliberately left out.

Conflict resolutions favored main's versions where the two sides solved
the same problem (config location, build deploy target, COM-port
release, tab rename) since main's fixes were validated against a real
rebuild-wipes-config incident. docs/architecture.md and
docs/data-model.md updated to describe the resulting %APPDATA% config
path and the note field.
2026-08-15 18:16:00 +02:00
cjjohn
9464cafaab Document today's config-path, window-chrome and tooling gotchas
AGENTS.md gains the rules the day's debugging produced: never put the
config in the install dir (the build script wipes it, silently costing
all local-only fields), never branch app_dir() on sys.frozen (two
diverging configs), the consequences that come with a frameless window,
and why the resize grip must be placed rather than packed. Also records
that Claude's Bash and PowerShell tools can see different files under
AppData, which made "the notes are in the file" and "the app shows no
notes" both true at once -- with the diagnostic-module approach that
finally settled it.

README: config now lives in %APPDATA%\VersaPadViewer, plus the frameless
window and notes in the feature list.
2026-08-15 12:19:39 +02:00
cjjohn
91bac353b4 Keep user config out of the install dir; frameless window with working resize
Three related fixes after the notes feature landed:

Config location: build_and_deploy.ps1 wipes its target directory before
every deploy, and app_dir() had just been pointed at that same directory
-- so every rebuild silently deleted the user's config and the app
rebuilt it empty from the board, losing all notes. Config now lives in
%APPDATA%\VersaPadViewer (roaming), separate from the install dir, and
the build script additionally rescues any versapad_config*.json it finds
in the target so legacy installs survive an upgrade.

Window chrome: hide the title bar (plain Tk overrideredirect, no ctypes
window manipulation) and move the mode checkboxes up next to the title,
which reclaims two full rows of header height that the taller note cards
had eaten. Removing the title bar also removes the resize borders, so
add a grip -- anchored with place() to the window corner rather than
packed after the content, which would push it out of view exactly when
the window is too small and the grip is needed. Default geometry grown
to fit the taller cards, and empty encoder note lines are no longer
rendered at all.

Note truncation: the note area was a fixed 34px and cut longer notes
mid-word; it now takes the remaining card height.
2026-08-15 12:12:33 +02:00
cjjohn
4b5da69174 Add free-text notes per button/encoder action
Lets you record what a binding actually does (e.g. "Save in Fusion
360") alongside the auto-generated label ("Strg+S"). Notes live in a
new "note" field on every action dict, purely local like profile
names -- the firmware struct has no room for strings, and
pack_config/unpack_config already only touch type/data so the extra
key round-trips harmlessly.

Two things had to be handled carefully: changing a button's key/type
must not wipe its note (all set_button_*/set_encoder_* setters and the
edit dialog now carry the previous note forward), and re-reading from
the board must not erase notes either, since the firmware doesn't know
about them -- versapad_combined.merge_notes() restores them onto the
freshly-fetched state by button/encoder index.

Editable via the Programmiermodus dialog (new text field), visible on
both the desktop card (grown from 84 to 114px to fit it) and the
browser view. MCP server gets set_button_note()/set_encoder_note() so
notes can be set programmatically too.
2026-08-15 11:15:36 +02:00
cjjohn
9f0ae12794 Keep build_and_deploy.ps1 deploying to %LOCALAPPDATA%, document upstream cherry-picks
Cherry-picked commits from Julian Appel's dev/jappel branch changed the
default deploy target to $projectDir\dist\VersaPadViewer. Revert just
that to %LOCALAPPDATA%\VersaPadViewer, which is where the actually
installed/running instance on this machine lives -- switching would
orphan the existing install and any shortcuts pointing at it. Document
the cherry-pick provenance and what was deliberately left out
(.mcp.json's hardcoded path, the docs/ tree) in AGENTS.md.
2026-08-14 23:55:41 +02:00
02d6c0c5e9 Make build_and_deploy.ps1 self-installing and fail loudly
pip install of pyinstaller/pystray/pillow was a separate manual step the
README only mentioned in prose, so the script silently died on missing
packages -- especially bad on Explorer double-click, where the window
closes before any error is visible. Consolidate all dependencies into
requirements.txt (also used by README's plain "pip install -r" flow), have
the script install it itself, wrap the whole build in try/catch with
exit-code checks after every native call, and pause on both success and
failure unless -NoPause is passed.

Also move the build output from %LOCALAPPDATA% into dist/ next to the
script, so it's easy to find and matches the already-gitignored dist/ entry.

(cherry picked from commit 13c3745479)
2026-08-14 23:51:42 +02:00
9e21360393 Allow renaming profile tabs outside Programmiermodus
Double-click-to-rename already existed but silently no-op'd unless
Programmiermodus was on, and even there it only updated the in-memory
self.combined without saving -- the name was lost unless some later button
edit happened to trigger an autosave. Since profile_names lives purely in
the combined JSON and never touches the board, there's no reason to gate it
behind the heavier editing mode: it now works from any mode, loading/saving
the combined file directly (auto-creating it if needed) when not already in
an active Programmiermodus session, and always persists immediately.

(cherry picked from commit 82c3fd0056)
2026-08-14 23:51:34 +02:00
b4ad7698ed Move config storage next to the install and auto-create it if missing
versapad_combined.DEFAULT_PATH was hardcoded to this one machine's OneDrive
desktop, which made the tool unusable anywhere else. versapad_data.app_dir()
now resolves to the running .exe's own folder when frozen, or the project
directory when run from source, and DEFAULT_PATH hangs off that instead.

load_or_fetch() previously raised when both the file was missing and the
board unreachable, blocking a fresh install with no config and no board
attached. It now falls back to an empty default_combined() in that case, so
the tool is immediately usable either way. desktop_viewer's
_current_profile_view() picks up the same fallback instead of re-implementing
a narrower version of it.

Also drop the hardcoded PROFILE_NAMES dict, which had drifted out of sync
with the profile_names already stored in the combined JSON -- renaming a
profile in Programmiermodus never showed up in the read-only/browser views.
server.py and desktop_viewer.py now read names from the same JSON everywhere.

versapad_data.CONFIG_PATHS (read-only interop with the official C# VersaGUI's
JSON export) is intentionally left on the OneDrive desktop -- nothing in
this codebase writes there, it's not part of this tool's own config.

(cherry picked from commit 5d14bdd826)
2026-08-14 23:50:46 +02:00
83429363c1 Add architecture/data-model/protocol reference docs
Human-facing reference documentation, split from AGENTS.md's agent-facing
domain rules and bug history (which stays there, not duplicated here):

- docs/architecture.md: layer diagram, module responsibilities, config
  storage location, the three GUI modes, and the port-exclusivity /
  multi-process caveats around concurrent access
- docs/data-model.md: the combined and legacy JSON formats, the binary
  SDeviceConfig/SDeviceProfile/SMacroTable NVM layout byte-for-byte, action
  types, LED fields, macro-slot conventions, button grid geometry
- docs/protocol.md: the 8-byte serial packet format, command/event tables,
  the read/write/status-poll flows, connection lifecycle, and error states

README.md now links to all three from a new "Dokumentation" section, and
AGENTS.md's outdated "docs/ tree isn't warranted yet" note is removed now
that it exists on explicit user request.
2026-08-14 23:19:13 +02:00
7d40fdaa60 Close the serial link after each MCP board operation
VersaPadLink never closes itself; get_board_status()/load_from_board()/
write_to_board() were leaving the exclusive COM port open for the rest of
the MCP server process's lifetime after a single call. That locked out
Live-Sync, the official VersaGUI, and even the MCP server's own next call
with "busy", live-observed today after a single write_to_board() call. Each
of the three now closes the link in a finally block regardless of outcome.

Also documented in AGENTS.md: this environment can run several independent
versapad_mcp_server.py processes at once, each with its own in-memory
state, which caused a write_to_board() call to silently write blank data
from a fresh process instead of the config that had just been built up on
another one (ACK still said {"ok": true}). Recommended workaround noted
there: load_local() right before write_to_board(), and read back with
load_from_board() + get_profile() afterwards instead of trusting the ACK.
2026-08-14 23:18:32 +02:00
09fbd6ad96 Register versapad MCP server via project-scoped .mcp.json
Lets Claude Code offer the "versapad" MCP server automatically when this
project is opened, instead of requiring a manual `claude mcp add -s user`
per machine. Note: the script path is currently absolute (this machine's
checkout location) rather than relative -- .mcp.json doesn't reliably
support workspace-relative variables across Claude Code environments, so
this only works as-is on this specific checkout path for now.
2026-08-14 23:18:08 +02:00
82c3fd0056 Allow renaming profile tabs outside Programmiermodus
Double-click-to-rename already existed but silently no-op'd unless
Programmiermodus was on, and even there it only updated the in-memory
self.combined without saving -- the name was lost unless some later button
edit happened to trigger an autosave. Since profile_names lives purely in
the combined JSON and never touches the board, there's no reason to gate it
behind the heavier editing mode: it now works from any mode, loading/saving
the combined file directly (auto-creating it if needed) when not already in
an active Programmiermodus session, and always persists immediately.
2026-08-14 23:17:38 +02:00
5d14bdd826 Move config storage next to the install and auto-create it if missing
versapad_combined.DEFAULT_PATH was hardcoded to this one machine's OneDrive
desktop, which made the tool unusable anywhere else. versapad_data.app_dir()
now resolves to the running .exe's own folder when frozen, or the project
directory when run from source, and DEFAULT_PATH hangs off that instead.

load_or_fetch() previously raised when both the file was missing and the
board unreachable, blocking a fresh install with no config and no board
attached. It now falls back to an empty default_combined() in that case, so
the tool is immediately usable either way. desktop_viewer's
_current_profile_view() picks up the same fallback instead of re-implementing
a narrower version of it.

Also drop the hardcoded PROFILE_NAMES dict, which had drifted out of sync
with the profile_names already stored in the combined JSON -- renaming a
profile in Programmiermodus never showed up in the read-only/browser views.
server.py and desktop_viewer.py now read names from the same JSON everywhere.

versapad_data.CONFIG_PATHS (read-only interop with the official C# VersaGUI's
JSON export) is intentionally left on the OneDrive desktop -- nothing in
this codebase writes there, it's not part of this tool's own config.
2026-08-14 23:17:11 +02:00
13c3745479 Make build_and_deploy.ps1 self-installing and fail loudly
pip install of pyinstaller/pystray/pillow was a separate manual step the
README only mentioned in prose, so the script silently died on missing
packages -- especially bad on Explorer double-click, where the window
closes before any error is visible. Consolidate all dependencies into
requirements.txt (also used by README's plain "pip install -r" flow), have
the script install it itself, wrap the whole build in try/catch with
exit-code checks after every native call, and pause on both success and
failure unless -NoPause is passed.

Also move the build output from %LOCALAPPDATA% into dist/ next to the
script, so it's easy to find and matches the already-gitignored dist/ entry.
2026-08-14 23:15:25 +02:00
cjjohn
8ba524c07b Release COM port after each versapad MCP tool call
get_board_status()/load_from_board()/write_to_board() kept the serial
link open indefinitely after use (VersaPadLink._ensure_open() never
closes on its own). Any session that called one of these left the COM
port locked for VersaGUI/desktop_viewer/server.py's board fallback
until the MCP server process was killed. Close the link in a finally
block after each call instead.
2026-08-09 18:23:33 +02:00
cjjohn
540ce5b1eb Fix crash when desktop config JSON is deleted: self-heal from board
server.py and desktop_viewer.py read-only mode required the desktop
config JSONs to exist and crashed/showed "Config-Datei fehlt" if one
was deleted, even though the board already holds the config durably
in NVM. Add versapad_combined.fetch_from_board()/load_or_fetch(): a
missing combined JSON is now transparently rebuilt from the board via
serial and cached back to disk, falling back to the legacy per-profile
JSONs (or a clear error) only when the board is unreachable.
2026-08-08 17:06:53 +02:00
cjjohn
8a2a73c68d Fix Programmiermodus default-seed bug wiping other profiles
_on_toggle_editing() seeded self.combined from default_combined() on first
activation, which reads the stale per-profile JSONs for all 3 profiles
instead of the current versapad_config_all.json. Writing to board while
only editing one profile silently reverted the other two. Now prefers
loading the current combined file, falling back to defaults only if it
doesn't exist.

Also documents the READ_STATUS polling change and the jappel PR workflow
constraint in AGENTS.md.
2026-08-07 20:50:52 +02:00
cjjohn
4215323f3f Use lightweight READ_STATUS instead of full CONFIG_READ for profile polling
Live-Sync polls read_active_profile() every 1.5s, but it was requesting
a full 740-byte config dump (~124 chunk packets) just to read one
byte out of it. The firmware handles CONFIG_READ synchronously and
blocking, which delayed its LED animation update enough to make
Pulse/Blink visibly stutter on every poll cycle -- see
VersaMCU's doc/07_serial_protocol.md ("READ_STATUS vs. CONFIG_READ
fuer Polling") for the root-cause writeup on the firmware side.

read_active_profile() now sends VersaMCU's new CMD_READ_STATUS (0x06)
and reads back a single EVT_STATUS (0x86) packet instead of driving
the chunked dump protocol. Requires the corresponding firmware update
(VersaMCU commit "Add lightweight READ_STATUS command..."); older
firmware without it just times out gracefully (last_error stays
"timeout", no crash).

desktop_viewer.py's SERIAL_POLL_S/SERIAL_IDLE_S already sit back at
their original 1.5s/3.0s (temporarily raised to 8s as a stopgap before
the firmware fix landed) since the expensive dump is gone now.

Verified end-to-end against a freshly flashed board: correct profile
returned, no more visible LED stutter with Live-Sync on.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-07 15:37:40 +02:00
cjjohn
553ecc54d8 Restructure AGENTS.md to the projekt-doku standard, audit README status
AGENTS.md now follows the mandated section structure (Project Goal,
Aktuell unterstützte Architektur, Kritische Domänenregeln,
Existing-Codebase-Regel, Implementierungsdisziplin, Naming, Deferred
Work, Dokumentation und Verifikation) instead of an organically grown
set of headings. Folds in all prior technical notes (protocol
validation, network-drive build/runtime gotchas, threading rules)
under the appropriate section, and fixes a stale "no tray icon yet"
line that no longer matched the code. README gets an explicit honest
status paragraph (what's tested, what's not: no automated tests, no
prebuilt exe download, hardcoded paths).

No docs/ tree: project has no database or persistent multi-user
service, so the full doc structure isn't warranted per the standard.
2026-08-06 16:05:36 +02:00
cjjohn
d25044be25 Add user-facing README (install, usage, architecture overview)
AGENTS.md is agent-facing session notes, not a proper intro for
someone landing on the repo cold. README covers: what this is,
requirements, how to run from source or build the .exe (and why no
prebuilt .exe is committed -- network-drive build/runtime gotchas),
file overview, MCP server usage, known limitations.
2026-08-06 15:53:05 +02:00
cjjohn
78d412ffa6 Fix: Programmiermodus-Aenderungen erschienen nicht im Nur-Lese-Modus
Ursache: Programmiermodus arbeitet auf der kombinierten Datei
(versapad_config_all.json), der Nur-Lese-Modus las aber immer nur die
alten Einzel-JSONs (versapad_config{1,2,3}.json) -- zwei komplett
getrennte Datenquellen. Jetzt: jede Aenderung im Programmiermodus wird
sofort in die kombinierte Datei geschrieben, und der Nur-Lese-Modus
bevorzugt diese Datei, wenn sie existiert.
2026-08-05 17:54:44 +02:00
cjjohn
b924273400 Add "Immer im Vordergrund" (always-on-top) toggle 2026-08-05 14:34:38 +02:00
cjjohn
3615ff3122 Make MCP info text tool-agnostic; simplify Live-Sync status
Info dialog no longer assumes Claude Code's own CLI setup command --
other users may register the MCP server with a different assistant.
Live-Sync status just says "verbunden" instead of repeating the
profile name (already visible via the active tab).
2026-08-05 08:40:37 +02:00
cjjohn
4ac29a3e5c 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.
2026-08-05 08:30:50 +02:00
cjjohn
e456af7c19 Add MCP server so Claude (or any MCP client) can reprogram VersaPad directly
Wraps the existing protocol/serial/combined-config layers as MCP tools
(set_button_key, set_encoder_consumer, set_macro, load_from_board,
write_to_board, ...) instead of requiring hand-written JSON + manual
GUI import. Registered at user scope via `claude mcp add`.
2026-08-05 08:16:22 +02:00
cjjohn
50658107d5 Add PyInstaller packaging (--onedir) as alternative to pyw launch
Avoids the cmd.exe flash from double-clicking run_desktop.bat and
gives a proper double-click .exe like VersaGUI.exe. Must be deployed
locally (%LOCALAPPDATA%) -- Windows silently refuses to load the
_internal DLLs when the .exe sits on the Z: network share, no error
shown. build_and_deploy.ps1 handles build + local copy in one step.
2026-08-05 08:04:38 +02:00
cjjohn
6227903ebd Initial commit: VersaPad viewer + programming mode 2026-08-04 20:03:49 +02:00
cjjohn
ef0faec100 Initial commit: seed main branch 2026-08-04 19:58:37 +02:00