diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/main.rs | 132 |
1 files changed, 107 insertions, 25 deletions
diff --git a/src/main.rs b/src/main.rs index 5d28f90..7eb4a5e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -69,11 +69,29 @@ fn fetch_state() -> Result<(Vec<Workspace>, Vec<Window>), Box<dyn std::error::Er } fn output_status(workspaces: &[Workspace], windows: &[Window]) { - // Pre-compute terminal apps + // Pre-compute terminal apps. + // Avoid per-pid process detection for foot/footclient when multiple windows + // share the same pid, to prevent false positives across windows. + let mut pid_counts: HashMap<i32, usize> = HashMap::new(); + for w in windows { + if let Some(pid) = w.pid { + *pid_counts.entry(pid).or_insert(0) += 1; + } + } + let terminal_apps: HashMap<i32, &str> = windows .iter() .filter(|w| is_terminal(w.app_id.as_deref().unwrap_or(""))) - .filter_map(|w| w.pid.map(|pid| (pid, detect_terminal_app(pid as u32)))) + .filter_map(|w| { + let pid = w.pid?; + let app_id = w.app_id.as_deref().unwrap_or(""); + if (app_id == "foot" || app_id == "footclient" || app_id == "kitty") + && pid_counts.get(&pid).copied().unwrap_or(0) > 1 + { + return None; + } + Some((pid, detect_terminal_app(pid as u32))) + }) .filter(|(_, app)| !app.is_empty()) .collect(); @@ -84,7 +102,6 @@ fn output_status(workspaces: &[Workspace], windows: &[Window]) { let mut after_focused: Vec<String> = Vec::new(); let mut focused_output = String::new(); let mut current_section = "before"; - let mut chrome_idx = 0; let mut tooltip = String::new(); for ws in &sorted_workspaces { @@ -96,32 +113,47 @@ fn output_status(workspaces: &[Workspace], windows: &[Window]) { ws_windows.sort_by_key(|win| window_sort_key(*win)); let win_count = ws_windows.len(); - tooltip.push_str(&format!("ws{}: {} windows\\n", ws.idx, win_count)); - let ws_output = if win_count == 0 { - if ws.is_focused { - "<span color='#888888'>█</span>".to_string() + // Get workspace name if it's not default/empty + let ws_name = ws.name.as_deref().unwrap_or(""); + let has_custom_name = !ws_name.is_empty(); + + // Only add to tooltip if workspace has windows + if win_count > 0 { + if has_custom_name { + tooltip.push_str(&format!("{}: {} windows\\n", ws_name, win_count)); } else { - "<span color='#444444'>·</span>".to_string() + tooltip.push_str(&format!("ws{}: {} windows\\n", ws.idx, win_count)); } + } + + // Skip empty unfocused workspaces entirely + if win_count == 0 && !ws.is_focused { + continue; + } + + let ws_output = if win_count == 0 { + // Empty but focused - just show a dot, no name + "<span color='#888888'>·</span>".to_string() } else { let mut output = String::new(); + + // Show workspace name only when focused + if has_custom_name && ws.is_focused { + output.push_str(&format!("<span color='#cccccc'>{}</span> ", ws_name)); + } + for win in &ws_windows { let app_id = win.app_id.as_deref().unwrap_or(""); let title = win.title.as_deref().unwrap_or(""); let mut color = get_color(app_id, title, win.pid, &terminal_apps); - - if color == "chrome" { - const CHROME_COLORS: [&str; 4] = ["#ea4335", "#fbbc05", "#34a853", "#4285f4"]; - color = CHROME_COLORS[chrome_idx % 4].to_string(); - chrome_idx += 1; - } + let is_tmux = is_tmux_title(title); if !ws.is_focused { color = dim_color(&color); } - let bar = if win.is_focused { + let mut bar = if win.is_focused { "█" } else if !ws.is_focused && ws.active_window_id == Some(win.id) { "▌" @@ -129,6 +161,11 @@ fn output_status(workspaces: &[Workspace], windows: &[Window]) { "|" }; + // Use a broken bar for tmux without changing focus semantics. + if is_tmux && bar == "|" { + bar = "¦"; + } + output.push_str(&format!("<span color='{}'>{}</span>", color, bar)); } output @@ -163,25 +200,59 @@ fn output_status(workspaces: &[Workspace], windows: &[Window]) { } fn is_terminal(app_id: &str) -> bool { - app_id == "Alacritty" || app_id == "kitty" || app_id == "com.mitchellh.ghostty" + app_id == "Alacritty" + || app_id == "kitty" + || app_id == "com.mitchellh.ghostty" + || app_id == "foot" + || app_id == "footclient" +} + +fn is_tmux_title(title: &str) -> bool { + title.to_lowercase().contains("tmux") +} + +fn is_claude_title(title: &str) -> bool { + let lower = title.to_lowercase(); + lower.contains("claude") || title.contains("✳") } fn detect_terminal_app(pid: u32) -> &'static str { + let mut found_claude = false; + let mut found_codex = false; + let mut found_jcode = false; + let mut found_nvim = false; + if let Ok(children) = get_all_descendants(pid) { for child_pid in children { if let Ok(cmdline) = fs::read_to_string(format!("/proc/{}/cmdline", child_pid)) { let cmdline = cmdline.to_lowercase(); + if cmdline.contains("jcode") { + found_jcode = true; + } if cmdline.contains("claude") { - return "claude"; - } else if cmdline.contains("codex") { - return "codex"; - } else if cmdline.contains("nvim") || cmdline.contains("vim") { - return "nvim"; + found_claude = true; + } + if cmdline.contains("codex") { + found_codex = true; + } + if cmdline.contains("nvim") || cmdline.contains("vim") { + found_nvim = true; } } } } - "" + + if found_jcode { + "jcode" + } else if found_claude { + "claude" + } else if found_codex { + "codex" + } else if found_nvim { + "nvim" + } else { + "" + } } fn get_all_descendants(pid: u32) -> std::io::Result<Vec<u32>> { @@ -203,11 +274,18 @@ fn get_all_descendants(pid: u32) -> std::io::Result<Vec<u32>> { Ok(descendants) } -fn get_color(app_id: &str, title: &str, pid: Option<i32>, terminal_apps: &HashMap<i32, &str>) -> String { +fn get_color( + app_id: &str, + title: &str, + pid: Option<i32>, + terminal_apps: &HashMap<i32, &str>, +) -> String { if is_terminal(app_id) { let title_lower = title.to_lowercase(); - if title_lower.contains("claude") { + if title_lower.contains("jcode") { + return "#999999".to_string(); + } else if is_claude_title(title) { return "#f5a623".to_string(); } else if title_lower.contains("codex") { return "#56b6c2".to_string(); @@ -218,6 +296,7 @@ fn get_color(app_id: &str, title: &str, pid: Option<i32>, terminal_apps: &HashMa if let Some(pid) = pid { if let Some(&app) = terminal_apps.get(&pid) { return match app { + "jcode" => "#999999", "claude" => "#f5a623", "codex" => "#56b6c2", "nvim" => "#98c379", @@ -234,7 +313,7 @@ fn get_color(app_id: &str, title: &str, pid: Option<i32>, terminal_apps: &HashMa return "#98c379".to_string(); } if app_id == "google-chrome" || app_id.contains("chrome") { - return "chrome".to_string(); + return "#ea4335".to_string(); } if app_id == "firefox" { return "#ff7139".to_string(); @@ -242,6 +321,9 @@ fn get_color(app_id: &str, title: &str, pid: Option<i32>, terminal_apps: &HashMa if app_id == "vesktop" || app_id == "discord" { return "#c678dd".to_string(); } + if app_id == "spotify" || app_id.contains("spotify") { + return "#1DB954".to_string(); + } if app_id.contains("todoist") { return "#e06c75".to_string(); } |
