git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

commitf6dde96dacb9fd20ddc00b58d177daa253e3788a
parentf2cfb674e6
authorLucas Galante <[email protected]>
date2026-06-29 12:02
Support XDG Base Directory specification for config paths

 src/color.rs                         | 18 +++++++-----------
 src/config.rs                        | 33 ++++++++++++++++++++++++++-------
 src/layout.rs                        | 10 +++-------
 src/widget/display/graph.rs          | 21 ++++++++-------------
 src/widget/input/keybinds_control.rs | 12 ++++++++----
 src/widget/input/multi_control.rs    |  9 +++++++--
 6 files changed, 59 insertions(+), 44 deletions(-)

diff --git a/src/color.rs b/src/color.rs
index 9ca5b43..4539bc0 100644
--- a/src/color.rs
+++ b/src/color.rs
@@ -79,13 +79,9 @@ pub fn node_drag_color() -> [f32; 4] {
 }
 
 fn read_config() -> Option<String> {
-    let paths = [
-        "/home/lsgalante/.config/cce/config.json",
-    ];
-    for path in &paths {
-        if let Ok(content) = std::fs::read_to_string(path) {
-            return Some(content);
-        }
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        return Some(content);
     }
     None
 }
@@ -530,8 +526,8 @@ pub fn active_window_mode() -> String {
         return "floating".to_string();
     }
 
