system settings
git clone https://git.lucas.co/cce-system-interface.git
Migrate configuration from TOML to JSON
src/pages/display.rs | 162 +++------------
src/pages/input.rs | 156 +++++++--------
src/pages/interface.rs | 529 +++++++++++++------------------------------------
src/pages/layout.rs | 141 +++++--------
src/pages/services.rs | 147 +++-----------
5 files changed, 330 insertions(+), 805 deletions(-)
diff --git a/src/pages/display.rs b/src/pages/display.rs
index d9f6c08..1334f32 100644
--- a/src/pages/display.rs
+++ b/src/pages/display.rs
@@ -2,7 +2,7 @@ use crate::app::{PageContent, SectionContextExt};
use cce_ui::layout::{render_widget, PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Spinbox, Label, Element, Toggle, Dropdown, Slider};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
#[derive(Debug, Clone)]
pub struct DisplayOutput {
@@ -104,152 +104,54 @@ pub enum DisplayMessage {
StartScreensaverPreview,
}
+fn parse_json(content: &str) -> serde_json::Value {
+ serde_json::from_str(content).unwrap_or_default()
+}
+
fn parse_screensaver_enable(content: &str) -> bool {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[screensaver]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("enable") {
- if let Some(val) = trimmed.split('=').nth(1) {
- return val.trim() == "true";
- }
- }
- }
- true // default to true
+ let val = parse_json(content);
+ val["screensaver"]["enable"].as_bool().unwrap_or(true)
}
fn parse_screensaver_lock_screen(content: &str) -> bool {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[screensaver]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("lock_screen") {
- if let Some(val) = trimmed.split('=').nth(1) {
- return val.trim() == "true";
- }
- }
- }
- true // default to true
+ let val = parse_json(content);
+ val["screensaver"]["lock_screen"].as_bool().unwrap_or(true)
}
fn parse_screensaver_timeout(content: &str) -> i32 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[screensaver]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("timeout") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(t) = val.trim().parse::<i32>() {
- return t;
- }
- }
- }
- }
- 10 // default to 10 minutes
+ let val = parse_json(content);
+ val["screensaver"]["timeout"].as_i64().map(|v| v as i32).unwrap_or(10)
}
fn parse_screensaver_style(content: &str) -> String {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[screensaver]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("style") {
- if let Some(val) = trimmed.split('=').nth(1) {
- return val.trim().trim_matches('"').to_string();
- }
- }
- }
- "starfield".to_string() // default to starfield
+ let val = parse_json(content);
+ val["screensaver"]["style"].as_str().unwrap_or("starfield").to_string()
}
pub fn write_config_value(key: &str, value: &str) {
let content = std::fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let mut updated_lines = Vec::new();
- let mut in_section = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[screensaver]" {
- in_section = true;
- updated_lines.push(line.to_string());
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- in_section = false;
- }
- if in_section && trimmed.starts_with(key) {
- found = true;
- updated_lines.push(new_line.clone());
- } else {
- updated_lines.push(line.to_string());
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
+ } else {
+ serde_json::json!(value)
+ };
+ if let Some(screensaver) = val.get_mut("screensaver").and_then(|s| s.as_object_mut()) {
+ screensaver.insert(key.to_string(), j_val);
+ } else {
+ let mut map = serde_json::Map::new();
+ map.insert(key.to_string(), j_val);
+ if let Some(obj) = val.as_object_mut() {
+ obj.insert("screensaver".to_string(), serde_json::Value::Object(map));
}
}
-
- let mut updated = updated_lines.join("\n");
-
- if !found {
- let mut result = String::new();
- let has_section = content.lines().any(|l| l.trim() == "[screensaver]");
- if has_section {
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == "[screensaver]" {
- in_section = true;
- result.push_str(line);
- result.push('\n');
- continue;
- }
- if line.trim().starts_with('[') && in_section {
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- inserted = true;
- }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- }
- updated = result;
- } else {
- updated.push_str("\n[screensaver]\n");
- updated.push_str(&new_line);
- updated.push_str("\n");
- }
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = std::fs::write(CONFIG_PATH, updated_str);
}
- let _ = std::fs::write(CONFIG_PATH, updated);
}
pub async fn fetch_display_state() -> DisplayState {
diff --git a/src/pages/input.rs b/src/pages/input.rs
index e5a202a..132de69 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -5,7 +5,7 @@ use crate::app::PageContent;
use cce_ui::layout::{PageLayoutBuilder, LayoutStrategy};
use cce_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element, TextBox};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
@@ -277,64 +277,61 @@ pub fn read_input_config() -> InputState {
}
}
+fn parse_json(content: &str) -> serde_json::Value {
+ serde_json::from_str(content).unwrap_or_default()
+}
+
+fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
+ if let Some(obj) = val.as_object() {
+ for (_, sec_val) in obj.iter() {
+ if let Some(sec_obj) = sec_val.as_object() {
+ if let Some(v) = sec_obj.get(key) {
+ return Some(v);
+ }
+ }
+ }
+ }
+ None
+}
+
fn parse_bool_from(content: &str, key: &str) -> bool {
- content.lines().find(|l| l.trim().starts_with(key))
- .and_then(|l| l.split('=').nth(1))
- .map(|v| v.trim() == "true")
- .unwrap_or(false)
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_bool()).unwrap_or(false)
}
fn parse_bool_from_default(content: &str, key: &str, default: bool) -> bool {
- content.lines().find(|l| l.trim().starts_with(key))
- .and_then(|l| l.split('=').nth(1))
- .map(|v| v.trim() == "true")
- .unwrap_or(default)
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_bool()).unwrap_or(default)
}
fn parse_u16_key(content: &str, key: &str, default: u16) -> u16 {
- content.lines().find(|l| l.trim().starts_with(key))
- .and_then(|l| l.split('=').nth(1))
- .and_then(|v| v.trim().parse::<u16>().ok())
- .unwrap_or(default)
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_u64()).map(|n| n as u16).unwrap_or(default)
}
fn parse_f32_key(content: &str, key: &str, default: f32) -> f32 {
- content.lines().find(|l| l.trim().starts_with(key))
- .and_then(|l| l.split('=').nth(1))
- .and_then(|v| v.trim().parse::<f32>().ok())
- .unwrap_or(default)
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(default)
}
fn parse_string_key(content: &str, key: &str, default: &str) -> String {
- content.lines().find(|l| l.trim().starts_with(key))
- .and_then(|l| l.split('=').nth(1))
- .map(|v| v.trim().trim_matches('"').to_string())
- .unwrap_or_else(|| default.to_string())
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or_else(|| default.to_string())
}
fn parse_keybinds(content: &str) -> Vec<Keybind> {
+ let val = parse_json(content);
let mut keybinds = Vec::new();
- let mut current: Option<Keybind> = None;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[[keybind]]" {
- if let Some(kb) = current.take() { keybinds.push(kb); }
- current = Some(Keybind {
- mods: String::new(), key: String::new(),
- action: String::new(), command: String::new(),
+ if let Some(arr) = val.get("keybind").and_then(|k| k.as_array()) {
+ for v in arr {
+ keybinds.push(Keybind {
+ mods: v.get("mods").and_then(|m| m.as_str()).unwrap_or("").to_string(),
+ key: v.get("key").and_then(|k| k.as_str()).unwrap_or("").to_string(),
+ action: v.get("action").and_then(|a| a.as_str()).unwrap_or("").to_string(),
+ command: v.get("command").and_then(|c| c.as_str()).unwrap_or("").to_string(),
});
- continue;
- }
- if let Some(ref mut kb) = current {
- let set = |rest: &str| rest.trim_start_matches(|c: char| c == ' ' || c == '=').trim_matches('"').to_string();
- if let Some(rest) = trimmed.strip_prefix("mods") { kb.mods = set(rest); }
- else if let Some(rest) = trimmed.strip_prefix("key") { kb.key = set(rest); }
- else if let Some(rest) = trimmed.strip_prefix("action") { kb.action = set(rest); }
- else if let Some(rest) = trimmed.strip_prefix("command") { kb.command = set(rest); }
}
}
- if let Some(kb) = current.take() { keybinds.push(kb); }
keybinds
}
@@ -346,52 +343,43 @@ fn send_ipc_command(cmd: &str) {
fn write_config_value(key: &str, value: &str) {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let updated: String = content.lines()
- .map(|line| {
- if line.trim().starts_with(key) { found = true; new_line.clone() }
- else { line.to_string() }
- })
- .collect::<Vec<_>>()
- .join("\n");
-
- if !found {
- let section = if key == "zoom_in" || key == "zoom_out" {
- "[graph]"
- } else if key == "tap_to_click" || key == "dwtp"
- || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
- || key == "cursor_theme" || key == "cursor_size" || key == "natural_scroll" {
- "[input]"
- } else if key == "inertial_scroll" || key == "scroll_friction"
- || key == "inertial_pointer" || key == "pointer_friction"
- || key == "inertial_trackpad" || key == "trackpad_friction" || key == "scroll_speed" {
- "[inertial]"
- } else {
- "[repeat]"
- };
-
- let mut result = String::new();
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == section { in_section = true; }
- else if line.trim().starts_with('[') && in_section {
- if !inserted { result.push_str(&new_line); result.push('\n'); inserted = true; }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- if !in_section { result.push('\n'); result.push_str(section); result.push('\n'); }
- result.push_str(&new_line);
- result.push('\n');
- }
- let _ = fs::write(CONFIG_PATH, result);
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
} else {
- let _ = fs::write(CONFIG_PATH, updated);
+ serde_json::json!(value)
+ };
+
+ let section = if key == "zoom_in" || key == "zoom_out" {
+ "graph"
+ } else if key == "tap_to_click" || key == "dwtp"
+ || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
+ || key == "cursor_theme" || key == "cursor_size" || key == "natural_scroll" {
+ "input"
+ } else if key == "inertial_scroll" || key == "scroll_friction"
+ || key == "inertial_pointer" || key == "pointer_friction"
+ || key == "inertial_trackpad" || key == "trackpad_friction" || key == "scroll_speed" {
+ "inertial"
+ } else {
+ "repeat"
+ };
+
+ if let Some(sec_obj) = val.get_mut(section).and_then(|s| s.as_object_mut()) {
+ sec_obj.insert(key.to_string(), j_val);
+ } else {
+ let mut map = serde_json::Map::new();
+ map.insert(key.to_string(), j_val);
+ if let Some(obj) = val.as_object_mut() {
+ obj.insert(section.to_string(), serde_json::Value::Object(map));
+ }
+ }
+
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = fs::write(CONFIG_PATH, updated_str);
}
}
diff --git a/src/pages/interface.rs b/src/pages/interface.rs
index d9f930c..1e04d44 100644
--- a/src/pages/interface.rs
+++ b/src/pages/interface.rs
@@ -6,7 +6,7 @@ use cce_ui::widget::{
ColorSelector, Spinbox, Element, Dropdown, TextBox, FontSelector, Toggle, MultiControl
};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
const LINKS_PATH: &str = "/home/lsgalante/.config/cce/cce-system-interface/links.json";
fn get_socket_path() -> String {
@@ -718,30 +718,38 @@ pub fn read_interface_config() -> InterfaceState {
}
}
+fn parse_json(content: &str) -> serde_json::Value {
+ serde_json::from_str(content).unwrap_or_default()
+}
+
+fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
+ if let Some(obj) = val.as_object() {
+ for (_, sec_val) in obj.iter() {
+ if let Some(sec_obj) = sec_val.as_object() {
+ if let Some(v) = sec_obj.get(key) {
+ return Some(v);
+ }
+ }
+ }
+ }
+ None
+}
+
pub fn parse_string_from(content: &str, key: &str, default: &str) -> String {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=');
- let rest = rest.trim();
- let val_str = if rest.starts_with('"') && rest.ends_with('"') && rest.len() >= 2 {
- &rest[1..rest.len() - 1]
- } else {
- rest
- };
- return val_str.trim().to_string();
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(s) = v.as_str() {
+ return s.to_string();
}
}
default.to_string()
}
fn parse_color_from_key(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- return parse_hex(hex);
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(s) = v.as_str() {
+ return parse_hex(s);
}
}
default
@@ -782,8 +790,8 @@ pub fn get_links_path(path: &str) -> Vec<(String, String)> {
pub fn write_config_value_path(path: &str, key: &str, value: &str) -> bool {
let content = fs::read_to_string(path).unwrap_or_default();
+ let mut val = parse_json(&content);
- // Resolve all linked keys transitively
let mut keys_to_update = vec![key.to_string()];
let links = get_links();
let mut i = 0;
@@ -800,65 +808,55 @@ pub fn write_config_value_path(path: &str, key: &str, value: &str) -> bool {
i += 1;
}
- let mut updated = content.clone();
- for k in &keys_to_update {
- let old_key = if k == key {
- match key {
- "low_color" => "background_color",
- "high_color" => "border_color",
- _ => "",
- }
+ let set_val = |val_obj: &mut serde_json::Value, k: &str, val_str: &str| {
+ let j_val = if let Ok(b) = val_str.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = val_str.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = val_str.parse::<f64>() {
+ serde_json::json!(f)
} else {
- ""
+ serde_json::json!(val_str)
};
- let new_line = format!("{} = {}", k, value);
- let mut found = false;
- let next_update: String = updated.lines()
- .map(|line| {
- let trimmed = line.trim();
- if trimmed.starts_with(k) {
- let rest = trimmed.strip_prefix(k).unwrap_or("");
- let next_char = rest.trim_start().chars().next();
- if next_char == Some('=') {
- found = true;
- new_line.clone()
- } else {
- line.to_string()
- }
- } else if !old_key.is_empty() && trimmed.starts_with(old_key) {
- let rest = trimmed.strip_prefix(old_key).unwrap_or("");
- let next_char = rest.trim_start().chars().next();
- if next_char == Some('=') {
- found = true;
- new_line.clone()
- } else {
- line.to_string()
+
+ let mut updated = false;
+ if let Some(obj) = val_obj.as_object_mut() {
+ for (sec_name, sec_val) in obj.iter_mut() {
+ if let Some(sec_obj) = sec_val.as_object_mut() {
+ if sec_obj.contains_key(k) {
+ sec_obj.insert(k.to_string(), j_val.clone());
+ updated = true;
+ break;
}
- } else {
- line.to_string()
}
- }).collect::<Vec<_>>().join("\n");
-
- if !found {
- let mut result = String::new();
- let mut in_layout = false;
- let mut inserted = false;
- for line in next_update.lines() {
- if line.trim() == "[layout]" { in_layout = true; }
- else if line.trim().starts_with('[') && in_layout {
- if !inserted { result.push_str(&new_line); result.push('\n'); inserted = true; }
- in_layout = false;
+ }
+ if !updated {
+ if let Some(layout_obj) = obj.get_mut("layout").and_then(|l| l.as_object_mut()) {
+ layout_obj.insert(k.to_string(), j_val);
}
- result.push_str(line); result.push('\n');
}
- if in_layout && !inserted { result.push_str(&new_line); result.push('\n'); }
- updated = result;
+ }
+ };
+
+ for k in &keys_to_update {
+ let mapped_k = if k == key {
+ match key {
+ "low_color" => "low_color",
+ "high_color" => "border_color",
+ _ => k,
+ }
} else {
- updated = next_update;
+ k
+ };
+ set_val(&mut val, mapped_k, value);
+ }
+
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ if fs::write(path, updated_str).is_ok() {
+ return true;
}
}
-
- fs::write(path, updated).is_ok()
+ false
}
pub fn propagate_links(state: &mut InterfaceState, key: &str, val_str: &str) {
@@ -1671,56 +1669,40 @@ pub fn save_preferred_fonts(
}
pub fn parse_i16_from(content: &str, key: &str, default: i16) -> i16 {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let val_str = rest.trim_end_matches('"').trim();
- if let Ok(val) = val_str.parse::<i16>() {
- return val;
- }
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(n) = v.as_i64() {
+ return n as i16;
}
}
default
}
pub fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let val_str = rest.trim_end_matches('"').trim();
- if let Ok(val) = val_str.parse::<u16>() {
- return val;
- }
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(n) = v.as_u64() {
+ return n as u16;
}
}
default
}
pub fn parse_bool_from(content: &str, key: &str, default: bool) -> bool {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let val_str = rest.trim_end_matches('"').trim();
- if let Ok(val) = val_str.parse::<bool>() {
- return val;
- }
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(b) = v.as_bool() {
+ return b;
}
}
default
}
pub fn parse_f32_from(content: &str, key: &str, default: f32) -> f32 {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let val_str = rest.trim_end_matches('"').trim();
- if let Ok(val) = val_str.parse::<f32>() {
- return val;
- }
+ let val = parse_json(content);
+ if let Some(v) = json_find_key(&val, key) {
+ if let Some(n) = v.as_f64() {
+ return n as f32;
}
}
default
@@ -3179,91 +3161,28 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
}
pub fn parse_transparency_opacity(content: &str) -> f32 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[transparency]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("opacity") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(o) = val.trim().parse::<f32>() {
- return o.clamp(0.0, 1.0);
- }
- }
- }
- }
- 0.9 // default to 0.9
+ let val = parse_json(content);
+ val["transparency"]["opacity"].as_f64().map(|v| v as f32).unwrap_or(0.9)
}
pub fn write_transparency_config_value(key: &str, value: &str) {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let mut updated_lines = Vec::new();
- let mut in_section = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[transparency]" {
- in_section = true;
- updated_lines.push(line.to_string());
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- in_section = false;
- }
- if in_section && trimmed.starts_with(key) {
- found = true;
- updated_lines.push(new_line.clone());
- } else {
- updated_lines.push(line.to_string());
- }
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
+ } else {
+ serde_json::json!(value)
+ };
+ if let Some(transparency) = val.get_mut("transparency").and_then(|t| t.as_object_mut()) {
+ transparency.insert(key.to_string(), j_val);
}
-
- let mut updated = updated_lines.join("\n");
-
- if !found {
- let mut result = String::new();
- let has_section = content.lines().any(|l| l.trim() == "[transparency]");
- if has_section {
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == "[transparency]" {
- in_section = true;
- result.push_str(line);
- result.push('\n');
- continue;
- }
- if line.trim().starts_with('[') && in_section {
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- inserted = true;
- }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- }
- updated = result;
- } else {
- updated.push_str("\n[transparency]\n");
- updated.push_str(&new_line);
- updated.push_str("\n");
- }
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = fs::write(CONFIG_PATH, updated_str);
}
- let _ = fs::write(CONFIG_PATH, updated);
}
fn write_surfaces_config_value(key: &str, value: &str) {
@@ -3272,178 +3191,56 @@ fn write_surfaces_config_value(key: &str, value: &str) {
fn write_surfaces_config_value_path(path: &str, key: &str, value: &str) {
let content = fs::read_to_string(path).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let mut updated_lines = Vec::new();
- let mut in_section = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[surfaces]" {
- in_section = true;
- updated_lines.push(line.to_string());
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- in_section = false;
- }
- if in_section && trimmed.starts_with(key) {
- found = true;
- updated_lines.push(new_line.clone());
- } else {
- updated_lines.push(line.to_string());
- }
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
+ } else {
+ serde_json::json!(value)
+ };
+ if let Some(surfaces) = val.get_mut("surfaces").and_then(|s| s.as_object_mut()) {
+ surfaces.insert(key.to_string(), j_val);
}
-
- let mut updated = updated_lines.join("\n");
-
- if !found {
- let mut result = String::new();
- let has_section = content.lines().any(|l| l.trim() == "[surfaces]");
- if has_section {
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == "[surfaces]" {
- in_section = true;
- result.push_str(line);
- result.push('\n');
- continue;
- }
- if line.trim().starts_with('[') && in_section {
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- inserted = true;
- }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- }
- updated = result;
- } else {
- updated.push_str("\n[surfaces]\n");
- updated.push_str(&new_line);
- updated.push_str("\n");
- }
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = fs::write(path, updated_str);
}
- let _ = fs::write(path, updated);
}
fn parse_surfaces_color(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[surfaces]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with(key) {
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- return parse_hex(hex);
- }
- }
+ let val = parse_json(content);
+ if let Some(s) = val["surfaces"].get(key).and_then(|v| v.as_str()) {
+ return parse_hex(s);
}
default
}
fn parse_surfaces_opacity(content: &str) -> f32 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[surfaces]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("window_opacity") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(o) = val.trim().parse::<f32>() {
- return o.clamp(0.0, 1.0);
- }
- }
- }
- }
- 0.9 // default to 0.9
+ let val = parse_json(content);
+ val["surfaces"]["window_opacity"].as_f64().map(|v| v as f32).unwrap_or(0.9)
}
fn parse_surfaces_u16(content: &str, key: &str, default: u16) -> u16 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[surfaces]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with(key) {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(v) = val.trim().parse::<u16>() {
- return v;
- }
- }
- }
+ let val = parse_json(content);
+ if let Some(n) = val["surfaces"].get(key).and_then(|v| v.as_u64()) {
+ return n as u16;
}
default
}
fn parse_notifications_color(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with(key) {
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- let hex = rest.trim_end_matches('"').trim().trim_start_matches('#');
- return parse_hex(hex);
- }
- }
+ let val = parse_json(content);
+ if let Some(s) = val["notifications"].get(key).and_then(|v| v.as_str()) {
+ return parse_hex(s);
}
default
}
fn parse_notifications_opacity(content: &str) -> f32 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("opacity") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(o) = val.trim().parse::<f32>() {
- return o.clamp(0.0, 1.0);
- }
- }
- }
- }
- 0.9 // default to 0.9
+ let val = parse_json(content);
+ val["notifications"]["opacity"].as_f64().map(|v| v as f32).unwrap_or(0.9)
}
fn write_notifications_config_value(key: &str, value: &str) {
@@ -3452,68 +3249,22 @@ fn write_notifications_config_value(key: &str, value: &str) {
fn write_notifications_config_value_path(path: &str, key: &str, value: &str) {
let content = fs::read_to_string(path).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let mut updated_lines = Vec::new();
- let mut in_section = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- updated_lines.push(line.to_string());
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- in_section = false;
- }
- if in_section && trimmed.starts_with(key) {
- found = true;
- updated_lines.push(new_line.clone());
- } else {
- updated_lines.push(line.to_string());
- }
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
+ } else {
+ serde_json::json!(value)
+ };
+ if let Some(notifications) = val.get_mut("notifications").and_then(|n| n.as_object_mut()) {
+ notifications.insert(key.to_string(), j_val);
}
-
- let mut updated = updated_lines.join("\n");
-
- if !found {
- let mut result = String::new();
- let has_section = content.lines().any(|l| l.trim() == "[notifications]");
- if has_section {
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == "[notifications]" {
- in_section = true;
- result.push_str(line);
- result.push('\n');
- continue;
- }
- if line.trim().starts_with('[') && in_section {
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- inserted = true;
- }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- }
- updated = result;
- } else {
- updated.push_str("\n[notifications]\n");
- updated.push_str(&new_line);
- updated.push_str("\n");
- }
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = fs::write(path, updated_str);
}
- let _ = fs::write(path, updated);
}
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index 8e166ff..237f5b2 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -7,7 +7,7 @@ use cce_ui::widget::{Spinbox, Dropdown, LayoutPreview, PreviewLayoutMode, Toggle
use crate::pages::interface::{parse_bool_from, parse_transparency_opacity, write_transparency_config_value, status_interface_reload};
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
@@ -247,44 +247,36 @@ pub fn read_layout_config() -> LayoutState {
}
}
-fn parse_tag_layouts_from_config(content: &str) -> Vec<String> {
- let mut modes = vec!["cascade".to_string(); 4];
- let mut current_tag = None;
- let mut current_mode = None;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[[tag_layout]]" {
- if let (Some(tag), Some(mode)) = (current_tag, current_mode.take()) {
- if tag >= 1 && tag <= 4 {
- modes[tag - 1] = mode;
+fn parse_json(content: &str) -> serde_json::Value {
+ serde_json::from_str(content).unwrap_or_default()
+}
+
+fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
+ if let Some(obj) = val.as_object() {
+ for (_, sec_val) in obj.iter() {
+ if let Some(sec_obj) = sec_val.as_object() {
+ if let Some(v) = sec_obj.get(key) {
+ return Some(v);
}
}
- current_tag = None;
- continue;
}
- if trimmed.starts_with('[') && !trimmed.starts_with("[[") {
- if let (Some(tag), Some(mode)) = (current_tag, current_mode.take()) {
+ }
+ None
+}
+
+fn parse_tag_layouts_from_config(content: &str) -> Vec<String> {
+ let val = parse_json(content);
+ let mut modes = vec!["cascade".to_string(); 4];
+ if let Some(arr) = val.get("tag_layout").and_then(|t| t.as_array()) {
+ for item in arr {
+ if let (Some(tag), Some(mode)) = (
+ item.get("tag").and_then(|t| t.as_u64()),
+ item.get("mode").and_then(|m| m.as_str())
+ ) {
if tag >= 1 && tag <= 4 {
- modes[tag - 1] = mode;
+ modes[tag as usize - 1] = mode.to_string();
}
}
- current_tag = None;
- continue;
- }
- if let Some(rest) = trimmed.strip_prefix("tag") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=').trim();
- if let Ok(tag) = rest.parse::<usize>() {
- current_tag = Some(tag);
- }
- } else if let Some(rest) = trimmed.strip_prefix("mode") {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=').trim().trim_matches('"').to_string();
- current_mode = Some(rest);
- }
- }
- if let (Some(tag), Some(mode)) = (current_tag, current_mode.take()) {
- if tag >= 1 && tag <= 4 {
- modes[tag - 1] = mode;
}
}
modes
@@ -292,74 +284,49 @@ fn parse_tag_layouts_from_config(content: &str) -> Vec<String> {
fn write_tag_layout(tag_num: usize, mode_str: &str) -> bool {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let mut lines: Vec<String> = content.lines().map(|s| s.to_string()).collect();
+ let mut val = parse_json(&content);
- let mut i = 0;
- while i < lines.len() {
- let line = lines[i].trim();
- if line == "[[tag_layout]]" {
- let mut tag_val = None;
- let mut mode_line_idx = None;
-
- let mut j = i + 1;
- while j < lines.len() {
- let next_line = lines[j].trim();
- if next_line.starts_with("[[") || (next_line.starts_with('[') && !next_line.starts_with("[[")) {
+ let mut found = false;
+ if let Some(arr) = val.get_mut("tag_layout").and_then(|t| t.as_array_mut()) {
+ for item in arr.iter_mut() {
+ if item.get("tag").and_then(|t| t.as_u64()) == Some(tag_num as u64) {
+ if let Some(obj) = item.as_object_mut() {
+ obj.insert("mode".to_string(), serde_json::json!(mode_str.to_lowercase()));
+ found = true;
break;
}
- if next_line.starts_with("tag") {
- if let Some(val_str) = next_line.split('=').nth(1) {
- if let Ok(v) = val_str.trim().parse::<usize>() {
- tag_val = Some(v);
- }
- }
- } else if next_line.starts_with("mode") {
- mode_line_idx = Some(j);
- }
- j += 1;
- }
-
- if tag_val == Some(tag_num) {
- if let Some(idx) = mode_line_idx {
- lines[idx] = format!("mode = \"{}\"", mode_str.to_lowercase());
- let result = lines.join("\n") + "\n";
- return fs::write(CONFIG_PATH, result).is_ok();
- }
}
- i = j;
- } else {
- i += 1;
+ }
+ if !found {
+ arr.push(serde_json::json!({
+ "tag": tag_num,
+ "mode": mode_str.to_lowercase()
+ }));
+ }
+ } else {
+ let arr = vec![serde_json::json!({
+ "tag": tag_num,
+ "mode": mode_str.to_lowercase()
+ })];
+ if let Some(obj) = val.as_object_mut() {
+ obj.insert("tag_layout".to_string(), serde_json::Value::Array(arr));
}
}
- let mut result = lines.join("\n");
- if !result.ends_with('\n') {
- result.push('\n');
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ return fs::write(CONFIG_PATH, updated_str).is_ok();
}
- result.push_str(&format!("\n[[tag_layout]]\ntag = {}\nmode = \"{}\"\n", tag_num, mode_str.to_lowercase()));
- fs::write(CONFIG_PATH, result).is_ok()
+ false
}
fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- return rest.trim_end_matches('"').trim().parse::<u16>().unwrap_or(default);
- }
- }
- default
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_u64()).map(|n| n as u16).unwrap_or(default)
}
fn parse_string_from(content: &str, key: &str, default: &str) -> String {
- for line in content.lines() {
- let trimmed = line.trim();
- if let Some(rest) = trimmed.strip_prefix(key) {
- let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
- return rest.trim_end_matches('"').trim().to_string();
- }
- }
- default.to_string()
+ let val = parse_json(content);
+ json_find_key(&val, key).and_then(|v| v.as_str()).map(|s| s.to_string()).unwrap_or_else(|| default.to_string())
}
fn write_config_value(key: &str, value: &str) -> bool {
diff --git a/src/pages/services.rs b/src/pages/services.rs
index a154df7..12348fe 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -598,7 +598,7 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
// ── Notifications Configuration Reader & Writer ──
-const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
+const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.json";
fn get_socket_path() -> String {
match std::env::var("WAYLAND_DISPLAY") {
@@ -619,66 +619,23 @@ pub fn read_notifications_config() -> NotificationsConfig {
}
}
+fn parse_json(content: &str) -> serde_json::Value {
+ serde_json::from_str(content).unwrap_or_default()
+}
+
fn parse_notifications_enable(content: &str) -> bool {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("enable") {
- if let Some(val) = trimmed.split('=').nth(1) {
- return val.trim() == "true";
- }
- }
- }
- true // default to true
+ let val = parse_json(content);
+ val["notifications"]["enable"].as_bool().unwrap_or(true)
}
fn parse_notifications_bell(content: &str) -> bool {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("bell") {
- if let Some(val) = trimmed.split('=').nth(1) {
- return val.trim() == "true";
- }
- }
- }
- false // default to false
+ let val = parse_json(content);
+ val["notifications"]["bell"].as_bool().unwrap_or(false)
}
fn parse_notifications_duration(content: &str) -> i32 {
- let mut in_section = false;
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- break;
- }
- if in_section && trimmed.starts_with("duration") {
- if let Some(val) = trimmed.split('=').nth(1) {
- if let Ok(d) = val.trim().parse::<i32>() {
- return d;
- }
- }
- }
- }
- 5 // default to 5 seconds
+ let val = parse_json(content);
+ val["notifications"]["duration"].as_i64().map(|v| v as i32).unwrap_or(5)
}
fn send_ipc_command(cmd: &str) {
@@ -689,68 +646,28 @@ fn send_ipc_command(cmd: &str) {
fn write_config_value(key: &str, value: &str) {
let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
- let new_line = format!("{} = {}", key, value);
-
- let mut found = false;
- let mut updated_lines = Vec::new();
- let mut in_section = false;
-
- for line in content.lines() {
- let trimmed = line.trim();
- if trimmed == "[notifications]" {
- in_section = true;
- updated_lines.push(line.to_string());
- continue;
- }
- if trimmed.starts_with('[') && in_section {
- in_section = false;
- }
- if in_section && trimmed.starts_with(key) {
- found = true;
- updated_lines.push(new_line.clone());
- } else {
- updated_lines.push(line.to_string());
+ let mut val = parse_json(&content);
+ let j_val = if let Ok(b) = value.parse::<bool>() {
+ serde_json::json!(b)
+ } else if let Ok(n) = value.parse::<i64>() {
+ serde_json::json!(n)
+ } else if let Ok(f) = value.parse::<f64>() {
+ serde_json::json!(f)
+ } else {
+ serde_json::json!(value)
+ };
+ if let Some(notifications) = val.get_mut("notifications").and_then(|n| n.as_object_mut()) {
+ notifications.insert(key.to_string(), j_val);
+ } else {
+ let mut map = serde_json::Map::new();
+ map.insert(key.to_string(), j_val);
+ if let Some(obj) = val.as_object_mut() {
+ obj.insert("notifications".to_string(), serde_json::Value::Object(map));
}
}
-
- let mut updated = updated_lines.join("\n");
-
- if !found {
- let mut result = String::new();
- let has_section = content.lines().any(|l| l.trim() == "[notifications]");
- if has_section {
- let mut in_section = false;
- let mut inserted = false;
- for line in updated.lines() {
- if line.trim() == "[notifications]" {
- in_section = true;
- result.push_str(line);
- result.push('\n');
- continue;
- }
- if line.trim().starts_with('[') && in_section {
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- inserted = true;
- }
- in_section = false;
- }
- result.push_str(line);
- result.push('\n');
- }
- if !inserted {
- result.push_str(&new_line);
- result.push('\n');
- }
- updated = result;
- } else {
- updated.push_str("\n[notifications]\n");
- updated.push_str(&new_line);
- updated.push_str("\n");
- }
+ if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
+ let _ = fs::write(CONFIG_PATH, updated_str);
}
- let _ = fs::write(CONFIG_PATH, updated);
}
fn write_enable_notifications(enabled: bool) {
@@ -770,12 +687,12 @@ fn get_config_path() -> String {
if let Some(path) = p.borrow().as_ref() {
return path.clone();
}
- "/home/lsgalante/.config/cce/config.toml".to_string()
+ "/home/lsgalante/.config/cce/config.json".to_string()
})
}
#[cfg(not(test))]
{
- "/home/lsgalante/.config/cce/config.toml".to_string()
+ "/home/lsgalante/.config/cce/config.json".to_string()
}
}