// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #pragma once #include #include #include "source/source_config.hpp" namespace udpscope { /// Everything the command line can say. Defaults are the ones the spec names /// (10 M samples per signal, 10 ms trigger hold-off) unless the user overrides them. struct Config { std::string server = "127.0.0.1"; ///< IPv4 dotted quad of the stream source. std::uint16_t port = 8000; ///< UDP port (unicast) or TCP port (multicast). std::string multicast; ///< Empty selects unicast. std::string interface_addr; ///< Local IPv4 to join the group on; empty = default route. /// Whether `--server` was given. Absent, the application asks which source to open rather /// than silently taking the default above -- so this is "did the user choose", not "is /// `server` valid". The default stays a usable value for every other path. bool server_specified = false; /// `--source NAME`: which registered kind opens. `udps` is the default, and `--server/--port/ /// --multicast/--interface` keep meaning UDPS. std::string source_kind = "udps"; /// Whether `--source` was given (the other half of "did the user choose a source"). bool source_specified = false; /// Repeatable `--source-opt k=v`: the kind's own settings. For `udps` they overlay the fields above. SourceConfig source_opts; /// A source was named on the command line (`--server` or `--source`), so the chooser is skipped. bool has_source() const noexcept { return server_specified || source_specified; } /// The settings the kind's `make()` receives: for UDPS the four connection fields plus the receive /// buffer, then `source_opts` on top. SourceConfig source_config() const; /// The (host, port) that name this source's session file; see `session_host()` in the registry. std::string session_host() const; std::uint16_t session_port() const; /// The sample budget: how many samples to keep for **every acquired signal**, and for the shared /// time ring. The rings are powers of two, so the request is rounded *down* to one (`buffer_ticks()` /// in model.hpp) -- "up to" this many -- and the UI shows both figures. 10 M asks for 8 388 608. std::uint64_t buffer_samples = 10'000'000; /// Whether `--buffer-samples` was given. A stored configuration's sample budget fills in only when /// the flag was *absent*: an explicit flag beats a saved setting, and "not asked" is not the same /// question as "asked for 10 M". bool buffer_samples_specified = false; /// The hard ceiling on the ring storage, in MiB, for the whole application rather than per source. /// `plan_partition()` halves the rings until they fit under it, so it can clip `buffer_samples`. /// Default 8 GiB; the preferences carry it between runs and `--memory-limit-mb` overrides it. std::uint64_t memory_limit_mb = 8192; /// Whether `--memory-limit-mb` was given, so the stored preference only fills the gap. bool memory_limit_specified = false; /// Largest time window the toolbar offers, in seconds. The buffer partition is /// sized to hold at least this much time (spec: decimation). double max_window_s = 600.0; double trigger_holdoff_s = 0.010; int recv_buffer_bytes = 8 * 1024 * 1024; /// Number of stacked plots (rows) to start with, 1..`RowSet::kMaxRows`. Only the /// starting point: rows are added and removed from the layout editor. int plots = 1; /// Take the plot's time from the packet counter and the declared sample rate rather than /// from the stream's own time signal, so arrival and producer-clock jitter cannot move a /// sample's x. Absolute stream time is still recorded, for export only (`todo.md` item 2). bool relative_time = false; /// Whether relative time was *stated* rather than left at its default -- by `--relative-time`, or /// by the preferences, which the source chooser can reach before this run is planned. A source's /// stored configuration is a statement from an *older* session, so it only fills in the gap. bool relative_time_specified = false; /// `--capture-packets N`: the ring size of the raw-datagram capture, 1..100000. /// 0 means the flag was absent: the capture is off, and the popover button then /// uses the 1000-slot default when the user toggles it. std::uint32_t capture_packets = 0; /// `--open FILE`: a `.npy` an earlier run exported, opened for viewing and analysis instead of a /// live source. File mode and source mode are exclusive -- there is no acquisition behind a file, /// and the chooser's Waveforms page sets this rather than the connection fields. std::string open_waveform; /// `--renderer NAME`: the GSK renderer to force, by exporting `GSK_RENDERER` **before any GTK /// call**. Empty leaves GTK's own choice alone. `cairo` is the software preset over SSH/X11 /// forwarding: it also exports `LIBGL_ALWAYS_SOFTWARE=1`, because the plot's `GtkGLArea` asks /// for a GL context whatever GSK draws with. `gl` is the faster alternative when indirect GL /// works. The variables are read when the renderer/context is created, so setting them anywhere /// after `gtk_init()`/`adw_init()` is a no-op -- which is why this is a `Config` field applied /// at the top of `main()` and not a preference. std::string renderer; /// The source chooser's "Guided tour" entry. The tour is drawn on the application's own window, /// which does not exist while the chooser runs, so the request rides on the one value `main()` /// owns and hands to both the chooser and the front end (`open_waveform` and the preferences' /// relative time cross the same way) and the window starts the tour once it is up and laid out. /// It is *not* a preference: it describes this request and nothing else, so nothing persists it. bool tour_pending = false; }; /// Parses argv. On `--help` prints usage to stdout and exits(0); on a bad /// argument prints a diagnostic to stderr and exits(2). Config parse_args(int argc, char** argv); /// Writes the usage text to `out`. void print_usage(std::FILE* out, const char* argv0); } // namespace udpscope