Files
udpscope/src/settings.cpp
T
Martino Ferrari 0e256764d5 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.
2026-10-11 15:21:40 +02:00

88 lines
3.0 KiB
C++

// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 Martino Ferrari
#include "settings.hpp"
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <glib.h>
#include "layout_store.hpp" // `mkdirs()`, so a first run's config directory is created with parents
namespace udpscope {
namespace {
/// One `key = value` per line, `#` for a comment: the same shape the layout files use, and parsed just
/// as leniently -- an unknown key, an empty line or a file written by an older version is not an error.
bool parse_bool(const std::string& v, bool fallback) {
if (v == "true" || v == "1" || v == "yes") return true;
if (v == "false" || v == "0" || v == "no") return false;
return fallback;
}
std::string unquote(const std::string& v) {
if (v.size() >= 2 && v.front() == '"' && v.back() == '"') return v.substr(1, v.size() - 2);
return v;
}
} // namespace
Settings default_settings() { return Settings{}; }
std::string settings_path() {
const std::string dir = std::string(g_get_user_config_dir()) + "/udpscope";
mkdirs(dir); // every component: `<config>` itself may not exist on a fresh install
return dir + "/settings.toml";
}
Settings load_settings() {
Settings s; // the defaults, so the file only has to mention what a user changed
std::ifstream in(settings_path());
if (!in) return s;
std::string line;
while (std::getline(in, line)) {
const std::size_t hash = line.find('#');
if (hash != std::string::npos) line = line.substr(0, hash);
const std::size_t eq = line.find('=');
if (eq == std::string::npos) continue;
std::string key = line.substr(0, eq);
std::string value = line.substr(eq + 1);
const auto trim = [](std::string& t) {
const std::size_t a = t.find_first_not_of(" \t\r\n");
const std::size_t b = t.find_last_not_of(" \t\r\n");
t = a == std::string::npos ? std::string() : t.substr(a, b - a + 1);
};
trim(key);
trim(value);
value = unquote(value);
if (value.empty()) continue;
if (key == "event_cache_dir") {
s.event_cache_dir = value;
} else if (key == "event_cache_max") {
s.event_cache_max = std::max(0, std::atoi(value.c_str()));
} else if (key == "relative_time") {
s.relative_time = parse_bool(value, s.relative_time);
} else if (key == "theme") {
if (value == "auto" || value == "light" || value == "dark") s.theme = value;
}
}
return s;
}
bool save_settings(const Settings& settings) {
std::ofstream out(settings_path(), std::ios::trunc);
if (!out) return false;
out << "# udpscope preferences -- written by the preferences dialog, read at start-up.\n";
out << "# A key that is absent means its default, so this file only has to mention what changed.\n";
out << "event_cache_dir = \"" << settings.event_cache_dir << "\"\n";
out << "event_cache_max = " << settings.event_cache_max << "\n";
out << "relative_time = " << (settings.relative_time ? "true" : "false") << "\n";
out << "theme = \"" << settings.theme << "\"\n";
out.flush();
return out.good();
}
} // namespace udpscope