git.lucas.co / cce-status-interface
status bar
git clone https://git.lucas.co/cce-status-interface.git

commit52db397f1a289ef2a46a2f82e88b547f0ec9ba22
parent08dd5b1b47
authorLucas Galante <[email protected]>
date2026-07-13 15:48
refactor: split main.rs into tray/cloud/stats/config/listeners modules (proposal phase 1)

Mechanical moves only — no logic changes:
- tray.rs: SNI watcher/host D-Bus interfaces, item fetching, icon loading
- cloud.rs: cce-cloud popups (DBusMenu proxy, menu paging, window picker)
- stats.rs: /proc//sys/pactl readers and the stats polling task
- config.rs: cached KDL config lookup, color/font/dimension readers,
  cce/ccectl/cce-cloud binary resolution
- listeners.rs: compositor status-feed and switcher-trigger sockets

Moved items are re-exported at the crate root so all call sites keep
their pre-split names. The window-picker spawn body left
trigger_switcher as cloud::spawn_window_picker with the source string
as a parameter — the only signature change in the split.
main.rs: 3672 -> 2172 lines.

Co-Authored-By: Claude Fable 5 <[email protected]>

 src/cloud.rs     |  484 +++++++++++++++++
 src/config.rs    |  242 +++++++++
 src/listeners.rs |   75 +++
 src/main.rs      | 1532 +-----------------------------------------------------
 src/stats.rs     |  209 ++++++++
 src/tray.rs      |  543 +++++++++++++++++++
 6 files changed, 1569 insertions(+), 1516 deletions(-)

