// SPDX-License-Identifier: MIT // SPDX-FileCopyrightText: 2026 Martino Ferrari #include "settings.hpp" #include #include #include #include #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: `` 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