srdusr
aboutsummaryrefslogtreecommitdiffstats
path: root/crates/platform
diff options
context:
space:
mode:
Diffstat (limited to 'crates/platform')
-rw-r--r--crates/platform/src/ipc/dispatch.rs38
-rw-r--r--crates/platform/src/ipc/mod.rs1
-rw-r--r--crates/platform/src/ipc/tests.rs39
-rw-r--r--crates/platform/src/lib.rs2
4 files changed, 79 insertions, 1 deletions
diff --git a/crates/platform/src/ipc/dispatch.rs b/crates/platform/src/ipc/dispatch.rs
index d2232c8..749b2aa 100644
--- a/crates/platform/src/ipc/dispatch.rs
+++ b/crates/platform/src/ipc/dispatch.rs
@@ -437,6 +437,44 @@ pub(crate) fn handle_request(line: &[u8], wm: &std::rc::Rc<std::cell::RefCell<Wi
/// predicate is what the two colour/width arms below walk existing
/// windows with, rather than touching every window unconditionally.
fn handle_set(req: &serde_json::Value, wm: &std::rc::Rc<std::cell::RefCell<WindowManager>>) -> (Vec<u8>, bool) {
+ let (response, changed) = apply_set(req, wm);
+ // Recorded only on success, so a rejected value is never replayed --
+ // and only for a real client call, not for the replay itself (see
+ // `replay_live_settings`, which calls `apply_set` directly and would
+ // otherwise rewrite what it is currently reading).
+ if changed {
+ if let (Some(key), Some(value)) = (req.get("key").and_then(|v| v.as_str()), req.get("value")) {
+ wm.borrow_mut().record_live_setting(key, value.to_string());
+ }
+ }
+ (response, changed)
+}
+
+/// Re-applies every setting changed live since startup.
+///
+/// Called after a config reload, which rebuilds the theme and general
+/// settings from the config file and would otherwise silently undo them --
+/// see `WindowManager::live_settings`' own doc comment for why that matters
+/// more now that a reload happens on every save.
+///
+/// Replayed through `apply_set`, the same function that applied them
+/// originally, so a replayed setting cannot behave differently from a real
+/// one. Failures are ignored: a value that no longer applies (a monitor
+/// that has gone away, say) should not stop the rest being restored.
+pub fn replay_live_settings(wm: &std::rc::Rc<std::cell::RefCell<WindowManager>>) -> usize {
+ let recorded = wm.borrow().live_settings();
+ let mut applied = 0;
+ for (key, raw) in recorded {
+ let Ok(value) = serde_json::from_str::<serde_json::Value>(&raw) else { continue };
+ let req = serde_json::json!({ "cmd": "set", "key": key, "value": value });
+ if apply_set(&req, wm).1 {
+ applied += 1;
+ }
+ }
+ applied
+}
+
+fn apply_set(req: &serde_json::Value, wm: &std::rc::Rc<std::cell::RefCell<WindowManager>>) -> (Vec<u8>, bool) {
let key = req.get("key").and_then(|v| v.as_str()).unwrap_or("");
let value = req.get("value");
match key {
diff --git a/crates/platform/src/ipc/mod.rs b/crates/platform/src/ipc/mod.rs
index 796c459..7094f6e 100644
--- a/crates/platform/src/ipc/mod.rs
+++ b/crates/platform/src/ipc/mod.rs
@@ -37,6 +37,7 @@ use std::path::PathBuf;
use srdwm_core::WindowManager;
mod dispatch;
+pub use dispatch::replay_live_settings;
mod types;
#[cfg(test)]
mod tests;
diff --git a/crates/platform/src/ipc/tests.rs b/crates/platform/src/ipc/tests.rs
index c2a678c..2e61761 100644
--- a/crates/platform/src/ipc/tests.rs
+++ b/crates/platform/src/ipc/tests.rs
@@ -26,6 +26,45 @@ fn read_line(reader: &mut std::io::BufReader<UnixStream>) -> String {
}
#[test]
+fn a_live_setting_is_recorded_and_survives_a_replay() {
+ // The concrete regression: a config reload rebuilds the theme from the
+ // file, so a value changed by hand (the titlebar menu's own Customize
+ // rows are all live `srd set`s) was silently reverted.
+ let wm = std::rc::Rc::new(std::cell::RefCell::new(WindowManager::new()));
+ let req = serde_json::json!({"cmd": "set", "key": "button_side", "value": "left"});
+ let (_, changed) = super::dispatch::handle_request(serde_json::to_vec(&req).unwrap().as_slice(), &wm);
+ assert!(changed);
+ assert!(wm.borrow().theme.buttons_left);
+
+ // Stand in for what a reload does to the theme.
+ wm.borrow_mut().theme = srdwm_core::ThemeConfig::default();
+ assert!(!wm.borrow().theme.buttons_left, "the rebuild really did drop it");
+
+ assert_eq!(super::replay_live_settings(&wm), 1);
+ assert!(wm.borrow().theme.buttons_left, "the live change must come back");
+}
+
+#[test]
+fn a_rejected_value_is_never_recorded_for_replay() {
+ let wm = std::rc::Rc::new(std::cell::RefCell::new(WindowManager::new()));
+ let req = serde_json::json!({"cmd": "set", "key": "button_side", "value": "sideways"});
+ let (_, changed) = super::dispatch::handle_request(serde_json::to_vec(&req).unwrap().as_slice(), &wm);
+ assert!(!changed, "an invalid value must be rejected");
+ assert_eq!(super::replay_live_settings(&wm), 0, "nothing should have been recorded");
+}
+
+#[test]
+fn setting_the_same_key_twice_replays_only_the_last_value() {
+ let wm = std::rc::Rc::new(std::cell::RefCell::new(WindowManager::new()));
+ for value in ["left", "right"] {
+ let req = serde_json::json!({"cmd": "set", "key": "button_side", "value": value});
+ super::dispatch::handle_request(serde_json::to_vec(&req).unwrap().as_slice(), &wm);
+ }
+ assert_eq!(super::replay_live_settings(&wm), 1, "one entry per key, not one per call");
+ assert!(!wm.borrow().theme.buttons_left, "the last value written is the one that survives");
+}
+
+#[test]
fn subscribe_gets_an_immediate_snapshot() {
let dir = tempfile::tempdir().unwrap();
let mut server = IpcServer::bind_in(dir.path(), "test").unwrap();
diff --git a/crates/platform/src/lib.rs b/crates/platform/src/lib.rs
index 5de701f..b69aa02 100644
--- a/crates/platform/src/lib.rs
+++ b/crates/platform/src/lib.rs
@@ -12,7 +12,7 @@ mod appmenu_registrar;
pub use appmenu_registrar::{AppmenuRegistrarState, RegistrarEvent};
mod ipc;
-pub use ipc::IpcServer;
+pub use ipc::{replay_live_settings, IpcServer};
#[cfg(unix)]
mod pam_auth;