@@ -2,6 +2,7 @@ |
| 2 | 2 | //! ers is a standalone border renderer that handles its own window events, |
| 3 | 3 | //! focus detection, and rendering. Tarmac just spawns and manages the process. |
| 4 | 4 | |
| 5 | +use std::path::PathBuf; |
| 5 | 6 | use std::process::{Child, Command}; |
| 6 | 7 | |
| 7 | 8 | /// RGBA color for border configuration. |
@@ -17,15 +18,34 @@ impl BorderColor { |
| 17 | 18 | /// Parse a hex color string (#RRGGBB or #RRGGBBAA). |
| 18 | 19 | pub fn from_hex(hex: &str) -> Self { |
| 19 | 20 | let hex = hex.trim_start_matches('#'); |
| 20 | | - let r = u8::from_str_radix(&hex[0..2], 16).unwrap_or(0) as f64 / 255.0; |
| 21 | | - let g = u8::from_str_radix(&hex[2..4], 16).unwrap_or(0) as f64 / 255.0; |
| 22 | | - let b = u8::from_str_radix(&hex[4..6], 16).unwrap_or(0) as f64 / 255.0; |
| 23 | | - let a = if hex.len() >= 8 { |
| 24 | | - u8::from_str_radix(&hex[6..8], 16).unwrap_or(255) as f64 / 255.0 |
| 21 | + match Self::try_from_hex(hex) { |
| 22 | + Some(color) => color, |
| 23 | + None => { |
| 24 | + tracing::warn!(input = hex, "invalid border color, falling back to black"); |
| 25 | + Self { |
| 26 | + r: 0.0, |
| 27 | + g: 0.0, |
| 28 | + b: 0.0, |
| 29 | + a: 1.0, |
| 30 | + } |
| 31 | + } |
| 32 | + } |
| 33 | + } |
| 34 | + |
| 35 | + fn try_from_hex(hex: &str) -> Option<Self> { |
| 36 | + if hex.len() != 6 && hex.len() != 8 { |
| 37 | + return None; |
| 38 | + } |
| 39 | + |
| 40 | + let r = u8::from_str_radix(hex.get(0..2)?, 16).ok()? as f64 / 255.0; |
| 41 | + let g = u8::from_str_radix(hex.get(2..4)?, 16).ok()? as f64 / 255.0; |
| 42 | + let b = u8::from_str_radix(hex.get(4..6)?, 16).ok()? as f64 / 255.0; |
| 43 | + let a = if hex.len() == 8 { |
| 44 | + u8::from_str_radix(hex.get(6..8)?, 16).ok()? as f64 / 255.0 |
| 25 | 45 | } else { |
| 26 | 46 | 1.0 |
| 27 | 47 | }; |
| 28 | | - Self { r, g, b, a } |
| 48 | + Some(Self { r, g, b, a }) |
| 29 | 49 | } |
| 30 | 50 | |
| 31 | 51 | fn hex_string(self) -> String { |
@@ -78,24 +98,34 @@ impl BorderManager { |
| 78 | 98 | .ok() |
| 79 | 99 | .and_then(|p| p.parent().map(|d| d.join("ers"))) |
| 80 | 100 | .filter(|p| p.exists()) |
| 81 | | - .map(|p| p.to_string_lossy().to_string()) |
| 82 | | - .unwrap_or_else(|| "ers".to_string()); |
| 83 | | - |
| 84 | | - let cmd = format!( |
| 85 | | - "{} --active-only --width {} --radius {} --color '{}' --inactive '{}'", |
| 86 | | - ers_bin, |
| 87 | | - self.border_width, |
| 88 | | - self.radius, |
| 89 | | - self.focused_color.hex_string(), |
| 90 | | - self.unfocused_color.hex_string(), |
| 101 | + .unwrap_or_else(|| PathBuf::from("ers")); |
| 102 | + |
| 103 | + tracing::debug!( |
| 104 | + ers_bin = ?ers_bin, |
| 105 | + border_width = self.border_width, |
| 106 | + radius = self.radius, |
| 107 | + focused = %self.focused_color.hex_string(), |
| 108 | + unfocused = %self.unfocused_color.hex_string(), |
| 109 | + "spawning ers" |
| 91 | 110 | ); |
| 92 | | - tracing::debug!(cmd, "spawning ers"); |
| 93 | | - match Command::new("/bin/sh").args(["-c", &cmd]).spawn() { |
| 111 | + |
| 112 | + match Command::new(&ers_bin) |
| 113 | + .arg("--active-only") |
| 114 | + .arg("--width") |
| 115 | + .arg(self.border_width.to_string()) |
| 116 | + .arg("--radius") |
| 117 | + .arg(self.radius.to_string()) |
| 118 | + .arg("--color") |
| 119 | + .arg(self.focused_color.hex_string()) |
| 120 | + .arg("--inactive") |
| 121 | + .arg(self.unfocused_color.hex_string()) |
| 122 | + .spawn() |
| 123 | + { |
| 94 | 124 | Ok(child) => { |
| 95 | 125 | self.child = Some(child); |
| 96 | 126 | } |
| 97 | 127 | Err(e) => { |
| 98 | | - tracing::warn!(err = %e, "failed to spawn ers"); |
| 128 | + tracing::warn!(err = %e, ers_bin = ?ers_bin, "failed to spawn ers"); |
| 99 | 129 | } |
| 100 | 130 | } |
| 101 | 131 | } |
@@ -132,3 +162,23 @@ impl Drop for BorderManager { |
| 132 | 162 | self.kill(); |
| 133 | 163 | } |
| 134 | 164 | } |
| 165 | + |
| 166 | +#[cfg(test)] |
| 167 | +mod tests { |
| 168 | + use super::BorderColor; |
| 169 | + |
| 170 | + #[test] |
| 171 | + fn malformed_hex_falls_back_without_panicking() { |
| 172 | + let color = BorderColor::from_hex("#fff"); |
| 173 | + assert_eq!(color.r, 0.0); |
| 174 | + assert_eq!(color.g, 0.0); |
| 175 | + assert_eq!(color.b, 0.0); |
| 176 | + assert_eq!(color.a, 1.0); |
| 177 | + } |
| 178 | + |
| 179 | + #[test] |
| 180 | + fn parses_rgba_hex() { |
| 181 | + let color = BorderColor::from_hex("#11223380"); |
| 182 | + assert_eq!(color.hex_string(), "#11223380"); |
| 183 | + } |
| 184 | +} |