git.lucas.co / cce-files
file manager
git clone https://git.lucas.co/cce-files.git

commit84f354ed5a3e3dc73f3885c335d255d5cdd0abf8
parent47dd3e0f0b
authorLucas Galante <[email protected]>
date2026-06-29 15:16
Support custom MIME associations via ~/.config/cce/mime.kdl

 Cargo.toml         |  2 ++
 src/main.rs        |  8 ++----
 src/services/fs.rs | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 79 insertions(+), 7 deletions(-)

diff --git a/Cargo.toml b/Cargo.toml
index 54da47b..570f53d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,6 +15,8 @@ tokio = { version = "1", features = ["full"] }
 notify = "8.2.0"
 image = { version = "0.25.10", features = ["png", "jpeg"] }
 log = "0.4"
+kdl = "6.7.1"
+
 
 
 [lib]
diff --git a/src/main.rs b/src/main.rs
index 8fec0a3..85ca716 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -715,9 +715,7 @@ impl Application for FilesystemApp {
                                 println!("{}", path.display());
                                 std::process::exit(0);
                             } else {
-                                let _ = std::process::Command::new("xdg-open")
-                                    .arg(&path)
-                                    .spawn();
+                                crate::services::fs::open_file(&path);
                             }
                         }
                     } else {
@@ -734,9 +732,7 @@ impl Application for FilesystemApp {
                                     println!("{}", path.display());
                                     std::process::exit(0);
                                 } else {
-                                    let _ = std::process::Command::new("xdg-open")
-                                        .arg(&path)
-                                        .spawn();
+                                    crate::services::fs::open_file(&path);
                                 }
                             }
                         }
diff --git a/src/services/fs.rs b/src/services/fs.rs
index 3fc240b..b4e7b97 100644
--- a/src/services/fs.rs
+++ b/src/services/fs.rs
@@ -401,8 +401,54 @@ pub fn get_mime_type(path: &Path) -> Option<String> {
     None
 }
 
+pub fn load_kdl_associations() -> Option<std::collections::HashMap<String, String>> {
+    let home = std::env::var("HOME").ok()?;
+    let path = PathBuf::from(home).join(".config").join("cce").join("mime.kdl");
+    if !path.exists() {
+        return None;
+    }
+    let content = fs::read_to_string(path).ok()?;
+    let doc: kdl::KdlDocument = content.parse().ok()?;
+    let mut map = std::collections::HashMap::new();
+
+    if let Some(associations_node) = doc.get("associations") {
+        for child in associations_node.iter_children() {
+            if child.name().value() == "association" {
+                let mime = child.get(0)
+                    .or_else(|| child.get("mime"))
+                    .and_then(|val| match val {
+                        kdl::KdlValue::String(s) => Some(s.clone()),
+                        _ => None,
+                    });
+                let exec = child.get(1)
+                    .or_else(|| child.get("exec"))
+                    .and_then(|val| match val {
+                        kdl::KdlValue::String(s) => Some(s.clone()),
+                        _ => None,
+                    });
+                if let (Some(m), Some(e)) = (mime, exec) {
+                    map.insert(m, e);
+                }
+            }
+        }
+    }
+
+    if map.is_empty() {
+        None
+    } else {
+        Some(map)
+    }
+}
+
 pub fn get_default_application(mime: &str) -> Option<(String, String)> {
-    // Query default handler desktop file name
+    // 1. Check KDL configuration file override
+    if let Some(associations) = load_kdl_associations() {
+        if let Some(custom_exec) = associations.get(mime) {
+            return Some((custom_exec.clone(), custom_exec.clone()));
+        }
+    }
+
+    // 2. Query default handler desktop file name
     let output = std::process::Command::new("xdg-mime")
         .args(&["query", "default", mime])
         .output()
@@ -463,3 +509,31 @@ pub fn get_default_application(mime: &str) -> Option<(String, String)> {
     }
 }
 
+pub fn open_file(path: &Path) {
+    let mut opened = false;
+    if let Some(mime) = get_mime_type(path) {
+        if let Some((_, cmd)) = get_default_application(&mime) {
+            if !cmd.is_empty() {
+                let parts: Vec<&str> = cmd.split_whitespace().collect();
+                if !parts.is_empty() {
+                    let program = parts[0];
+                    let mut command = std::process::Command::new(program);
+                    for arg in &parts[1..] {
+                        command.arg(arg);
+                    }
+                    command.arg(path);
+                    if command.spawn().is_ok() {
+                        opened = true;
+                    }
+                }
+            }
+        }
+    }
+    if !opened {
+        let _ = std::process::Command::new("xdg-open")
+            .arg(path)
+            .spawn();
+    }
+}
+
+