#[cfg(any(target_os = "macos", target_os = "windows"))]
use std::str::FromStr;
pub(super) fn physical_core_count() -> Option<usize> {
#[cfg(target_os = "linux")]
{
linux_physical_cores()
}
#[cfg(target_os = "macos")]
{
macos_physical_cores()
}
#[cfg(target_os = "windows")]
{
windows_physical_cores()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
None
}
}
#[cfg(target_os = "linux")]
fn linux_physical_cores() -> Option<usize> {
let content = std::fs::read_to_string("/proc/cpuinfo").ok()?; linux_physical_cores_from_cpuinfo(&content)
}
#[cfg(target_os = "linux")]
pub(crate) fn linux_physical_cores_from_cpuinfo(content: &str) -> Option<usize> {
let mut pairs = std::collections::HashSet::new();
let mut physical_id = None::<usize>;
let mut core_id = None::<usize>;
for line in content.lines() {
if line.starts_with("physical id") {
physical_id = parse_proc_usize_field(line);
} else if line.starts_with("core id") {
core_id = parse_proc_usize_field(line);
} else if line.trim().is_empty() {
if let (Some(p), Some(c)) = (physical_id, core_id) {
pairs.insert((p, c));
}
physical_id = None;
core_id = None;
}
}
if let (Some(p), Some(c)) = (physical_id, core_id) {
pairs.insert((p, c));
}
if pairs.is_empty() {
None
} else {
Some(pairs.len())
}
}
#[cfg(target_os = "linux")]
fn parse_proc_usize_field(line: &str) -> Option<usize> {
let Some((_, value)) = line.split_once(':') else {
return None;
};
match value.trim().parse() {
Ok(parsed) => Some(parsed),
Err(_) => None, }
}
#[cfg(target_os = "macos")]
fn macos_physical_cores() -> Option<usize> {
run_probe_command("sysctl", &["-n", "hw.physicalcpu"])
.and_then(|stdout| parse_trimmed_probe_value(&stdout))
}
#[cfg(target_os = "windows")]
fn windows_physical_cores() -> Option<usize> {
let core_count = run_probe_command(
"powershell",
&[
"-NoProfile",
"-Command",
"(Get-CimInstance Win32_Processor).NumberOfCores",
],
)
.and_then(|stdout| parse_trimmed_probe_value(&stdout));
if core_count.is_some() {
return core_count;
}
run_probe_command("wmic", &["cpu", "get", "NumberOfCores", "/value"]).and_then(|stdout| {
stdout
.lines()
.find_map(|line| parse_wmic_value::<usize>(line, "NumberOfCores"))
})
}
pub(super) fn detect_total_memory_mb() -> Option<u64> {
#[cfg(target_os = "linux")]
{
let content = std::fs::read_to_string("/proc/meminfo").ok()?; linux_total_memory_mb_from_meminfo(&content)
}
#[cfg(target_os = "macos")]
{
run_probe_command("sysctl", &["-n", "hw.memsize"])
.and_then(|stdout| parse_trimmed_probe_value::<u64>(&stdout))
.map(|bytes| bytes / 1024 / 1024)
}
#[cfg(target_os = "windows")]
{
let memory = run_probe_command(
"powershell",
&[
"-NoProfile",
"-Command",
"(Get-CimInstance Win32_ComputerSystem).TotalPhysicalMemory",
],
)
.and_then(|stdout| parse_trimmed_probe_value::<u64>(&stdout))
.map(|bytes| bytes / 1024 / 1024);
if memory.is_some() {
return memory;
}
run_probe_command(
"wmic",
&["computersystem", "get", "TotalPhysicalMemory", "/value"],
)
.and_then(|stdout| {
stdout
.lines()
.find_map(|line| parse_wmic_value::<u64>(line, "TotalPhysicalMemory"))
})
.map(|bytes| bytes / 1024 / 1024)
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
None
}
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
fn run_probe_command(bin_name: &str, args: &[&str]) -> Option<String> {
let bin = keyhog_core::resolve_safe_bin(bin_name)?;
let output = match std::process::Command::new(&bin).args(args).output() {
Ok(output) => output,
Err(_) => return None, };
Some(String::from_utf8_lossy(&output.stdout).into_owned())
}
#[cfg(any(target_os = "macos", target_os = "windows"))]
fn parse_trimmed_probe_value<T>(stdout: &str) -> Option<T>
where
T: FromStr,
{
match stdout.trim().parse() {
Ok(value) => Some(value),
Err(_) => None, }
}
#[cfg(target_os = "windows")]
fn parse_wmic_value<T>(line: &str, key: &str) -> Option<T>
where
T: FromStr,
{
let (field, raw_value) = line.split_once('=')?;
if field != key {
return None;
}
parse_trimmed_probe_value(raw_value)
}
#[cfg(target_os = "linux")]
pub(crate) fn linux_total_memory_mb_from_meminfo(content: &str) -> Option<u64> {
for line in content.lines() {
if line.starts_with("MemTotal:") {
let Some(kb_text) = line.split_whitespace().nth(1) else {
continue;
};
let Ok(kb) = kb_text.parse::<u64>() else {
continue;
};
return Some(kb / 1024);
}
}
None
}
#[cfg(target_os = "linux")]
pub(crate) fn kernel_supports_io_uring(osrelease: &str) -> bool {
let parts: Vec<&str> = osrelease.trim().split('.').collect();
if parts.len() < 2 {
return false;
}
let (Ok(major), Ok(minor)) = (parts[0].parse::<u32>(), parts[1].parse::<u32>()) else {
return false; };
major > 5 || (major == 5 && minor >= 1)
}
pub(super) fn detect_io_uring() -> bool {
#[cfg(target_os = "linux")]
{
let kernel_ok = std::fs::read_to_string("/proc/sys/kernel/osrelease")
.ok() .map(|s| kernel_supports_io_uring(&s))
.unwrap_or(false); if !kernel_ok {
return false;
}
io_uring::IoUring::new(1).is_ok()
}
#[cfg(not(target_os = "linux"))]
{
false
}
}
#[must_use]
pub(crate) fn detect_physical_gpu_name() -> Option<String> {
#[cfg(target_os = "linux")]
{
linux_physical_gpu_name()
}
#[cfg(target_os = "macos")]
{
macos_physical_gpu_name()
}
#[cfg(target_os = "windows")]
{
windows_gpu_name()
}
#[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
{
None
}
}
#[cfg(target_os = "linux")]
fn linux_physical_gpu_name() -> Option<String> {
if let Ok(entries) = std::fs::read_dir("/proc/driver/nvidia/gpus") {
for entry in entries.flatten() {
let info_path = entry.path().join("information");
if let Ok(info) = std::fs::read_to_string(info_path) {
for line in info.lines() {
if let Some(model) = line.strip_prefix("Model:") {
let trimmed = model.trim();
if !trimmed.is_empty() {
return Some(trimmed.to_string());
}
}
}
}
}
}
if let Ok(entries) = std::fs::read_dir("/sys/bus/pci/devices") {
for entry in entries.flatten() {
let path = entry.path();
let class_str = std::fs::read_to_string(path.join("class")).unwrap_or_default(); let class =
u32::from_str_radix(class_str.trim().trim_start_matches("0x"), 16).unwrap_or(0); if (class >> 16) == 0x03 {
let vendor_str = std::fs::read_to_string(path.join("vendor")).unwrap_or_default(); let vendor = u32::from_str_radix(vendor_str.trim().trim_start_matches("0x"), 16)
.unwrap_or(0); let driver_name = std::fs::read_link(path.join("driver"))
.ok() .and_then(|d| d.file_name().map(|n| n.to_string_lossy().into_owned()));
let name = match vendor {
0x10de => {
if let Some(drv) = driver_name {
format!("NVIDIA GPU ({drv})")
} else {
"NVIDIA GPU".to_string()
}
}
0x1002 | 0x1022 => {
if let Some(drv) = driver_name {
format!("AMD GPU ({drv})")
} else {
"AMD GPU".to_string()
}
}
0x8086 => {
if let Some(drv) = driver_name {
format!("Intel GPU ({drv})")
} else {
"Intel GPU".to_string()
}
}
_ => {
if let Some(drv) = driver_name {
format!("PCI GPU ({drv})")
} else {
"PCI GPU".to_string()
}
}
};
return Some(name);
}
}
}
None
}
#[cfg(target_os = "macos")]
fn macos_physical_gpu_name() -> Option<String> {
#[cfg(target_arch = "aarch64")]
{
Some("Apple Silicon GPU".to_string())
}
#[cfg(not(target_arch = "aarch64"))]
{
run_probe_command("system_profiler", &["SPDisplaysDataType"]).and_then(|out| {
for line in out.lines() {
let trimmed = line.trim();
if let Some(chipset) = trimmed.strip_prefix("Chipset Model:") {
let name = chipset.trim();
if !name.is_empty() {
return Some(name.to_string());
}
}
}
None
})
}
}
#[cfg(target_os = "windows")]
fn windows_gpu_name() -> Option<String> {
let name = run_probe_command(
"powershell",
&[
"-NoProfile",
"-Command",
"(Get-CimInstance Win32_VideoController | Select-Object -First 1).Name",
],
)
.and_then(|stdout| {
let trimmed = stdout.trim();
if trimmed.is_empty() {
None
} else {
Some(trimmed.to_string())
}
});
if name.is_some() {
return name;
}
run_probe_command("wmic", &["path", "win32_VideoController", "get", "name"]).and_then(
|stdout| {
stdout
.lines()
.map(str::trim)
.find(|line| !line.is_empty() && !line.eq_ignore_ascii_case("name"))
.map(|s| s.to_string())
},
)
}