From ef45d18fbd2f06c0d68e1535c844b74111d323e7 Mon Sep 17 00:00:00 2001 From: xscriptor Date: Tue, 18 Aug 2026 10:15:37 +0200 Subject: [PATCH] upload temperature plugin with docs and standard --- Cargo.lock | 9 ++ Cargo.toml | 1 + plugins/temperature/CHANGELOG.md | 11 +++ plugins/temperature/Cargo.toml | 9 ++ plugins/temperature/README.md | 29 +++++++ plugins/temperature/src/main.rs | 139 +++++++++++++++++++++++++++++++ 6 files changed, 198 insertions(+) create mode 100644 plugins/temperature/CHANGELOG.md create mode 100644 plugins/temperature/Cargo.toml create mode 100644 plugins/temperature/README.md create mode 100644 plugins/temperature/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index 1d1562a..c95e884 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -257,6 +257,15 @@ dependencies = [ "xfetch-plugin-api", ] +[[package]] +name = "xfetch-plugin-temperature" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "xfetch-plugin-api", +] + [[package]] name = "xfetch-plugin-theme-detection" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3cdcaec..6b36a09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,7 @@ members = [ "plugins/timezone", "plugins/user-info", "plugins/weather", + "plugins/temperature", ] resolver = "2" diff --git a/plugins/temperature/CHANGELOG.md b/plugins/temperature/CHANGELOG.md new file mode 100644 index 0000000..8c6642a --- /dev/null +++ b/plugins/temperature/CHANGELOG.md @@ -0,0 +1,11 @@ +# Changelog + +## 2026-08-18 — v0.1.0 + +### Initial Release + +- New `temperature` info plugin: reads kernel thermal zones (`/sys/class/thermal/thermal_zone*/` — world-readable `type` and `temp` files, no subprocess) and renders one line per zone with its label, e.g. `52°C (x86_pkg_temp)`. +- Configurable unit via plugin args: `unit: "celsius"` (default) or `"fahrenheit"`. +- Windows support: WMI `MSAcpi_ThermalZoneTemperature` via `wmic` with a `powershell` fallback (same probe pattern as the core's battery/GPU detectors); WMI reports tenths of Kelvin, converted to the configured unit. +- macOS and other platforms report `Unsupported platform` (no portable world-readable sensor source yet). +- Standard info-plugin protocol (`xfetch_plugin_api`): usable from any xfetch config as `plugin:temperature` with custom icon/color. diff --git a/plugins/temperature/Cargo.toml b/plugins/temperature/Cargo.toml new file mode 100644 index 0000000..93d03c3 --- /dev/null +++ b/plugins/temperature/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "xfetch-plugin-temperature" +version = "0.1.0" +edition = "2024" + +[dependencies] +serde = { version = "1", features = ["derive"] } +serde_json = "1" +xfetch-plugin-api = { workspace = true } diff --git a/plugins/temperature/README.md b/plugins/temperature/README.md new file mode 100644 index 0000000..ae0b65e --- /dev/null +++ b/plugins/temperature/README.md @@ -0,0 +1,29 @@ +# xfetch-plugin-temperature + +CPU/SoC temperature module for [xfetch](https://github.com/xfetch-cli/xfetch). + +Reads the kernel thermal zones (`/sys/class/thermal/thermal_zone*/` — world-readable files, no subprocess) on Linux. Other platforms report unsupported. + +## Usage + +Install the plugin binary in the xfetch plugin dir, then add it to your config: + +```jsonc +{ + "info_plugins": [ + { "plugin": "temperature", "args": { "unit": "celsius" } } + ], + "modules": [ "plugin:temperature" ], + "icons": { "plugin:temperature": "" } +} +``` + +`unit` accepts `"celsius"` (default) or `"fahrenheit"`. + +## Platform support + +| Platform | Source | Notes | +|---|---|---| +| Linux | `/sys/class/thermal/thermal_zone*/` | World-readable files, no subprocess. | +| Windows | WMI `MSAcpi_ThermalZoneTemperature` | Via `wmic`, `powershell` fallback; needs no admin on most machines. | +| macOS | — | Unsupported: the only real sources (`powermetrics`, SMC) require root or third-party tools. Reports `Unsupported platform`. | diff --git a/plugins/temperature/src/main.rs b/plugins/temperature/src/main.rs new file mode 100644 index 0000000..df19286 --- /dev/null +++ b/plugins/temperature/src/main.rs @@ -0,0 +1,139 @@ +use xfetch_plugin_api::{read_info_plugin_args_or_default, write_info_lines}; + +#[derive(Debug, Default, serde::Deserialize)] +struct PluginArgs { + /// "celsius" (default) or "fahrenheit" + unit: Option, +} + +fn main() { + let args = match read_info_plugin_args_or_default::() { + Ok(value) => value, + Err(err) => { + eprintln!("{}", err); + std::process::exit(1); + } + }; + + let lines = get_temperature_info(args.unit.as_deref()); + + if let Err(err) = write_info_lines(lines) { + eprintln!("{}", err); + std::process::exit(1); + } +} + +fn format_temp(celsius: f64, unit: Option<&str>) -> String { + let temp = celsius.max(0.0); + if unit == Some("fahrenheit") { + format!("{:.0}°F", temp * 9.0 / 5.0 + 32.0) + } else { + format!("{:.0}°C", temp) + } +} + +/// Kernel-exposed thermal zones. Linux: `/sys/class/thermal/thermal_zone*/` +/// (world-readable `type` + `temp` files, no subprocess). Windows: WMI +/// `MSAcpi_ThermalZoneTemperature` via `wmic` with a `powershell` fallback +/// (the same probe pattern as the core's battery/GPU detectors). +fn get_temperature_info(unit: Option<&str>) -> Vec { + let mut lines = Vec::new(); + #[cfg(target_os = "linux")] + { + if let Ok(entries) = std::fs::read_dir("/sys/class/thermal") { + for entry in entries.flatten() { + let name = entry.file_name(); + let name = name.to_string_lossy(); + if !name.starts_with("thermal_zone") { + continue; + } + let path = entry.path(); + let zone_type = std::fs::read_to_string(path.join("type")) + .ok() + .map(|s| s.trim().to_string()); + let Ok(raw) = std::fs::read_to_string(path.join("temp")) else { + continue; + }; + let Ok(milli) = raw.trim().parse::() else { + continue; + }; + let celsius = milli as f64 / 1000.0; + let label = zone_type.unwrap_or_else(|| "thermal".to_string()); + lines.push(format!(" {} ({})", format_temp(celsius, unit), label)); + } + } + } + #[cfg(target_os = "windows")] + { + lines.extend(get_windows_temperature(unit)); + } + if lines.is_empty() { + lines.push(" Unsupported platform".to_string()); + } + lines +} + +/// WMI reports `MSAcpi_ThermalZoneTemperature.CurrentTemperature` in tenths of +/// degrees Kelvin; the probe returns (zone name, tenths-of-kelvin) pairs. +#[cfg(target_os = "windows")] +fn run_windows_probe(cmd: &str, args: &[&str]) -> Vec<(String, i64)> { + let Ok(output) = std::process::Command::new(cmd).args(args).output() else { + return Vec::new(); + }; + if !output.status.success() { + return Vec::new(); + } + let stdout = String::from_utf8_lossy(&output.stdout); + let mut zones = Vec::new(); + for line in stdout.lines().skip(1) { + let mut name_parts: Vec<&str> = Vec::new(); + let mut tenths = None; + for token in line.split_whitespace() { + if let Ok(t) = token.parse::() { + tenths = Some(t); + } else { + name_parts.push(token); + } + } + if let Some(t) = tenths { + let name = if name_parts.is_empty() { + "thermal".to_string() + } else { + name_parts.join(" ") + }; + zones.push((name, t)); + } + } + zones +} + +#[cfg(target_os = "windows")] +fn get_windows_temperature(unit: Option<&str>) -> Vec { + let zones = run_windows_probe( + "wmic", + &[ + "path", + "MSAcpi_ThermalZoneTemperature", + "get", + "CurrentTemperature,InstanceName", + ], + ); + let zones = if zones.is_empty() { + run_windows_probe( + "powershell", + &[ + "-Command", + "Get-CimInstance -Namespace root/wmi -ClassName MSAcpi_ThermalZoneTemperature | ForEach-Object { \"$($_.CurrentTemperature) $($_.InstanceName)\" }", + ], + ) + } else { + zones + }; + zones + .iter() + .map(|(name, tenths)| { + let celsius = *tenths as f64 / 10.0 - 273.15; + format!(" {} ({})", format_temp(celsius, unit), name) + }) + .collect() +}