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

commit6721e8dc0c99d1ce7f2140314724140fb28dd32f
parent125521b2ed
authorLucas Galante <[email protected]>
date2026-06-18 14:55
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()
     }
 }