rcpufetch 0.0.5

[ALPHA] A rusty crossplatform, but simple CLI binutil for reading CPU information.
//! Windows CPU information module. Core/cache/frequency data comes from WMI via
//! PowerShell's `Get-CimInstance` (a stdlib `Command` shell-out, same pattern macOS uses
//! for `sysctl` -- no external crate needed); vendor/microarchitecture on x86 comes from
//! the `cpuid` instruction directly via `crate::cpu::x86`, which works identically under
//! Windows since it isn't a syscall.

use crate::cpu::ArchInfo;
use crate::report::CpuReport;
use std::process::Command;

pub struct WindowsCpuInfo {
    model: String,
    vendor: String,
    physical_cores: u32,
    logical_cores: u32,
    base_mhz: Option<f32>,
    l2_size: Option<u32>,
    l3_size: Option<u32>,
    arch_info: ArchInfo,
}

/// Query a single WMI property off Win32_Processor via PowerShell. Stdlib-only: shells
/// out rather than linking a WMI/COM crate, matching the project's zero-dependency goal.
fn wmi_property(property: &str) -> Option<String> {
    let output = Command::new("powershell")
        .args([
            "-NoProfile",
            "-Command",
            &format!("(Get-CimInstance Win32_Processor | Select-Object -First 1 -ExpandProperty {}).ToString()", property),
        ])
        .output()
        .ok()?;
    if !output.status.success() {
        return None;
    }
    let s = String::from_utf8_lossy(&output.stdout).trim().to_string();
    if s.is_empty() { None } else { Some(s) }
}

impl WindowsCpuInfo {
    pub fn new() -> Result<Self, String> {
        let model = wmi_property("Name").unwrap_or_else(|| "Unknown".to_string());

        #[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
        let arch_info = crate::cpu::x86::detect();
        #[cfg(not(any(target_arch = "x86", target_arch = "x86_64")))]
        let arch_info = ArchInfo::default();

        let vendor = arch_info.vendor.clone().unwrap_or_else(|| {
            if model.to_lowercase().contains("intel") {
                "Intel".to_string()
            } else if model.to_lowercase().contains("amd") {
                "AMD".to_string()
            } else {
                "Unknown".to_string()
            }
        });

        // available_parallelism() (stdlib, std::thread) gives logical core count reliably
        // cross-platform; physical core count still needs WMI since Windows has no stdlib API for it.
        let logical_cores = std::thread::available_parallelism()
            .map(|n| n.get() as u32)
            .unwrap_or(0);
        let physical_cores = wmi_property("NumberOfCores")
            .and_then(|s| s.parse().ok())
            .unwrap_or(logical_cores);
        let base_mhz = wmi_property("MaxClockSpeed").and_then(|s| s.parse::<f32>().ok());
        let l2_size = wmi_property("L2CacheSize").and_then(|s| s.parse().ok());
        let l3_size = wmi_property("L3CacheSize").and_then(|s| s.parse().ok());

        Ok(Self {
            model,
            vendor,
            physical_cores,
            logical_cores,
            base_mhz,
            l2_size,
            l3_size,
            arch_info,
        })
    }

    pub fn to_report(&self) -> CpuReport {
        let info = &self.arch_info;
        CpuReport {
            model: self.model.clone(),
            vendor: info.vendor.clone().unwrap_or_else(|| self.vendor.clone()),
            uarch: info.uarch.clone(),
            soc: None,
            technology_nm: info.process_nm,
            architecture: std::env::consts::ARCH.to_string(),
            physical_cores: self.physical_cores,
            logical_cores: self.logical_cores,
            max_ghz: self.base_mhz.map(|mhz| mhz / 1000.0),
            base_ghz: None,
            l1i_kb: None,
            l1d_kb: None,
            l2_kb: self.l2_size.map(|kb| (0, kb)),
            l3_kb: self.l3_size.map(|kb| (0, kb)),
            flags: info.features.clone(),
        }
    }
}