git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit70639d00b105d04efe3bb2eec212eebcf408d2a7
parentb17ae22d50
authorLucas Galante <[email protected]>
date2026-06-28 20:47
Introduce last_write_mtime tracking to eliminate background thread config reload race conditions

 src/main.rs     | 45 +++++++++++++++++++++++++++++++++------------
 src/watchers.rs | 18 ++++++++++++------
 2 files changed, 45 insertions(+), 18 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 39fd9d5..16288fc 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -172,15 +172,15 @@ struct SystemInterface {
     rx_display: std::sync::mpsc::Receiver<pages::display::DisplayState>,
     rx_network: std::sync::mpsc::Receiver<pages::network::NetworkState>,
     rx_layout: std::sync::mpsc::Receiver<pages::interface::WindowsState>,
-    rx_input: std::sync::mpsc::Receiver<pages::input::InputState>,
+    rx_input: std::sync::mpsc::Receiver<(pages::input::InputState, std::time::SystemTime)>,
     rx_fingers: std::sync::mpsc::Receiver<Vec<Finger>>,
-    rx_processes: std::sync::mpsc::Receiver<pages::processes::ProcessesState>,
+    pub rx_processes: std::sync::mpsc::Receiver<pages::processes::ProcessesState>, // wait, keep it as in original
     rx_system: std::sync::mpsc::Receiver<pages::system_info::SystemState>,
     rx_storage: std::sync::mpsc::Receiver<pages::storage::StorageState>,
     rx_notifications: std::sync::mpsc::Receiver<pages::system_info::NotificationsConfig>,
     rx_typeface: std::sync::mpsc::Receiver<pages::interface::InterfaceState>,
     rx_services: std::sync::mpsc::Receiver<Vec<pages::processes::ServiceInfo>>,
-    rx_interface: std::sync::mpsc::Receiver<pages::interface::InterfaceState>,
+    rx_interface: std::sync::mpsc::Receiver<(pages::interface::InterfaceState, std::time::SystemTime)>,
     rx_accounts: std::sync::mpsc::Receiver<Vec<pages::accounts::AccountInfo>>,
     tx_backup: std::sync::mpsc::Sender<pages::storage::StorageMessage>,
     rx_backup: std::sync::mpsc::Receiver<pages::storage::StorageMessage>,
@@ -214,6 +214,7 @@ struct SystemInterface {
     search_open: bool,
     search_query: String,
     search_box: cce_ui::widget::input::TextBox,
+    last_write_mtime: std::time::SystemTime,
 }
 
 impl cce_ui::engine::Application for SystemInterface {
@@ -288,6 +289,9 @@ impl cce_ui::engine::Application for SystemInterface {
             rx_packages: watchers.rx_packages,
             tx_update,
             rx_update,
+            last_write_mtime: std::fs::metadata("/home/lsgalante/.config/cce/config.json")
+                .and_then(|m| m.modified())
+                .unwrap_or_else(|_| std::time::SystemTime::now()),
 
             scale_factor: 1.0,
             width: 820,
@@ -351,11 +355,16 @@ impl cce_ui::engine::Application for SystemInterface {
     }
 
     fn update(&mut self, msg: Self::Message, needs_rebuild: &mut bool, exit: &mut bool) {
-        if matches!(msg, AppAction::Exit) {
+        if let AppAction::Exit = msg {
             *exit = true;
             return;
         }
         self.handle_action(&msg);
+        if let Ok(metadata) = std::fs::metadata("/home/lsgalante/.config/cce/config.json") {
+            if let Ok(mtime) = metadata.modified() {
+                self.last_write_mtime = mtime;
+            }
+        }
         *needs_rebuild = true;
         self.needs_rebuild = true;
     }
@@ -370,9 +379,17 @@ impl cce_ui::engine::Application for SystemInterface {
         }
         let mut actions = Vec::new();
         self.propagate_widget_changes(&mut actions);
+        let has_actions = !actions.is_empty();
         for action in actions {
             self.handle_action(&action);
         }
+        if has_actions {
+            if let Ok(metadata) = std::fs::metadata("/home/lsgalante/.config/cce/config.json") {
+                if let Ok(mtime) = metadata.modified() {
+                    self.last_write_mtime = mtime;
+                }
+            }
+        }
         if self.needs_rebuild || self.ui_context.is_dirty() {
             *needs_rebuild = true;
             self.needs_rebuild = true;
@@ -658,10 +675,12 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
             }
         }
 
-        while let Ok(s) = self.rx_input.try_recv() {
-            input::update(&mut self.app.input, input::InputMessage::Refreshed(s));
-            if self.app.current_page == Page::Input {
-                self.needs_rebuild = true;
+        while let Ok((s, mtime)) = self.rx_input.try_recv() {
+            if mtime > self.last_write_mtime {
+                input::update(&mut self.app.input, input::InputMessage::Refreshed(s));
+                if self.app.current_page == Page::Input {
+                    self.needs_rebuild = true;
+                }
             }
         }
         let mut got_fingers = None;
@@ -713,10 +732,12 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
                 self.needs_rebuild = true;
             }
         }
-        while let Ok(s) = self.rx_interface.try_recv() {
-            interface::update(&mut self.app.interface, pages::interface::InterfaceMessage::Refreshed(s));
-            if self.app.current_page == Page::Interface {
-                self.needs_rebuild = true;
+        while let Ok((s, mtime)) = self.rx_interface.try_recv() {
+            if mtime > self.last_write_mtime {
+                interface::update(&mut self.app.interface, pages::interface::InterfaceMessage::Refreshed(s));
+                if self.app.current_page == Page::Interface {
+                    self.needs_rebuild = true;
+                }
             }
         }
 
diff --git a/src/watchers.rs b/src/watchers.rs
index 3f02252..c4c3adf 100644
--- a/src/watchers.rs
+++ b/src/watchers.rs
@@ -9,7 +9,7 @@ pub struct Watchers {
     pub rx_display: Receiver<display::DisplayState>,
     pub rx_network: Receiver<network::NetworkState>,
     pub rx_layout: Receiver<interface::WindowsState>,
-    pub rx_input: Receiver<input::InputState>,
+    pub rx_input: Receiver<(input::InputState, std::time::SystemTime)>,
     pub rx_fingers: Receiver<Vec<Finger>>,
     pub rx_processes: Receiver<processes::ProcessesState>,
     pub rx_system: Receiver<system_info::SystemState>,
@@ -17,7 +17,7 @@ pub struct Watchers {
     pub rx_notifications: Receiver<system_info::NotificationsConfig>,
     pub rx_typeface: Receiver<interface::InterfaceState>,
     pub rx_services: Receiver<Vec<processes::ServiceInfo>>,
-    pub rx_interface: Receiver<interface::InterfaceState>,
+    pub rx_interface: Receiver<(interface::InterfaceState, std::time::SystemTime)>,
     pub rx_accounts: Receiver<Vec<accounts::AccountInfo>>,
     pub rx_packages: Receiver<packages::PackagesState>,
 }
@@ -95,7 +95,7 @@ pub fn spawn_all(
 
 
     let rx_input = {
-        let (tx, rx) = channel::<input::InputState>();
+        let (tx, rx) = channel::<(input::InputState, std::time::SystemTime)>();
         let current_page_shared = current_page_shared.clone();
         tokio::spawn(async move {
             let mut last_fetch: Option<std::time::Instant> = None;
@@ -107,9 +107,12 @@ pub fn spawn_all(
                         Some(t) => t.elapsed() >= std::time::Duration::from_secs(30),
                     };
                     if should_fetch {
+                        let mtime = std::fs::metadata("/home/lsgalante/.config/cce/config.json")
+                            .and_then(|m| m.modified())
+                            .unwrap_or_else(|_| std::time::SystemTime::now());
                         let val = tokio::task::spawn_blocking(|| input::read_input_config()).await;
                         if let Ok(val) = val {
-                            if tx.send(val).is_err() { break; }
+                            if tx.send((val, mtime)).is_err() { break; }
                         }
                         last_fetch = Some(std::time::Instant::now());
                     }
@@ -188,7 +191,7 @@ pub fn spawn_all(
     let rx_accounts = spawn_bg_active(current_page_shared.clone(), Page::Accounts.index() as u8, 3, || accounts::fetch_accounts());
 
     let rx_interface = {
-        let (tx, rx) = channel::<interface::InterfaceState>();
+        let (tx, rx) = channel::<(interface::InterfaceState, std::time::SystemTime)>();
         let current_page_shared = current_page_shared.clone();
         tokio::spawn(async move {
             let mut last_fetch: Option<std::time::Instant> = None;
@@ -200,9 +203,12 @@ pub fn spawn_all(
                         Some(t) => t.elapsed() >= std::time::Duration::from_secs(30),
                     };
                     if should_fetch {
+                        let mtime = std::fs::metadata("/home/lsgalante/.config/cce/config.json")
+                            .and_then(|m| m.modified())
+                            .unwrap_or_else(|_| std::time::SystemTime::now());
                         let val = tokio::task::spawn_blocking(|| interface::read_interface_config()).await;
                         if let Ok(val) = val {
-                            if tx.send(val).is_err() { break; }
+                            if tx.send((val, mtime)).is_err() { break; }
                         }
                         last_fetch = Some(std::time::Instant::now());
                     }