// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #pragma once #include #include #include #include "math_channel.hpp" #include "measure.hpp" namespace udpscope { /// The user-arranged part of a session, persisted per data source in /// `~/.config/udpscope/_.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 signals; }; double window_s = 0.0; ///< 0 keeps the default window std::vector rows; struct Calibration { std::string name; double gain = 1.0; double offset = 0.0; }; std::vector 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 enabled; /// How this source was reached, so the startup chooser can reconnect to it exactly. The file is /// named `_.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 math; /// The measurements pane, by signal *name* and with the type and options a row was built with. std::vector 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 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/_.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