Implemented qt port + e2e
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user