-    let config_path = "/home/lsgalante/.config/cce/config.json";
-    let content = match std::fs::read_to_string(config_path) {
+    let config_path = crate::config::get_config_path();
+    let content = match std::fs::read_to_string(&config_path) {
         Ok(c) => c,
         Err(_) => return "floating".to_string(),
     };
@@ -573,8 +569,8 @@ pub fn active_window_mode() -> String {
 
 pub fn active_backplate_opacity() -> f32 {
     let mode = active_window_mode();
-    let config_path = "/home/lsgalante/.config/cce/config.json";
-    let content = match std::fs::read_to_string(config_path) {
+    let config_path = crate::config::get_config_path();
+    let content = match std::fs::read_to_string(&config_path) {
         Ok(c) => c,
         Err(_) => return 0.9,
     };
diff --git a/src/config.rs b/src/config.rs
index 2855618..c0ad2ff 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -34,30 +34,49 @@ pub fn update_json_in_memory(val_obj: &mut Value, key: &str, value: &str, defaul
     updated
 }
 
+pub fn get_config_path() -> std::path::PathBuf {
+    let dir = if let Ok(xdg_config) = std::env::var("XDG_CONFIG_HOME") {
+        if !xdg_config.is_empty() {
+            std::path::PathBuf::from(xdg_config)
+        } else {
+            let home = std::env::var("HOME").unwrap_or_else(|_| "/home/lsgalante".to_string());
+            std::path::PathBuf::from(home).join(".config")
+        }
+    } else {
+        let home = std::env::var("HOME").unwrap_or_else(|_| "/home/lsgalante".to_string());
+        std::path::PathBuf::from(home).join(".config")
+    };
+    dir.join("cce").join("config.json")
+}
+
 fn perform_rolling_backup(path: &str) {
-    if path != "/home/lsgalante/.config/cce/config.json" {
+    let config_path = get_config_path();
+    if std::path::Path::new(path) != config_path {
         return;
     }
     if !std::path::Path::new(path).exists() {
         return;
     }
-    let backup_dir = "/home/lsgalante/.config/cce/backups";
-    if let Err(_) = fs::create_dir_all(backup_dir) {
+    let backup_dir = config_path.parent().unwrap().join("backups");
+    if let Err(_) = fs::create_dir_all(&backup_dir) {
         return;
     }
     for i in (1..=4).rev() {
-        let src = format!("{}/config.json.{}.bak", backup_dir, i);
-        let dst = format!("{}/config.json.{}.bak", backup_dir, i + 1);
-        if std::path::Path::new(&src).exists() {
+        let src = backup_dir.join(format!("config.json.{}.bak", i));
+        let dst = backup_dir.join(format!("config.json.{}.bak", i + 1));
+        if src.exists() {
             let _ = fs::rename(src, dst);
         }
     }
-    let dst = format!("{}/config.json.1.bak", backup_dir);
+    let dst = backup_dir.join("config.json.1.bak");
     let _ = fs::copy(path, dst);
 }
 
 fn safe_write(path: &str, content: &str) -> bool {
     perform_rolling_backup(path);
+    if let Some(parent) = std::path::Path::new(path).parent() {
+        let _ = fs::create_dir_all(parent);
+    }
     let temp_path = format!("{}.tmp", path);
     if fs::write(&temp_path, content).is_ok() {
         if fs::rename(&temp_path, path).is_ok() {
diff --git a/src/layout.rs b/src/layout.rs
index f6c6fcb..c989b30 100644
--- a/src/layout.rs
+++ b/src/layout.rs
@@ -3,12 +3,9 @@ use crate::context::UiContext;
 use std::sync::RwLock;
 
 fn read_config() -> Option<String> {
-    let paths = [
-        "/home/lsgalante/.config/cce/config.json",
-    ];
-    for path in &paths {
-        if let Ok(content) = std::fs::read_to_string(path) {
-            if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
                 let mut toml_like = String::new();
                 if let Some(layout) = val.get("layout").and_then(|l| l.as_object()) {
                     for (k, v) in layout {
@@ -52,7 +49,6 @@ fn read_config() -> Option<String> {
                     }
                 }
                 return Some(toml_like);
-            }
         }
     }
     None
diff --git a/src/widget/display/graph.rs b/src/widget/display/graph.rs
index cd1b220..8fd492b 100644
--- a/src/widget/display/graph.rs
+++ b/src/widget/display/graph.rs
@@ -197,20 +197,15 @@ impl Graph {
 fn read_zoom_bindings() -> (String, String) {
     let mut zoom_in_val = "=".to_string();
     let mut zoom_out_val = "-".to_string();
-    let paths = [
-        "/home/lsgalante/.config/cce/config.json",
-    ];
-    for path in &paths {
-        if let Ok(content) = std::fs::read_to_string(path) {
-            if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
-                if let Some(zoom_in) = val.pointer("/layout/zoom_in").and_then(|v| v.as_str()) {
-                    zoom_in_val = zoom_in.to_string();
-                }
-                if let Some(zoom_out) = val.pointer("/layout/zoom_out").and_then(|v| v.as_str()) {
-                    zoom_out_val = zoom_out.to_string();
-                }
+    let path = crate::config::get_config_path();
+    if let Ok(content) = std::fs::read_to_string(&path) {
+        if let Ok(val) = serde_json::from_str::<serde_json::Value>(&content) {
+            if let Some(zoom_in) = val.pointer("/layout/zoom_in").and_then(|v| v.as_str()) {
+                zoom_in_val = zoom_in.to_string();
+            }
+            if let Some(zoom_out) = val.pointer("/layout/zoom_out").and_then(|v| v.as_str()) {
+                zoom_out_val = zoom_out.to_string();
             }
-            break;
         }
     }
     (zoom_in_val, zoom_out_val)
diff --git a/src/widget/input/keybinds_control.rs b/src/widget/input/keybinds_control.rs
index 1a14a19..219509e 100644
--- a/src/widget/input/keybinds_control.rs
+++ b/src/widget/input/keybinds_control.rs
@@ -53,7 +53,8 @@ impl KeybindsControl {
     }
 
     pub fn load_from_config(&mut self) {
-        let content = std::fs::read_to_string("/home/lsgalante/.config/cce/config.json").unwrap_or_default();
+        let path = crate::config::get_config_path();
+        let content = std::fs::read_to_string(&path).unwrap_or_default();
         let val: serde_json::Value = serde_json::from_str(&content).unwrap_or_default();
         let mut loaded = Vec::new();
         if let Some(arr) = val.get("keybind").and_then(|k| k.as_array()) {
@@ -82,8 +83,11 @@ impl KeybindsControl {
     }
 
     pub fn save_to_config(&self) {
-        let path = "/home/lsgalante/.config/cce/config.json";
-        let content = std::fs::read_to_string(path).unwrap_or_default();
+        let path = crate::config::get_config_path();
+        if let Some(parent) = path.parent() {
+            let _ = std::fs::create_dir_all(parent);
+        }
+        let content = std::fs::read_to_string(&path).unwrap_or_default();
         let mut val: serde_json::Value = serde_json::from_str(&content).unwrap_or_default();
         
         let mut keybinds = Vec::new();
@@ -123,7 +127,7 @@ impl KeybindsControl {
         }
         
         if let Ok(updated_str) = serde_json::to_string_pretty(&val) {
-            let _ = std::fs::write(path, updated_str);
+            let _ = std::fs::write(&path, updated_str);
         }
     }
 }
diff --git a/src/widget/input/multi_control.rs b/src/widget/input/multi_control.rs
index 2b88da8..248002e 100644
--- a/src/widget/input/multi_control.rs
+++ b/src/widget/input/multi_control.rs
@@ -961,8 +961,13 @@ impl Element for MultiControl {
 impl Control for MultiControl {}
 
 fn get_application_config_path() -> std::path::PathBuf {
-    let home = std::env::var("HOME").unwrap_or_else(|_| "/home/lsgalante".to_string());
-    let base_dir = std::path::PathBuf::from(home).join(".config").join("cce");
+    let base_dir = crate::config::get_config_path()
+        .parent()
+        .map(|p| p.to_path_buf())
+        .unwrap_or_else(|| {
+            let home = std::env::var("HOME").unwrap_or_else(|_| "/home/lsgalante".to_string());
+            std::path::PathBuf::from(home).join(".config").join("cce")
+        });
 
     let mut app_name = std::env::current_exe()
         .ok()