system settings
git clone https://git.lucas.co/cce-system-interface.git
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; }
}