@@ -72,11 +72,45 @@ fn stop_graphical_session() { |
| 72 | 72 | } |
| 73 | 73 | } |
| 74 | 74 | |
| 75 | | -/// Spawn garbar as a child process. |
| 76 | | -/// Returns the child process handle if successful. |
| 75 | +/// Get garbar socket path |
| 76 | +fn garbar_socket_path() -> String { |
| 77 | + std::env::var("XDG_RUNTIME_DIR") |
| 78 | + .map(|dir| format!("{}/garbar.sock", dir)) |
| 79 | + .unwrap_or_else(|_| "/tmp/garbar.sock".to_string()) |
| 80 | +} |
| 81 | + |
| 82 | +/// Check if garbar is healthy (socket exists) |
| 83 | +fn is_garbar_healthy() -> bool { |
| 84 | + std::path::Path::new(&garbar_socket_path()).exists() |
| 85 | +} |
| 86 | + |
| 87 | +/// Kill any stale garbar process that isn't responding |
| 88 | +fn cleanup_stale_garbar() { |
| 89 | + let pid_path = std::env::var("XDG_RUNTIME_DIR") |
| 90 | + .map(|dir| format!("{}/garbar.pid", dir)) |
| 91 | + .unwrap_or_else(|_| "/tmp/garbar.pid".to_string()); |
| 92 | + |
| 93 | + if let Ok(pid_str) = std::fs::read_to_string(&pid_path) { |
| 94 | + if let Ok(pid) = pid_str.trim().parse::<i32>() { |
| 95 | + // Check if process exists but socket doesn't (stale process) |
| 96 | + let proc_path = format!("/proc/{}", pid); |
| 97 | + if std::path::Path::new(&proc_path).exists() && !is_garbar_healthy() { |
| 98 | + tracing::warn!("Killing stale garbar process (PID {})", pid); |
| 99 | + unsafe { |
| 100 | + libc::kill(pid, libc::SIGKILL); |
| 101 | + } |
| 102 | + std::thread::sleep(std::time::Duration::from_millis(100)); |
| 103 | + } |
| 104 | + } |
| 105 | + } |
| 106 | +} |
| 107 | + |
| 77 | 108 | fn spawn_garbar() -> Option<std::process::Child> { |
| 78 | 109 | tracing::info!("Spawning garbar..."); |
| 79 | 110 | |
| 111 | + // Clean up any stale garbar process first |
| 112 | + cleanup_stale_garbar(); |
| 113 | + |
| 80 | 114 | // Try to find garbar in PATH or common locations |
| 81 | 115 | let garbar_cmd = which_garbar().unwrap_or_else(|| "garbar".to_string()); |
| 82 | 116 | |
@@ -85,13 +119,29 @@ fn spawn_garbar() -> Option<std::process::Child> { |
| 85 | 119 | .map(|dir| format!("{}/gar-i3.sock", dir)) |
| 86 | 120 | .unwrap_or_else(|_| "/tmp/gar-i3.sock".to_string()); |
| 87 | 121 | |
| 122 | + // Inherit DISPLAY from current environment, fallback to :0 |
| 123 | + let x_display = std::env::var("DISPLAY").unwrap_or_else(|_| ":0".to_string()); |
| 124 | + |
| 88 | 125 | match Command::new(&garbar_cmd) |
| 89 | 126 | .arg("daemon") |
| 90 | 127 | .env("I3SOCK", &i3sock) |
| 128 | + .env("DISPLAY", &x_display) |
| 91 | 129 | .spawn() |
| 92 | 130 | { |
| 93 | 131 | Ok(child) => { |
| 94 | | - tracing::info!("garbar started (PID {}), I3SOCK={}", child.id(), i3sock); |
| 132 | + tracing::info!("garbar started (PID {}), DISPLAY={}, I3SOCK={}", child.id(), x_display, i3sock); |
| 133 | + |
| 134 | + // Wait briefly and verify garbar becomes healthy |
| 135 | + for attempt in 1..=10 { |
| 136 | + std::thread::sleep(std::time::Duration::from_millis(200)); |
| 137 | + if is_garbar_healthy() { |
| 138 | + tracing::info!("garbar socket ready after {}ms", attempt * 200); |
| 139 | + return Some(child); |
| 140 | + } |
| 141 | + } |
| 142 | + |
| 143 | + // Socket never appeared - garbar might be stuck |
| 144 | + tracing::warn!("garbar socket not ready after 2s, process may be stuck"); |
| 95 | 145 | Some(child) |
| 96 | 146 | } |
| 97 | 147 | Err(e) => { |
@@ -2148,28 +2198,36 @@ impl WindowManager { |
| 2148 | 2198 | |
| 2149 | 2199 | // Handle garbar lifecycle based on new config |
| 2150 | 2200 | if self.config.bar_enabled { |
| 2151 | | - // Check if garbar is still running |
| 2152 | | - let garbar_alive = if let Some(ref mut child) = self.garbar_process { |
| 2201 | + // Check if garbar is still running AND healthy (socket exists) |
| 2202 | + let (garbar_alive, garbar_healthy) = if let Some(ref mut child) = self.garbar_process { |
| 2153 | 2203 | match child.try_wait() { |
| 2154 | | - Ok(None) => true, // Still running |
| 2204 | + Ok(None) => (true, is_garbar_healthy()), // Process running, check socket |
| 2155 | 2205 | Ok(Some(status)) => { |
| 2156 | 2206 | tracing::info!("garbar exited with status {}, will respawn", status); |
| 2157 | | - false |
| 2207 | + (false, false) |
| 2158 | 2208 | } |
| 2159 | 2209 | Err(e) => { |
| 2160 | 2210 | tracing::warn!("Failed to check garbar status: {}", e); |
| 2161 | | - false |
| 2211 | + (false, false) |
| 2162 | 2212 | } |
| 2163 | 2213 | } |
| 2164 | 2214 | } else { |
| 2165 | | - false |
| 2215 | + (false, false) |
| 2166 | 2216 | }; |
| 2167 | 2217 | |
| 2168 | | - if garbar_alive { |
| 2169 | | - // garbar running, signal it to reload |
| 2218 | + if garbar_alive && garbar_healthy { |
| 2219 | + // garbar running and healthy, signal it to reload |
| 2170 | 2220 | if let Some(ref child) = self.garbar_process { |
| 2171 | 2221 | reload_garbar(child); |
| 2172 | 2222 | } |
| 2223 | + } else if garbar_alive && !garbar_healthy { |
| 2224 | + // garbar process exists but socket doesn't - it's stuck |
| 2225 | + tracing::warn!("garbar process alive but socket missing, restarting..."); |
| 2226 | + if let Some(ref mut child) = self.garbar_process { |
| 2227 | + let _ = child.kill(); |
| 2228 | + let _ = child.wait(); |
| 2229 | + } |
| 2230 | + self.garbar_process = spawn_garbar(); |
| 2173 | 2231 | } else { |
| 2174 | 2232 | // garbar not running, spawn it |
| 2175 | 2233 | self.garbar_process = spawn_garbar(); |