diff --git a/src/cloud.rs b/src/cloud.rs
new file mode 100644
index 0000000..040badd
--- /dev/null
+++ b/src/cloud.rs
@@ -0,0 +1,484 @@
+//! cce-cloud popups: the window picker and D-Bus menus rendered by spawning
+//! a `cce-cloud` process fed JSON pages on stdin.
+
+use crate::{parse_ccectl_windows, CustomEvent};
+use crate::config::{get_cce_cloud_cmd, get_ccectl_cmd};
+
+#[zbus::proxy(
+    interface = "com.canonical.dbusmenu",
+    default_path = "/StatusNotifierItem/menu"
+)]
+pub(crate) trait DBusMenu {
+    fn get_layout(
+        &self,
+        parent_id: i32,
+        recursion_depth: i32,
+        property_names: Vec<String>,
+    ) -> zbus::Result<(u32, (i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>))>;
+
+    fn event(
+        &self,
+        id: i32,
+        event_id: &str,
+        data: &zbus::zvariant::Value<'_>,
+        timestamp: u32,
+    ) -> zbus::Result<()>;
+
+    fn about_to_show(&self, id: i32) -> zbus::Result<bool>;
+}
+
+pub(crate) struct MenuItem {
+    id: i32,
+    label: String,
+    enabled: bool,
+    is_separator: bool,
+    toggle_state: i32, // -1 if not toggleable, 0 if unchecked, 1 if checked
+    children: Vec<MenuItem>,
+}
+
+pub(crate) fn parse_menu_item(
+    id: i32,
+    mut properties: std::collections::HashMap<String, zbus::zvariant::OwnedValue>,
+    children_vals: Vec<zbus::zvariant::OwnedValue>,
+) -> Option<MenuItem> {
+    let type_: String = properties.remove("type")
+        .and_then(|v| {
+            let s: Result<String, _> = v.try_into();
+            s.ok()
+        })
+        .unwrap_or_default();
+    let is_separator = type_ == "separator";
+
+    let label: String = properties.remove("label")
+        .and_then(|v| {
+            let s: Result<String, _> = v.try_into();
+            s.ok()
+        })
+        .unwrap_or_default();
+
+    let enabled: bool = properties.remove("enabled")
+        .and_then(|v| {
+            let b: Result<bool, _> = v.try_into();
+            b.ok()
+        })
+        .unwrap_or(true);
+
+    let toggle_state: i32 = properties.remove("toggle-state")
+        .and_then(|v| {
+            let i: Result<i32, _> = v.try_into();
+            i.ok()
+        })
+        .unwrap_or(-1);
+
+    let mut children = Vec::new();
+    for child_val in children_vals {
+        let child_val_inner = zbus::zvariant::Value::from(child_val);
+        if let Ok(child) = <(i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>)>::try_from(child_val_inner) {
+            if let Some(parsed) = parse_menu_item(child.0, child.1, child.2) {
+                children.push(parsed);
+            }
+        }
+    }
+
+    Some(MenuItem {
+        id,
+        label,
+        enabled,
+        is_separator,
+        toggle_state,
+        children,
+    })
+}
+
+pub(crate) fn get_currently_focused_window() -> Option<String> {
+    let output = std::process::Command::new(get_ccectl_cmd())
+        .arg("windows")
+        .output();
+    if let Ok(out) = output {
+        let stdout_str = String::from_utf8_lossy(&out.stdout);
+        for line in stdout_str.lines() {
+            let focused = if let Some(idx) = line.find("focused=") {
+                let rest = &line[idx + 8..];
+                let end = rest.find(' ').unwrap_or(rest.len());
+                rest[..end].trim() == "true"
+            } else {
+                false
+            };
+
+            if focused {
+                let app_id = if let Some(idx) = line.find("app_id=") {
+                    let rest = &line[idx + 7..];
+                    let end = rest.find(' ').unwrap_or(rest.len());
+                    rest[..end].to_string()
+                } else {
+                    continue;
+                };
+                if app_id == "cce-status" || app_id == "cce-cloud" {
+                    continue;
+                }
+                
+                // Return the unique window ID if present, otherwise fall back to app_id
+                let id = if let Some(idx) = line.find("window id=") {
+                    let rest = &line[idx + 10..];
+                    let end = rest.find(' ').unwrap_or(rest.len());
+                    rest[..end].to_string()
+                } else {
+                    app_id
+                };
+                return Some(id);
+            }
+        }
+    }
+    None
+}
+
+pub(crate) async fn show_cce_cloud_menu(
+    conn: &zbus::Connection,
+    destination: &str,
+    menu_path: &str,
+    x_pos: i32,
+    y_pos: i32,
+    align_right: bool,
+    thread_sender: calloop::channel::Sender<CustomEvent>,
+    source: String,
+    parent_app_id: String,
+) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
+    let mut last_spawned_pid = 0;
+
+    let res = async {
+        let menu_proxy = DBusMenuProxy::builder(conn)
+            .destination(destination)?
+            .path(menu_path)?
+            .build()
+            .await?;
+
+        let _ = menu_proxy.about_to_show(0).await;
+        let (_, layout) = menu_proxy.get_layout(0, 5, vec![]).await?;
+
+        let root_item = match parse_menu_item(layout.0, layout.1, layout.2) {
+            Some(item) => item,
+            None => return Ok(()),
+        };
+
+        // Assign page indices to submenus.
+        let mut page_indices = std::collections::HashMap::new();
+        page_indices.insert(root_item.id, 0);
+        let mut parent_pages = std::collections::HashMap::new();
+        let mut next_page = 1;
+
+        fn assign_pages(
+            item: &MenuItem,
+            current_page: usize,
+            page_indices: &mut std::collections::HashMap<i32, usize>,
+            parent_pages: &mut std::collections::HashMap<usize, usize>,
+            next_page: &mut usize,
+        ) {
+            for child in &item.children {
+                if child.is_separator || !child.enabled {
+                    continue;
+                }
+                if !child.children.is_empty() && *next_page < 16 {
+                    let child_page = *next_page;
+                    page_indices.insert(child.id, child_page);
+                    parent_pages.insert(child_page, current_page);
+                    *next_page += 1;
+                    assign_pages(child, child_page, page_indices, parent_pages, next_page);
+                }
+            }
+        }
+
+        assign_pages(&root_item, 0, &mut page_indices, &mut parent_pages, &mut next_page);
+
+        #[derive(Debug, Clone)]
+        struct LocalWidget {
+            widget_type: String,
+            text: String,
+            id: Option<String>,
+            target_page: Option<usize>,
+        }
+
+        #[derive(Debug, Clone)]
+        struct LocalPage {
+            title: String,
+            widgets: Vec<LocalWidget>,
+        }
+
+        let mut pages = vec![LocalPage {
+            title: "".to_string(),
+            widgets: Vec::new(),
+        }; next_page];
+
+        fn build_pages(
+            item: &MenuItem,
+            current_page: usize,
+            page_indices: &std::collections::HashMap<i32, usize>,
+            parent_pages: &std::collections::HashMap<usize, usize>,
+            pages: &mut [LocalPage],
+        ) {
+            let mut widgets = Vec::new();
+
+            if current_page > 0 {
+                if let Some(&parent_page) = parent_pages.get(&current_page) {
+                    widgets.push(LocalWidget {
+                        widget_type: "button".to_string(),
+                        text: "< Back".to_string(),
+                        id: Some(format!("back_to_{}", parent_page)),
+                        target_page: Some(parent_page),
+                    });
+                }
+            }
+
+            for child in &item.children {
+                if child.is_separator || !child.enabled {
+                    continue;
+                }
+
+                let mut display_label = if child.toggle_state == 1 {
+                    format!("[x] {}", child.label)
+                } else if child.toggle_state == 0 {
+                    format!("[ ] {}", child.label)
+                } else {
+                    child.label.clone()
+                };
+
+                if !child.children.is_empty() {
+                    if let Some(&target_page) = page_indices.get(&child.id) {
+                        display_label = format!("{} >", display_label);
+
+                        widgets.push(LocalWidget {
+                            widget_type: "button".to_string(),
+                            text: display_label,
+                            id: Some(format!("submenu_{}", child.id)),
+                            target_page: Some(target_page),
+                        });
+
+                        build_pages(child, target_page, page_indices, parent_pages, pages);
+                    } else {
+                        widgets.push(LocalWidget {
+                            widget_type: "button".to_string(),
+                            text: display_label,
+                            id: Some(format!("item_{}", child.id)),
+                            target_page: None,
+                        });
+                    }
+                } else {
+                    widgets.push(LocalWidget {
+                        widget_type: "button".to_string(),
+                        text: display_label,
+                        id: Some(format!("item_{}", child.id)),
+                        target_page: None,
+                    });
+                }
+            }
+
+            let title = if item.label.is_empty() {
+                if current_page == 0 {
+                    "Tray Menu".to_string()
+                } else {
+                    "".to_string()
+                }
+            } else {
+                item.label.clone()
+            };
+
+            pages[current_page] = LocalPage {
+                title,
+                widgets,
+            };
+        }
+
+        build_pages(&root_item, 0, &page_indices, &parent_pages, &mut pages);
+
+        // Serialize to JSON value
+        let mut pages_json = Vec::new();
+        for page in pages {
+            let mut widgets_json = Vec::new();
+            for w in page.widgets {
+                let mut w_val = serde_json::json!({
+                    "type": w.widget_type,
+                    "text": w.text,
+                });
+                if let Some(id) = w.id {
+                    w_val["id"] = serde_json::Value::String(id);
+                }
+                if let Some(tp) = w.target_page {
+                    w_val["target_page"] = serde_json::Value::Number(tp.into());
+                }
+                widgets_json.push(w_val);
+            }
+            pages_json.push(serde_json::json!({
+                "title": page.title,
+                "widgets": widgets_json,
+            }));
+        }
+
+        let layout_json = serde_json::json!({
+            "width": 260,
+            "pages": pages_json,
+        });
+        let layout_str = layout_json.to_string();
+
+        let mut cmd_args = vec![
+            "--json".to_string(),
+            "-x".to_string(),
+            x_pos.to_string(),
+            "-y".to_string(),
+            y_pos.to_string(),
+            "--parent-app-id".to_string(),
+            parent_app_id,
+        ];
+        if align_right {
+            cmd_args.push("--align-right".to_string());
+        }
+
+        let mut child = std::process::Command::new(get_cce_cloud_cmd())
+            .args(&cmd_args)
+            .stdin(std::process::Stdio::piped())
+            .stdout(std::process::Stdio::piped())
+            .stderr(std::process::Stdio::inherit())
+            .spawn()?;
+
+        let pid = child.id();
+        last_spawned_pid = pid;
+        let _ = thread_sender.send(CustomEvent::CloudSpawned { pid, source: source.clone() });
+
+        if let Some(mut stdin) = child.stdin.take() {
+            use std::io::Write;
+            stdin.write_all(layout_str.as_bytes())?;
+        }
+
+        let output = child.wait_with_output()?;
+        if output.status.success() {
+            let stdout_str = String::from_utf8_lossy(&output.stdout);
+            if let Ok(parsed_json) = serde_json::from_str::<serde_json::Value>(stdout_str.trim()) {
+                if let Some(btn_id) = parsed_json.get("button").and_then(|v| v.as_str()) {
+                    if btn_id.starts_with("item_") {
+                        if let Ok(item_id) = btn_id["item_".len()..].parse::<i32>() {
+                            let timestamp = std::time::SystemTime::now()
+                                .duration_since(std::time::UNIX_EPOCH)
+                                .unwrap_or_default()
+                                .as_secs() as u32;
+                            let val = zbus::zvariant::Value::from("");
+                            let _ = menu_proxy.event(item_id, "clicked", &val, timestamp).await;
+                        }
+                    }
+                }
+            }
+        }
+        Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
+    }.await;
+
+    let _ = thread_sender.send(CustomEvent::CloudClosed { pid: last_spawned_pid, source });
+    res
+}
+
+
+/// Spawn the click-to-pick window list as a cce-cloud dmenu process, report
+/// lifecycle via CloudSpawned/CloudClosed, and focus the picked window.
+pub(crate) fn spawn_window_picker(
+    x_pos: i32,
+    y_pos: i32,
+    thread_sender: calloop::channel::Sender<CustomEvent>,
+    source: String,
+) {
+    std::thread::spawn(move || {
+        // Run "ccectl windows" to fetch the windows list
+        let output = std::process::Command::new(get_ccectl_cmd())
+            .arg("windows")
+            .output();
+
+        let windows = if let Ok(out) = output {
+            parse_ccectl_windows(&String::from_utf8_lossy(&out.stdout))
+        } else {
+            Vec::new()
+        };
+
+        if windows.is_empty() {
+            // If there are no windows, don't open a switcher and clear state
+            let _ = thread_sender.send(CustomEvent::CloudClosed { pid: 0, source: source.clone() });
+            return;
+        }
+
+        // Format items for dmenu, keeping the stable order returned by ccectl
+        let mut input_str = String::new();
+        for (_, app_id, title, _) in &windows {
+            let display = if title.is_empty() {
+                app_id.clone()
+            } else {
+                format!("{} ({})", title, app_id)
+            };
+            input_str.push_str(&display);
+            input_str.push('\n');
+        }
+
+        let cmd_args = vec![
+            "--dmenu".to_string(),
+            "-p".to_string(),
+            "Windows:".to_string(),
+            "-x".to_string(),
+            x_pos.to_string(),
+            "-y".to_string(),
+            y_pos.to_string(),
+        ];
+
+        let mut child = match std::process::Command::new(get_cce_cloud_cmd())
+            .args(&cmd_args)
+            .stdin(std::process::Stdio::piped())
+            .stdout(std::process::Stdio::piped())
+            .stderr(std::process::Stdio::inherit())
+            .spawn()
+        {
+            Ok(c) => c,
+            Err(e) => {
+                eprintln!("[switcher] Failed to spawn cce-cloud: {:?}", e);
+                let _ = thread_sender.send(CustomEvent::CloudClosed { pid: 0, source: source.clone() });
+                return;
+            }
+        };
+
+        let pid = child.id();
+        let mut stdin = child.stdin.take().unwrap();
+        let mut stdout = child.stdout.take().unwrap();
+
+        // Write the item list, then drop stdin so cce-cloud sees EOF.
+        use std::io::Write;
+        let _ = stdin.write_all(input_str.as_bytes());
+        let _ = stdin.flush();
+        drop(stdin);
+
+        // Spawn stdout reader
+        let (stdout_tx, stdout_rx) = std::sync::mpsc::channel();
+        std::thread::spawn(move || {
+            let mut out_str = String::new();
+            use std::io::Read;
+            let _ = stdout.read_to_string(&mut out_str);
+            let _ = stdout_tx.send(out_str);
+        });
+
+        let _ = thread_sender.send(CustomEvent::CloudSpawned { pid, source: source.clone() });
+
+        let _ = child.wait();
+        let stdout_str = stdout_rx.recv().unwrap_or_default();
+
+        let selected = stdout_str.trim().to_string();
+        if !selected.is_empty() {
+            // Find the matched window
+            for (id, app_id, title, _) in windows {
+                let display = if title.is_empty() {
+                    app_id.clone()
+                } else {
+                    format!("{} ({})", title, app_id)
+                };
+                if display == selected {
+                    eprintln!("[switcher] Selecting window title: {}, app_id: {}, id: {}", title, app_id, id);
+                    let _ = std::process::Command::new(get_ccectl_cmd())
+                        .args(["focus-window", &id])
+                        .spawn();
+                    break;
+                }
+            }
+        }
+
+        let _ = thread_sender.send(CustomEvent::CloudClosed { pid, source: source.clone() });
+    });
+}
diff --git a/src/config.rs b/src/config.rs
new file mode 100644
index 0000000..6753a73
--- /dev/null
+++ b/src/config.rs
@@ -0,0 +1,242 @@
+//! Config access: cached KDL config lookup, color/font/dimension readers,
+//! and resolution of the cce/ccectl/cce-cloud binaries.
+
+pub(crate) fn parse_json(content: &str) -> serde_json::Value {
+    cce_ui::config::parse_kdl_to_json(content)
+}
+
+pub(crate) fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
+    fn find_recursive<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
+        if let Some(obj) = val.as_object() {
+            if let Some(v) = obj.get(key) {
+                return Some(v);
+            }
+            let parts: Vec<&str> = key.split('_').collect();
+            for i in 1..parts.len() {
+                let (prefix_parts, suffix_parts) = parts.split_at(i);
+                let prefix = prefix_parts.join("_");
+                let suffix = suffix_parts.join("_");
+                if let Some(sub_val) = obj.get(&prefix) {
+                    if let Some(found) = find_recursive(sub_val, &suffix) {
+                        return Some(found);
+                    }
+                }
+            }
+            for (_, sub_val) in obj.iter() {
+                if let Some(found) = find_recursive(sub_val, key) {
+                    return Some(found);
+                }
+            }
+        }
+        None
+    }
+    find_recursive(val, key)
+}
+
+pub(crate) fn get_cached_config() -> serde_json::Value {
+    cce_ui::config::cached_config()
+}
+
+pub(crate) fn get_cached_config_content() -> String {
+    cce_ui::config::cached_config_content()
+}
+
+pub(crate) fn read_normal_color_from_config() -> Option<[f32; 4]> {
+    let content = get_cached_config_content();
+    parse_srgb_color_from_key(&content, "status_normal_color")
+}
+
+pub(crate) fn read_disabled_color_from_config() -> Option<[f32; 4]> {
+    let content = get_cached_config_content();
+    parse_srgb_color_from_key(&content, "disabled_color")
+}
+
+pub(crate) fn parse_srgb_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
+    let val = parse_json(content);
+    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
+        if let Some(rgb) = parse_hex(s) {
+            let r = rgb[0] as f32 / 255.0;
+            let g = rgb[1] as f32 / 255.0;
+            let b = rgb[2] as f32 / 255.0;
+            return Some([r, g, b, 1.0]);
+        }
+    }
+    None
+}
+
+pub(crate) fn read_status_font_from_config() -> String {
+    let val = get_cached_config();
+    if let Some(font_str) = json_find_key(&val, "status_font").and_then(|v| v.as_str()) {
+        return font_str.to_string();
+    }
+
+    let font_conf_path = cce_ui::config::config_home().join("fontconfig").join("fonts.conf");
+    if let Ok(content) = std::fs::read_to_string(&font_conf_path) {
+        if let Some(font) = parse_font_for_alias(&content, "status-interface") {
+            return font;
+        }
+    }
+    "sans-serif".to_string()
+}
+
+pub(crate) fn read_status_height_from_config() -> f32 {
+    let val = get_cached_config();
+    json_find_key(&val, "bar_height").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(28.0)
+}
+
+pub(crate) fn read_status_font_size_from_config() -> f32 {
+    let val = get_cached_config();
+    
+    if let Some(font_str) = json_find_key(&val, "status_font").and_then(|v| v.as_str()) {
+        let (_, parsed_size) = cce_ui::layout::parse_font_string(font_str);
+        if let Some(size) = parsed_size {
+            return size;
+        }
+    }
+    
+    json_find_key(&val, "status_font_size").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(11.0)
+}
+
+pub(crate) fn read_status_padding_from_config() -> f32 {
+    let val = get_cached_config();
+    json_find_key(&val, "status_padding").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(8.0)
+}
+
+pub(crate) fn read_status_module_spacing_from_config() -> f32 {
+    let val = get_cached_config();
+    json_find_key(&val, "status_module_spacing").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(8.0)
+}
+
+pub(crate) fn read_separator_color_from_config() -> Option<[f32; 4]> {
+    let content = get_cached_config_content();
+    parse_color_from_key(&content, "status_separator_color")
+}
+
+
+
+pub(crate) fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
+    let lines: Vec<&str> = content.lines().collect();
+    for i in 0..lines.len() {
+        let line = lines[i].trim();
+        if line.contains("<test") && line.contains("name=\"family\"") && line.contains(&format!("<string>{}</string>", alias)) {
+            for j in (i + 1)..(i + 6).min(lines.len()) {
+                let next_line = lines[j].trim();
+                if next_line.contains("<edit") {
+                    for k in (j + 1)..(j + 6).min(lines.len()) {
+                        let str_line = lines[k].trim();
+                        if str_line.contains("<string>") && str_line.contains("</string>") {
+                            if let Some(start) = str_line.find("<string>") {
+                                if let Some(end) = str_line.find("</string>") {
+                                    return Some(str_line[start + 8..end].to_string());
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+    None
+}
+
+pub(crate) fn read_bg_color_from_config() -> Option<[f32; 4]> {
+    let content = get_cached_config_content();
+    parse_color_from_key(&content, "background_color")
+        .or_else(|| parse_color_from_key(&content, "low_color"))
+        .or_else(|| parse_color_from_key(&content, "desktop_gap_color"))
+}
+
+pub(crate) fn parse_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
+    let val = parse_json(content);
+    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
+        if let Some(rgba) = parse_hex_rgba(s) {
+            let r = (rgba[0] as f32 / 255.0).powf(2.2);
+            let g = (rgba[1] as f32 / 255.0).powf(2.2);
+            let b = (rgba[2] as f32 / 255.0).powf(2.2);
+            let a = rgba[3] as f32 / 255.0;
+            return Some([r, g, b, a]);
+        }
+    }
+    None
+}
+
+pub(crate) fn parse_hex(s: &str) -> Option<[u8; 3]> {
+    cce_ui::color::parse_hex_bytes(s).map(|[r, g, b, _]| [r, g, b])
+}
+pub(crate) fn parse_hex_rgba(s: &str) -> Option<[u8; 4]> {
+    cce_ui::color::parse_hex_bytes(s)
+}
+
+pub(crate) fn parse_rgba_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
+    let val = parse_json(content);
+    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
+        if let Some(rgba) = parse_hex_rgba(s) {
+            let r = (rgba[0] as f32 / 255.0).powf(2.2);
+            let g = (rgba[1] as f32 / 255.0).powf(2.2);
+            let b = (rgba[2] as f32 / 255.0).powf(2.2);
+            let a = rgba[3] as f32 / 255.0;
+            return Some([r, g, b, a]);
+        }
+    }
+    None
+}
+
+pub(crate) fn read_status_background_blur_from_config() -> f32 {
+    let val = get_cached_config();
+    json_find_key(&val, "status_background_blur").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(0.0)
+}
+
+pub(crate) fn read_status_box_background_color_from_config() -> Option<[f32; 4]> {
+    let content = get_cached_config_content();
+    let mut color = parse_rgba_color_from_key(&content, "status_background_color")
+        .unwrap_or_else(|| {
+            let r = (0x15 as f32 / 255.0).powf(2.2);
+            let g = (0x15 as f32 / 255.0).powf(2.2);
+            let b = (0x20 as f32 / 255.0).powf(2.2);
+            [r, g, b, 0.9]
+        });
+
+    let blur = read_status_background_blur_from_config();
+    
+    // Scale RGB by (1.0 - blur) to apply tint factor while keeping alpha as full opacity for the blur shader
+    color[0] *= 1.0 - blur;
+    color[1] *= 1.0 - blur;
+    color[2] *= 1.0 - blur;
+
+    Some(color)
+}
+
+pub(crate) fn read_status_box_corner_radius_from_config() -> f32 {
+    let val = get_cached_config();
+    json_find_key(&val, "status_box_corner_radius").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(4.0)
+}
+
+pub(crate) fn get_cce_cloud_cmd() -> String {
+    if let Ok(home) = std::env::var("HOME") {
+        let path = format!("{}/.local/bin/cce-cloud", home);
+        if std::path::Path::new(&path).exists() {
+            return path;
+        }
+    }
+    "cce-cloud".to_string()
+}
+
+pub(crate) fn get_cce_cmd() -> String {
+    if let Ok(home) = std::env::var("HOME") {
+        let path = format!("{}/.local/bin/cce", home);
+        if std::path::Path::new(&path).exists() {
+            return path;
+        }
+    }
+    "cce".to_string()
+}
+
+pub(crate) fn get_ccectl_cmd() -> String {
+    if let Ok(home) = std::env::var("HOME") {
+        let path = format!("{}/.local/bin/ccectl", home);
+        if std::path::Path::new(&path).exists() {
+            return path;
+        }
+    }
+    "ccectl".to_string()
+}
diff --git a/src/listeners.rs b/src/listeners.rs
new file mode 100644
index 0000000..1bbca25
--- /dev/null
+++ b/src/listeners.rs
@@ -0,0 +1,75 @@
+//! Compositor push-update listeners: the status-feed subscriptions and the
+//! switcher trigger socket.
+
+use crate::CustomEvent;
+
+pub(crate) async fn spawn_status_listener(sub: &'static str, sender: calloop::channel::Sender<CustomEvent>) {
+    use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
+    use tokio::net::UnixStream;
+    loop {
+        let socket_path = match std::env::var("WAYLAND_DISPLAY") {
+            Ok(display) => {
+                let primary = format!("/tmp/cce-status-interface-{}.sock", display);
+                if std::path::Path::new(&primary).exists() {
+                    primary
+                } else {
+                    format!("/tmp/cce-status-{}.sock", display)
+                }
+            }
+            Err(_) => {
+                let primary = "/tmp/cce-status-interface.sock".to_string();
+                if std::path::Path::new(&primary).exists() {
+                    primary
+                } else {
+                    "/tmp/cce-status.sock".to_string()
+                }
+            }
+        };
+        if let Ok(mut stream) = UnixStream::connect(&socket_path).await {
+            eprintln!("[status-listener] connected to {} for sub '{}'", socket_path, sub);
+            if stream.write_all(format!("{}\n", sub).as_bytes()).await.is_ok() {
+                let mut reader = BufReader::new(stream);
+                let mut line = String::new();
+                while reader.read_line(&mut line).await.unwrap_or(0) > 0 {
+                    let val = line.trim().to_string();
+                    eprintln!("[status-listener] received '{}' update: '{}'", sub, val);
+                    if !val.is_empty() {
+                        let ev = match sub {
+                            "viewport" => CustomEvent::ViewportUpdated(val.clone()),
+                            "layout" => CustomEvent::LayoutUpdated(val.clone()),
+                            "title" => CustomEvent::TitleUpdated(val.clone()),
+                            "modifiers" => CustomEvent::ModifiersUpdated(val.clone()),
+                            _ => unreachable!(),
+                        };
+                        let _ = sender.send(ev);
+                    }
+                    line.clear();
+                }
+            }
+        }
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+}
+
+pub(crate) async fn spawn_switcher_listener(sender: calloop::channel::Sender<CustomEvent>) {
+    use tokio::io::AsyncBufReadExt;
+    use tokio::net::UnixListener;
+    let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
+    let socket_path = format!("/tmp/cce-status-interface-switcher-{}.sock", display);
+    let _ = std::fs::remove_file(&socket_path);
+
+    if let Ok(listener) = UnixListener::bind(&socket_path) {
+        eprintln!("[switcher-listener] Listening on {}", socket_path);
+        loop {
+            if let Ok((stream, _)) = listener.accept().await {
+                let mut reader = tokio::io::BufReader::new(stream);
+                let mut line = String::new();
+                if reader.read_line(&mut line).await.is_ok() {
+                    let _ = sender.send(CustomEvent::SwitcherTriggered);
+                }
+            }
+        }
+    } else {
+        eprintln!("[switcher-listener] Failed to bind to {}", socket_path);
+    }
+}
diff --git a/src/main.rs b/src/main.rs
index a4e9e5f..377ab7b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,8 +1,21 @@
+mod cloud;
+mod config;
+mod listeners;
 mod modules;
+mod stats;
+mod tray;
+
+// Re-export at the crate root so call sites (here and in modules.rs) keep
+// their pre-split names.
+pub(crate) use cloud::*;
+pub(crate) use config::*;
+pub(crate) use listeners::*;
+pub(crate) use stats::*;
+pub(crate) use tray::*;
+
 use modules::{StatusModule, WindowModule, ClockModule, BatteryModule, VolumeModule, BrightnessModule, MemoryModule, CpuModule, TrayModule, LightSourceModule};
 
 use std::collections::HashMap;
-use std::sync::Arc;
 use glyphon::{
     Attrs, Buffer, FontSystem, Metrics,
 };
@@ -72,7 +85,7 @@ pub struct SystemStats {
 }
 
 #[derive(Debug, Clone)]
-enum CustomEvent {
+pub(crate) enum CustomEvent {
     ViewportUpdated(String),
     LayoutUpdated(String),
     TitleUpdated(String),
@@ -151,86 +164,6 @@ pub(crate) fn parse_viewport_text(input: &str) -> Vec<([f32; 4], String)> {
     result
 }
 
-fn read_cpu_ticks() -> Option<(u64, u64)> {
-    let stat = std::fs::read_to_string("/proc/stat").ok()?;
-    let first_line = stat.lines().next()?;
-    if first_line.starts_with("cpu ") {
-        let parts: Vec<u64> = first_line
-            .split_whitespace()
-            .skip(1)
-            .filter_map(|s| s.parse::<u64>().ok())
-            .collect();
-        if parts.len() >= 4 {
-            let idle = parts[3];
-            let total: u64 = parts.iter().sum();
-            return Some((total, idle));
-        }
-    }
-    None
-}
-
-fn read_memory_usage() -> Option<String> {
-    let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
-    let mut total = 0.0;
-    let mut free = 0.0;
-    let mut buffers = 0.0;
-    let mut cached = 0.0;
-    for line in meminfo.lines() {
-        if line.starts_with("MemTotal:") {
-            total = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
-        } else if line.starts_with("MemFree:") {
-            free = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
-        } else if line.starts_with("Buffers:") {
-            buffers = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
-        } else if line.starts_with("Cached:") {
-            cached = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
-        }
-    }
-    if total > 0.0 {
-        let used = total - free - buffers - cached;
-        Some(format!("Mem {:.1}/{:.1}G", used, total))
-    } else {
-        None
-    }
-}
-
-fn read_battery_details() -> Option<(String, i32, bool)> {
-    for bat in &["BAT0", "BAT1"] {
-        let cap_path = format!("/sys/class/power_supply/{}/capacity", bat);
-        let status_path = format!("/sys/class/power_supply/{}/status", bat);
-        if let Ok(cap_str) = std::fs::read_to_string(&cap_path) {
-            let cap_trimmed = cap_str.trim();
-            let cap = cap_trimmed.parse::<i32>().unwrap_or(0);
-            let status = std::fs::read_to_string(&status_path).unwrap_or_default();
-            let is_charging = status.trim() == "Charging";
-            let charge_symbol = if is_charging { "⚡" } else { "Bat" };
-            return Some((format!("{} {}%", charge_symbol, cap_trimmed), cap, is_charging));
-        }
-    }
-    None
-}
-
-fn read_brightness() -> Option<String> {
-    let dir = std::fs::read_dir("/sys/class/backlight").ok()?;
-    for entry in dir {
-        if let Ok(entry) = entry {
-            let path = entry.path();
-            let cur_path = path.join("brightness");
-            let max_path = path.join("max_brightness");
-            if cur_path.exists() && max_path.exists() {
-                let cur_str = std::fs::read_to_string(cur_path).ok()?;
-                let max_str = std::fs::read_to_string(max_path).ok()?;
-                let cur = cur_str.trim().parse::<f32>().ok()?;
-                let max = max_str.trim().parse::<f32>().ok()?;
-                if max > 0.0 {
-                    let pct = (cur / max * 100.0).round() as i32;
-                    return Some(format!("Bri {}%", pct));
-                }
-            }
-        }
-    }
-    None
-}
 
 pub struct RectWidget {
     pub x: f32, pub y: f32, pub w: f32, pub h: f32,
@@ -832,109 +765,7 @@ impl StatusApp {
         let x_pos = target_x as i32;
         let y_pos = bar_height;
 
-        let thread_sender = self.sender.clone();
-        let switcher_source_clone = switcher_source.clone();
-
-        std::thread::spawn(move || {
-            // Run "ccectl windows" to fetch the windows list
-            let output = std::process::Command::new(get_ccectl_cmd())
-                .arg("windows")
-                .output();
-
-            let windows = if let Ok(out) = output {
-                parse_ccectl_windows(&String::from_utf8_lossy(&out.stdout))
-            } else {
-                Vec::new()
-            };
-
-            if windows.is_empty() {
-                // If there are no windows, don't open a switcher and clear state
-                let _ = thread_sender.send(CustomEvent::CloudClosed { pid: 0, source: switcher_source_clone });
-                return;
-            }
-
-            // Format items for dmenu, keeping the stable order returned by ccectl
-            let mut input_str = String::new();
-            for (_, app_id, title, _) in &windows {
-                let display = if title.is_empty() {
-                    app_id.clone()
-                } else {
-                    format!("{} ({})", title, app_id)
-                };
-                input_str.push_str(&display);
-                input_str.push('\n');
-            }
-
-            let cmd_args = vec![
-                "--dmenu".to_string(),
-                "-p".to_string(),
-                "Windows:".to_string(),
-                "-x".to_string(),
-                x_pos.to_string(),
-                "-y".to_string(),
-                y_pos.to_string(),
-            ];
-
-            let mut child = match std::process::Command::new(get_cce_cloud_cmd())
-                .args(&cmd_args)
-                .stdin(std::process::Stdio::piped())
-                .stdout(std::process::Stdio::piped())
-                .stderr(std::process::Stdio::inherit())
-                .spawn()
-            {
-                Ok(c) => c,
-                Err(e) => {
-                    eprintln!("[switcher] Failed to spawn cce-cloud: {:?}", e);
-                    let _ = thread_sender.send(CustomEvent::CloudClosed { pid: 0, source: switcher_source_clone });
-                    return;
-                }
-            };
-
-            let pid = child.id();
-            let mut stdin = child.stdin.take().unwrap();
-            let mut stdout = child.stdout.take().unwrap();
-
-            // Write the item list, then drop stdin so cce-cloud sees EOF.
-            use std::io::Write;
-            let _ = stdin.write_all(input_str.as_bytes());
-            let _ = stdin.flush();
-            drop(stdin);
-
-            // Spawn stdout reader
-            let (stdout_tx, stdout_rx) = std::sync::mpsc::channel();
-            std::thread::spawn(move || {
-                let mut out_str = String::new();
-                use std::io::Read;
-                let _ = stdout.read_to_string(&mut out_str);
-                let _ = stdout_tx.send(out_str);
-            });
-
-            let _ = thread_sender.send(CustomEvent::CloudSpawned { pid, source: switcher_source_clone.clone() });
-
-            let _ = child.wait();
-            let stdout_str = stdout_rx.recv().unwrap_or_default();
-
-            let selected = stdout_str.trim().to_string();
-            if !selected.is_empty() {
-                // Find the matched window
-                for (id, app_id, title, _) in windows {
-                    let display = if title.is_empty() {
-                        app_id.clone()
-                    } else {
-                        format!("{} ({})", title, app_id)
-                    };
-                    if display == selected {
-                        eprintln!("[switcher] Selecting window title: {}, app_id: {}, id: {}", title, app_id, id);
-                        let _ = std::process::Command::new(get_ccectl_cmd())
-                            .args(["focus-window", &id])
-                            .spawn();
-                        break;
-                    }
-                }
-            }
-
-            let _ = thread_sender.send(CustomEvent::CloudClosed { pid, source: switcher_source_clone });
-        });
+        cloud::spawn_window_picker(x_pos, y_pos, self.sender.clone(), switcher_source);
     }
 }
 
@@ -1916,1115 +1747,12 @@ impl cce_ui::engine::Application for StatusApp {
     fn handle_key_input(&mut self, _event: &KeyEvent, _needs_rebuild: &mut bool) -> Option<Self::Message> { None }
 }
 
-async fn spawn_status_listener(sub: &'static str, sender: calloop::channel::Sender<CustomEvent>) {
-    use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
-    use tokio::net::UnixStream;
-    loop {
-        let socket_path = match std::env::var("WAYLAND_DISPLAY") {
-            Ok(display) => {
-                let primary = format!("/tmp/cce-status-interface-{}.sock", display);
-                if std::path::Path::new(&primary).exists() {
-                    primary
-                } else {
-                    format!("/tmp/cce-status-{}.sock", display)
-                }
-            }
-            Err(_) => {
-                let primary = "/tmp/cce-status-interface.sock".to_string();
-                if std::path::Path::new(&primary).exists() {
-                    primary
-                } else {
-                    "/tmp/cce-status.sock".to_string()
-                }
-            }
-        };
-        if let Ok(mut stream) = UnixStream::connect(&socket_path).await {
-            eprintln!("[status-listener] connected to {} for sub '{}'", socket_path, sub);
-            if stream.write_all(format!("{}\n", sub).as_bytes()).await.is_ok() {
-                let mut reader = BufReader::new(stream);
-                let mut line = String::new();
-                while reader.read_line(&mut line).await.unwrap_or(0) > 0 {
-                    let val = line.trim().to_string();
-                    eprintln!("[status-listener] received '{}' update: '{}'", sub, val);
-                    if !val.is_empty() {
-                        let ev = match sub {
-                            "viewport" => CustomEvent::ViewportUpdated(val.clone()),
-                            "layout" => CustomEvent::LayoutUpdated(val.clone()),
-                            "title" => CustomEvent::TitleUpdated(val.clone()),
-                            "modifiers" => CustomEvent::ModifiersUpdated(val.clone()),
-                            _ => unreachable!(),
-                        };
-                        let _ = sender.send(ev);
-                    }
-                    line.clear();
-                }
-            }
-        }
-        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
-    }
-}
-
-async fn spawn_switcher_listener(sender: calloop::channel::Sender<CustomEvent>) {
-    use tokio::io::AsyncBufReadExt;
-    use tokio::net::UnixListener;
-    let display = std::env::var("WAYLAND_DISPLAY").unwrap_or_else(|_| "wayland-0".to_string());
-    let socket_path = format!("/tmp/cce-status-interface-switcher-{}.sock", display);
-    let _ = std::fs::remove_file(&socket_path);
-
-    if let Ok(listener) = UnixListener::bind(&socket_path) {
-        eprintln!("[switcher-listener] Listening on {}", socket_path);
-        loop {
-            if let Ok((stream, _)) = listener.accept().await {
-                let mut reader = tokio::io::BufReader::new(stream);
-                let mut line = String::new();
-                if reader.read_line(&mut line).await.is_ok() {
-                    let _ = sender.send(CustomEvent::SwitcherTriggered);
-                }
-            }
-        }
-    } else {
-        eprintln!("[switcher-listener] Failed to bind to {}", socket_path);
-    }
-}
-
-async fn read_volume() -> Option<(String, bool)> {
-    let vol_output = match tokio::process::Command::new("pactl")
-        .args(["get-sink-volume", "@DEFAULT_SINK@"])
-        .output()
-        .await
-    {
-        Ok(o) => o,
-        Err(e) => {
-            eprintln!("[read_volume] failed to spawn pactl: {:?}", e);
-            return None;
-        }
-    };
-    if !vol_output.status.success() {
-        eprintln!("[read_volume] pactl get-sink-volume exited with error: {:?}", String::from_utf8_lossy(&vol_output.stderr));
-        return None;
-    }
-    let vol_str = String::from_utf8_lossy(&vol_output.stdout);
-    
-    let mute_output = match tokio::process::Command::new("pactl")
-        .args(["get-sink-mute", "@DEFAULT_SINK@"])
-        .output()
-        .await
-    {
-        Ok(o) => o,
-        Err(e) => {
-            eprintln!("[read_volume] failed to spawn pactl mute: {:?}", e);
-            return None;
-        }
-    };
-    if !mute_output.status.success() {
-        eprintln!("[read_volume] pactl get-sink-mute exited with error: {:?}", String::from_utf8_lossy(&mute_output.stderr));
-        return None;
-    }
-    let mute_str = String::from_utf8_lossy(&mute_output.stdout);
-    let muted = mute_str.contains("yes");
-
-    let mut pct = None;
-    if let Some(pos) = vol_str.find('%') {
-        let start = vol_str[..pos].rfind(|c: char| !c.is_ascii_digit()).map(|i| i + 1).unwrap_or(0);
-        if let Ok(num) = vol_str[start..pos].parse::<u32>() {
-            pct = Some(num);
-        }
-    }
-
-    match (muted, pct) {
-        (true, Some(p)) => Some((format!("Vol {}%", p), true)),
-        (true, None) => Some(("Vol Muted".to_string(), true)),
-        (false, Some(p)) => Some((format!("Vol {}%", p), false)),
-        (false, None) => Some(("Vol N/A".to_string(), false)),
-    }
-}
-
-fn get_initial_stats() -> SystemStats {
-    let clock = chrono::Local::now().format("%A, %B %d, %Y %I:%M %p").to_string();
-    let memory = read_memory_usage().unwrap_or_else(|| "Mem N/A".to_string());
-
-    let (battery_str, battery_capacity, battery_charging) = if let Some((s, cap, chg)) = read_battery_details() {
-        (s, cap, chg)
-    } else {
-        ("".to_string(), 0, false)
-    };
-    let (volume, volume_muted) = pollster::block_on(read_volume()).unwrap_or_else(|| ("".to_string(), false));
-    let brightness = read_brightness().unwrap_or_default();
-
-    SystemStats {
-        clock,
-        memory,
-        cpu: "Cpu 0.0%".to_string(),
-        battery: battery_str,
-        battery_capacity,
-        battery_charging,
-        volume,
-        volume_muted,
-        brightness,
-    }
-}
-
-async fn spawn_system_stats(sender: calloop::channel::Sender<CustomEvent>) {
-    eprintln!("[spawn_system_stats] Starting system stats loop!");
-    let mut last_cpu = read_cpu_ticks().unwrap_or((0, 0));
-    loop {
-        eprintln!("[spawn_system_stats] loop iteration start");
-        let clock = chrono::Local::now().format("%A, %B %d, %Y %I:%M %p").to_string();
-        let memory = read_memory_usage().unwrap_or_else(|| "Mem N/A".to_string());
-        
-        let cpu_str = if let Some(current_cpu) = read_cpu_ticks() {
-            let total_diff = current_cpu.0 - last_cpu.0;
-            let idle_diff = current_cpu.1 - last_cpu.1;
-            last_cpu = current_cpu;
-            if total_diff > 0 {
-                let usage = 100.0 - (idle_diff as f32 * 100.0 / total_diff as f32);
-                format!("Cpu {:.1}%", usage)
-            } else {
-                "Cpu 0.0%".to_string()
-            }
-        } else {
-            "Cpu N/A".to_string()
-        };
-
-        let (battery_str, battery_capacity, battery_charging) = if let Some((s, cap, chg)) = read_battery_details() {
-            (s, cap, chg)
-        } else {
-            ("".to_string(), 0, false)
-        };
-        let (volume, volume_muted) = read_volume().await.unwrap_or_else(|| ("".to_string(), false));
-        let brightness = read_brightness().unwrap_or_default();
-
-        let stats = SystemStats {
-            clock,
-            memory,
-            cpu: cpu_str,
-            battery: battery_str,
-            battery_capacity,
-            battery_charging,
-            volume,
-            volume_muted,
-            brightness,
-        };
-        eprintln!("[spawn_system_stats] stats: {:?}", stats);
-        let _ = sender.send(CustomEvent::SystemStatsUpdated(stats));
-        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
-    }
-}
-
-#[derive(Debug, Clone)]
-pub struct NotifierAddress {
-    pub destination: String,
-    pub path: String,
-}
-
-impl NotifierAddress {
-    pub fn from_notifier_service(service: &str, sender: &str) -> Result<Self, String> {
-        if service.starts_with('/') {
-            Ok(NotifierAddress {
-                destination: sender.to_string(),
-                path: service.to_string(),
-            })
-        } else if let Some((destination, path)) = service.split_once('/') {
-            Ok(NotifierAddress {
-                destination: destination.to_string(),
-                path: format!("/{}", path),
-            })
-        } else if service.contains(':') {
-            let split = service.split(':').collect::<Vec<&str>>();
-            Ok(NotifierAddress {
-                destination: format!(":{}", split[1]),
-                path: "/StatusNotifierItem".to_string(),
-            })
-        } else {
-            Ok(NotifierAddress {
-                destination: service.to_string(),
-                path: "/StatusNotifierItem".to_string(),
-            })
-        }
-    }
-}
-
-#[zbus::proxy(
-    interface = "org.kde.StatusNotifierItem",
-    default_path = "/StatusNotifierItem"
-)]
-trait StatusNotifierItem {
-    #[zbus(property)]
-    fn id(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn category(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn status(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn title(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn icon_name(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn icon_theme_path(&self) -> zbus::Result<String>;
-
-    #[zbus(property)]
-    fn icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;
-
-    #[zbus(signal)]
-    fn new_icon(&self) -> zbus::Result<()>;
-    #[zbus(signal)]
-    fn new_title(&self) -> zbus::Result<()>;
-
-    #[zbus(signal)]
-    fn new_status(&self) -> zbus::Result<()>;
-
-    fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
-    fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>;
-
-    #[zbus(property)]
-    fn item_is_menu(&self) -> zbus::Result<bool>;
-
-    #[zbus(property)]
-    fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
-}
-
-#[zbus::proxy(
-    interface = "com.canonical.dbusmenu",
-    default_path = "/StatusNotifierItem/menu"
-)]
-trait DBusMenu {
-    fn get_layout(
-        &self,
-        parent_id: i32,
-        recursion_depth: i32,
-        property_names: Vec<String>,
-    ) -> zbus::Result<(u32, (i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>))>;
-
-    fn event(
-        &self,
-        id: i32,
-        event_id: &str,
-        data: &zbus::zvariant::Value<'_>,
-        timestamp: u32,
-    ) -> zbus::Result<()>;
-
-    fn about_to_show(&self, id: i32) -> zbus::Result<bool>;
-}
-
-struct MenuItem {
-    id: i32,
-    label: String,
-    enabled: bool,
-    is_separator: bool,
-    toggle_state: i32, // -1 if not toggleable, 0 if unchecked, 1 if checked
-    children: Vec<MenuItem>,
-}
-
-fn parse_menu_item(
-    id: i32,
-    mut properties: std::collections::HashMap<String, zbus::zvariant::OwnedValue>,
-    children_vals: Vec<zbus::zvariant::OwnedValue>,
-) -> Option<MenuItem> {
-    let type_: String = properties.remove("type")
-        .and_then(|v| {
-            let s: Result<String, _> = v.try_into();
-            s.ok()
-        })
-        .unwrap_or_default();
-    let is_separator = type_ == "separator";
-
-    let label: String = properties.remove("label")
-        .and_then(|v| {
-            let s: Result<String, _> = v.try_into();
-            s.ok()
-        })
-        .unwrap_or_default();
-
-    let enabled: bool = properties.remove("enabled")
-        .and_then(|v| {
-            let b: Result<bool, _> = v.try_into();
-            b.ok()
-        })
-        .unwrap_or(true);
-
-    let toggle_state: i32 = properties.remove("toggle-state")
-        .and_then(|v| {
-            let i: Result<i32, _> = v.try_into();
-            i.ok()
-        })
-        .unwrap_or(-1);
-
-    let mut children = Vec::new();
-    for child_val in children_vals {
-        let child_val_inner = zbus::zvariant::Value::from(child_val);
-        if let Ok(child) = <(i32, std::collections::HashMap<String, zbus::zvariant::OwnedValue>, Vec<zbus::zvariant::OwnedValue>)>::try_from(child_val_inner) {
-            if let Some(parsed) = parse_menu_item(child.0, child.1, child.2) {
-                children.push(parsed);
-            }
-        }
-    }
-
-    Some(MenuItem {
-        id,
-        label,
-        enabled,
-        is_separator,
-        toggle_state,
-        children,
-    })
-}
-
-fn get_cce_cloud_cmd() -> String {
-    if let Ok(home) = std::env::var("HOME") {
-        let path = format!("{}/.local/bin/cce-cloud", home);
-        if std::path::Path::new(&path).exists() {
-            return path;
-        }
-    }
-    "cce-cloud".to_string()
-}
-
-fn get_currently_focused_window() -> Option<String> {
-    let output = std::process::Command::new(get_ccectl_cmd())
-        .arg("windows")
-        .output();
-    if let Ok(out) = output {
-        let stdout_str = String::from_utf8_lossy(&out.stdout);
-        for line in stdout_str.lines() {
-            let focused = if let Some(idx) = line.find("focused=") {
-                let rest = &line[idx + 8..];
-                let end = rest.find(' ').unwrap_or(rest.len());
-                rest[..end].trim() == "true"
-            } else {
-                false
-            };
-
-            if focused {
-                let app_id = if let Some(idx) = line.find("app_id=") {
-                    let rest = &line[idx + 7..];
-                    let end = rest.find(' ').unwrap_or(rest.len());
-                    rest[..end].to_string()
-                } else {
-                    continue;
-                };
-                if app_id == "cce-status" || app_id == "cce-cloud" {
-                    continue;
-                }
-                
-                // Return the unique window ID if present, otherwise fall back to app_id
-                let id = if let Some(idx) = line.find("window id=") {
-                    let rest = &line[idx + 10..];
-                    let end = rest.find(' ').unwrap_or(rest.len());
-                    rest[..end].to_string()
-                } else {
-                    app_id
-                };
-                return Some(id);
-            }
-        }
-    }
-    None
-}
-
-async fn show_cce_cloud_menu(
-    conn: &zbus::Connection,
-    destination: &str,
-    menu_path: &str,
-    x_pos: i32,
-    y_pos: i32,
-    align_right: bool,
-    thread_sender: calloop::channel::Sender<CustomEvent>,
-    source: String,
-    parent_app_id: String,
-) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
-    let mut last_spawned_pid = 0;
-
-    let res = async {
-        let menu_proxy = DBusMenuProxy::builder(conn)
-            .destination(destination)?
-            .path(menu_path)?
-            .build()
-            .await?;
-
-        let _ = menu_proxy.about_to_show(0).await;
-        let (_, layout) = menu_proxy.get_layout(0, 5, vec![]).await?;
-
-        let root_item = match parse_menu_item(layout.0, layout.1, layout.2) {
-            Some(item) => item,
-            None => return Ok(()),
-        };
-
-        // Assign page indices to submenus.
-        let mut page_indices = std::collections::HashMap::new();
-        page_indices.insert(root_item.id, 0);
-        let mut parent_pages = std::collections::HashMap::new();
-        let mut next_page = 1;
-
-        fn assign_pages(
-            item: &MenuItem,
-            current_page: usize,
-            page_indices: &mut std::collections::HashMap<i32, usize>,
-            parent_pages: &mut std::collections::HashMap<usize, usize>,
-            next_page: &mut usize,
-        ) {
-            for child in &item.children {
-                if child.is_separator || !child.enabled {
-                    continue;
-                }
-                if !child.children.is_empty() && *next_page < 16 {
-                    let child_page = *next_page;
-                    page_indices.insert(child.id, child_page);
-                    parent_pages.insert(child_page, current_page);
-                    *next_page += 1;
-                    assign_pages(child, child_page, page_indices, parent_pages, next_page);
-                }
-            }
-        }
-
-        assign_pages(&root_item, 0, &mut page_indices, &mut parent_pages, &mut next_page);
-
-        #[derive(Debug, Clone)]
-        struct LocalWidget {
-            widget_type: String,
-            text: String,
-            id: Option<String>,
-            target_page: Option<usize>,
-        }
-
-        #[derive(Debug, Clone)]
-        struct LocalPage {
-            title: String,
-            widgets: Vec<LocalWidget>,
-        }
-
-        let mut pages = vec![LocalPage {
-            title: "".to_string(),
-            widgets: Vec::new(),
-        }; next_page];
-
-        fn build_pages(
-            item: &MenuItem,
-            current_page: usize,
-            page_indices: &std::collections::HashMap<i32, usize>,
-            parent_pages: &std::collections::HashMap<usize, usize>,
-            pages: &mut [LocalPage],
-        ) {
-            let mut widgets = Vec::new();
-
-            if current_page > 0 {
-                if let Some(&parent_page) = parent_pages.get(&current_page) {
-                    widgets.push(LocalWidget {
-                        widget_type: "button".to_string(),
-                        text: "< Back".to_string(),
-                        id: Some(format!("back_to_{}", parent_page)),
-                        target_page: Some(parent_page),
-                    });
-                }
-            }
-
-            for child in &item.children {
-                if child.is_separator || !child.enabled {
-                    continue;
-                }
-
-                let mut display_label = if child.toggle_state == 1 {
-                    format!("[x] {}", child.label)
-                } else if child.toggle_state == 0 {
-                    format!("[ ] {}", child.label)
-                } else {
-                    child.label.clone()
-                };
-
-                if !child.children.is_empty() {
-                    if let Some(&target_page) = page_indices.get(&child.id) {
-                        display_label = format!("{} >", display_label);
-
-                        widgets.push(LocalWidget {
-                            widget_type: "button".to_string(),
-                            text: display_label,
-                            id: Some(format!("submenu_{}", child.id)),
-                            target_page: Some(target_page),
-                        });
-
-                        build_pages(child, target_page, page_indices, parent_pages, pages);
-                    } else {
-                        widgets.push(LocalWidget {
-                            widget_type: "button".to_string(),
-                            text: display_label,
-                            id: Some(format!("item_{}", child.id)),
-                            target_page: None,
-                        });
-                    }
-                } else {
-                    widgets.push(LocalWidget {
-                        widget_type: "button".to_string(),
-                        text: display_label,
-                        id: Some(format!("item_{}", child.id)),
-                        target_page: None,
-                    });
-                }
-            }
-
-            let title = if item.label.is_empty() {
-                if current_page == 0 {
-                    "Tray Menu".to_string()
-                } else {
-                    "".to_string()
-                }
-            } else {
-                item.label.clone()
-            };
-
-            pages[current_page] = LocalPage {
-                title,
-                widgets,
-            };
-        }
-
-        build_pages(&root_item, 0, &page_indices, &parent_pages, &mut pages);
-
-        // Serialize to JSON value
-        let mut pages_json = Vec::new();
-        for page in pages {
-            let mut widgets_json = Vec::new();
-            for w in page.widgets {
-                let mut w_val = serde_json::json!({
-                    "type": w.widget_type,
-                    "text": w.text,
-                });
-                if let Some(id) = w.id {
-                    w_val["id"] = serde_json::Value::String(id);
-                }
-                if let Some(tp) = w.target_page {
-                    w_val["target_page"] = serde_json::Value::Number(tp.into());
-                }
-                widgets_json.push(w_val);
-            }
-            pages_json.push(serde_json::json!({
-                "title": page.title,
-                "widgets": widgets_json,
-            }));
-        }
-
-        let layout_json = serde_json::json!({
-            "width": 260,
-            "pages": pages_json,
-        });
-        let layout_str = layout_json.to_string();
-
-        let mut cmd_args = vec![
-            "--json".to_string(),
-            "-x".to_string(),
-            x_pos.to_string(),
-            "-y".to_string(),
-            y_pos.to_string(),
-            "--parent-app-id".to_string(),
-            parent_app_id,
-        ];
-        if align_right {
-            cmd_args.push("--align-right".to_string());
-        }
-
-        let mut child = std::process::Command::new(get_cce_cloud_cmd())
-            .args(&cmd_args)
-            .stdin(std::process::Stdio::piped())
-            .stdout(std::process::Stdio::piped())
-            .stderr(std::process::Stdio::inherit())
-            .spawn()?;
-
-        let pid = child.id();
-        last_spawned_pid = pid;
-        let _ = thread_sender.send(CustomEvent::CloudSpawned { pid, source: source.clone() });
-
-        if let Some(mut stdin) = child.stdin.take() {
-            use std::io::Write;
-            stdin.write_all(layout_str.as_bytes())?;
-        }
-
-        let output = child.wait_with_output()?;
-        if output.status.success() {
-            let stdout_str = String::from_utf8_lossy(&output.stdout);
-            if let Ok(parsed_json) = serde_json::from_str::<serde_json::Value>(stdout_str.trim()) {
-                if let Some(btn_id) = parsed_json.get("button").and_then(|v| v.as_str()) {
-                    if btn_id.starts_with("item_") {
-                        if let Ok(item_id) = btn_id["item_".len()..].parse::<i32>() {
-                            let timestamp = std::time::SystemTime::now()
-                                .duration_since(std::time::UNIX_EPOCH)
-                                .unwrap_or_default()
-                                .as_secs() as u32;
-                            let val = zbus::zvariant::Value::from("");
-                            let _ = menu_proxy.event(item_id, "clicked", &val, timestamp).await;
-                        }
-                    }
-                }
-            }
-        }
-        Ok::<(), Box<dyn std::error::Error + Send + Sync>>(())
-    }.await;
-
-    let _ = thread_sender.send(CustomEvent::CloudClosed { pid: last_spawned_pid, source });
-    res
-}
-
 
-fn find_icon_file(dir: &std::path::Path, icon_name: &str) -> Option<std::path::PathBuf> {
-    if let Ok(entries) = std::fs::read_dir(dir) {
-        for entry in entries.filter_map(Result::ok) {
-            if let Ok(file_type) = entry.file_type() {
-                let path = entry.path();
-                if file_type.is_dir() {
-                    if !file_type.is_symlink() {
-                        if let Some(found) = find_icon_file(&path, icon_name) {
-                            return Some(found);
-                        }
-                    }
-                } else if file_type.is_file() {
-                    if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
-                        if file_name == format!("{}.png", icon_name) || file_name == format!("{}.svg", icon_name) {
-                            return Some(path);
-                        }
-                    }
-                }
-            }
-        }
-    }
-    None
-}
 
-fn load_png_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
-    let file = std::fs::File::open(path).ok()?;
-    let mut decoder = png::Decoder::new(file);
-    decoder.set_transformations(png::Transformations::EXPAND);
-    let mut reader = decoder.read_info().ok()?;
-    let mut buf = vec![0; reader.output_buffer_size()];
-    let info = reader.next_frame(&mut buf).ok()?;
-    
-    let width = info.width as i32;
-    let height = info.height as i32;
-    let mut argb_pixels = Vec::with_capacity((width * height * 4) as usize);
-    
-    let actual_bytes = &buf[..info.buffer_size()];
-    match info.color_type {
-        png::ColorType::Rgba => {
-            for chunk in actual_bytes.chunks_exact(4) {
-                argb_pixels.push(chunk[3]); // A
-                argb_pixels.push(chunk[0]); // R
-                argb_pixels.push(chunk[1]); // G
-                argb_pixels.push(chunk[2]); // B
-            }
-        }
-        png::ColorType::Rgb => {
-            for chunk in actual_bytes.chunks_exact(3) {
-                argb_pixels.push(255);      // A
-                argb_pixels.push(chunk[0]); // R
-                argb_pixels.push(chunk[1]); // G
-                argb_pixels.push(chunk[2]); // B
-            }
-        }
-        png::ColorType::Grayscale => {
-            for &g in actual_bytes {
-                argb_pixels.push(255); // A
-                argb_pixels.push(g);   // R
-                argb_pixels.push(g);   // G
-                argb_pixels.push(g);   // B
-            }
-        }
-        png::ColorType::GrayscaleAlpha => {
-            for chunk in actual_bytes.chunks_exact(2) {
-                argb_pixels.push(chunk[1]); // A
-                argb_pixels.push(chunk[0]); // R
-                argb_pixels.push(chunk[0]); // G
-                argb_pixels.push(chunk[0]); // B
-            }
-        }
-        _ => return None,
-    }
-    
-    Some(TrayPixmap {
-        width,
-        height,
-        pixels: argb_pixels,
-    })
-}
 
-fn load_svg_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
-    let svg_data = std::fs::read(path).ok()?;
-    let opt = resvg::usvg::Options::default();
-    let fontdb = resvg::usvg::fontdb::Database::new();
-    let tree = resvg::usvg::Tree::from_data(&svg_data, &opt, &fontdb).ok()?;
-    
-    let target_w = 48;
-    let target_h = 48;
-    let mut pixmap = resvg::tiny_skia::Pixmap::new(target_w, target_h)?;
-    
-    let orig_w = tree.size().width();
-    let orig_h = tree.size().height();
-    let sx = target_w as f32 / orig_w;
-    let sy = target_h as f32 / orig_h;
-    let transform = resvg::tiny_skia::Transform::from_scale(sx, sy);
-    
-    resvg::render(&tree, transform, &mut pixmap.as_mut());
-    
-    let raw_pixels = pixmap.data();
-    let mut argb_pixels = Vec::with_capacity((target_w * target_h * 4) as usize);
-    for chunk in raw_pixels.chunks_exact(4) {
-        argb_pixels.push(chunk[3]); // A
-        argb_pixels.push(chunk[0]); // R
-        argb_pixels.push(chunk[1]); // G
-        argb_pixels.push(chunk[2]); // B
-    }
-    
-    Some(TrayPixmap {
-        width: target_w as i32,
-        height: target_h as i32,
-        pixels: argb_pixels,
-    })
-}
-
-fn resolve_icon_path(theme_path: Option<&str>, icon_name: &str) -> Option<std::path::PathBuf> {
-    if icon_name.is_empty() {
-        return None;
-    }
 
-    let icon_name = if icon_name == "dropbox" { "dropboxstatus-idle" } else { icon_name };
 
-    if let Some(path_str) = theme_path {
-        if !path_str.is_empty() {
-            let path = std::path::Path::new(path_str);
-            if path.exists() {
-                if let Some(found) = find_icon_file(path, icon_name) {
-                    return Some(found);
-                }
-            }
-        }
-    }
 
-    let mut search_dirs = Vec::new();
-    if let Ok(home) = std::env::var("HOME") {
-        search_dirs.push(format!("{}/.local/share/icons", home));
-        search_dirs.push(format!("{}/.icons", home));
-    }
-    search_dirs.push("/usr/share/icons".to_string());
-    search_dirs.push("/usr/share/pixmaps".to_string());
-
-    let sub_paths = [
-        "hicolor/16x16/status",
-        "hicolor/22x22/status",
-        "hicolor/24x24/status",
-        "hicolor/32x32/status",
-        "hicolor/48x48/status",
-        "hicolor/scalable/status",
-        "hicolor/16x16/apps",
-        "hicolor/22x22/apps",
-        "hicolor/24x24/apps",
-        "hicolor/32x32/apps",
-        "hicolor/48x48/apps",
-        "hicolor/scalable/apps",
-        "gnome/16x16/status",
-        "gnome/22x22/status",
-        "gnome/24x24/status",
-        "gnome/32x32/status",
-        "gnome/48x48/status",
-        "gnome/scalable/status",
-        "gnome/16x16/apps",
-        "gnome/22x22/apps",
-        "gnome/24x24/apps",
-        "gnome/32x32/apps",
-        "gnome/48x48/apps",
-        "gnome/scalable/apps",
-    ];
-
-    for base in &search_dirs {
-        for sub in &sub_paths {
-            let path_png = std::path::Path::new(base).join(sub).join(format!("{}.png", icon_name));
-            if path_png.exists() && path_png.is_file() {
-                return Some(path_png);
-            }
-            let path_svg = std::path::Path::new(base).join(sub).join(format!("{}.svg", icon_name));
-            if path_svg.exists() && path_svg.is_file() {
-                return Some(path_svg);
-            }
-        }
-        let base_path = std::path::Path::new(base);
-        if base_path.exists() {
-            if let Some(found) = find_icon_file(base_path, icon_name) {
-                return Some(found);
-            }
-        }
-    }
-
-    None
-}
-
-async fn fetch_tray_item(conn: &zbus::Connection, addr: &NotifierAddress) -> Result<TrayItem, zbus::Error> {
-    let proxy = StatusNotifierItemProxy::builder(conn)
-        .destination(addr.destination.clone())?
-        .path(addr.path.clone())?
-        .build()
-        .await?;
-
-    let id = format!("{}/{}", addr.destination, addr.path.trim_start_matches('/'));
-    let icon_name = proxy.icon_name().await.ok();
-    let icon_theme_path = proxy.icon_theme_path().await.ok();
-    let title = proxy.title().await.ok();
-    let dbus_id = proxy.id().await.ok();
-
-    let mut pixmaps = proxy.icon_pixmap().await.ok().and_then(|v| {
-        if v.is_empty() || (v.len() == 1 && v[0].0 == 0 && v[0].1 == 0) {
-            None
-        } else {
-            Some(v.into_iter()
-                .map(|(w, h, pixels)| TrayPixmap {
-                    width: w,
-                    height: h,
-                    pixels,
-                })
-                .collect::<Vec<_>>())
-        }
-    });
-
-    if pixmaps.is_none() {
-        if let Some(ref name) = icon_name {
-            if let Some(icon_path) = resolve_icon_path(icon_theme_path.as_deref(), name) {
-                let ext = icon_path.extension().and_then(|e| e.to_str()).unwrap_or("");
-                let pixmap = if ext.eq_ignore_ascii_case("svg") {
-                    load_svg_as_pixmap(&icon_path)
-                } else {
-                    load_png_as_pixmap(&icon_path)
-                };
-                if let Some(pixmap) = pixmap {
-                    pixmaps = Some(vec![pixmap]);
-                }
-            }
-        }
-    }
-
-    Ok(TrayItem {
-        id,
-        icon_name,
-        icon_theme_path,
-        pixmaps,
-        title,
-        dbus_id,
-    })
-}
-
-struct Watcher {
-    registered_items: Arc<tokio::sync::Mutex<HashMap<String, NotifierAddress>>>,
-    sender: calloop::channel::Sender<CustomEvent>,
-    tokio_handle: tokio::runtime::Handle,
-}
-
-#[zbus::interface(name = "org.kde.StatusNotifierWatcher")]
-impl Watcher {
-    async fn register_status_notifier_item(
-        &self,
-        service: &str,
-        #[zbus(header)] header: zbus::MessageHeader<'_>,
-        #[zbus(connection)] conn: &zbus::Connection,
-    ) {
-        let sender = header
-            .sender()
-            .map(|s| s.to_string())
-            .unwrap_or_else(|| service.to_string());
-        
-        if let Ok(addr) = NotifierAddress::from_notifier_service(service, &sender) {
-            let mut items = self.registered_items.lock().await;
-            let full_address = format!("{}/{}", addr.destination, addr.path.trim_start_matches('/'));
-            if !items.contains_key(&full_address) {
-                items.insert(full_address.clone(), addr.clone());
-                
-                let conn = conn.clone();
-                let addr_clone = addr.clone();
-                let sender_clone = self.sender.clone();
-                
-                self.tokio_handle.spawn(async move {
-                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
-                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
-                    }
-                    
-                    // Listen for updates
-                    if let Ok(proxy) = StatusNotifierItemProxy::builder(&conn)
-                        .destination(addr_clone.destination.clone())
-                        .unwrap()
-                        .path(addr_clone.path.clone())
-                        .unwrap()
-                        .build()
-                        .await
-                    {
-                        let mut new_icon_stream = proxy.receive_new_icon().await.ok();
-                        let mut new_title_stream = proxy.receive_new_title().await.ok();
-                        let mut new_status_stream = proxy.receive_new_status().await.ok();
-                        
-                        use tokio_stream::StreamExt;
-                        loop {
-                            tokio::select! {
-                                Some(_) = async {
-                                    if let Some(ref mut s) = new_icon_stream {
-                                        s.next().await
-                                    } else {
-                                        std::future::pending().await
-                                    }
-                                } => {
-                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
-                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
-                                    }
-                                }
-                                Some(_) = async {
-                                    if let Some(ref mut s) = new_title_stream {
-                                        s.next().await
-                                    } else {
-                                        std::future::pending().await
-                                    }
-                                } => {
-                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
-                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
-                                    }
-                                }
-                                Some(_) = async {
-                                    if let Some(ref mut s) = new_status_stream {
-                                        s.next().await
-                                    } else {
-                                        std::future::pending().await
-                                    }
-                                } => {
-                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
-                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
-                                    }
-                                }
-                            }
-                        }
-                    }
-                });
-            }
-        }
-    }
-
-    async fn register_status_notifier_host(&self, _service: &str) {}
-
-    #[zbus(property)]
-    async fn protocol_version(&self) -> i32 {
-        0
-    }
-
-    #[zbus(property)]
-    async fn is_status_notifier_host_registered(&self) -> bool {
-        true
-    }
-
-    #[zbus(property)]
-    async fn registered_status_notifier_items(&self) -> Vec<String> {
-        let items = self.registered_items.lock().await;
-        items.keys().cloned().collect()
-    }
-}
-
-struct StatusInterface;
-
-#[zbus::interface(name = "org.clear.StatusInterface")]
-impl StatusInterface {
-    async fn notify_attention(&self, app_id: String, title: String) {
-        eprintln!("[status-interface] Received NotifyAttention: app_id={}, title={}", app_id, title);
-        let title_escaped = title.replace('\'', "'\\''");
-        let app_id_escaped = app_id.replace('\'', "'\\''");
-        let cmd = format!(
-            "notify-send -a '{}' '{} needs attention' 'This window has requested activation.'",
-            app_id_escaped, title_escaped
-        );
-        std::process::Command::new("sh")
-            .args(["-c", &cmd])
-            .spawn()
-            .ok();
-    }
-}
-
-async fn spawn_status_tray(sender: calloop::channel::Sender<CustomEvent>) {
-    let registered_items = Arc::new(tokio::sync::Mutex::new(HashMap::new()));
-    let tokio_handle = tokio::runtime::Handle::current();
-
-    let watcher = Watcher {
-        registered_items: registered_items.clone(),
-        sender: sender.clone(),
-        tokio_handle,
-    };
-
-    let conn = match zbus::ConnectionBuilder::session() {
-        Ok(builder) => {
-            match builder
-                .name("org.kde.StatusNotifierWatcher")
-                .unwrap()
-                .serve_at("/StatusNotifierWatcher", watcher)
-                .unwrap()
-                .serve_at("/StatusInterface", StatusInterface)
-                .unwrap()
-                .build()
-                .await
-            {
-                Ok(c) => c,
-                Err(e) => {
-                    eprintln!("Failed to build D-Bus connection: {:?}", e);
-                    return;
-                }
-            }
-        }
-        Err(e) => {
-            eprintln!("Failed to initialize D-Bus session: {:?}", e);
-            return;
-        }
-    };
-
-    println!("StatusNotifierWatcher running successfully on D-Bus!");
-
-    // Start NameOwnerChanged listener to detect when tray apps disconnect
-    let dbus_proxy = match zbus::fdo::DBusProxy::new(&conn).await {
-        Ok(p) => p,
-        Err(e) => {
-            eprintln!("Failed to create DBusProxy: {:?}", e);
-            return;
-        }
-    };
-    let mut owner_changes = match dbus_proxy.receive_name_owner_changed().await {
-        Ok(oc) => oc,
-        Err(e) => {
-            eprintln!("Failed to receive name owner changed: {:?}", e);
-            return;
-        }
-    };
-
-    let registered_items_clone = registered_items.clone();
-    let sender_clone = sender.clone();
-    
-    tokio::spawn(async move {
-        use tokio_stream::StreamExt;
-        while let Some(signal) = owner_changes.next().await {
-            if let Ok(args) = signal.args() {
-                let old = args.old_owner;
-                let new = args.new_owner;
-                let old_opt: &Option<_> = &*old;
-                if let Some(ref old_owner) = old_opt {
-                    if new.is_none() {
-                        let mut items = registered_items_clone.lock().await;
-                        let mut to_remove = Vec::new();
-                        for (key, addr) in items.iter() {
-                            if addr.destination == old_owner.as_str() {
-                                to_remove.push(key.clone());
-                            }
-                        }
-                        for key in to_remove {
-                            items.remove(&key);
-                            let _ = sender_clone.send(CustomEvent::TrayRemoved(key));
-                        }
-                    }
-                }
-            }
-        }
-    });
-
-    // Keep the task alive
-    loop {
-        tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
-    }
-}
 
 fn main() {
     env_logger::Builder::from_default_env()
@@ -3155,215 +1883,6 @@ fn main() {
     cce_ui::engine::run::<StatusApp>();
 }
 
-fn parse_json(content: &str) -> serde_json::Value {
-    cce_ui::config::parse_kdl_to_json(content)
-}
-
-pub(crate) fn json_find_key<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
-    fn find_recursive<'a>(val: &'a serde_json::Value, key: &str) -> Option<&'a serde_json::Value> {
-        if let Some(obj) = val.as_object() {
-            if let Some(v) = obj.get(key) {
-                return Some(v);
-            }
-            let parts: Vec<&str> = key.split('_').collect();
-            for i in 1..parts.len() {
-                let (prefix_parts, suffix_parts) = parts.split_at(i);
-                let prefix = prefix_parts.join("_");
-                let suffix = suffix_parts.join("_");
-                if let Some(sub_val) = obj.get(&prefix) {
-                    if let Some(found) = find_recursive(sub_val, &suffix) {
-                        return Some(found);
-                    }
-                }
-            }
-            for (_, sub_val) in obj.iter() {
-                if let Some(found) = find_recursive(sub_val, key) {
-                    return Some(found);
-                }
-            }
-        }
-        None
-    }
-    find_recursive(val, key)
-}
-
-pub(crate) fn get_cached_config() -> serde_json::Value {
-    cce_ui::config::cached_config()
-}
-
-pub(crate) fn get_cached_config_content() -> String {
-    cce_ui::config::cached_config_content()
-}
-
-fn read_normal_color_from_config() -> Option<[f32; 4]> {
-    let content = get_cached_config_content();
-    parse_srgb_color_from_key(&content, "status_normal_color")
-}
-
-pub(crate) fn read_disabled_color_from_config() -> Option<[f32; 4]> {
-    let content = get_cached_config_content();
-    parse_srgb_color_from_key(&content, "disabled_color")
-}
-
-fn parse_srgb_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
-    let val = parse_json(content);
-    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
-        if let Some(rgb) = parse_hex(s) {
-            let r = rgb[0] as f32 / 255.0;
-            let g = rgb[1] as f32 / 255.0;
-            let b = rgb[2] as f32 / 255.0;
-            return Some([r, g, b, 1.0]);
-        }
-    }
-    None
-}
-
-fn read_status_font_from_config() -> String {
-    let val = get_cached_config();
-    if let Some(font_str) = json_find_key(&val, "status_font").and_then(|v| v.as_str()) {
-        return font_str.to_string();
-    }
-
-    let font_conf_path = cce_ui::config::config_home().join("fontconfig").join("fonts.conf");
-    if let Ok(content) = std::fs::read_to_string(&font_conf_path) {
-        if let Some(font) = parse_font_for_alias(&content, "status-interface") {
-            return font;
-        }
-    }
-    "sans-serif".to_string()
-}
-
-fn read_status_height_from_config() -> f32 {
-    let val = get_cached_config();
-    json_find_key(&val, "bar_height").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(28.0)
-}
-
-fn read_status_font_size_from_config() -> f32 {
-    let val = get_cached_config();
-    
-    if let Some(font_str) = json_find_key(&val, "status_font").and_then(|v| v.as_str()) {
-        let (_, parsed_size) = cce_ui::layout::parse_font_string(font_str);
-        if let Some(size) = parsed_size {
-            return size;
-        }
-    }
-    
-    json_find_key(&val, "status_font_size").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(11.0)
-}
-
-fn read_status_padding_from_config() -> f32 {
-    let val = get_cached_config();
-    json_find_key(&val, "status_padding").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(8.0)
-}
-
-fn read_status_module_spacing_from_config() -> f32 {
-    let val = get_cached_config();
-    json_find_key(&val, "status_module_spacing").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(8.0)
-}
-
-fn read_separator_color_from_config() -> Option<[f32; 4]> {
-    let content = get_cached_config_content();
-    parse_color_from_key(&content, "status_separator_color")
-}
-
-
-
-fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
-    let lines: Vec<&str> = content.lines().collect();
-    for i in 0..lines.len() {
-        let line = lines[i].trim();
-        if line.contains("<test") && line.contains("name=\"family\"") && line.contains(&format!("<string>{}</string>", alias)) {
-            for j in (i + 1)..(i + 6).min(lines.len()) {
-                let next_line = lines[j].trim();
-                if next_line.contains("<edit") {
-                    for k in (j + 1)..(j + 6).min(lines.len()) {
-                        let str_line = lines[k].trim();
-                        if str_line.contains("<string>") && str_line.contains("</string>") {
-                            if let Some(start) = str_line.find("<string>") {
-                                if let Some(end) = str_line.find("</string>") {
-                                    return Some(str_line[start + 8..end].to_string());
-                                }
-                            }
-                        }
-                    }
-                }
-            }
-        }
-    }
-    None
-}
-
-fn read_bg_color_from_config() -> Option<[f32; 4]> {
-    let content = get_cached_config_content();
-    parse_color_from_key(&content, "background_color")
-        .or_else(|| parse_color_from_key(&content, "low_color"))
-        .or_else(|| parse_color_from_key(&content, "desktop_gap_color"))
-}
-
-fn parse_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
-    let val = parse_json(content);
-    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
-        if let Some(rgba) = parse_hex_rgba(s) {
-            let r = (rgba[0] as f32 / 255.0).powf(2.2);
-            let g = (rgba[1] as f32 / 255.0).powf(2.2);
-            let b = (rgba[2] as f32 / 255.0).powf(2.2);
-            let a = rgba[3] as f32 / 255.0;
-            return Some([r, g, b, a]);
-        }
-    }
-    None
-}
-
-fn parse_hex(s: &str) -> Option<[u8; 3]> {
-    cce_ui::color::parse_hex_bytes(s).map(|[r, g, b, _]| [r, g, b])
-}
-fn parse_hex_rgba(s: &str) -> Option<[u8; 4]> {
-    cce_ui::color::parse_hex_bytes(s)
-}
-
-fn parse_rgba_color_from_key(content: &str, key: &str) -> Option<[f32; 4]> {
-    let val = parse_json(content);
-    if let Some(s) = json_find_key(&val, key).and_then(|v| v.as_str()) {
-        if let Some(rgba) = parse_hex_rgba(s) {
-            let r = (rgba[0] as f32 / 255.0).powf(2.2);
-            let g = (rgba[1] as f32 / 255.0).powf(2.2);
-            let b = (rgba[2] as f32 / 255.0).powf(2.2);
-            let a = rgba[3] as f32 / 255.0;
-            return Some([r, g, b, a]);
-        }
-    }
-    None
-}
-
-fn read_status_background_blur_from_config() -> f32 {
-    let val = get_cached_config();
-    json_find_key(&val, "status_background_blur").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(0.0)
-}
-
-fn read_status_box_background_color_from_config() -> Option<[f32; 4]> {
-    let content = get_cached_config_content();
-    let mut color = parse_rgba_color_from_key(&content, "status_background_color")
-        .unwrap_or_else(|| {
-            let r = (0x15 as f32 / 255.0).powf(2.2);
-            let g = (0x15 as f32 / 255.0).powf(2.2);
-            let b = (0x20 as f32 / 255.0).powf(2.2);
-            [r, g, b, 0.9]
-        });
-
-    let blur = read_status_background_blur_from_config();
-    
-    // Scale RGB by (1.0 - blur) to apply tint factor while keeping alpha as full opacity for the blur shader
-    color[0] *= 1.0 - blur;
-    color[1] *= 1.0 - blur;
-    color[2] *= 1.0 - blur;
-
-    Some(color)
-}
-
-fn read_status_box_corner_radius_from_config() -> f32 {
-    let val = get_cached_config();
-    json_find_key(&val, "status_box_corner_radius").and_then(|v| v.as_f64()).map(|n| n as f32).unwrap_or(4.0)
-}
 
 #[cfg(test)]
 mod tests {
@@ -3651,22 +2170,3 @@ style {
     }
 }
 
