feat(config): persist backend selection and idle microphone policy

This commit is contained in:
baketnk committed 2026-09-24 22:52:51 -04:00
1 parent 9832ec1fd4
commit 665d003782
4 files changed
+57

No files matched your search

+2
View File
@@ -3,6 +3,8 @@
"input_priority": "normal",
"advanced_debug": false,
"auto_insert": false,
"close_mic_when_idle": false,
"backend": "redux",
"lock_layout": false,
"clock_24h": false,
"date_format": "mdy",
+20
View File
@@ -196,12 +196,22 @@ Config load_config(const std::filesystem::path& path) {
if (key == "font") {
if (!value.is_string) throw std::runtime_error("Config font must be a path string");
config.font = value.value;
} else if (key == "backend") {
if (!value.is_string || value.value.empty() || value.value.size() > 48 ||
value.value[0] < 'a' || value.value[0] > 'z' ||
!std::all_of(value.value.begin(), value.value.end(), [](unsigned char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
})) throw std::runtime_error("Config backend must be a manifest ID");
config.backend = value.value;
} else if (key == "advanced_debug") {
if (!value.is_bool) throw std::runtime_error("Config advanced_debug must be a boolean");
config.advanced_debug = value.value == "true";
} else if (key == "auto_insert") {
if (!value.is_bool) throw std::runtime_error("Config auto_insert must be a boolean");
config.auto_insert = value.value == "true";
} else if (key == "close_mic_when_idle") {
if (!value.is_bool) throw std::runtime_error("Config close_mic_when_idle must be a boolean");
config.close_mic_when_idle = value.value == "true";
} else if (key == "lock_layout") {
if (!value.is_bool) throw std::runtime_error("Config lock_layout must be a boolean");
config.lock_layout = value.value == "true";
@@ -323,6 +333,9 @@ bool save_advanced_debug(const std::filesystem::path& path, bool enabled) noexce
bool save_auto_insert(const std::filesystem::path& path, bool enabled) noexcept {
return save_bool_option(path, "auto_insert", enabled);
}
bool save_close_mic_when_idle(const std::filesystem::path& path, bool enabled) noexcept {
return save_bool_option(path, "close_mic_when_idle", enabled);
}
bool save_lock_layout(const std::filesystem::path& path, bool enabled) noexcept {
return save_bool_option(path, "lock_layout", enabled);
}
@@ -338,6 +351,13 @@ bool save_date_format(const std::filesystem::path& path, DateFormat format) noex
}
return false;
}
bool save_backend(const std::filesystem::path& path, const std::string& id) noexcept {
if (id.empty() || id.size() > 48 || id[0] < 'a' || id[0] > 'z' ||
!std::all_of(id.begin(), id.end(), [](unsigned char c) {
return (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '_' || c == '-';
})) return false;
return save_option(path, "backend", id, true);
}
std::string resolve_font(const std::string& assets, const std::string& requested) {
const auto bundled = std::filesystem::path(assets) / "fonts/Inconsolata-Regular.ttf";
const std::array<std::filesystem::path, 4> candidates{requested, bundled,
+4
View File
@@ -18,10 +18,12 @@ enum class DateFormat { Off, MonthDayYear, DayMonthYear, Iso };
struct Config {
Theme theme;
std::string font; // absolute TTF/OTF path; empty uses the bundled face
std::string backend = "redux"; // selected manifest ID; never triggers a download
// Requests OpenVR's experimental global action priority; SteamVR must allow it too.
bool experimental_input_priority = false;
bool advanced_debug = false; // opt-in full diagnostic logging; never raw audio recording
bool auto_insert = false; // opt-in; runtime also requires uninterrupted verified Xwayland focus
bool close_mic_when_idle = false; // default keeps the device open, discarding idle audio
bool lock_layout = false; // hide the grab and scale handles when enabled
bool clock_24h = false;
DateFormat date_format = DateFormat::MonthDayYear;
@@ -36,9 +38,11 @@ Config load_config(const std::filesystem::path& path);
// Other user customizations and formatting are retained; creates a minimal config if absent.
bool save_advanced_debug(const std::filesystem::path& path, bool enabled) noexcept;
bool save_auto_insert(const std::filesystem::path& path, bool enabled) noexcept;
bool save_close_mic_when_idle(const std::filesystem::path& path, bool enabled) noexcept;
bool save_lock_layout(const std::filesystem::path& path, bool enabled) noexcept;
bool save_clock_24h(const std::filesystem::path& path, bool enabled) noexcept;
bool save_date_format(const std::filesystem::path& path, DateFormat format) noexcept;
bool save_backend(const std::filesystem::path& path, const std::string& id) noexcept;
std::string resolve_font(const std::string& assets, const std::string& requested);
// No writes or OpenVR access when no custom button mappings are specified.
// When customized, build a generated manifest and bindings under XDG cache.
+31
View File
@@ -40,9 +40,11 @@ int main(int argc, char** argv) {
::setenv("XDG_CONFIG_HOME", dir.c_str(), 1);
assert(load_config(path).buttons.empty());
assert((load_config(path).quick_inputs == std::vector<std::string>{"/new", "/questions", "/help"}));
assert(load_config(path).backend == "redux");
assert(!load_config(path).experimental_input_priority);
assert(!load_config(path).advanced_debug);
assert(!load_config(path).auto_insert);
assert(!load_config(path).close_mic_when_idle);
assert(!load_config(path).lock_layout);
assert(!load_config(path).clock_24h);
assert(load_config(path).date_format == DateFormat::MonthDayYear);
@@ -56,10 +58,14 @@ int main(int argc, char** argv) {
assert(!example.experimental_input_priority);
assert(!example.advanced_debug);
assert(!example.auto_insert);
assert(!example.close_mic_when_idle);
assert(!example.lock_layout);
assert(!example.clock_24h && example.date_format == DateFormat::MonthDayYear);
assert(example.wrist.y == .18f && example.wrist.z == .089f);
std::filesystem::create_directories(path.parent_path());
assert(save_backend(path, "fixture_cpu"));
assert(load_config(path).backend == "fixture_cpu");
assert(!save_backend(path, "../escaped") && !save_backend(path, "UPPER"));
assert(save_advanced_debug(path, true));
assert(load_config(path).advanced_debug);
assert(get(path).find("\"advanced_debug\":true") != std::string::npos);
@@ -69,6 +75,10 @@ int main(int argc, char** argv) {
assert(load_config(path).auto_insert);
assert(save_auto_insert(path, false));
assert(!load_config(path).auto_insert);
assert(save_close_mic_when_idle(path, true));
assert(load_config(path).close_mic_when_idle);
assert(save_close_mic_when_idle(path, false));
assert(!load_config(path).close_mic_when_idle);
assert(save_lock_layout(path, true));
assert(load_config(path).lock_layout);
assert(save_lock_layout(path, false));
@@ -95,6 +105,11 @@ int main(int argc, char** argv) {
assert(!load_config(path).advanced_debug);
auto before = get(path);
assert(save_advanced_debug(path, false) && get(path) == before);
for (const auto* invalid : {R"({"backend":"../escape"})", R"({"backend":""})",
R"({"backend":2})"}) {
put(path, invalid); fails([&] { load_config(path); });
assert(!save_backend(path, "redux"));
}
put(path, R"({"advanced_debug":false, "font":"kept"})");
assert(save_advanced_debug(path, true));
assert(get(path) == R"({"advanced_debug":true, "font":"kept"})");
@@ -107,6 +122,7 @@ int main(int argc, char** argv) {
std::filesystem::create_symlink(path, link);
assert(!save_advanced_debug(link, false));
assert(!save_auto_insert(link, true));
assert(!save_close_mic_when_idle(link, true));
assert(!save_lock_layout(link, true));
assert(get(path) == before);
std::filesystem::remove(link);
@@ -128,6 +144,21 @@ int main(int argc, char** argv) {
put(path, R"({"date_format":"off","font":"kept"})");
assert(save_date_format(path, DateFormat::Iso));
assert(get(path) == R"({"date_format":"iso","font":"kept"})");
for (const auto* invalid : {R"({"close_mic_when_idle":"true"})", R"({"close_mic_when_idle":1})",
R"({"close_mic_when_idle":null})", R"({"close_mic_when_idle":[]})"}) {
put(path, invalid);
fails([&] { load_config(path); });
assert(!save_close_mic_when_idle(path, true));
assert(get(path) == invalid);
}
put(path, R"({"close_mic_when_idle":false,"font":"kept"})");
assert(save_close_mic_when_idle(path, true));
assert(load_config(path).close_mic_when_idle);
assert(get(path) == R"({"close_mic_when_idle":true,"font":"kept"})");
before = get(path);
assert(save_close_mic_when_idle(path, true) && get(path) == before);
assert(save_close_mic_when_idle(path, false));
assert(get(path) == R"({"close_mic_when_idle":false,"font":"kept"})");
put(path, R"({"auto_insert":"on"})");
fails([&] { load_config(path); });
assert(!save_auto_insert(path, true));