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

commit984b81d58a7e83ba6696136413c801e4da8f2d59
parent21d2d792b3
authorLucas Galante <[email protected]>
date2026-07-05 19:29
Create dedicated Notifications settings page

 src/app.rs                 |   6 +
 src/main.rs                |  10 +-
 src/pages/mod.rs           |   6 +-
 src/pages/notifications.rs | 347 +++++++++++++++++++++++++++++++++++++++++++++
 src/pages/system_info.rs   | 276 +----------------------------------
 src/watchers.rs            |  10 +-
 6 files changed, 372 insertions(+), 283 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index 6844f97..c6e0d1b 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -8,6 +8,7 @@ use crate::pages::storage;
 use crate::pages::fonts;
 use crate::pages::accounts;
 use crate::pages::packages;
+use crate::pages::notifications;
 use crate::pages::Page;
 
 pub struct AppState {
@@ -20,6 +21,7 @@ pub struct AppState {
     pub fonts: fonts::FontsState,
     pub accounts: accounts::AccountsState,
     pub packages: packages::PackagesState,
+    pub notifications: notifications::NotificationsState,
 }
 
 impl Default for AppState {
@@ -34,6 +36,7 @@ impl Default for AppState {
             fonts: fonts::FontsState::default(),
             accounts: accounts::AccountsState::default_mock(),
             packages: packages::PackagesState::default(),
+            notifications: notifications::NotificationsState::default(),
         }
     }
 }
@@ -49,6 +52,7 @@ impl AppState {
             Page::Storage => &self.storage,
             Page::System => &self.system_info,
             Page::Fonts => &self.fonts,
+            Page::Notifications => &self.notifications,
         }
     }
 
@@ -62,6 +66,7 @@ impl AppState {
             Page::Storage => &mut self.storage,
             Page::System => &mut self.system_info,
             Page::Fonts => &mut self.fonts,
+            Page::Notifications => &mut self.notifications,
         }
     }
 
@@ -85,6 +90,7 @@ pub enum AppAction {
     Fonts(fonts::FontsMessage),
     Accounts(accounts::AccountsMessage),
     Packages(packages::PackagesMessage),
+    Notifications(notifications::NotificationsMessage),
 }
 
 
