320ac67
commit 320ac67
5 files changed
+190 -0
Showing 5 changed files+190-0
▼homebrew/Formula/fetchx.rb
+28-0
| @@ -0,0 +1,28 @@ | ||
1 | +require "language/node" | |
2 | + | |
3 | +class Fetchx < Formula | |
4 | + desc "A blazing-fast, feature-rich system information tool written in Rust" | |
5 | + homepage "https://github.com/swadhinbiswas/fetchx" | |
6 | + license "MIT" | |
7 | + version "0.2.0" | |
8 | + | |
9 | + on_macos do | |
10 | + if Hardware::CPU.arm? | |
11 | + url "https://github.com/swadhinbiswas/fetchx/releases/download/v#{version}/fetchx-#{version}-aarch64-apple-darwin.tar.gz" | |
12 | + sha256 "REPLACE_WITH_ACTUAL_SHA256_aarch64" | |
13 | + end | |
14 | + | |
15 | + if Hardware::CPU.intel? | |
16 | + url "https://github.com/swadhinbiswas/fetchx/releases/download/v#{version}/fetchx-#{version}-x86_64-apple-darwin.tar.gz" | |
17 | + sha256 "REPLACE_WITH_ACTUAL_SHA256_x86_64" | |
18 | + end | |
19 | + end | |
20 | + | |
21 | + def install | |
22 | + bin.install "fetchx" | |
23 | + end | |
24 | + | |
25 | + test do | |
26 | + assert_match version.to_s, shell_output("#{bin}/fetchx --version") | |
27 | + end | |
28 | +end | |
0 | 29 | |
▼homebrew/fetchx.rb
+25-0
| @@ -0,0 +1,25 @@ | ||
1 | +class Fetchx < Formula | |
2 | + desc "A blazing-fast, feature-rich system information tool written in Rust" | |
3 | + homepage "https://github.com/swadhinbiswas/fetchx" | |
4 | + url "https://github.com/swadhinbiswas/fetchx/releases/download/v0.2.0/fetchx-0.2.0-x86_64-apple-darwin.tar.gz" | |
5 | + version "0.2.0" | |
6 | + sha256 "REPLACE_WITH_ACTUAL_SHA256" | |
7 | + license "MIT" | |
8 | + head "https://github.com/swadhinbiswas/fetchx.git" | |
9 | + | |
10 | + depends_on "rust" => :build | |
11 | + | |
12 | + def install | |
13 | + # Build from source if using head | |
14 | + if build.head? | |
15 | + system "cargo", "build", "--release", "--locked" | |
16 | + bin.install "target/release/fetchx" | |
17 | + else | |
18 | + bin.install "fetchx" | |
19 | + end | |
20 | + end | |
21 | + | |
22 | + test do | |
23 | + system "#{bin}/fetchx", "--version" | |
24 | + end | |
25 | +end | |
0 | 26 | |
▼src/config/mod.rs
+4-0
| @@ -143,6 +143,8 @@ pub struct Config { | ||
143 | 143 | pub show_cpu: bool, |
144 | 144 | #[serde(default = "default_true")] |
145 | 145 | pub show_gpu: bool, |
146 | + #[serde(default = "default_false")] | |
147 | + pub show_temperature: bool, | |
146 | 148 | #[serde(default = "default_true")] |
147 | 149 | pub show_memory: bool, |
148 | 150 | #[serde(default = "default_false")] |
| @@ -273,6 +275,7 @@ impl Default for Config { | ||
273 | 275 | show_term_font: true, |
274 | 276 | show_cpu: true, |
275 | 277 | show_gpu: true, |
278 | + show_temperature: false, | |
276 | 279 | show_memory: true, |
277 | 280 | show_disk: false, |
278 | 281 | show_battery: false, |
| @@ -480,6 +483,7 @@ show_terminal = true | ||
480 | 483 | show_term_font = true |
481 | 484 | show_cpu = true |
482 | 485 | show_gpu = true |
486 | +show_temperature = false | |
483 | 487 | show_memory = true |
484 | 488 | show_disk = false |
485 | 489 | show_battery = false |
486 | 490 | |
▼src/display/mod.rs
+8-0
| @@ -508,6 +508,14 @@ impl Display { | ||
508 | 508 | } |
509 | 509 | } |
510 | 510 | |
511 | + // Temperature | |
512 | + add_field!( | |
513 | + self.config.show_temperature, | |
514 | + "Temperature", | |
515 | + "\u{f76c}", | |
516 | + &sys.temperature | |
517 | + ); | |
518 | + | |
511 | 519 | // Memory — optionally with progress bar |
512 | 520 | if self.config.show_memory && sys.memory != "Unknown" && !sys.memory.is_empty() { |
513 | 521 | if self.config.memory_bar { |
514 | 522 | |
▼src/system/mod.rs
+125-0
| @@ -27,6 +27,7 @@ pub struct SystemInfo { | ||
27 | 27 | pub term_font: String, |
28 | 28 | pub cpu: String, |
29 | 29 | pub gpu: Vec<String>, |
30 | + pub temperature: String, | |
30 | 31 | pub memory: String, |
31 | 32 | pub memory_percent: f64, |
32 | 33 | pub disk: String, |
| @@ -51,6 +52,7 @@ impl SystemInfo { | ||
51 | 52 | let h_packages = thread::spawn(get_packages); |
52 | 53 | let h_resolution = thread::spawn(get_resolution); |
53 | 54 | let h_gpu = thread::spawn(get_gpu); |
55 | + let h_temperature = thread::spawn(get_temperature); | |
54 | 56 | let h_public_ip = thread::spawn(get_public_ip); |
55 | 57 | let h_song = thread::spawn(get_song); |
56 | 58 | let h_cpu = thread::spawn(get_cpu); |
| @@ -81,6 +83,7 @@ impl SystemInfo { | ||
81 | 83 | let packages = h_packages.join().unwrap_or_default(); |
82 | 84 | let resolution = h_resolution.join().unwrap_or_default(); |
83 | 85 | let gpu = h_gpu.join().unwrap_or_default(); |
86 | + let temperature = h_temperature.join().unwrap_or_default(); | |
84 | 87 | let public_ip = h_public_ip.join().unwrap_or_else(|_| "Unknown".to_string()); |
85 | 88 | let song = h_song.join().unwrap_or_default(); |
86 | 89 | let cpu = h_cpu.join().unwrap_or_default(); |
| @@ -107,6 +110,7 @@ impl SystemInfo { | ||
107 | 110 | term_font, |
108 | 111 | cpu, |
109 | 112 | gpu, |
113 | + temperature, | |
110 | 114 | memory, |
111 | 115 | memory_percent, |
112 | 116 | disk, |
| @@ -1150,6 +1154,127 @@ fn get_gpu() -> Vec<String> { | ||
1150 | 1154 | vec!["Unknown".to_string()] |
1151 | 1155 | } |
1152 | 1156 | |
1157 | +/// Detect system temperature from various sources. | |
1158 | +fn get_temperature() -> String { | |
1159 | + // Try sensors (lm-sensors) | |
1160 | + if command_exists("sensors") { | |
1161 | + let out = run_cmd("sensors", &[]); | |
1162 | + if !out.is_empty() { | |
1163 | + // Try to find CPU temperature | |
1164 | + for line in out.lines() { | |
1165 | + let lc = line.to_lowercase(); | |
1166 | + if lc.contains("cpu") && lc.contains("°c") || lc.contains("°f") { | |
1167 | + // Look for temp like "Core 0: +45.0°C" | |
1168 | + if let Some(temp) = extract_temp_from_line(&line) { | |
1169 | + return temp; | |
1170 | + } | |
1171 | + } | |
1172 | + } | |
1173 | + // Try "Package" or "CPU" temperature | |
1174 | + for line in out.lines() { | |
1175 | + if line.to_lowercase().contains("package") || line.to_lowercase().contains("cpu") { | |
1176 | + if let Some(temp) = extract_temp_from_line(&line) { | |
1177 | + return temp; | |
1178 | + } | |
1179 | + } | |
1180 | + } | |
1181 | + } | |
1182 | + } | |
1183 | + | |
1184 | + // Try reading from /sys/class/thermal (Linux) | |
1185 | + if let Ok(thermal_dir) = std::fs::read_dir("/sys/class/thermal") { | |
1186 | + let mut temps: Vec<i32> = Vec::new(); | |
1187 | + for entry in thermal_dir.flatten() { | |
1188 | + let path = entry.path(); | |
1189 | + // Look for thermal_zone nodes | |
1190 | + if path.join("type").exists() { | |
1191 | + let temp_file = path.join("temp"); | |
1192 | + if temp_file.exists() { | |
1193 | + if let Ok(content) = std::fs::read_to_string(temp_file) { | |
1194 | + if let Ok(temp_millidegrees) = content.trim().parse::<i32>() { | |
1195 | + // Convert from millidegrees to degrees | |
1196 | + let temp = temp_millidegrees / 1000; | |
1197 | + if temp > 0 && temp < 150 { | |
1198 | + temps.push(temp); | |
1199 | + } | |
1200 | + } | |
1201 | + } | |
1202 | + } | |
1203 | + } | |
1204 | + } | |
1205 | + if !temps.is_empty() { | |
1206 | + let avg: i32 = temps.iter().sum::<i32>() / temps.len() as i32; | |
1207 | + return format!("{}°C", avg); | |
1208 | + } | |
1209 | + } | |
1210 | + | |
1211 | + // Try nvidia-smi for GPU temperature | |
1212 | + if command_exists("nvidia-smi") { | |
1213 | + let out = run_cmd( | |
1214 | + "nvidia-smi", | |
1215 | + &[ | |
1216 | + "--query-gpu=temperature.gpu", | |
1217 | + "--format=csv,noheader,nounits", | |
1218 | + ], | |
1219 | + ); | |
1220 | + if !out.is_empty() { | |
1221 | + if let Some(temp) = out.trim().parse::<i32>().ok() { | |
1222 | + return format!("{}°C (GPU)", temp); | |
1223 | + } | |
1224 | + } | |
1225 | + } | |
1226 | + | |
1227 | + // Try ati sensors for AMD GPU | |
1228 | + if command_exists("aticonfig") { | |
1229 | + let out = run_cmd("aticonfig", &["--od-gettemperature"]); | |
1230 | + if !out.is_empty() { | |
1231 | + for line in out.lines() { | |
1232 | + if line.contains("Temperature") { | |
1233 | + if let Some(temp) = line.split(':').nth(1) { | |
1234 | + let temp = temp.trim().replace(" C", "").replace("°C", ""); | |
1235 | + if let Ok(t) = temp.parse::<i32>() { | |
1236 | + return format!("{}°C (GPU)", t); | |
1237 | + } | |
1238 | + } | |
1239 | + } | |
1240 | + } | |
1241 | + } | |
1242 | + } | |
1243 | + | |
1244 | + String::new() | |
1245 | +} | |
1246 | + | |
1247 | +/// Helper to extract temperature from sensors output line. | |
1248 | +fn extract_temp_from_line(line: &str) -> Option<String> { | |
1249 | + // Look for patterns like "+45.0°C" or "45°C" or "45.0 C" | |
1250 | + let parts: Vec<&str> = line.split_whitespace().collect(); | |
1251 | + for (i, part) in parts.iter().enumerate() { | |
1252 | + let part_lower = part.to_lowercase(); | |
1253 | + if part_lower.contains("°c") | |
1254 | + || part_lower.contains("°f") | |
1255 | + || part_lower.contains("c") | |
1256 | + || part_lower.contains("f") | |
1257 | + { | |
1258 | + // Check if previous part was a number | |
1259 | + if i > 0 { | |
1260 | + let prev = parts[i - 1]; | |
1261 | + // Remove any leading + or - | |
1262 | + let temp = prev.trim_start_matches(['+', '-']); | |
1263 | + if temp.parse::<f64>().is_ok() { | |
1264 | + // Get the unit | |
1265 | + let unit = if part_lower.contains("°f") || part_lower.contains("f") { | |
1266 | + "°F" | |
1267 | + } else { | |
1268 | + "°C" | |
1269 | + }; | |
1270 | + return Some(format!("{}{}", temp, unit)); | |
1271 | + } | |
1272 | + } | |
1273 | + } | |
1274 | + } | |
1275 | + None | |
1276 | +} | |
1277 | + | |
1153 | 1278 | fn get_memory_with_percent() -> (String, f64) { |
1154 | 1279 | let content = read_file("/proc/meminfo"); |
1155 | 1280 | if content.is_empty() { |
1156 | 1281 | |