Files
udpscope/src/layout_store.hpp
T

102 lines
4.4 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "math_channel.hpp"
#include "measure.hpp"
namespace udpscope {
/// The user-arranged part of a session, persisted per data source in
/// `~/.config/udpscope/<host>_<port>.toml`: the display window, the plot layout
/// (rows and which signal each of a row's slots holds) and the per-signal
/// calibration. Signal names are the stable identity (channel indexes are
/// renumbered by every enable/disable, see the model's own invariant).
struct LayoutState {
struct Row {
/// One entry per slot; an empty string is an empty slot.
std::vector<std::string> signals;
};
double window_s = 0.0; ///< 0 keeps the default window
std::vector<Row> rows;
struct Calibration {
std::string name;
double gain = 1.0;
double offset = 0.0;
};
std::vector<Calibration> calibration;
/// Signals the session had enabled. Enabling is what starts acquisition, so this is what makes a
/// reopened scope look like the one that was closed -- and it is saved by *name*, like every other
/// identity here, because a CONFIG renumbers the channels.
std::vector<std::string> enabled;
/// How this source was reached, so the startup chooser can reconnect to it exactly. The file is
/// named `<host>_<port>.toml` and that name can say neither of these. Empty `multicast` is
/// unicast; empty `interface_addr` is the default route.
std::string multicast;
std::string interface_addr;
/// The derived channels, graphs and all: the editor's work is part of the session, not of the
/// process. A graph is stored as one string per node so the file stays in this store's TOML
/// subset (tables, string arrays, scalars) rather than growing a nested-table parser for it; see
/// `math_node_text()` for the field order.
std::vector<MathChannel> math;
/// The measurements pane, by signal *name* and with the type and options a row was built with.
std::vector<Measure> measures;
};
/// A data source udpscope has a stored session for: one row of the startup chooser.
struct KnownSource {
std::string host; ///< the CONFIG server, even for a multicast session
std::uint16_t port = 0;
std::string multicast; ///< empty = unicast
std::string interface_addr; ///< empty = the default route
int rows = 0; ///< plots the session had
int signals = 0; ///< named slots across those plots
std::int64_t mtime = 0; ///< orders the list; never shown
};
/// Every session file that names at least one signal, newest first.
///
/// The filter is what makes the list usable: a development machine accumulates one session file
/// per probe run on a throwaway port -- 140 of them when this was written -- and every one holds
/// `signals = ["", "", "", ""]`. A listed source is one somebody actually configured.
std::vector<KnownSource> known_sources();
/// Deletes one source's session file; the chooser's `x`. False means the file was already gone or
/// could not be removed, which costs the caller nothing -- there is no state to roll back.
bool forget_source(const KnownSource& src);
/// One `MathNode` as `kind|source|in_a|in_b|gain|offset|fc|q|lo|hi|x|y`, and back. The kind is the
/// op's *index* into `math_op_at()`, which is why the enum's order is a stored format and not just
/// an implementation detail: reordering it renames every saved graph's operations.
std::string math_node_text(const MathNode& node);
bool math_node_from_text(const std::string& text, MathNode& out);
/// The directory session files live in: $XDG_CONFIG_HOME/udpscope, else
/// ~/.config/udpscope, else /tmp/udpscope-config. Not created by this call.
std::string config_dir();
/// `~/.config/udpscope/<host>_<port>.toml`, with the directory created.
/// Characters unsafe in a filename are replaced in the host part.
std::string layout_path(const std::string& host, std::uint16_t port);
/// Parses the file's TOML subset. Returns false when the file is missing or
/// malformed; `out` is left usable either way (a malformed file loads as much as
/// parsed, which beats refusing a hand-edited file over one stray character).
bool load_layout(const std::string& path, LayoutState& out);
/// Writes `state` as TOML. Atomic in effect: writes a sibling temp file and
/// renames it over the target.
bool save_layout(const std::string& path, const LayoutState& state);
} // namespace udpscope