summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/main.rs132
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();
}