diff options
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/config/src/engine/general.rs | 7 | ||||
| -rw-r--r-- | crates/config/src/engine/mod.rs | 17 | ||||
| -rw-r--r-- | crates/config/src/engine/support.rs | 4 | ||||
| -rw-r--r-- | crates/core/src/manager/lock.rs | 9 | ||||
| -rw-r--r-- | crates/srdwm/src/main.rs | 61 |
5 files changed, 96 insertions, 2 deletions
diff --git a/crates/config/src/engine/general.rs b/crates/config/src/engine/general.rs index 68b441e..7c22747 100644 --- a/crates/config/src/engine/general.rs +++ b/crates/config/src/engine/general.rs @@ -8,7 +8,12 @@ impl Engine { let state = self.state.clone(); Ok(self.lua.create_function(move |_, (key, value): (String, Value)| { if let Some(v) = ConfigValue::from_lua(&value) { - state.borrow_mut().values.insert(key, v); + let mut s = state.borrow_mut(); + // Recorded so a reload can tell "the config states this" + // from "this is just the seeded default" - see + // `SharedState::config_set_keys`. + s.config_set_keys.insert(key.clone()); + s.values.insert(key, v); } Ok(()) })?) diff --git a/crates/config/src/engine/mod.rs b/crates/config/src/engine/mod.rs index 8facd56..1dd3c35 100644 --- a/crates/config/src/engine/mod.rs +++ b/crates/config/src/engine/mod.rs @@ -35,6 +35,17 @@ struct SharedState { /// `WindowManager` by `main.rs` and served over IPC as /// `srd keybindings`. key_descriptions: HashMap<String, String>, + /// Every key the *config* set via `srd.set`, as opposed to a seeded + /// default. Cleared and rebuilt on each load, so it always describes + /// the config currently in force. + /// + /// `values` alone cannot answer this: defaults are seeded into it + /// before any script runs, so every key looks set. The distinction + /// matters because a config file stating a value must be able to + /// override a live `srd set` when it is reloaded - otherwise a + /// setting changed at runtime can never be corrected from the config + /// again. + config_set_keys: std::collections::HashSet<String>, /// Combos registered with `srd.bind_repeat`, which fire repeatedly while /// held (Hyprland's `binde`). A subset of `key_bindings`. repeat_keys: std::collections::HashSet<String>, @@ -77,6 +88,7 @@ impl Engine { values: default_config(), key_bindings: HashMap::new(), key_descriptions: HashMap::new(), + config_set_keys: std::collections::HashSet::new(), repeat_keys: std::collections::HashSet::new(), event_handlers: HashMap::new(), config_dir: config_dir.into(), @@ -195,6 +207,11 @@ impl Engine { } } + /// Keys the loaded config explicitly set - see `config_set_keys`. + pub fn config_set_keys(&self) -> Vec<String> { + self.state.borrow().config_set_keys.iter().cloned().collect() + } + pub fn bound_keys(&self) -> Vec<String> { self.state.borrow().key_bindings.keys().cloned().collect() } diff --git a/crates/config/src/engine/support.rs b/crates/config/src/engine/support.rs index 9d81035..b0c8caf 100644 --- a/crates/config/src/engine/support.rs +++ b/crates/config/src/engine/support.rs @@ -34,10 +34,11 @@ pub(super) fn do_reload(lua: &Lua, state: &Rc<RefCell<SharedState>>) -> Result<( std::mem::take(&mut s.event_handlers), std::mem::take(&mut s.repeat_keys), std::mem::take(&mut s.key_descriptions), + std::mem::take(&mut s.config_set_keys), ); (s.config_dir.clone(), previous) }; - let restore = |state: &Rc<RefCell<SharedState>>, previous: (_, _, _, _)| { + let restore = |state: &Rc<RefCell<SharedState>>, previous: (_, _, _, _, _)| { let mut s = state.borrow_mut(); // Whatever the failed run managed to register before erroring is // discarded, not merged: half of a broken config is not a config. @@ -45,6 +46,7 @@ pub(super) fn do_reload(lua: &Lua, state: &Rc<RefCell<SharedState>>) -> Result<( s.event_handlers = previous.1; s.repeat_keys = previous.2; s.key_descriptions = previous.3; + s.config_set_keys = previous.4; }; let path = config_dir.join("init.lua"); let src = match std::fs::read_to_string(&path) { diff --git a/crates/core/src/manager/lock.rs b/crates/core/src/manager/lock.rs index ecea5fe..d24ccae 100644 --- a/crates/core/src/manager/lock.rs +++ b/crates/core/src/manager/lock.rs @@ -73,6 +73,15 @@ impl WindowManager { self.live_settings.insert(key.to_string(), value_json); } + /// Drops the recorded live override for `key`, so the next replay does + /// not reapply it. Used when a reloaded config states that key itself: + /// the file is then the more deliberate statement of intent, and has to + /// win, or a value changed at runtime could never be corrected by + /// editing the config again. + pub fn forget_live_setting(&mut self, key: &str) { + self.live_settings.remove(key); + } + /// Every live setting recorded so far, for replay after a config /// reload. Cloned rather than borrowed: the replay mutates the same /// `WindowManager` this came from. diff --git a/crates/srdwm/src/main.rs b/crates/srdwm/src/main.rs index 7670b99..940db50 100644 --- a/crates/srdwm/src/main.rs +++ b/crates/srdwm/src/main.rs @@ -227,6 +227,64 @@ fn publish_gtk_button_layout(wm: &Rc<RefCell<WindowManager>>) { } } +/// `srd set` key -> the config key that states the same thing. +/// +/// Needed so a reload can tell which live overrides the config file itself +/// has an opinion about. Only the keys that exist on both sides appear; a +/// live setting with no config equivalent simply survives every reload. +fn config_key_for_live_setting(key: &str) -> Option<&'static str> { + Some(match key { + "shadows" => "general.shadows", + "rounded_corners" => "general.rounded_corners", + "animations" => "general.animations", + "night_light" => "general.night_light", + "reading_mode" => "general.reading_mode", + "phone_mode" => "general.phone_mode", + "multi_cursor" => "general.multi_cursor", + "per_monitor" => "workspace.per_monitor", + "desktop_icons" => "general.desktop_icons", + "desktop_icons_all_monitors" => "general.desktop_icons_all_monitors", + "close_focus_follows_workspace" => "general.close_focus_follows_workspace", + "gap_inner" => "general.window_gap", + "border_width" => "theme.decorations.border.width", + "border_color" => "theme.decorations.border.active_color", + "corner_radius" => "theme.decorations.border.radius", + "decoration_mode" => "theme.decorations.default_mode", + "button_style" => "theme.decorations.title_bar.button_style", + "button_side" => "theme.decorations.title_bar.button_side", + "button_mode" => "theme.decorations.title_bar.button_mode", + "button_order" => "theme.decorations.title_bar.button_order", + "title_centered" => "theme.decorations.title_bar.text_align", + "button_glyph_always" => "theme.decorations.title_bar.button_glyph", + _ => return None, + }) +} + +/// Drops every live `srd set` override that the freshly-loaded config +/// states for itself, so the config file wins. +/// +/// Live overrides survive a reload by design - otherwise the titlebar +/// menu's own Customize rows would revert every time the config was saved, +/// which with reload-on-write is constantly. But "live always wins" is the +/// opposite failure and a worse one: a value changed once at runtime could +/// never be corrected by editing the config, because every reload put it +/// straight back. Found exactly that way - a session reporting +/// `shadows: true` while its own `init.lua` said `false`, with no way to +/// fix it from the config. +/// +/// So the rule is: the config wins for anything it actually mentions, and a +/// live override survives only where the config is silent. +fn drop_live_settings_the_config_states(engine: &Engine, wm: &Rc<RefCell<WindowManager>>) { + let stated: std::collections::HashSet<String> = engine.config_set_keys().into_iter().collect(); + let live: Vec<String> = wm.borrow().live_settings().into_iter().map(|(k, _)| k).collect(); + let mut wm = wm.borrow_mut(); + for key in live { + if config_key_for_live_setting(&key).is_some_and(|config_key| stated.contains(config_key)) { + wm.forget_live_setting(&key); + } + } +} + /// Copies the loaded config's key bindings into the `WindowManager`, where /// the IPC layer can serve them (`srd keybindings`). /// @@ -964,6 +1022,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { // `WindowManager::live_settings` for why a hand-made // change has to win over that on a *reload*, even // though the file wins at startup. + drop_live_settings_the_config_states(&engine, &wm); let replayed = srdwm_platform::replay_live_settings(&wm); if replayed > 0 { log::info!("re-applied {replayed} live setting(s) after the reload"); @@ -984,6 +1043,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { publish_keybindings(&engine, &wm); publish_gtk_button_layout(&wm); publish_gtk_stylesheet(&wm); + drop_live_settings_the_config_states(&engine, &wm); srdwm_platform::replay_live_settings(&wm); // After the reload, so a handler edited in the config since // startup is the one that runs. @@ -1004,6 +1064,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { publish_keybindings(&engine, &wm); publish_gtk_button_layout(&wm); publish_gtk_stylesheet(&wm); + drop_live_settings_the_config_states(&engine, &wm); srdwm_platform::replay_live_settings(&wm); } else if !engine.dispatch_keybinding(&combo) { log::debug!("no binding for '{combo}'"); |