Files
MARTe-Integrated-Components/docs/superpowers/specs/2026-06-25-streamhub-qt-client-design.md
T
2026-06-26 09:11:10 +02:00

135 lines
8.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# StreamHub Qt Client — Design Spec
**Date:** 2026-06-25
**Sub-project:** D (port `Client/streamhub/` ImGui/SDL2/ImPlot desktop client to Qt5/6)
**Status:** Approved-by-default (user away; standing instruction: "implement the specs, do not wait for my approval").
> Decisions below were made autonomously per the standing instruction. They are
> recorded with rationale so they can be revisited.
## Goal
Produce a native Qt desktop oscilloscope client (`Client/streamhub-qt/`) that is
feature- and UX-equivalent to the existing ImGui client, talking the identical
StreamHub WebSocket protocol (JSON commands/events + binary v1 push / v2 trigger
capture frames), and builds against **either Qt5 or Qt6** (autodetected), for
back-compatibility with older Linux distributions.
The existing ImGui client (`Client/streamhub/`) stays untouched as a reference.
## Framework decisions (with rationale)
1. **Qt Widgets, not QML/Qt Quick.**
The client is a dense desktop tool: multi-panel layouts, drag-and-drop of
signals, context menus, splitters, modal dialogs. Widgets is the natural fit,
renders without a GPU/OpenGL pipeline (important on old hardware), and has the
most stable API surface shared between Qt5 and Qt6.
2. **Custom `QPainter`-based `PlotWidget`, not QCustomPlot/QtCharts.**
The oscilloscope plotting is highly bespoke (fixed ±4-division Y space,
wall-clock live X, LTTB decimation, normal/digital/mixed band normalization,
per-trace V/div + screen position, A/B cursors, box/scroll zoom + pan, zoom
history, drag-drop, hi-res WS zoom overlay, trigger-capture view). Replicating
this on top of a general charting library fights the library as much as it
helps. A focused `QPainter` widget:
- gives exact behavioral parity with the ImPlot version;
- adds **zero external dependencies** (max portability on old Linux);
- avoids GPL/commercial licensing entanglement (repo is EUPL v1.1);
- is straightforward to test in isolation.
3. **`QtWebSockets` (`QWebSocket`), not the hand-rolled POSIX RFC6455 client.**
`QWebSocket` integrates with the Qt event loop, exposes
`textMessageReceived`/`binaryMessageReceived` signals, and removes the need
for a manual receive thread + mutex-guarded queue. Auto-reconnect is a 3 s
`QTimer`. Both Qt5 and Qt6 ship the WebSockets module (verified present).
4. **Qt5/6 autodetect in CMake.**
`find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets WebSockets)` then
`find_package(Qt${QT_VERSION_MAJOR} ...)`; link `Qt${QT_VERSION_MAJOR}::*`.
This is the upstream-recommended idiom; prefers Qt6 when present, falls back
to Qt5. `CMAKE_AUTOMOC ON`.
5. **Reuse, do not fork, the wire layer.**
`Protocol.h`, `Protocol.cpp`, and `SignalBuffer.h` in `Client/streamhub/` are
pure C++17/STL with **no ImGui/SDL dependency** (verified). The Qt project
compiles `../streamhub/Protocol.cpp` directly and adds `../streamhub` to its
include path. Single source of truth for the protocol — any future protocol
change is picked up by both clients. The only ImGui type leaking into the
reference *domain* model is `ImVec4 color`; the Qt project defines its own
domain model (`Model.h`) using `QColor` instead.
## Threading model
Single-threaded around the Qt event loop:
- `QWebSocket` delivers frames as signals on the GUI thread → parsed via the
reused `Protocol` functions → mutate the model directly (no locks needed,
unlike the ImGui version whose WS ran on a background thread).
- A `QTimer` at ~60 Hz calls `update()` on visible `PlotWidget`s for smooth
live scrolling (decoupled from data arrival rate).
This is simpler and safer than the ImGui client's background-thread + drain-queue
model, and is the idiomatic Qt approach.
## Module / file structure (`Client/streamhub-qt/`)
| File | Responsibility |
|------|----------------|
| `CMakeLists.txt` | Qt5/6 autodetect, sources, reuse `../streamhub/Protocol.cpp`, install rules |
| `main.cpp` | `QApplication`, arg parse (`-host`/`-port`), apply dark theme, show `MainWindow` |
| `Theme.h/.cpp` | Catppuccin-Mocha `QPalette` + stylesheet, trace color palette |
| `Model.h` | Domain types: `Signal` (with `QColor`), `Source`, `TriggerCfgState`, `VScale`, `PlotAssignment`, `PlotLayout` enum + dims |
| `Hub.h/.cpp` | `QObject` controller: owns `QWebSocket`, model (`std::vector<Source>`), trigger state, capture, zoom caches, history info. Parses WS messages (reused `Protocol`), exposes command senders, emits Qt signals (`sourcesChanged`, `configChanged`, `statsChanged`, `triggerStateChanged`, `captureReceived`, `zoomReceived`, `historyInfoChanged`, `connectedChanged`). |
| `WsClient.h/.cpp` | Thin `QWebSocket` wrapper: connect/reconnect (`QTimer`), `sendText`, `textReceived`/`binaryReceived`/`connectedChanged` signals |
| `PlotWidget.h/.cpp` | Custom `QPainter` oscilloscope panel (one per grid cell). Owns per-plot view state (live/window, stored X range, zoom history, vMode, active slot, zoom caches, paused snapshot, trig-zoom). Renders, handles mouse zoom/pan + drag-drop, draws cursors, requests hi-res/history zoom through `Hub`. |
| `PlotGrid.h/.cpp` | Grid of `PlotWidget`s using nested `QSplitter`s per `PlotLayout`; rebuilds on layout change; routes drops |
| `SourceSidebar.h/.cpp` | `QTreeWidget` of sources→signals (drag source), connect/remove, "Add source" dialog launcher |
| `TriggerBar.h/.cpp` | Trigger config widgets (signal combo, edge, threshold, window combo, pre% slider, normal/single) + Arm/Disarm/Rearm/Stop + state badge |
| `StatsDialog.h/.cpp` | Per-source metrics table + 20-bin histogram (custom `QPainter` bar widget) |
| `HistoryBar.h/.cpp` | Live/range/pan/jump-preset/All history navigation |
| `MainWindow.h/.cpp` | `QMainWindow`: toolbar (sidebar toggle, layout picker, pause, cursors, trigger, history, window presets, stats, connection, status LED), assembles sidebar + grid + bars, owns `Hub`, the 60 Hz repaint `QTimer`, and the add-source dialog |
## Feature parity checklist (must match the ImGui client)
- Live wall-clock X window with presets {1,5,10,30,60 s} + Ctrl+scroll resize.
- Plot layouts {1×1, 2×1, 1×2, 3×1, 1×3, 2×2, 4×1, 1×4} via splitters.
- Drag a signal from the sidebar onto a plot; per-trace color/width/marker via
context menu; remove-from-plot; "active" trace selection.
- V-scale per trace: Auto / Range / Manual (V/div, offset, screen pos); Y ticks
labelled from the active trace; ±4-division fixed Y space.
- Per-plot V-mode: Normal / Digital / Mixed band normalization (ports
`applyDigitalNorm`/`applyMixedNorm` math already in `PlotPanel.cpp`).
- Mouse: scroll = Y zoom of active trace (or X zoom if none); Ctrl+scroll = X
zoom / window resize; Shift+scroll = Y pan; right-drag = X pan (live→non-live).
- Zoom history Back / Fit / Live per plot.
- Global pause (freeze view snapshot; rings keep filling); global A/B cursors
with ΔT, 1/ΔT, per-trace A/B/Δ readouts.
- Hi-res zoom over WS (`zoom` cmd, 2400 pts) for live-narrow and zoomed views;
history zoom (`historyZoom`) for disk-backed ranges; "HIST" indicator.
- Trigger: full config → `setTrigger`; Arm/Disarm/Rearm/Stop; state badge
(idle/armed/collecting/triggered); v2 capture frame → capture view rendered in
`[-pre,+post]` with trigger marker at t=0; capture-view zoom + Reset.
- Stats dialog: state, totalReceived, totalLost, rateHz±std, frags/bytes per
cycle, cycle avg/std/min/max ms, 20-bin cycle-time histogram.
- Add/remove source; connection host/port edit + reconnect; status LED.
- LTTB decimation to ≤2400 pts/trace before drawing (reuse `LTTBDecimate`).
## Build & run
```bash
cd Client/streamhub-qt
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j
./build/StreamHubQtClient -host 127.0.0.1 -port 8090
```
Verification: run `./run_streamhub.sh` (or the recorder E2E streamer) to get a
live hub, then confirm live streaming, zoom (live + box), trigger capture, stats
histogram, and history browse all behave like the ImGui client.
## Out of scope
- No new protocol features; byte-for-byte the same WS protocol.
- Sub-project E (Go `Client/debugger/` → Qt) is a separate spec.
- Font Awesome glyph icons are replaced by Qt standard icons / unicode; exact
icon glyphs are not required for parity.