mod acpi_thermal;
mod amd_ryzen;
mod intel_wmi;
mod libre_hwmon;
pub use acpi_thermal::AcpiThermalSource;
pub use amd_ryzen::AmdRyzenSource;
pub use intel_wmi::IntelWmiSource;
pub use libre_hwmon::LibreHardwareMonitorSource;
use once_cell::sync::OnceCell;
use std::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};
use wmi::WMIConnection;
fn read_lock<T>(lock: &RwLock<T>) -> RwLockReadGuard<'_, T> {
lock.read().unwrap_or_else(|poisoned| poisoned.into_inner())
}
fn write_lock<T>(lock: &RwLock<T>) -> RwLockWriteGuard<'_, T> {
lock.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[allow(dead_code)]
const WBEM_E_NOT_FOUND: i32 = -0x7FFBEFEC_i32;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CpuVendor {
Intel,
Amd,
Unknown,
}
static CPU_VENDOR: OnceCell<CpuVendor> = OnceCell::new();
pub fn get_cpu_vendor() -> CpuVendor {
*CPU_VENDOR.get_or_init(detect_cpu_vendor)
}
fn detect_cpu_vendor() -> CpuVendor {
use sysinfo::{CpuRefreshKind, System};
let mut system = System::new();
system.refresh_cpu_specifics(CpuRefreshKind::everything());
let cpus = system.cpus();
if cpus.is_empty() {
return CpuVendor::Unknown;
}
let brand = cpus[0].brand().to_lowercase();
if brand.contains("intel") {
CpuVendor::Intel
} else if brand.contains("amd") {
CpuVendor::Amd
} else {
CpuVendor::Unknown
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SourceAvailability {
Unknown,
Available,
Unavailable,
}
pub struct TemperatureManager {
acpi_available: RwLock<SourceAvailability>,
amd_available: RwLock<SourceAvailability>,
intel_available: RwLock<SourceAvailability>,
libre_available: RwLock<SourceAvailability>,
acpi_source: AcpiThermalSource,
amd_source: AmdRyzenSource,
intel_source: IntelWmiSource,
libre_source: LibreHardwareMonitorSource,
}
impl Default for TemperatureManager {
fn default() -> Self {
Self::new()
}
}
impl TemperatureManager {
pub fn new() -> Self {
Self {
acpi_available: RwLock::new(SourceAvailability::Unknown),
amd_available: RwLock::new(SourceAvailability::Unknown),
intel_available: RwLock::new(SourceAvailability::Unknown),
libre_available: RwLock::new(SourceAvailability::Unknown),
acpi_source: AcpiThermalSource::new(),
amd_source: AmdRyzenSource::new(),
intel_source: IntelWmiSource::new(),
libre_source: LibreHardwareMonitorSource::new(),
}
}
pub fn get_temperature(&self, root_wmi_conn: Option<&WMIConnection>) -> Option<u32> {
if self.is_source_potentially_available(&self.acpi_available) {
match self.acpi_source.get_temperature(root_wmi_conn) {
TemperatureResult::Success(temp) => {
self.mark_available(&self.acpi_available);
return Some(temp);
}
TemperatureResult::NotFound => {
self.mark_unavailable(&self.acpi_available);
}
TemperatureResult::Error => {
}
TemperatureResult::NoValidReading => {
}
}
}
let vendor = get_cpu_vendor();
if vendor == CpuVendor::Amd && self.is_source_potentially_available(&self.amd_available) {
match self.amd_source.get_temperature() {
TemperatureResult::Success(temp) => {
self.mark_available(&self.amd_available);
return Some(temp);
}
TemperatureResult::NotFound => {
self.mark_unavailable(&self.amd_available);
}
TemperatureResult::Error => {
}
TemperatureResult::NoValidReading => {}
}
}
if vendor == CpuVendor::Intel && self.is_source_potentially_available(&self.intel_available)
{
match self.intel_source.get_temperature() {
TemperatureResult::Success(temp) => {
self.mark_available(&self.intel_available);
return Some(temp);
}
TemperatureResult::NotFound => {
self.mark_unavailable(&self.intel_available);
}
TemperatureResult::Error => {}
TemperatureResult::NoValidReading => {}
}
}
if self.is_source_potentially_available(&self.libre_available) {
match self.libre_source.get_temperature() {
TemperatureResult::Success(temp) => {
self.mark_available(&self.libre_available);
return Some(temp);
}
TemperatureResult::NotFound => {
self.mark_unavailable(&self.libre_available);
}
TemperatureResult::Error => {}
TemperatureResult::NoValidReading => {}
}
}
None
}
fn is_source_potentially_available(&self, status: &RwLock<SourceAvailability>) -> bool {
!matches!(*read_lock(status), SourceAvailability::Unavailable)
}
fn mark_available(&self, status: &RwLock<SourceAvailability>) {
let mut guard = write_lock(status);
if *guard == SourceAvailability::Unknown {
*guard = SourceAvailability::Available;
}
}
fn mark_unavailable(&self, status: &RwLock<SourceAvailability>) {
*write_lock(status) = SourceAvailability::Unavailable;
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemperatureResult {
Success(u32),
NotFound,
Error,
NoValidReading,
}
pub fn is_wmi_not_found_error(error: &wmi::WMIError) -> bool {
let error_str = format!("{error}");
error_str.contains("0x8004100C")
|| error_str.contains("WBEM_E_NOT_FOUND")
|| error_str.contains(&format!("{WBEM_E_NOT_FOUND}"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cpu_vendor_detection() {
let vendor = detect_cpu_vendor();
assert!(matches!(
vendor,
CpuVendor::Intel | CpuVendor::Amd | CpuVendor::Unknown
));
}
#[test]
fn test_temperature_manager_creation() {
let manager = TemperatureManager::new();
assert!(manager.is_source_potentially_available(&manager.acpi_available));
}
}