diff --git a/src/main.rs b/src/main.rs
index b30cf80..41208a1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -170,7 +170,7 @@ struct SystemInterface {
     pub rx_processes: std::sync::mpsc::Receiver<pages::processes::ProcessesState>,
     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_notifications: std::sync::mpsc::Receiver<pages::notifications::NotificationsConfig>,
     rx_services: std::sync::mpsc::Receiver<Vec<pages::processes::ServiceInfo>>,
     rx_fonts: std::sync::mpsc::Receiver<pages::fonts::FontsState>,
     rx_accounts: std::sync::mpsc::Receiver<Vec<pages::accounts::AccountInfo>>,
@@ -552,8 +552,8 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
             }
         }
         while let Ok(s) = self.rx_notifications.try_recv() {
-            system_info::update(&mut self.app.system_info, system_info::SystemMessage::NotificationsRefreshed(s));
-            if self.app.current_page == Page::System {
+            pages::notifications::update(&mut self.app.notifications, pages::notifications::NotificationsMessage::Refreshed(s));
+            if self.app.current_page == Page::Notifications {
                 self.needs_rebuild = true;
             }
         }
@@ -607,6 +607,7 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
             AppAction::Radios(m) => network::update(&mut self.app.network, m.clone()),
             AppAction::SystemInfo(m) => system_info::update(&mut self.app.system_info, m.clone()),
             AppAction::Processes(m) => processes::update(&mut self.app.processes, m.clone()),
+            AppAction::Notifications(m) => notifications::update(&mut self.app.notifications, m.clone()),
             AppAction::Storage(m) => match m {
                 pages::storage::StorageMessage::StartBackup => {
                     pages::storage::update(&mut self.app.storage, pages::storage::StorageMessage::StartBackup);
@@ -707,9 +708,10 @@ fn collect_popover_rects(w: &dyn cce_ui::widget::Element, popovers: &mut Vec<(f3
             Page::Fonts => "Fonts Settings: Adjust font family preferences, typography, and scaling.",
             Page::Packages => "Package Manager: Search, install, and update system packages.",
             Page::Processes => "System Monitor: Inspect running tasks, system resources, and services.",
+            Page::Notifications => "Notifications: Configure system notifications, sound, and duration.",
             Page::Radios => "Network Settings: Configure wireless networks, radios, and connections.",
             Page::Storage => "Storage Settings: Manage local disks, partition structures, and backup runs.",
-            Page::System => "System Settings: System properties, update checks, and notification parameters.",
+            Page::System => "System Settings: System properties, power management, and update checks.",
         };
         self.statusbar.set_text(msg);
     }
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index dae214e..c95cec3 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -7,11 +7,13 @@ pub mod processes;
 pub mod fonts;
 pub mod accounts;
 pub mod packages;
+pub mod notifications;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Page {
     Accounts,
     Audio,
+    Notifications,
     Radios,
     Storage,
     System,
@@ -21,10 +23,11 @@ pub enum Page {
 }
 
 impl Page {
-    pub const ALL: [Page; 8] = [
+    pub const ALL: [Page; 9] = [
         Page::Accounts,
         Page::Audio,
         Page::Fonts,
+        Page::Notifications,
         Page::Packages,
         Page::Processes,
         Page::Radios,
@@ -36,6 +39,7 @@ impl Page {
         match self {
             Page::Accounts => "Accounts",
             Page::Audio => "Audio",
+            Page::Notifications => "Notifications",
             Page::Radios => "Radios",
             Page::Storage => "Storage",
             Page::System => "System",
diff --git a/src/pages/notifications.rs b/src/pages/notifications.rs
new file mode 100644
index 0000000..a87d7c7
--- /dev/null
+++ b/src/pages/notifications.rs
@@ -0,0 +1,347 @@
+use std::fs;
+use std::io::Write;
+use cce_ui::widget::input::{Toggle, Dropdown, Spinbox};
+use cce_ui::widget::Element;
+use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
+use crate::app::{AppAction, PageContent, SectionContextExt};
+use crate::pages::AppPage;
+
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.kdl";
+
+#[derive(Debug, Clone)]
+pub struct NotificationsConfig {
+    pub enable: bool,
+    pub bell: String,
+    pub duration: i32,
+}
+
+#[derive(Debug, Clone)]
+pub struct NotificationsState {
+    pub loaded: bool,
+    pub enable: bool,
+    pub enable_toggle: Toggle,
+    pub bell: String,
+    pub bell_menu: Dropdown,
+    pub duration: i32,
+    pub duration_spinbox: Spinbox,
+}
+
+impl Default for NotificationsState {
+    fn default() -> Self {
+        Self {
+            loaded: false,
+            enable: true,
+            enable_toggle: Toggle::new()
+                .with_label("Enable Notifications")
+                .with_config(&get_config_path(), "enable"),
+            bell: "none".to_string(),
+            bell_menu: Dropdown::new(
+                vec![
+                    "None".to_string(),
+                    "Bell".to_string(),
+                    "Dialog".to_string(),
+                    "Message".to_string(),
+                ],
+                0,
+            ).with_label("Notification Sound"),
+            duration: 5,
+            duration_spinbox: Spinbox::new(5, 1, 60, 1)
+                .with_label("Notification Duration")
+                .with_unit("s")
+                .with_config(&get_config_path(), "duration"),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum NotificationsMessage {
+    ToggleNotificationsEnable,
+    SetNotificationsBell(String),
+    SetNotificationsDuration(i32),
+    SendTestNotification,
+    Refreshed(NotificationsConfig),
+}
+
+pub fn update(state: &mut NotificationsState, msg: NotificationsMessage) {
+    match msg {
+        NotificationsMessage::ToggleNotificationsEnable => {
+            state.enable = !state.enable;
+            write_enable_notifications(state.enable);
+        }
+        NotificationsMessage::SetNotificationsBell(sound) => {
+            state.bell = sound.clone();
+            write_config_value("bell", &sound);
+            send_ipc_command("reload");
+        }
+        NotificationsMessage::SetNotificationsDuration(d) => {
+            state.duration = d;
+            write_config_value("duration", &state.duration.to_string());
+            send_ipc_command("reload");
+        }
+        NotificationsMessage::SendTestNotification => {
+            tokio::spawn(async move {
+                if let Ok(connection) = zbus::Connection::session().await {
+                    let _ = connection.call_method(
+                        Some("org.freedesktop.Notifications"),
+                        "/org/freedesktop/Notifications",
+                        Some("org.freedesktop.Notifications"),
+                        "Notify",
+                        &(
+                            "cce-settings",
+                            0u32,
+                            "",
+                            "Test Notification",
+                            "System notifications are working correctly!",
+                            Vec::<&str>::new(),
+                            std::collections::HashMap::<&str, zbus::zvariant::Value>::new(),
+                            -1i32,
+                        )
+                    ).await;
+                }
+            });
+        }
+        NotificationsMessage::Refreshed(new) => {
+            state.loaded = true;
+            state.enable = new.enable;
+            state.bell = new.bell;
+            state.duration = new.duration;
+        }
+    }
+}
+
+fn get_socket_path() -> String {
+    match std::env::var("WAYLAND_DISPLAY") {
+        Ok(display) => format!("/tmp/cce-{}.sock", display),
+        Err(_) => "/tmp/cce.sock".to_string(),
+    }
+}
+
+pub fn read_notifications_config() -> NotificationsConfig {
+    let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+    let enable = parse_notifications_enable(&content);
+    let bell = parse_notifications_bell(&content);
+    let duration = parse_notifications_duration(&content);
+    NotificationsConfig {
+        enable,
+        bell,
+        duration,
+    }
+}
+
+fn parse_json(content: &str) -> serde_json::Value {
+    cce_ui::config::parse_kdl_to_json(content)
+}
+
+fn parse_notifications_enable(content: &str) -> bool {
+    let val = parse_json(content);
+    val["notifications"]["enable"].as_bool().unwrap_or(true)
+}
+
+fn parse_notifications_bell(content: &str) -> String {
+    let val = parse_json(content);
+    val["notifications"]["bell"].as_str().unwrap_or("none").to_string()
+}
+
+fn parse_notifications_duration(content: &str) -> i32 {
+    let val = parse_json(content);
+    val["notifications"]["duration"].as_i64().map(|v| v as i32).unwrap_or(5)
+}
+
+fn send_ipc_command(cmd: &str) {
+    if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(get_socket_path()) {
+        let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
+    }
+}
+
+fn write_config_value(key: &str, value: &str) {
+    cce_ui::config::write_config_value(&get_config_path(), key, value, "notifications");
+}
+
+fn write_enable_notifications(enabled: bool) {
+    write_config_value("enable", &enabled.to_string());
+    send_ipc_command("reload");
+}
+
+thread_local! {
+    static TEST_CONFIG_PATH: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
+}
+
+fn get_config_path() -> String {
+    #[cfg(test)]
+    {
+        TEST_CONFIG_PATH.with(|p| {
+            if let Some(path) = p.borrow().as_ref() {
+                return path.clone();
+            }
+            "/home/lsgalante/.config/cce/config.kdl".to_string()
+        })
+    }
+    #[cfg(not(test))]
+    {
+        "/home/lsgalante/.config/cce/config.kdl".to_string()
+    }
+}
+
+impl AppPage for NotificationsState {
+    fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
+        self.enable_toggle.clear_children(ctx);
+        self.enable_toggle.set_parent(None, ctx);
+        self.bell_menu.clear_children(ctx);
+        self.bell_menu.set_parent(None, ctx);
+        self.duration_spinbox.clear_children(ctx);
+        self.duration_spinbox.set_parent(None, ctx);
+    }
+
+    fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
+        vec![
+            cce_ui::widget::SectionContainer::new("Notifications Settings")
+                .with_layout(cce_ui::widget::AdaptiveGridLayout {
+                    min_col_width: 140.0,
+                    gap: 8.0,
+                    padding_x: 0.0,
+                    padding_y: 0.0,
+                    grid: None,
+                }),
+        ]
+    }
+
+    fn link_children(
+        &mut self,
+        page_root: &mut dyn cce_ui::widget::Element,
+        sec_containers: &mut [cce_ui::widget::SectionContainer],
+        ctx: &mut cce_ui::context::UiContext,
+    ) {
+        for sec in sec_containers.iter_mut() {
+            cce_ui::widget::link_parent_child(page_root, sec, ctx);
+        }
+    }
+
+    fn view(
+        &mut self,
+        cx: f32,
+        cy: f32,
+        cw: f32,
+        ch: f32,
+        _root_focused: bool,
+        sec_focused: &[bool],
+        layout: &mut dyn LayoutStrategy,
+        ctx: &mut cce_ui::context::UiContext,
+    ) -> PageContent {
+        let mut final_pc = PageContent::new();
+        let sec_w = 320.0f32;
+        let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(1);
+
+        builder.add_section(&mut final_pc, "Notifications Settings", sec_focused.first().copied().unwrap_or(false), |sec| {
+            let sec_w = sec.cw;
+            self.enable_toggle.set_toggled(self.enable);
+            sec.widget_full(&mut self.enable_toggle, cce_ui::layout::toggle_height(), ctx);
+
+            let selected_idx = match self.bell.as_str() {
+                "none" => 0,
+                "bell" => 1,
+                "dialog" => 2,
+                "message" => 3,
+                _ => 0,
+            };
+            self.bell_menu.selected = selected_idx;
+            sec.widget(&mut self.bell_menu, 14.0, sec_w - 28.0, 44.0, ctx);
+
+            self.duration_spinbox.value = self.duration;
+            self.duration_spinbox.set_label("Notification Duration");
+            sec.widget(&mut self.duration_spinbox, 14.0, sec_w - 28.0, 44.0, ctx);
+
+            let btn_h = 32.0;
+            let btn_y = sec.ay();
+            let white_color = [1.0, 1.0, 1.0, 1.0];
+            let btn_bg = [0.20, 0.40, 0.65, 1.0];
+            let btn_hover = [0.28, 0.50, 0.78, 1.0];
+
+            let cols = sec.row_layout(1, 0.0);
+            if let Some(&(x, w)) = cols.first() {
+                sec.button(
+                    "Send Test Notification",
+                    x,
+                    btn_y,
+                    w,
+                    btn_h,
+                    btn_bg,
+                    btn_hover,
+                    white_color,
+                    AppAction::Notifications(NotificationsMessage::SendTestNotification),
+                );
+            }
+        });
+
+        final_pc
+    }
+
+    fn propagate_widget_changes(&mut self, actions: &mut Vec<AppAction>) {
+        if self.enable_toggle.take_change() {
+            actions.push(AppAction::Notifications(NotificationsMessage::ToggleNotificationsEnable));
+        }
+        if self.bell_menu.take_change() {
+            let sound = match self.bell_menu.selected {
+                0 => "none",
+                1 => "bell",
+                2 => "dialog",
+                3 => "message",
+                _ => "none",
+            }.to_string();
+            actions.push(AppAction::Notifications(NotificationsMessage::SetNotificationsBell(sound)));
+        }
+        if self.duration_spinbox.take_change() {
+            actions.push(AppAction::Notifications(NotificationsMessage::SetNotificationsDuration(self.duration_spinbox.value)));
+        }
+    }
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+    use super::*;
+
+    #[test]
+    fn test_view_layout_grid() {
+        let mut state = NotificationsState::default();
+        let mut layout = cce_ui::layout::ColumnLayout::new(20.0);
+        let sec_focused = vec![false];
+        let mut ctx = cce_ui::context::UiContext::new();
+        let pc = state.view(10.0, 20.0, 800.0, 600.0, false, &sec_focused, &mut layout, &mut ctx);
+        assert!(!pc.rects.is_empty() || !pc.texts.is_empty());
+    }
+
+    #[test]
+    fn test_parse_notifications_enable_default() {
+        assert!(parse_notifications_enable(""));
+        assert!(parse_notifications_enable("[layout]\ngap = 18\n"));
+    }
+
+    #[test]
+    fn test_parse_notifications_enable_explicit() {
+        let content = "notifications {\n    enable (bool)false\n}\n";
+        assert!(!parse_notifications_enable(content));
+
+        let content = "notifications {\n    enable (bool)true\n}\n";
+        assert!(parse_notifications_enable(content));
+    }
+
+    #[test]
+    fn test_parse_notifications_enable_other_sections() {
+        let content = "layout {\n    enable (bool)false\n}\nnotifications {\n    enable (bool)true\n}\ninput {\n    enable (bool)false\n}\n";
+        assert!(parse_notifications_enable(content));
+
+        let content = "layout {\n    enable (bool)true\n}\nnotifications {\n    enable (bool)false\n}\ninput {\n    enable (bool)true\n}\n";
+        assert!(!parse_notifications_enable(content));
+    }
+
+    #[test]
+    fn test_parse_notifications_duration_default() {
+        assert_eq!(parse_notifications_duration(""), 5);
+        assert_eq!(parse_notifications_duration("notifications {}"), 5);
+    }
+
+    #[test]
+    fn test_parse_notifications_duration_explicit() {
+        let content = "notifications {\n    duration (i64)10\n}\n";
+        assert_eq!(parse_notifications_duration(content), 10);
+    }
+}
diff --git a/src/pages/system_info.rs b/src/pages/system_info.rs
index 1e0eae1..10b0b3e 100644
--- a/src/pages/system_info.rs
+++ b/src/pages/system_info.rs
@@ -1,10 +1,6 @@
 use crate::app::{AppAction, PageContent, SectionContextExt};
 use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
-use cce_ui::widget::{Label, Dropdown, InfoBox, Toggle, Spinbox, Element};
-use std::io::Write;
-use std::fs;
-
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.kdl";
+use cce_ui::widget::{Label, Dropdown, InfoBox, Element};
 
 #[derive(Debug, Clone, Default)]
 pub struct BatteryInfo {
@@ -51,14 +47,6 @@ pub struct SystemState {
     pub cpu_gov_menu: Dropdown,
     pub gpu_gov_menu: Dropdown,
 
-    // Notifications
-    pub notifications_loaded: bool,
-    pub notifications_enable: bool,
-    pub notifications_enable_toggle: Toggle,
-    pub notifications_bell: String,
-    pub notifications_bell_menu: Dropdown,
-    pub notifications_duration: i32,
-    pub notifications_duration_spinbox: Spinbox,
 }
 
 impl Default for SystemState {
@@ -91,25 +79,6 @@ impl Default for SystemState {
                 0,
             ).with_label("GPU Power Limit"),
 
-            notifications_loaded: false,
-            notifications_enable: true,
-            notifications_enable_toggle: Toggle::new().with_label("Enable Notifications").with_config(&get_config_path(), "enable"),
-            notifications_bell: "none".to_string(),
-            notifications_bell_menu: Dropdown::new(
-                vec![
-                    "None".to_string(),
-                    "Bell".to_string(),
-                    "Dialog".to_string(),
-                    "Message".to_string(),
-                ],
-                0,
-            ).with_label("Notification Sound"),
-            notifications_duration: 5,
-            notifications_duration_spinbox: Spinbox::new(5, 1, 60, 1)
-                .with_label("Notification Duration")
-                .with_unit("s")
-                .with_config(&get_config_path(), "duration"),
-
         }
     }
 }
@@ -127,12 +96,6 @@ pub enum SystemMessage {
     SetCpuPowersave,
     SetGpuDefault,
     SetGpuPowersave,
-    ToggleNotificationsEnable,
-    SetNotificationsBell(String),
-    SetNotificationsDuration(i32),
-    SendTestNotification,
-    NotificationsRefreshed(NotificationsConfig),
-
 }
 
 // ── zbus proxies ────────────────────────────────────────────────────
@@ -430,24 +393,6 @@ pub async fn fetch_system_state() -> SystemState {
             if gpu_powersave { 1 } else { 0 },
         ).with_label("GPU Power Limit"),
 
-        notifications_loaded: false,
-        notifications_enable: true,
-        notifications_enable_toggle: Toggle::new().with_label("Enable Notifications").with_config(&get_config_path(), "enable"),
-        notifications_bell: "none".to_string(),
-        notifications_bell_menu: Dropdown::new(
-            vec![
-                "None".to_string(),
-                "Bell".to_string(),
-                "Dialog".to_string(),
-                "Message".to_string(),
-            ],
-            0,
-        ).with_label("Notification Sound"),
-        notifications_duration: 5,
-        notifications_duration_spinbox: Spinbox::new(5, 1, 60, 1)
-            .with_label("Notification Duration")
-            .with_unit("s")
-            .with_config(&get_config_path(), "duration"),
     }
 }
 
@@ -464,7 +409,7 @@ const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
 pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_focused: bool, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut cce_ui::context::UiContext) -> PageContent {
     let mut final_pc = PageContent::new();
     let sec_w = 320.0f32;
-    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(9);
+    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(8);
 
     // ── 1. System Section ──
     builder.add_section(&mut final_pc, "System", false, |sec| {
@@ -638,47 +583,6 @@ pub fn view(state: &mut SystemState, cx: f32, cy: f32, cw: f32, ch: f32, _root_f
         }
     });
 
-    // ── 8. System Notifications Section ──
-    builder.add_section(&mut final_pc, "System Notifications", sec_focused.get(7).copied().unwrap_or(false), |sec2| {
-        let sec_w = sec2.cw;
-        state.notifications_enable_toggle.set_toggled(state.notifications_enable);
-        sec2.widget_full(&mut state.notifications_enable_toggle, cce_ui::layout::toggle_height(), ctx);
-
-        let selected_idx = match state.notifications_bell.as_str() {
-            "none" => 0,
-            "bell" => 1,
-            "dialog" => 2,
-            "message" => 3,
-            _ => 0,
-        };
-        state.notifications_bell_menu.selected = selected_idx;
-        sec2.widget(&mut state.notifications_bell_menu, 14.0, sec_w - 28.0, 44.0, ctx);
-
-        state.notifications_duration_spinbox.value = state.notifications_duration;
-        state.notifications_duration_spinbox.set_label("Notification Duration");
-        sec2.widget(&mut state.notifications_duration_spinbox, 14.0, sec_w - 28.0, 44.0, ctx);
-
-        let btn_h = 32.0;
-        let btn_y = sec2.ay();
-        let white_color = [1.0, 1.0, 1.0, 1.0];
-        let btn_bg = [0.20, 0.40, 0.65, 1.0];
-        let btn_hover = [0.28, 0.50, 0.78, 1.0];
-        
-        let cols = sec2.row_layout(1, 0.0);
-        if let Some(&(x, w)) = cols.first() {
-            sec2.button(
-                "Send Test Notification",
-                x,
-                btn_y,
-                w,
-                btn_h,
-                btn_bg,
-                btn_hover,
-                white_color,
-                AppAction::SystemInfo(SystemMessage::SendTestNotification),
-            );
-        }
-    });
 
 
     final_pc
@@ -733,104 +637,12 @@ pub fn update(state: &mut SystemState, msg: SystemMessage) {
             state.gpu_gov_menu.selected = 1;
             spawn_gpu_power(true);
         }
-        SystemMessage::ToggleNotificationsEnable => {
-            state.notifications_enable = !state.notifications_enable;
-            write_enable_notifications(state.notifications_enable);
-        }
-        SystemMessage::SetNotificationsBell(sound) => {
-            state.notifications_bell = sound.clone();
-            write_config_value("bell", &sound);
-        }
-        SystemMessage::SetNotificationsDuration(d) => {
-            state.notifications_duration = d;
-            write_config_value("duration", &state.notifications_duration.to_string());
-        }
-        SystemMessage::SendTestNotification => {
-            tokio::spawn(async move {
-                if let Ok(connection) = zbus::Connection::session().await {
-                    let _ = connection.call_method(
-                        Some("org.freedesktop.Notifications"),
-                        "/org/freedesktop/Notifications",
-                        Some("org.freedesktop.Notifications"),
-                        "Notify",
-                        &(
-                            "cce-client",
-                            0u32,
-                            "",
-                            "System notifications are working correctly!",
-                            "",
-                            Vec::<&str>::new(),
-                            std::collections::HashMap::<&str, zbus::zvariant::Value>::new(),
-                            -1i32,
-                        )
-                    ).await;
-                }
-            });
-        }
-        SystemMessage::NotificationsRefreshed(new) => {
-            state.notifications_loaded = true;
-            state.notifications_enable = new.enable;
-            state.notifications_bell = new.bell;
-            state.notifications_duration = new.duration;
-        }
-
-    }
-}
-
-// ── Notifications Configuration Reader & Writer ──
 
-fn get_socket_path() -> String {
-    match std::env::var("WAYLAND_DISPLAY") {
-        Ok(display) => format!("/tmp/cce-{}.sock", display),
-        Err(_) => "/tmp/cce.sock".to_string(),
-    }
-}
 
-pub fn read_notifications_config() -> NotificationsConfig {
-    let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
-    let enable = parse_notifications_enable(&content);
-    let bell = parse_notifications_bell(&content);
-    let duration = parse_notifications_duration(&content);
-    NotificationsConfig {
-        enable,
-        bell,
-        duration,
     }
 }
 
-fn parse_json(content: &str) -> serde_json::Value {
-    cce_ui::config::parse_kdl_to_json(content)
-}
-
-fn parse_notifications_enable(content: &str) -> bool {
-    let val = parse_json(content);
-    val["notifications"]["enable"].as_bool().unwrap_or(true)
-}
 
-fn parse_notifications_bell(content: &str) -> String {
-    let val = parse_json(content);
-    val["notifications"]["bell"].as_str().unwrap_or("none").to_string()
-}
-
-fn parse_notifications_duration(content: &str) -> i32 {
-    let val = parse_json(content);
-    val["notifications"]["duration"].as_i64().map(|v| v as i32).unwrap_or(5)
-}
-
-fn send_ipc_command(cmd: &str) {
-    if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(get_socket_path()) {
-        let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
-    }
-}
-
-fn write_config_value(key: &str, value: &str) {
-    cce_ui::config::write_config_value(&get_config_path(), key, value, "notifications");
-}
-
-fn write_enable_notifications(enabled: bool) {
-    write_config_value("enable", &enabled.to_string());
-    send_ipc_command("reload");
-}
 
 impl crate::pages::AppPage for SystemState {
     fn clear_children(&mut self, ctx: &mut cce_ui::context::UiContext) {
@@ -838,12 +650,6 @@ impl crate::pages::AppPage for SystemState {
         self.cpu_gov_menu.set_parent(None, ctx);
         self.gpu_gov_menu.clear_children(ctx);
         self.gpu_gov_menu.set_parent(None, ctx);
-        self.notifications_enable_toggle.clear_children(ctx);
-        self.notifications_enable_toggle.set_parent(None, ctx);
-        self.notifications_bell_menu.clear_children(ctx);
-        self.notifications_bell_menu.set_parent(None, ctx);
-        self.notifications_duration_spinbox.clear_children(ctx);
-        self.notifications_duration_spinbox.set_parent(None, ctx);
     }
 
     fn get_section_containers(&self) -> Vec<cce_ui::widget::SectionContainer> {
@@ -855,7 +661,6 @@ impl crate::pages::AppPage for SystemState {
             cce_ui::widget::SectionContainer::new("CPU Governor").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
             cce_ui::widget::SectionContainer::new("GPU Power").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
             cce_ui::widget::SectionContainer::new("Battery").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
-            cce_ui::widget::SectionContainer::new("System Notifications").with_layout(cce_ui::widget::AdaptiveGridLayout { min_col_width: 140.0, gap: 8.0, padding_x: 0.0, padding_y: 0.0, grid: None }),
         ]
     }
 
@@ -870,9 +675,6 @@ impl crate::pages::AppPage for SystemState {
         }
         cce_ui::widget::link_parent_child(&mut sec_containers[4], &mut self.cpu_gov_menu, ctx);
         cce_ui::widget::link_parent_child(&mut sec_containers[5], &mut self.gpu_gov_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[7], &mut self.notifications_enable_toggle, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[7], &mut self.notifications_bell_menu, ctx);
-        cce_ui::widget::link_parent_child(&mut sec_containers[7], &mut self.notifications_duration_spinbox, ctx);
     }
 
     fn view(
@@ -904,44 +706,10 @@ impl crate::pages::AppPage for SystemState {
                 actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetGpuPowersave));
             }
         }
-        if self.notifications_enable_toggle.take_change() {
-            actions.push(crate::app::AppAction::SystemInfo(SystemMessage::ToggleNotificationsEnable));
-        }
-        if self.notifications_bell_menu.take_change() {
-            let sound = match self.notifications_bell_menu.selected {
-                0 => "none",
-                1 => "bell",
-                2 => "dialog",
-                3 => "message",
-                _ => "none",
-            }.to_string();
-            actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetNotificationsBell(sound)));
-        }
-        if self.notifications_duration_spinbox.take_change() {
-            actions.push(crate::app::AppAction::SystemInfo(SystemMessage::SetNotificationsDuration(self.notifications_duration_spinbox.value)));
-        }
     }
 }
 
