Skip to content

Commit 5782d31

Browse files
committed
refactor(config): derive section lists from one registry
1 parent 106620c commit 5782d31

9 files changed

Lines changed: 302 additions & 349 deletions

meson.build

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,7 @@ _noctalia_sources = files(
420420
'src/config/config_validate.cpp',
421421
'src/config/widget_config.cpp',
422422
'src/config/schema/config_schema.cpp',
423+
'src/config/schema/config_sections.cpp',
423424
'src/config/state_store.cpp',
424425
'src/config/cli.cpp',
425426
'src/core/build_info.cpp',
@@ -1370,6 +1371,7 @@ config_schema_roundtrip_test = executable('config_schema_roundtrip_test',
13701371
sources: files(
13711372
'tests/config_schema_roundtrip_test.cpp',
13721373
'src/config/schema/config_schema.cpp',
1374+
'src/config/schema/config_sections.cpp',
13731375
'src/config/config_export.cpp',
13741376
'src/config/config_types.cpp',
13751377
'src/notification/notification_filter.cpp',
@@ -1429,6 +1431,7 @@ config_path_resolution_test = executable('config_path_resolution_test',
14291431
sources: files(
14301432
'tests/config_path_resolution_test.cpp',
14311433
'src/config/schema/config_schema.cpp',
1434+
'src/config/schema/config_sections.cpp',
14321435
'src/config/config_types.cpp',
14331436
'src/notification/notification_filter.cpp',
14341437
'src/render/core/color.cpp',

src/config/config_export.cpp

Lines changed: 6 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
#include "config/config_export.h"
22

33
#include "config/schema/config_schema.h"
4+
#include "config/schema/config_sections.h"
45
#include "config/schema/engine.h"
56

67
#include <algorithm>
@@ -282,41 +283,19 @@ namespace config_export {
282283
toml::table serialize(const Config& config) {
283284
toml::table root;
284285

285-
root.insert_or_assign("shell", schema::writeTable(config.shell, schema::shellSchema()));
286-
root.insert_or_assign("wallpaper", schema::writeTable(config.wallpaper, schema::wallpaperSchema()));
287-
root.insert_or_assign("theme", schema::writeTable(config.theme, schema::themeSchema()));
288-
root.insert_or_assign("accessibility", schema::writeTable(config.accessibility, schema::accessibilitySchema()));
289-
290-
root.insert_or_assign("backdrop", schema::writeTable(config.backdrop, schema::backdropSchema()));
286+
for (const schema::SectionSpec& spec : schema::sections()) {
287+
root.insert_or_assign(spec.name, spec.write(config));
288+
}
291289

292-
root.insert_or_assign("lockscreen", schema::writeTable(config.lockscreen, schema::lockscreenSchema()));
290+
// Root keys whose shape is not a plain section schema.
293291
root.insert_or_assign(
294292
"lockscreen_widgets",
295293
widgetsPlacementTable(
296294
config.lockscreenWidgets.enabled, config.lockscreenWidgets.schemaVersion, config.lockscreenWidgets.grid,
297295
config.lockscreenWidgets.widgets
298296
)
299297
);
300-
301-
root.insert_or_assign("notification", schema::writeTable(config.notification, schema::notificationSchema()));
302-
303-
root.insert_or_assign("osd", schema::writeTable(config.osd, schema::osdSchema()));
304-
305-
root.insert_or_assign("system", schema::writeTable(config.system, schema::systemSchema()));
306-
307-
root.insert_or_assign("weather", schema::writeTable(config.weather, schema::weatherSchema()));
308-
root.insert_or_assign("calendar", schema::writeTable(config.calendar, schema::calendarSchema()));
309-
root.insert_or_assign("audio", schema::writeTable(config.audio, schema::audioSchema()));
310-
311-
root.insert_or_assign("brightness", schema::writeTable(config.brightness, schema::brightnessSchema()));
312-
root.insert_or_assign("battery", schema::writeTable(config.battery, schema::batterySchema()));
313-
314-
root.insert_or_assign("nightlight", schema::writeTable(config.nightlight, schema::nightlightSchema()));
315-
root.insert_or_assign("location", schema::writeTable(config.location, schema::locationSchema()));
316-
317-
root.insert_or_assign("idle", schema::writeTable(config.idle, schema::idleSchema()));
318-
319-
root.insert_or_assign("keybinds", schema::writeTable(config.keybinds, schema::keybindsSchema()));
298+
root.insert_or_assign("desktop_widgets", desktopWidgetsTable(config.desktopWidgets));
320299

321300
toml::table barRoot;
322301
toml::array barOrder;
@@ -330,9 +309,6 @@ namespace config_export {
330309
barRoot.insert_or_assign("order", std::move(barOrder));
331310
root.insert_or_assign("bar", std::move(barRoot));
332311

333-
root.insert_or_assign("dock", schema::writeTable(config.dock, schema::dockSchema()));
334-
root.insert_or_assign("desktop_widgets", desktopWidgetsTable(config.desktopWidgets));
335-
336312
toml::table widgetRoot;
337313
std::vector<std::string> widgetNames;
338314
widgetNames.reserve(config.widgets.size());
@@ -346,11 +322,6 @@ namespace config_export {
346322
}
347323
root.insert_or_assign("widget", std::move(widgetRoot));
348324

349-
root.insert_or_assign("control_center", schema::writeTable(config.controlCenter, schema::controlCenterSchema()));
350-
351-
root.insert_or_assign("plugins", schema::writeTable(config.plugins, schema::pluginsSchema()));
352-
353-
root.insert_or_assign("hooks", schema::writeTable(config.hooks, schema::hooksSchema()));
354325
return root;
355326
}
356327

src/config/config_service.cpp

Lines changed: 34 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "config/config_migrations.h"
88
#include "config/config_validate.h"
99
#include "config/schema/config_schema.h"
10+
#include "config/schema/config_sections.h"
1011
#include "config/schema/engine.h"
1112
#include "config/widget_config.h"
1213
#include "core/build_info.h"
@@ -667,6 +668,7 @@ void ConfigService::fireReloadCallbacks() {
667668
add(m_lastChange.theme, "theme");
668669
add(m_lastChange.controlCenter, "controlCenter");
669670
add(m_lastChange.plugins, "plugins");
671+
add(m_lastChange.accessibility, "accessibility");
670672
kLog.info("reload: changed sections = [{}]", changed.empty() ? "none" : changed);
671673
}
672674

@@ -1645,53 +1647,44 @@ void ConfigService::parseConfigTable(
16451647
}
16461648
}
16471649

1648-
// Parse [shell]
1649-
bool sessionActionsConfigured = false;
1650-
if (auto* shellTbl = tbl["shell"].as_table()) {
1651-
// Schema reads can't tell whether an empty actions list was explicit.
1652-
sessionActionsConfigured = [&] {
1653-
const auto* sessionTbl = (*shellTbl)["session"].as_table();
1654-
return sessionTbl != nullptr && (*sessionTbl)["actions"].as_array() != nullptr;
1655-
}();
1656-
readConfigSection(*shellTbl, config.shell, schema::shellSchema(), "shell", schemaDiag);
1650+
// Every schema-backed section is read through the section registry, so the loader
1651+
// cannot recognize a section that the validator and exporter do not.
1652+
for (const schema::SectionSpec& spec : schema::sections()) {
1653+
const auto* sectionTbl = tbl[spec.name].as_table();
1654+
if (sectionTbl == nullptr) {
1655+
continue;
1656+
}
1657+
try {
1658+
spec.read(*sectionTbl, config, schemaDiag);
1659+
} catch (const std::exception& e) {
1660+
schemaDiag.error(std::string(spec.name), e.what());
1661+
kLog.warn("{}: {}", spec.name, e.what());
1662+
}
16571663
}
1664+
1665+
// Default seeding must apply even when the section is absent, so it runs after the
1666+
// registry pass. A schema read can't tell an explicitly empty list from a missing
1667+
// one, so these probe the raw table for the list key.
1668+
const auto hasExplicitArray = [&tbl](std::string_view section, std::string_view key) {
1669+
const auto* sectionTbl = tbl[section].as_table();
1670+
return sectionTbl != nullptr && (*sectionTbl)[key].as_array() != nullptr;
1671+
};
1672+
const bool sessionActionsConfigured = [&tbl] {
1673+
const auto* shellTbl = tbl["shell"].as_table();
1674+
const auto* sessionTbl = shellTbl != nullptr ? (*shellTbl)["session"].as_table() : nullptr;
1675+
return sessionTbl != nullptr && (*sessionTbl)["actions"].as_array() != nullptr;
1676+
}();
16581677
if (!sessionActionsConfigured && config.shell.session.actions.empty()) {
16591678
config.shell.session.actions = defaultSessionPanelActions();
16601679
}
1661-
1662-
// Parse [theme]
1663-
if (auto* themeTbl = tbl["theme"].as_table()) {
1664-
readConfigSection(*themeTbl, config.theme, schema::themeSchema(), "theme", schemaDiag);
1665-
}
1666-
1667-
// Parse [wallpaper] (config keys only; app-managed state keys default/last/
1668-
// monitors/favorite are handled separately by extractWallpaperFromTable).
1669-
if (auto* wpTbl = tbl["wallpaper"].as_table()) {
1670-
readConfigSection(*wpTbl, config.wallpaper, schema::wallpaperSchema(), "wallpaper", schemaDiag);
1671-
}
1672-
1673-
// Parse [backdrop]
1674-
if (auto* ovTbl = tbl["backdrop"].as_table()) {
1675-
readConfigSection(*ovTbl, config.backdrop, schema::backdropSchema(), "backdrop", schemaDiag);
1676-
}
1677-
1678-
// Parse [lockscreen]
1679-
if (auto* lockTbl = tbl["lockscreen"].as_table()) {
1680-
readConfigSection(*lockTbl, config.lockscreen, schema::lockscreenSchema(), "lockscreen", schemaDiag);
1681-
}
1682-
1683-
// Parse [osd]
1684-
if (auto* osdTbl = tbl["osd"].as_table()) {
1685-
readConfigSection(*osdTbl, config.osd, schema::osdSchema(), "osd", schemaDiag);
1680+
if (!hasExplicitArray("control_center", "shortcuts") && config.controlCenter.shortcuts.empty()) {
1681+
config.controlCenter.shortcuts = defaultControlCenterShortcuts();
16861682
}
1687-
1688-
if (auto* notifTbl = tbl["notification"].as_table()) {
1689-
readConfigSection(*notifTbl, config.notification, schema::notificationSchema(), "notification", schemaDiag);
1683+
if (!hasExplicitArray("plugins", "source") && config.plugins.sources.empty()) {
1684+
config.plugins.sources = defaultPluginSources();
16901685
}
1691-
1692-
// Parse [dock]
1693-
if (auto* dockTbl = tbl["dock"].as_table()) {
1694-
readConfigSection(*dockTbl, config.dock, schema::dockSchema(), "dock", schemaDiag);
1686+
if (config.idle.behaviors.empty()) {
1687+
config.idle.behaviors = defaultIdleBehaviors();
16951688
}
16961689

16971690
// Parse [desktop_widgets]
@@ -1720,87 +1713,6 @@ void ConfigService::parseConfigTable(
17201713
);
17211714
}
17221715

1723-
// Parse [hot_corners]
1724-
if (auto* hotCornersTbl = tbl["hot_corners"].as_table()) {
1725-
readConfigSection(*hotCornersTbl, config.hotCorners, schema::hotCornersSchema(), "hot_corners", schemaDiag);
1726-
}
1727-
1728-
// Parse [weather]
1729-
if (auto* weatherTbl = tbl["weather"].as_table()) {
1730-
readConfigSection(*weatherTbl, config.weather, schema::weatherSchema(), "weather", schemaDiag);
1731-
}
1732-
1733-
// Parse [calendar]
1734-
if (auto* calendarTbl = tbl["calendar"].as_table()) {
1735-
readConfigSection(*calendarTbl, config.calendar, schema::calendarSchema(), "calendar", schemaDiag);
1736-
}
1737-
1738-
// Parse [system]
1739-
if (auto* systemTbl = tbl["system"].as_table()) {
1740-
readConfigSection(*systemTbl, config.system, schema::systemSchema(), "system", schemaDiag);
1741-
}
1742-
1743-
// Parse [audio]
1744-
if (auto* audioTbl = tbl["audio"].as_table()) {
1745-
readConfigSection(*audioTbl, config.audio, schema::audioSchema(), "audio", schemaDiag);
1746-
}
1747-
1748-
// Parse [brightness]
1749-
if (auto* brightnessTbl = tbl["brightness"].as_table()) {
1750-
readConfigSection(*brightnessTbl, config.brightness, schema::brightnessSchema(), "brightness", schemaDiag);
1751-
}
1752-
1753-
// Parse [battery]
1754-
if (auto* batteryTbl = tbl["battery"].as_table()) {
1755-
readConfigSection(*batteryTbl, config.battery, schema::batterySchema(), "battery", schemaDiag);
1756-
}
1757-
1758-
// Parse [keybinds]
1759-
if (auto* keybindsTbl = tbl["keybinds"].as_table()) {
1760-
readConfigSection(*keybindsTbl, config.keybinds, schema::keybindsSchema(), "keybinds", schemaDiag);
1761-
}
1762-
1763-
// Parse [nightlight]
1764-
if (auto* nightlightTbl = tbl["nightlight"].as_table()) {
1765-
readConfigSection(*nightlightTbl, config.nightlight, schema::nightlightSchema(), "nightlight", schemaDiag);
1766-
}
1767-
1768-
// Parse [location]
1769-
if (auto* locationTbl = tbl["location"].as_table()) {
1770-
readConfigSection(*locationTbl, config.location, schema::locationSchema(), "location", schemaDiag);
1771-
}
1772-
1773-
// Parse [hooks]
1774-
if (auto* hooksTbl = tbl["hooks"].as_table()) {
1775-
readConfigSection(*hooksTbl, config.hooks, schema::hooksSchema(), "hooks", schemaDiag);
1776-
}
1777-
1778-
// Parse [control_center]. The default-shortcuts seeding stays here because it
1779-
// must apply even when [control_center] (or its shortcuts array) is absent.
1780-
bool controlCenterShortcutsConfigured = false;
1781-
if (auto* ccTbl = tbl["control_center"].as_table()) {
1782-
controlCenterShortcutsConfigured = (*ccTbl)["shortcuts"].as_array() != nullptr;
1783-
readConfigSection(*ccTbl, config.controlCenter, schema::controlCenterSchema(), "control_center", schemaDiag);
1784-
}
1785-
if (!controlCenterShortcutsConfigured && config.controlCenter.shortcuts.empty()) {
1786-
config.controlCenter.shortcuts = defaultControlCenterShortcuts();
1787-
}
1788-
1789-
// Parse [plugins]. Default-seeding stays here because it must apply even when
1790-
// [plugins] (or its source array) is absent.
1791-
bool pluginSourcesConfigured = false;
1792-
if (auto* pluginsTbl = tbl["plugins"].as_table()) {
1793-
pluginSourcesConfigured = (*pluginsTbl)["source"].as_array() != nullptr;
1794-
readConfigSection(*pluginsTbl, config.plugins, schema::pluginsSchema(), "plugins", schemaDiag);
1795-
}
1796-
if (!pluginSourcesConfigured && config.plugins.sources.empty()) {
1797-
config.plugins.sources = defaultPluginSources();
1798-
}
1799-
1800-
if (auto* accTbl = tbl["accessibility"].as_table()) {
1801-
readConfigSection(*accTbl, config.accessibility, schema::accessibilitySchema(), "accessibility", schemaDiag);
1802-
}
1803-
18041716
// Parse [plugin_settings."author/plugin"] — open-ended per-plugin setting maps,
18051717
// validated against the manifest schema (not the static pluginsSchema). Keys may
18061718
// contain '/', so this is a top-level table rather than nested under [plugins].
@@ -1819,15 +1731,6 @@ void ConfigService::parseConfigTable(
18191731
}
18201732
}
18211733

1822-
// Parse [idle] and [idle.behavior.*]. Default-seeding stays here because it
1823-
// must apply even when [idle] is absent.
1824-
if (auto* idleTbl = tbl["idle"].as_table()) {
1825-
readConfigSection(*idleTbl, config.idle, schema::idleSchema(), "idle", schemaDiag);
1826-
}
1827-
if (config.idle.behaviors.empty()) {
1828-
config.idle.behaviors = defaultIdleBehaviors();
1829-
}
1830-
18311734
if (config.bars.empty()) {
18321735
if (logSummary) {
18331736
kLog.info("no [bar.*] defined, using defaults");

0 commit comments

Comments
 (0)