@@ -734,6 +734,106 @@ namespace {
734734 return it != bar.monitorOverrides .end () ? &*it : nullptr ;
735735 }
736736
737+ // {"bar", name, lane} or {"bar", name, "monitor", match, lane}, lane being a widget list.
738+ bool isBarLanePath (const std::vector<std::string>& path) {
739+ const bool barScope = path.size () == 3 && path[0 ] == " bar" ;
740+ const bool monitorScope = path.size () == 5 && path[0 ] == " bar" && path[2 ] == " monitor" ;
741+ if (!barScope && !monitorScope) {
742+ return false ;
743+ }
744+ const std::string& lane = path.back ();
745+ return lane == " start" || lane == " center" || lane == " end" ;
746+ }
747+
748+ std::vector<std::string> capsuleGroupPathForBarLanePath (const std::vector<std::string>& lanePath) {
749+ std::vector<std::string> path (lanePath.begin (), lanePath.end () - 1 );
750+ path.emplace_back (" capsule_group" );
751+ return path;
752+ }
753+
754+ const std::vector<std::string>* barLaneWidgets (const Config& cfg, const std::vector<std::string>& lanePath) {
755+ const BarConfig* bar = findBarConfig (cfg, lanePath[1 ]);
756+ if (bar == nullptr ) {
757+ return nullptr ;
758+ }
759+ const std::string& lane = lanePath.back ();
760+ if (lanePath.size () == 5 ) {
761+ const BarMonitorOverride* ovr = findBarMonitorOverride (*bar, lanePath[3 ]);
762+ if (ovr == nullptr ) {
763+ return nullptr ;
764+ }
765+ const std::optional<std::vector<std::string>>& laneOverride =
766+ lane == " start" ? ovr->startWidgets : (lane == " center" ? ovr->centerWidgets : ovr->endWidgets );
767+ if (laneOverride.has_value ()) {
768+ return &*laneOverride;
769+ }
770+ }
771+ return lane == " start" ? &bar->startWidgets : (lane == " center" ? &bar->centerWidgets : &bar->endWidgets );
772+ }
773+
774+ const std::vector<BarCapsuleGroupStyle>*
775+ barLaneCapsuleGroups (const Config& cfg, const std::vector<std::string>& lanePath) {
776+ const BarConfig* bar = findBarConfig (cfg, lanePath[1 ]);
777+ if (bar == nullptr ) {
778+ return nullptr ;
779+ }
780+ if (lanePath.size () == 5 ) {
781+ const BarMonitorOverride* ovr = findBarMonitorOverride (*bar, lanePath[3 ]);
782+ if (ovr != nullptr && ovr->widgetCapsuleGroups .has_value ()) {
783+ return &*ovr->widgetCapsuleGroups ;
784+ }
785+ }
786+ return &bar->widgetCapsuleGroups ;
787+ }
788+
789+ void collectLaneGroupIds (const std::vector<std::string>& lane, std::set<std::string>& out) {
790+ for (const std::string& entry : lane) {
791+ if (isCapsuleGroupToken (entry)) {
792+ out.insert (capsuleGroupTokenId (entry));
793+ }
794+ }
795+ }
796+
797+ // Lane equality as the settings GUI presents it: the widget list plus the styles of the capsule
798+ // groups that list references. Groups the lane does not reference belong to another lane.
799+ bool barLaneContentEqual (const Config& a, const Config& b, const std::vector<std::string>& lanePath) {
800+ const std::vector<std::string>* laneA = barLaneWidgets (a, lanePath);
801+ const std::vector<std::string>* laneB = barLaneWidgets (b, lanePath);
802+ if (laneA == nullptr || laneB == nullptr ) {
803+ return laneA == laneB;
804+ }
805+ if (*laneA != *laneB) {
806+ return false ;
807+ }
808+ const std::vector<BarCapsuleGroupStyle>* groupsA = barLaneCapsuleGroups (a, lanePath);
809+ const std::vector<BarCapsuleGroupStyle>* groupsB = barLaneCapsuleGroups (b, lanePath);
810+ for (const std::string& entry : *laneA) {
811+ if (!isCapsuleGroupToken (entry)) {
812+ continue ;
813+ }
814+ const std::string id = capsuleGroupTokenId (entry);
815+ const auto findGroup = [&id](const std::vector<BarCapsuleGroupStyle>* groups) -> const BarCapsuleGroupStyle* {
816+ if (groups == nullptr ) {
817+ return nullptr ;
818+ }
819+ const auto it = std::ranges::find (*groups, id, &BarCapsuleGroupStyle::id);
820+ return it != groups->end () ? &*it : nullptr ;
821+ };
822+ const BarCapsuleGroupStyle* groupA = findGroup (groupsA);
823+ const BarCapsuleGroupStyle* groupB = findGroup (groupsB);
824+ if (groupA == nullptr || groupB == nullptr ) {
825+ if (groupA != groupB) {
826+ return false ;
827+ }
828+ continue ;
829+ }
830+ if (*groupA != *groupB) {
831+ return false ;
832+ }
833+ }
834+ return true ;
835+ }
836+
737837 bool overridePresenceIsSemantic (const std::vector<std::string>& path) {
738838 if (path.size () == 3 && path[0 ] == " widget" && path[2 ] == " type" ) {
739839 return true ;
@@ -1184,6 +1284,35 @@ bool ConfigService::hasEffectiveOverride(const std::vector<std::string>& path) c
11841284 return effective;
11851285}
11861286
1287+ bool ConfigService::hasEffectiveBarLaneOverride (const std::vector<std::string>& lanePath) const {
1288+ if (!isBarLanePath (lanePath)) {
1289+ return false ;
1290+ }
1291+ if (hasEffectiveOverride (lanePath)) {
1292+ return true ;
1293+ }
1294+
1295+ // The lane list itself matches the config file, but a group token in it can still carry the
1296+ // change: moving a widget into a lane's capsule group only edits the scope's capsule_group array.
1297+ const std::vector<std::string> groupPath = capsuleGroupPathForBarLanePath (lanePath);
1298+ if (findOverrideNode (m_overridesTable, groupPath) == nullptr ) {
1299+ return false ;
1300+ }
1301+
1302+ const std::string key = " lane:" + overrideCacheKey (lanePath);
1303+ if (const auto it = m_effectiveOverrideCache.find (key); it != m_effectiveOverrideCache.end ()) {
1304+ return it->second ;
1305+ }
1306+
1307+ toml::table without = m_overridesTable;
1308+ eraseOverridePath (without, lanePath, overridePreserveDepthForPath (lanePath));
1309+ eraseOverridePath (without, groupPath, overridePreserveDepthForPath (groupPath));
1310+ const auto baseline = configForOverrides (without);
1311+ const bool effective = !baseline.has_value () || !barLaneContentEqual (m_config, *baseline, lanePath);
1312+ m_effectiveOverrideCache[key] = effective;
1313+ return effective;
1314+ }
1315+
11871316std::size_t ConfigService::overridePreserveDepthForPath (const std::vector<std::string>& path) const {
11881317 if (path.size () > 4 && path[0 ] == " bar" && path[2 ] == " monitor" && isOverrideOnlyMonitorOverride (path[1 ], path[3 ])) {
11891318 return 4 ;
@@ -1768,6 +1897,10 @@ bool ConfigService::setOverrides(
17681897 }
17691898 }
17701899
1900+ return commitOverrideTable (std::move (next), changed);
1901+ }
1902+
1903+ bool ConfigService::commitOverrideTable (toml::table next, bool * changed) {
17711904 if (next == m_overridesTable) {
17721905 m_lastMutationError.clear ();
17731906 return true ;
@@ -1832,26 +1965,80 @@ bool ConfigService::clearOverrides(const std::vector<std::vector<std::string>>&
18321965
18331966 reconcileCapsuleGroupOverrides (next);
18341967
1835- if (!validateOverrideMutation (next)) {
1968+ return commitOverrideTable (std::move (next), changed);
1969+ }
1970+
1971+ bool ConfigService::resetBarLaneOverride (const std::vector<std::string>& lanePath, bool * changed) {
1972+ if (changed != nullptr ) {
1973+ *changed = false ;
1974+ }
1975+ if (m_overridesPath.empty () || !isBarLanePath (lanePath)) {
18361976 return false ;
18371977 }
18381978
1839- toml::table previous = std::move (m_overridesTable);
1840- m_overridesTable = std::move (next);
1841- if (!writeOverridesToFile ()) {
1842- m_overridesTable = std::move (previous);
1843- kLog .warn (" failed to write {}" , m_overridesPath);
1844- return false ;
1979+ toml::table next = m_overridesTable;
1980+ bool anyChanged = eraseOverridePath (next, lanePath, overridePreserveDepthForPath (lanePath));
1981+
1982+ // Restore the groups this lane holds. The scope's capsule_group array is shared with the other
1983+ // lanes, so it is rewritten rather than cleared: only ids this lane references go back to their
1984+ // config-file style, and GUI-created ones disappear with it.
1985+ const std::vector<std::string> groupPath = capsuleGroupPathForBarLanePath (lanePath);
1986+ if (findOverrideNode (next, groupPath) != nullptr ) {
1987+ toml::table baselineTable = next;
1988+ eraseOverridePath (baselineTable, groupPath, overridePreserveDepthForPath (groupPath));
1989+ const auto baseline = configForOverrides (baselineTable);
1990+ if (!baseline.has_value ()) {
1991+ return false ;
1992+ }
1993+ const std::vector<BarCapsuleGroupStyle>* baseGroups = barLaneCapsuleGroups (*baseline, lanePath);
1994+ // Current state comes from the live config: `next` already dropped the lane override, so its
1995+ // lane no longer names the groups that exist only because of it.
1996+ const std::vector<BarCapsuleGroupStyle>* currentGroups = barLaneCapsuleGroups (m_config, lanePath);
1997+ const std::vector<std::string>* baseLane = barLaneWidgets (*baseline, lanePath);
1998+ const std::vector<std::string>* currentLane = barLaneWidgets (m_config, lanePath);
1999+ if (baseGroups != nullptr && currentGroups != nullptr && baseLane != nullptr && currentLane != nullptr ) {
2000+ std::set<std::string> owned;
2001+ collectLaneGroupIds (*baseLane, owned);
2002+ collectLaneGroupIds (*currentLane, owned);
2003+
2004+ std::vector<BarCapsuleGroupStyle> restored;
2005+ restored.reserve (currentGroups->size ());
2006+ for (const auto & group : *currentGroups) {
2007+ if (!owned.contains (group.id )) {
2008+ restored.push_back (group);
2009+ continue ;
2010+ }
2011+ const auto it = std::ranges::find (*baseGroups, group.id , &BarCapsuleGroupStyle::id);
2012+ if (it != baseGroups->end ()) {
2013+ restored.push_back (*it);
2014+ }
2015+ }
2016+ for (const auto & group : *baseGroups) {
2017+ if (owned.contains (group.id ) && !std::ranges::contains (restored, group.id , &BarCapsuleGroupStyle::id)) {
2018+ restored.push_back (group);
2019+ }
2020+ }
2021+
2022+ if (restored != *currentGroups) {
2023+ toml::table* scope = &next;
2024+ for (std::size_t i = 0 ; i + 1 < groupPath.size () && scope != nullptr ; ++i) {
2025+ scope = scope->get_as <toml::table>(groupPath[i]);
2026+ }
2027+ if (scope != nullptr ) {
2028+ insertOverrideValue (*scope, groupPath.back (), restored);
2029+ anyChanged = true ;
2030+ }
2031+ }
2032+ }
18452033 }
18462034
1847- m_ownOverridesWritePending = true ;
1848- if (changed != nullptr ) {
1849- *changed = true ;
2035+ if (!anyChanged) {
2036+ m_lastMutationError. clear ();
2037+ return true ;
18502038 }
1851- extractWallpaperFromOverrides ();
1852- loadAll ();
1853- fireReloadCallbacks ();
1854- return true ;
2039+
2040+ reconcileCapsuleGroupOverrides (next);
2041+ return commitOverrideTable (std::move (next), changed);
18552042}
18562043
18572044bool ConfigService::renameOverrideTable (
0 commit comments