-fn get_cce_cmd() -> String {
-    if let Ok(home) = std::env::var("HOME") {
-        let path = format!("{}/.local/bin/cce", home);
-        if std::path::Path::new(&path).exists() {
-            return path;
-        }
-    }
-    "cce".to_string()
-}
-
-fn get_ccectl_cmd() -> String {
-    if let Ok(home) = std::env::var("HOME") {
-        let path = format!("{}/.local/bin/ccectl", home);
-        if std::path::Path::new(&path).exists() {
-            return path;
-        }
-    }
-    "ccectl".to_string()
-}
diff --git a/src/stats.rs b/src/stats.rs
new file mode 100644
index 0000000..9531be3
--- /dev/null
+++ b/src/stats.rs
@@ -0,0 +1,209 @@
+//! System statistics: /proc, /sys, and pactl readers plus the polling task
+//! that feeds `SystemStats` updates to the bar.
+
+use crate::{CustomEvent, SystemStats};
+
+pub(crate) fn read_cpu_ticks() -> Option<(u64, u64)> {
+    let stat = std::fs::read_to_string("/proc/stat").ok()?;
+    let first_line = stat.lines().next()?;
+    if first_line.starts_with("cpu ") {
+        let parts: Vec<u64> = first_line
+            .split_whitespace()
+            .skip(1)
+            .filter_map(|s| s.parse::<u64>().ok())
+            .collect();
+        if parts.len() >= 4 {
+            let idle = parts[3];
+            let total: u64 = parts.iter().sum();
+            return Some((total, idle));
+        }
+    }
+    None
+}
+
+pub(crate) fn read_memory_usage() -> Option<String> {
+    let meminfo = std::fs::read_to_string("/proc/meminfo").ok()?;
+    let mut total = 0.0;
+    let mut free = 0.0;
+    let mut buffers = 0.0;
+    let mut cached = 0.0;
+    for line in meminfo.lines() {
+        if line.starts_with("MemTotal:") {
+            total = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
+        } else if line.starts_with("MemFree:") {
+            free = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
+        } else if line.starts_with("Buffers:") {
+            buffers = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
+        } else if line.starts_with("Cached:") {
+            cached = line.split_whitespace().nth(1)?.parse::<f32>().ok()? / 1024.0 / 1024.0;
+        }
+    }
+    if total > 0.0 {
+        let used = total - free - buffers - cached;
+        Some(format!("Mem {:.1}/{:.1}G", used, total))
+    } else {
+        None
+    }
+}
+
+pub(crate) fn read_battery_details() -> Option<(String, i32, bool)> {
+    for bat in &["BAT0", "BAT1"] {
+        let cap_path = format!("/sys/class/power_supply/{}/capacity", bat);
+        let status_path = format!("/sys/class/power_supply/{}/status", bat);
+        if let Ok(cap_str) = std::fs::read_to_string(&cap_path) {
+            let cap_trimmed = cap_str.trim();
+            let cap = cap_trimmed.parse::<i32>().unwrap_or(0);
+            let status = std::fs::read_to_string(&status_path).unwrap_or_default();
+            let is_charging = status.trim() == "Charging";
+            let charge_symbol = if is_charging { "⚡" } else { "Bat" };
+            return Some((format!("{} {}%", charge_symbol, cap_trimmed), cap, is_charging));
+        }
+    }
+    None
+}
+
+pub(crate) fn read_brightness() -> Option<String> {
+    let dir = std::fs::read_dir("/sys/class/backlight").ok()?;
+    for entry in dir {
+        if let Ok(entry) = entry {
+            let path = entry.path();
+            let cur_path = path.join("brightness");
+            let max_path = path.join("max_brightness");
+            if cur_path.exists() && max_path.exists() {
+                let cur_str = std::fs::read_to_string(cur_path).ok()?;
+                let max_str = std::fs::read_to_string(max_path).ok()?;
+                let cur = cur_str.trim().parse::<f32>().ok()?;
+                let max = max_str.trim().parse::<f32>().ok()?;
+                if max > 0.0 {
+                    let pct = (cur / max * 100.0).round() as i32;
+                    return Some(format!("Bri {}%", pct));
+                }
+            }
+        }
+    }
+    None
+}
+
+pub(crate) async fn read_volume() -> Option<(String, bool)> {
+    let vol_output = match tokio::process::Command::new("pactl")
+        .args(["get-sink-volume", "@DEFAULT_SINK@"])
+        .output()
+        .await
+    {
+        Ok(o) => o,
+        Err(e) => {
+            eprintln!("[read_volume] failed to spawn pactl: {:?}", e);
+            return None;
+        }
+    };
+    if !vol_output.status.success() {
+        eprintln!("[read_volume] pactl get-sink-volume exited with error: {:?}", String::from_utf8_lossy(&vol_output.stderr));
+        return None;
+    }
+    let vol_str = String::from_utf8_lossy(&vol_output.stdout);
+    
+    let mute_output = match tokio::process::Command::new("pactl")
+        .args(["get-sink-mute", "@DEFAULT_SINK@"])
+        .output()
+        .await
+    {
+        Ok(o) => o,
+        Err(e) => {
+            eprintln!("[read_volume] failed to spawn pactl mute: {:?}", e);
+            return None;
+        }
+    };
+    if !mute_output.status.success() {
+        eprintln!("[read_volume] pactl get-sink-mute exited with error: {:?}", String::from_utf8_lossy(&mute_output.stderr));
+        return None;
+    }
+    let mute_str = String::from_utf8_lossy(&mute_output.stdout);
+    let muted = mute_str.contains("yes");
+
+    let mut pct = None;
+    if let Some(pos) = vol_str.find('%') {
+        let start = vol_str[..pos].rfind(|c: char| !c.is_ascii_digit()).map(|i| i + 1).unwrap_or(0);
+        if let Ok(num) = vol_str[start..pos].parse::<u32>() {
+            pct = Some(num);
+        }
+    }
+
+    match (muted, pct) {
+        (true, Some(p)) => Some((format!("Vol {}%", p), true)),
+        (true, None) => Some(("Vol Muted".to_string(), true)),
+        (false, Some(p)) => Some((format!("Vol {}%", p), false)),
+        (false, None) => Some(("Vol N/A".to_string(), false)),
+    }
+}
+
+pub(crate) fn get_initial_stats() -> SystemStats {
+    let clock = chrono::Local::now().format("%A, %B %d, %Y %I:%M %p").to_string();
+    let memory = read_memory_usage().unwrap_or_else(|| "Mem N/A".to_string());
+
+    let (battery_str, battery_capacity, battery_charging) = if let Some((s, cap, chg)) = read_battery_details() {
+        (s, cap, chg)
+    } else {
+        ("".to_string(), 0, false)
+    };
+    let (volume, volume_muted) = pollster::block_on(read_volume()).unwrap_or_else(|| ("".to_string(), false));
+    let brightness = read_brightness().unwrap_or_default();
+
+    SystemStats {
+        clock,
+        memory,
+        cpu: "Cpu 0.0%".to_string(),
+        battery: battery_str,
+        battery_capacity,
+        battery_charging,
+        volume,
+        volume_muted,
+        brightness,
+    }
+}
+
+pub(crate) async fn spawn_system_stats(sender: calloop::channel::Sender<CustomEvent>) {
+    eprintln!("[spawn_system_stats] Starting system stats loop!");
+    let mut last_cpu = read_cpu_ticks().unwrap_or((0, 0));
+    loop {
+        eprintln!("[spawn_system_stats] loop iteration start");
+        let clock = chrono::Local::now().format("%A, %B %d, %Y %I:%M %p").to_string();
+        let memory = read_memory_usage().unwrap_or_else(|| "Mem N/A".to_string());
+        
+        let cpu_str = if let Some(current_cpu) = read_cpu_ticks() {
+            let total_diff = current_cpu.0 - last_cpu.0;
+            let idle_diff = current_cpu.1 - last_cpu.1;
+            last_cpu = current_cpu;
+            if total_diff > 0 {
+                let usage = 100.0 - (idle_diff as f32 * 100.0 / total_diff as f32);
+                format!("Cpu {:.1}%", usage)
+            } else {
+                "Cpu 0.0%".to_string()
+            }
+        } else {
+            "Cpu N/A".to_string()
+        };
+
+        let (battery_str, battery_capacity, battery_charging) = if let Some((s, cap, chg)) = read_battery_details() {
+            (s, cap, chg)
+        } else {
+            ("".to_string(), 0, false)
+        };
+        let (volume, volume_muted) = read_volume().await.unwrap_or_else(|| ("".to_string(), false));
+        let brightness = read_brightness().unwrap_or_default();
+
+        let stats = SystemStats {
+            clock,
+            memory,
+            cpu: cpu_str,
+            battery: battery_str,
+            battery_capacity,
+            battery_charging,
+            volume,
+            volume_muted,
+            brightness,
+        };
+        eprintln!("[spawn_system_stats] stats: {:?}", stats);
+        let _ = sender.send(CustomEvent::SystemStatsUpdated(stats));
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+}
diff --git a/src/tray.rs b/src/tray.rs
new file mode 100644
index 0000000..2e05133
--- /dev/null
+++ b/src/tray.rs
@@ -0,0 +1,543 @@
+//! StatusNotifierItem tray host: the SNI watcher/host D-Bus interfaces,
+//! item fetching, and icon loading.
+
+use std::collections::HashMap;
+use std::sync::Arc;
+
+use crate::{CustomEvent, TrayItem, TrayPixmap};
+
+#[derive(Debug, Clone)]
+pub struct NotifierAddress {
+    pub destination: String,
+    pub path: String,
+}
+
+impl NotifierAddress {
+    pub fn from_notifier_service(service: &str, sender: &str) -> Result<Self, String> {
+        if service.starts_with('/') {
+            Ok(NotifierAddress {
+                destination: sender.to_string(),
+                path: service.to_string(),
+            })
+        } else if let Some((destination, path)) = service.split_once('/') {
+            Ok(NotifierAddress {
+                destination: destination.to_string(),
+                path: format!("/{}", path),
+            })
+        } else if service.contains(':') {
+            let split = service.split(':').collect::<Vec<&str>>();
+            Ok(NotifierAddress {
+                destination: format!(":{}", split[1]),
+                path: "/StatusNotifierItem".to_string(),
+            })
+        } else {
+            Ok(NotifierAddress {
+                destination: service.to_string(),
+                path: "/StatusNotifierItem".to_string(),
+            })
+        }
+    }
+}
+
+#[zbus::proxy(
+    interface = "org.kde.StatusNotifierItem",
+    default_path = "/StatusNotifierItem"
+)]
+pub(crate) trait StatusNotifierItem {
+    #[zbus(property)]
+    fn id(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn category(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn status(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn title(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn icon_name(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn icon_theme_path(&self) -> zbus::Result<String>;
+
+    #[zbus(property)]
+    fn icon_pixmap(&self) -> zbus::Result<Vec<(i32, i32, Vec<u8>)>>;
+
+    #[zbus(signal)]
+    fn new_icon(&self) -> zbus::Result<()>;
+    #[zbus(signal)]
+    fn new_title(&self) -> zbus::Result<()>;
+
+    #[zbus(signal)]
+    fn new_status(&self) -> zbus::Result<()>;
+
+    fn activate(&self, x: i32, y: i32) -> zbus::Result<()>;
+    fn context_menu(&self, x: i32, y: i32) -> zbus::Result<()>;
+
+    #[zbus(property)]
+    fn item_is_menu(&self) -> zbus::Result<bool>;
+
+    #[zbus(property)]
+    fn menu(&self) -> zbus::Result<zbus::zvariant::OwnedObjectPath>;
+}
+
+pub(crate) fn find_icon_file(dir: &std::path::Path, icon_name: &str) -> Option<std::path::PathBuf> {
+    if let Ok(entries) = std::fs::read_dir(dir) {
+        for entry in entries.filter_map(Result::ok) {
+            if let Ok(file_type) = entry.file_type() {
+                let path = entry.path();
+                if file_type.is_dir() {
+                    if !file_type.is_symlink() {
+                        if let Some(found) = find_icon_file(&path, icon_name) {
+                            return Some(found);
+                        }
+                    }
+                } else if file_type.is_file() {
+                    if let Some(file_name) = path.file_name().and_then(|f| f.to_str()) {
+                        if file_name == format!("{}.png", icon_name) || file_name == format!("{}.svg", icon_name) {
+                            return Some(path);
+                        }
+                    }
+                }
+            }
+        }
+    }
+    None
+}
+
+pub(crate) fn load_png_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
+    let file = std::fs::File::open(path).ok()?;
+    let mut decoder = png::Decoder::new(file);
+    decoder.set_transformations(png::Transformations::EXPAND);
+    let mut reader = decoder.read_info().ok()?;
+    let mut buf = vec![0; reader.output_buffer_size()];
+    let info = reader.next_frame(&mut buf).ok()?;
+    
+    let width = info.width as i32;
+    let height = info.height as i32;
+    let mut argb_pixels = Vec::with_capacity((width * height * 4) as usize);
+    
+    let actual_bytes = &buf[..info.buffer_size()];
+    match info.color_type {
+        png::ColorType::Rgba => {
+            for chunk in actual_bytes.chunks_exact(4) {
+                argb_pixels.push(chunk[3]); // A
+                argb_pixels.push(chunk[0]); // R
+                argb_pixels.push(chunk[1]); // G
+                argb_pixels.push(chunk[2]); // B
+            }
+        }
+        png::ColorType::Rgb => {
+            for chunk in actual_bytes.chunks_exact(3) {
+                argb_pixels.push(255);      // A
+                argb_pixels.push(chunk[0]); // R
+                argb_pixels.push(chunk[1]); // G
+                argb_pixels.push(chunk[2]); // B
+            }
+        }
+        png::ColorType::Grayscale => {
+            for &g in actual_bytes {
+                argb_pixels.push(255); // A
+                argb_pixels.push(g);   // R
+                argb_pixels.push(g);   // G
+                argb_pixels.push(g);   // B
+            }
+        }
+        png::ColorType::GrayscaleAlpha => {
+            for chunk in actual_bytes.chunks_exact(2) {
+                argb_pixels.push(chunk[1]); // A
+                argb_pixels.push(chunk[0]); // R
+                argb_pixels.push(chunk[0]); // G
+                argb_pixels.push(chunk[0]); // B
+            }
+        }
+        _ => return None,
+    }
+    
+    Some(TrayPixmap {
+        width,
+        height,
+        pixels: argb_pixels,
+    })
+}
+
+pub(crate) fn load_svg_as_pixmap(path: &std::path::Path) -> Option<TrayPixmap> {
+    let svg_data = std::fs::read(path).ok()?;
+    let opt = resvg::usvg::Options::default();
+    let fontdb = resvg::usvg::fontdb::Database::new();
+    let tree = resvg::usvg::Tree::from_data(&svg_data, &opt, &fontdb).ok()?;
+    
+    let target_w = 48;
+    let target_h = 48;
+    let mut pixmap = resvg::tiny_skia::Pixmap::new(target_w, target_h)?;
+    
+    let orig_w = tree.size().width();
+    let orig_h = tree.size().height();
+    let sx = target_w as f32 / orig_w;
+    let sy = target_h as f32 / orig_h;
+    let transform = resvg::tiny_skia::Transform::from_scale(sx, sy);
+    
+    resvg::render(&tree, transform, &mut pixmap.as_mut());
+    
+    let raw_pixels = pixmap.data();
+    let mut argb_pixels = Vec::with_capacity((target_w * target_h * 4) as usize);
+    for chunk in raw_pixels.chunks_exact(4) {
+        argb_pixels.push(chunk[3]); // A
+        argb_pixels.push(chunk[0]); // R
+        argb_pixels.push(chunk[1]); // G
+        argb_pixels.push(chunk[2]); // B
+    }
+    
+    Some(TrayPixmap {
+        width: target_w as i32,
+        height: target_h as i32,
+        pixels: argb_pixels,
+    })
+}
+
+pub(crate) fn resolve_icon_path(theme_path: Option<&str>, icon_name: &str) -> Option<std::path::PathBuf> {
+    if icon_name.is_empty() {
+        return None;
+    }
+
+    let icon_name = if icon_name == "dropbox" { "dropboxstatus-idle" } else { icon_name };
+
+    if let Some(path_str) = theme_path {
+        if !path_str.is_empty() {
+            let path = std::path::Path::new(path_str);
+            if path.exists() {
+                if let Some(found) = find_icon_file(path, icon_name) {
+                    return Some(found);
+                }
+            }
+        }
+    }
+
+    let mut search_dirs = Vec::new();
+    if let Ok(home) = std::env::var("HOME") {
+        search_dirs.push(format!("{}/.local/share/icons", home));
+        search_dirs.push(format!("{}/.icons", home));
+    }
+    search_dirs.push("/usr/share/icons".to_string());
+    search_dirs.push("/usr/share/pixmaps".to_string());
+
+    let sub_paths = [
+        "hicolor/16x16/status",
+        "hicolor/22x22/status",
+        "hicolor/24x24/status",
+        "hicolor/32x32/status",
+        "hicolor/48x48/status",
+        "hicolor/scalable/status",
+        "hicolor/16x16/apps",
+        "hicolor/22x22/apps",
+        "hicolor/24x24/apps",
+        "hicolor/32x32/apps",
+        "hicolor/48x48/apps",
+        "hicolor/scalable/apps",
+        "gnome/16x16/status",
+        "gnome/22x22/status",
+        "gnome/24x24/status",
+        "gnome/32x32/status",
+        "gnome/48x48/status",
+        "gnome/scalable/status",
+        "gnome/16x16/apps",
+        "gnome/22x22/apps",
+        "gnome/24x24/apps",
+        "gnome/32x32/apps",
+        "gnome/48x48/apps",
+        "gnome/scalable/apps",
+    ];
+
+    for base in &search_dirs {
+        for sub in &sub_paths {
+            let path_png = std::path::Path::new(base).join(sub).join(format!("{}.png", icon_name));
+            if path_png.exists() && path_png.is_file() {
+                return Some(path_png);
+            }
+            let path_svg = std::path::Path::new(base).join(sub).join(format!("{}.svg", icon_name));
+            if path_svg.exists() && path_svg.is_file() {
+                return Some(path_svg);
+            }
+        }
+        let base_path = std::path::Path::new(base);
+        if base_path.exists() {
+            if let Some(found) = find_icon_file(base_path, icon_name) {
+                return Some(found);
+            }
+        }
+    }
+
+    None
+}
+
+pub(crate) async fn fetch_tray_item(conn: &zbus::Connection, addr: &NotifierAddress) -> Result<TrayItem, zbus::Error> {
+    let proxy = StatusNotifierItemProxy::builder(conn)
+        .destination(addr.destination.clone())?
+        .path(addr.path.clone())?
+        .build()
+        .await?;
+
+    let id = format!("{}/{}", addr.destination, addr.path.trim_start_matches('/'));
+    let icon_name = proxy.icon_name().await.ok();
+    let icon_theme_path = proxy.icon_theme_path().await.ok();
+    let title = proxy.title().await.ok();
+    let dbus_id = proxy.id().await.ok();
+
+    let mut pixmaps = proxy.icon_pixmap().await.ok().and_then(|v| {
+        if v.is_empty() || (v.len() == 1 && v[0].0 == 0 && v[0].1 == 0) {
+            None
+        } else {
+            Some(v.into_iter()
+                .map(|(w, h, pixels)| TrayPixmap {
+                    width: w,
+                    height: h,
+                    pixels,
+                })
+                .collect::<Vec<_>>())
+        }
+    });
+
+    if pixmaps.is_none() {
+        if let Some(ref name) = icon_name {
+            if let Some(icon_path) = resolve_icon_path(icon_theme_path.as_deref(), name) {
+                let ext = icon_path.extension().and_then(|e| e.to_str()).unwrap_or("");
+                let pixmap = if ext.eq_ignore_ascii_case("svg") {
+                    load_svg_as_pixmap(&icon_path)
+                } else {
+                    load_png_as_pixmap(&icon_path)
+                };
+                if let Some(pixmap) = pixmap {
+                    pixmaps = Some(vec![pixmap]);
+                }
+            }
+        }
+    }
+
+    Ok(TrayItem {
+        id,
+        icon_name,
+        icon_theme_path,
+        pixmaps,
+        title,
+        dbus_id,
+    })
+}
+
+struct Watcher {
+    registered_items: Arc<tokio::sync::Mutex<HashMap<String, NotifierAddress>>>,
+    sender: calloop::channel::Sender<CustomEvent>,
+    tokio_handle: tokio::runtime::Handle,
+}
+
+#[zbus::interface(name = "org.kde.StatusNotifierWatcher")]
+impl Watcher {
+    async fn register_status_notifier_item(
+        &self,
+        service: &str,
+        #[zbus(header)] header: zbus::MessageHeader<'_>,
+        #[zbus(connection)] conn: &zbus::Connection,
+    ) {
+        let sender = header
+            .sender()
+            .map(|s| s.to_string())
+            .unwrap_or_else(|| service.to_string());
+        
+        if let Ok(addr) = NotifierAddress::from_notifier_service(service, &sender) {
+            let mut items = self.registered_items.lock().await;
+            let full_address = format!("{}/{}", addr.destination, addr.path.trim_start_matches('/'));
+            if !items.contains_key(&full_address) {
+                items.insert(full_address.clone(), addr.clone());
+                
+                let conn = conn.clone();
+                let addr_clone = addr.clone();
+                let sender_clone = self.sender.clone();
+                
+                self.tokio_handle.spawn(async move {
+                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
+                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
+                    }
+                    
+                    // Listen for updates
+                    if let Ok(proxy) = StatusNotifierItemProxy::builder(&conn)
+                        .destination(addr_clone.destination.clone())
+                        .unwrap()
+                        .path(addr_clone.path.clone())
+                        .unwrap()
+                        .build()
+                        .await
+                    {
+                        let mut new_icon_stream = proxy.receive_new_icon().await.ok();
+                        let mut new_title_stream = proxy.receive_new_title().await.ok();
+                        let mut new_status_stream = proxy.receive_new_status().await.ok();
+                        
+                        use tokio_stream::StreamExt;
+                        loop {
+                            tokio::select! {
+                                Some(_) = async {
+                                    if let Some(ref mut s) = new_icon_stream {
+                                        s.next().await
+                                    } else {
+                                        std::future::pending().await
+                                    }
+                                } => {
+                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
+                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
+                                    }
+                                }
+                                Some(_) = async {
+                                    if let Some(ref mut s) = new_title_stream {
+                                        s.next().await
+                                    } else {
+                                        std::future::pending().await
+                                    }
+                                } => {
+                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
+                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
+                                    }
+                                }
+                                Some(_) = async {
+                                    if let Some(ref mut s) = new_status_stream {
+                                        s.next().await
+                                    } else {
+                                        std::future::pending().await
+                                    }
+                                } => {
+                                    if let Ok(item) = fetch_tray_item(&conn, &addr_clone).await {
+                                        let _ = sender_clone.send(CustomEvent::TrayUpdated(item));
+                                    }
+                                }
+                            }
+                        }
+                    }
+                });
+            }
+        }
+    }
+
+    async fn register_status_notifier_host(&self, _service: &str) {}
+
+    #[zbus(property)]
+    async fn protocol_version(&self) -> i32 {
+        0
+    }
+
+    #[zbus(property)]
+    async fn is_status_notifier_host_registered(&self) -> bool {
+        true
+    }
+
+    #[zbus(property)]
+    async fn registered_status_notifier_items(&self) -> Vec<String> {
+        let items = self.registered_items.lock().await;
+        items.keys().cloned().collect()
+    }
+}
+
+struct StatusInterface;
+
+#[zbus::interface(name = "org.clear.StatusInterface")]
+impl StatusInterface {
+    async fn notify_attention(&self, app_id: String, title: String) {
+        eprintln!("[status-interface] Received NotifyAttention: app_id={}, title={}", app_id, title);
+        let title_escaped = title.replace('\'', "'\\''");
+        let app_id_escaped = app_id.replace('\'', "'\\''");
+        let cmd = format!(
+            "notify-send -a '{}' '{} needs attention' 'This window has requested activation.'",
+            app_id_escaped, title_escaped
+        );
+        std::process::Command::new("sh")
+            .args(["-c", &cmd])
+            .spawn()
+            .ok();
+    }
+}
+
+pub(crate) async fn spawn_status_tray(sender: calloop::channel::Sender<CustomEvent>) {
+    let registered_items = Arc::new(tokio::sync::Mutex::new(HashMap::new()));
+    let tokio_handle = tokio::runtime::Handle::current();
+
+    let watcher = Watcher {
+        registered_items: registered_items.clone(),
+        sender: sender.clone(),
+        tokio_handle,
+    };
+
+    let conn = match zbus::ConnectionBuilder::session() {
+        Ok(builder) => {
+            match builder
+                .name("org.kde.StatusNotifierWatcher")
+                .unwrap()
+                .serve_at("/StatusNotifierWatcher", watcher)
+                .unwrap()
+                .serve_at("/StatusInterface", StatusInterface)
+                .unwrap()
+                .build()
+                .await
+            {
+                Ok(c) => c,
+                Err(e) => {
+                    eprintln!("Failed to build D-Bus connection: {:?}", e);
+                    return;
+                }
+            }
+        }
+        Err(e) => {
+            eprintln!("Failed to initialize D-Bus session: {:?}", e);
+            return;
+        }
+    };
+
+    println!("StatusNotifierWatcher running successfully on D-Bus!");
+
+    // Start NameOwnerChanged listener to detect when tray apps disconnect
+    let dbus_proxy = match zbus::fdo::DBusProxy::new(&conn).await {
+        Ok(p) => p,
+        Err(e) => {
+            eprintln!("Failed to create DBusProxy: {:?}", e);
+            return;
+        }
+    };
+    let mut owner_changes = match dbus_proxy.receive_name_owner_changed().await {
+        Ok(oc) => oc,
+        Err(e) => {
+            eprintln!("Failed to receive name owner changed: {:?}", e);
+            return;
+        }
+    };
+
+    let registered_items_clone = registered_items.clone();
+    let sender_clone = sender.clone();
+    
+    tokio::spawn(async move {
+        use tokio_stream::StreamExt;
+        while let Some(signal) = owner_changes.next().await {
+            if let Ok(args) = signal.args() {
+                let old = args.old_owner;
+                let new = args.new_owner;
+                let old_opt: &Option<_> = &*old;
+                if let Some(ref old_owner) = old_opt {
+                    if new.is_none() {
+                        let mut items = registered_items_clone.lock().await;
+                        let mut to_remove = Vec::new();
+                        for (key, addr) in items.iter() {
+                            if addr.destination == old_owner.as_str() {
+                                to_remove.push(key.clone());
+                            }
+                        }
+                        for key in to_remove {
+                            items.remove(&key);
+                            let _ = sender_clone.send(CustomEvent::TrayRemoved(key));
+                        }
+                    }
+                }
+            }
+        }
+    });
+
+    // Keep the task alive
+    loop {
+        tokio::time::sleep(std::time::Duration::from_secs(3600)).await;
+    }
+}