Swadhin Biswas

[email protected]
2026-03-07 20:17:58 +0600 committed NaN years ago
320ac67
commit 320ac67
5 files changed
+190 -0
▼homebrew/Formula/fetchx.rb
+28-0
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
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
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
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
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
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
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
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
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
110
term_font,
108
111
cpu,
109
112
gpu,
113
+ temperature,
110
114
memory,
111
115
memory_percent,
112
116
disk,
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