-thread_local! {
-    static TEST_CONFIG_PATH: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);
-}
 
-fn get_config_path() -> String {
-    #[cfg(test)]
-    {
-        TEST_CONFIG_PATH.with(|p| {
-            if let Some(path) = p.borrow().as_ref() {
-                return path.clone();
-            }
-            "/home/lsgalante/.config/cce/config.kdl".to_string()
-        })
-    }
-    #[cfg(not(test))]
-    {
-        "/home/lsgalante/.config/cce/config.kdl".to_string()
-    }
-}
 
 
 
@@ -953,47 +721,9 @@ mod tests {
     fn test_view_layout_grid() {
         let mut state = SystemState::default();
         let mut layout = cce_ui::layout::ColumnLayout::new(20.0);
-        let sec_focused = vec![false, false, false, false, false, false, false, false];
+        let sec_focused = vec![false, false, false, false, false, false, false];
         let mut ctx = cce_ui::context::UiContext::new();
         let pc = view(&mut state, 10.0, 20.0, 800.0, 600.0, false, &sec_focused, &mut layout, &mut ctx);
         assert!(!pc.rects.is_empty() || !pc.texts.is_empty());
     }
-
-    #[test]
-    fn test_parse_notifications_enable_default() {
-        assert!(parse_notifications_enable(""));
-        assert!(parse_notifications_enable("[layout]\ngap = 18\n"));
-    }
-
-    #[test]
-    fn test_parse_notifications_enable_explicit() {
-        let content = "notifications {\n    enable (bool)false\n}\n";
-        assert!(!parse_notifications_enable(content));
-
-        let content = "notifications {\n    enable (bool)true\n}\n";
-        assert!(parse_notifications_enable(content));
-    }
-
-    #[test]
-    fn test_parse_notifications_enable_other_sections() {
-        let content = "layout {\n    enable (bool)false\n}\nnotifications {\n    enable (bool)true\n}\ninput {\n    enable (bool)false\n}\n";
-        assert!(parse_notifications_enable(content));
-
-        let content = "layout {\n    enable (bool)true\n}\nnotifications {\n    enable (bool)false\n}\ninput {\n    enable (bool)true\n}\n";
-        assert!(!parse_notifications_enable(content));
-    }
-
-    #[test]
-    fn test_parse_notifications_duration_default() {
-        assert_eq!(parse_notifications_duration(""), 5);
-        assert_eq!(parse_notifications_duration("notifications {}"), 5);
-    }
-
-    #[test]
-    fn test_parse_notifications_duration_explicit() {
-        let content = "notifications {\n    duration (i64)10\n}\n";
-        assert_eq!(parse_notifications_duration(content), 10);
-    }
-
-
 }
