udpscope: an oscilloscope view for a UDPStreamer source
GTK4 and libadwaita. It receives one UDP stream through the vendored udpsclient, buffers it in a large ring, and draws it with the vendored plotter: a trigger with a pre/post event window, a pause that freezes the window, cursors, measurements, math channels in a node editor, an event cache, named configurations, preferences, and an export of the visible window to .npy/.csv. Waveform files can be opened and analysed in place. The interface is two threads -- a producer that owns the socket and the GTK main thread that owns every widget and the GL context -- sharing one model under one mutex, with samples crossing through lock-free rings. deps/plotter is a submodule pinned to the commit this tree builds against; deps/udpsclient is vendored in place. docs/claude/ documents the rules that cost a defect to learn, and SPECS.md is the requirement document.
This commit is contained in:
1 parent
8f54841c2c
commit
0e256764d5
41 files changed
+2081
-154
No files matched your search
+8
-11
@@ -17,17 +17,12 @@
|
||||
namespace udpscope {
|
||||
namespace {
|
||||
|
||||
/// One directory under the cache holds *every* source's events, and each source gets a folder inside
|
||||
/// it: `<cache>/udpscope/events/<source>/`. The other way round -- a folder per source with an
|
||||
/// `events` inside -- spreads one feature over as many directories as there are sources, and turns
|
||||
/// "where are my events" into a question about which host you last used.
|
||||
constexpr const char* kEventsSubdir = "/events";
|
||||
|
||||
void mkdirs(const std::string& path) {
|
||||
std::string cur;
|
||||
for (std::size_t i = 0; i < path.size(); ++i) {
|
||||
cur.push_back(path[i]);
|
||||
if (path[i] != '/' && i + 1 != path.size()) continue;
|
||||
::mkdir(cur.c_str(), 0755);
|
||||
}
|
||||
}
|
||||
|
||||
/// `2026-10-09T11:32:00` in local time, plus the sequence number: two events inside one second are
|
||||
/// ordinary (a 5 Hz trigger latches five times a second), and a stem that collides would overwrite
|
||||
/// the event it collided with.
|
||||
@@ -236,7 +231,9 @@ bool event_meta_parse(const std::string& text, EventRecord* out) {
|
||||
return any;
|
||||
}
|
||||
|
||||
EventStore::EventStore(std::string source) {
|
||||
std::string default_events_dir() { return std::string(cache_dir()) + kEventsSubdir; }
|
||||
|
||||
EventStore::EventStore(std::string source, std::string base_dir) {
|
||||
if (source.empty()) return;
|
||||
std::string safe;
|
||||
for (char c : source) {
|
||||
@@ -244,7 +241,7 @@ EventStore::EventStore(std::string source) {
|
||||
c == '.' || c == '_' || c == '-';
|
||||
safe.push_back(ok ? c : '_');
|
||||
}
|
||||
dir_ = cache_dir() + "/" + safe + kEventsSubdir;
|
||||
dir_ = (base_dir.empty() ? default_events_dir() : base_dir) + "/" + safe;
|
||||
}
|
||||
|
||||
std::string EventStore::save(const ExportTable& table, const EventRecord& meta, std::size_t keep) {
|
||||
|
||||
Reference in new issue
Block a user