diff --git a/src/watchers.rs b/src/watchers.rs
index 1b90f5c..317549f 100644
--- a/src/watchers.rs
+++ b/src/watchers.rs
@@ -1,7 +1,7 @@
 use std::sync::Arc;
 use std::sync::atomic::{AtomicU8, Ordering};
 use std::sync::mpsc::{channel, Receiver, Sender};
-use crate::pages::{Page, audio, network, fonts, processes, system_info, storage, packages, accounts};
+use crate::pages::{Page, audio, network, fonts, processes, system_info, storage, packages, accounts, notifications};
 
 pub struct Watchers {
     pub rx_audio: Receiver<audio::AudioState>,
@@ -9,7 +9,7 @@ pub struct Watchers {
     pub rx_processes: Receiver<processes::ProcessesState>,
     pub rx_system: Receiver<system_info::SystemState>,
     pub rx_storage: Receiver<storage::StorageState>,
-    pub rx_notifications: Receiver<system_info::NotificationsConfig>,
+    pub rx_notifications: Receiver<notifications::NotificationsConfig>,
     pub rx_services: Receiver<Vec<processes::ServiceInfo>>,
     pub rx_fonts: Receiver<fonts::FontsState>,
     pub rx_accounts: Receiver<Vec<accounts::AccountInfo>>,
@@ -65,19 +65,19 @@ pub fn spawn_all(
     let rx_storage = spawn_bg_active(current_page_shared.clone(), Page::Storage.index() as u8, 10, || storage::fetch_storage_state());
 
     let rx_notifications = {
-        let (tx, rx) = channel::<system_info::NotificationsConfig>();
+        let (tx, rx) = channel::<notifications::NotificationsConfig>();
         let current_page_shared = current_page_shared.clone();
         tokio::spawn(async move {
             let mut last_fetch: Option<std::time::Instant> = None;
             loop {
                 let current_page = current_page_shared.load(Ordering::SeqCst);
-                if current_page == Page::System.index() as u8 {
+                if current_page == Page::Notifications.index() as u8 {
                     let should_fetch = match last_fetch {
                         None => true,
                         Some(t) => t.elapsed() >= std::time::Duration::from_secs(30),
                     };
                     if should_fetch {
-                        let val = tokio::task::spawn_blocking(|| system_info::read_notifications_config()).await;
+                        let val = tokio::task::spawn_blocking(|| notifications::read_notifications_config()).await;
                         if let Ok(val) = val {
                             if tx.send(val).is_err() { break; }
                         }