use std::path::{Component, Path, PathBuf};
use std::sync::OnceLock;
use windows::Win32::System::Registry::{
HKEY_LOCAL_MACHINE, REG_ROUTINE_FLAGS, RRF_RT_REG_SZ, RegGetValueW,
};
use windows::core::HSTRING;
use super::{Platform, not_supported};
use crate::error::PlatformError;
use crate::traits::device::{Device, DeviceHardware};
use crate::{DeviceInfo, ScreenInfo};
const BIOS_KEY: &str = r"HARDWARE\DESCRIPTION\System\BIOS";
const CURRENT_VERSION_KEY: &str = r"SOFTWARE\Microsoft\Windows NT\CurrentVersion";
impl Device for Platform {
fn device_info(&self) -> DeviceInfo {
static INFO: OnceLock<DeviceInfo> = OnceLock::new();
INFO.get_or_init(collect_device_info).clone()
}
fn screen_info(&self) -> ScreenInfo {
use windows::Win32::UI::HiDpi::GetDpiForSystem;
use windows::Win32::UI::WindowsAndMessaging::{GetSystemMetrics, SM_CXSCREEN, SM_CYSCREEN};
let dpi = unsafe { GetDpiForSystem() };
let scale = if dpi == 0 { 1.0 } else { f64::from(dpi) / 96.0 };
let width = unsafe { GetSystemMetrics(SM_CXSCREEN) };
let height = unsafe { GetSystemMetrics(SM_CYSCREEN) };
ScreenInfo {
width: f64::from(width.max(0)) / scale,
height: f64::from(height.max(0)) / scale,
scale,
}
}
fn vibrate(&self, _long: bool) -> Result<(), PlatformError> {
not_supported("vibrate")
}
fn make_phone_call(&self, _phone_number: &str) -> Result<(), PlatformError> {
Err(PlatformError::NotSupported(
"makePhoneCall is not supported on Windows".to_string(),
))
}
}
impl DeviceHardware for Platform {
fn get_memory_info(&self) -> Result<u64, PlatformError> {
use windows::Win32::System::SystemInformation::{GlobalMemoryStatusEx, MEMORYSTATUSEX};
let mut status = MEMORYSTATUSEX {
dwLength: std::mem::size_of::<MEMORYSTATUSEX>() as u32,
..Default::default()
};
unsafe { GlobalMemoryStatusEx(&mut status) }.map_err(|err| {
PlatformError::Platform(format!("GlobalMemoryStatusEx failed: {err}"))
})?;
Ok(status.ullTotalPhys)
}
fn get_storage_total_bytes(&self) -> Result<u64, PlatformError> {
use windows::Win32::Storage::FileSystem::GetDiskFreeSpaceExW;
let root = volume_root(self.data_dir());
let root_name = HSTRING::from(root.as_os_str());
let mut total_bytes = 0u64;
unsafe { GetDiskFreeSpaceExW(&root_name, None, Some(&mut total_bytes), None) }.map_err(
|err| {
PlatformError::Platform(format!(
"GetDiskFreeSpaceExW failed for {}: {err}",
root.display()
))
},
)?;
Ok(total_bytes)
}
}
fn volume_root(path: &Path) -> PathBuf {
let mut root = PathBuf::new();
for component in path.components().take(2) {
match component {
Component::Prefix(_) | Component::RootDir => root.push(component.as_os_str()),
_ => break,
}
}
if root.as_os_str().is_empty() {
std::env::var_os("SystemDrive")
.map(|drive| {
PathBuf::from(format!(
"{}\\",
drive.to_string_lossy().trim_end_matches('\\')
))
})
.unwrap_or_else(|| PathBuf::from("C:\\"))
} else {
root
}
}
fn collect_device_info() -> DeviceInfo {
let brand =
read_hklm_string(BIOS_KEY, "SystemManufacturer").unwrap_or_else(|| "Unknown".to_string());
let model = read_hklm_string(BIOS_KEY, "SystemProductName")
.unwrap_or_else(|| std::env::consts::ARCH.to_string());
let market_name = read_hklm_string(BIOS_KEY, "SystemFamily").unwrap_or_else(|| model.clone());
DeviceInfo {
brand,
model,
market_name,
os_name: "Windows".to_string(),
os_version: os_version_string(),
}
}
fn os_version_string() -> String {
let core = match rtl_get_version() {
Some((major, minor, build)) => format!("{major}.{minor}.{build}"),
None => read_hklm_string(CURRENT_VERSION_KEY, "CurrentBuild").unwrap_or_default(),
};
match read_hklm_string(CURRENT_VERSION_KEY, "DisplayVersion") {
Some(display) if !core.is_empty() => format!("{core} ({display})"),
Some(display) => display,
None => core,
}
}
fn rtl_get_version() -> Option<(u32, u32, u32)> {
#[repr(C)]
struct OsVersionInfoW {
os_version_info_size: u32,
major_version: u32,
minor_version: u32,
build_number: u32,
platform_id: u32,
csd_version: [u16; 128],
}
#[link(name = "ntdll")]
unsafe extern "system" {
fn RtlGetVersion(version_information: *mut OsVersionInfoW) -> i32;
}
let mut info = OsVersionInfoW {
os_version_info_size: std::mem::size_of::<OsVersionInfoW>() as u32,
major_version: 0,
minor_version: 0,
build_number: 0,
platform_id: 0,
csd_version: [0; 128],
};
let status = unsafe { RtlGetVersion(&mut info) };
(status == 0).then_some((info.major_version, info.minor_version, info.build_number))
}
fn read_hklm_string(subkey: &str, value: &str) -> Option<String> {
let data = read_hklm_value(subkey, value, RRF_RT_REG_SZ)?;
if data.len() < 2 {
return None;
}
let units: Vec<u16> = data
.chunks_exact(2)
.map(|pair| u16::from_le_bytes([pair[0], pair[1]]))
.take_while(|&unit| unit != 0)
.collect();
let text = String::from_utf16_lossy(&units).trim().to_string();
(!text.is_empty()).then_some(text)
}
fn read_hklm_value(subkey: &str, value: &str, flags: REG_ROUTINE_FLAGS) -> Option<Vec<u8>> {
let subkey = HSTRING::from(subkey);
let value = HSTRING::from(value);
let mut size = 0u32;
let status = unsafe {
RegGetValueW(
HKEY_LOCAL_MACHINE,
&subkey,
&value,
flags,
None,
None,
Some(&mut size),
)
};
if !status.is_ok() || size == 0 {
return None;
}
let mut data = vec![0u8; size as usize];
let status = unsafe {
RegGetValueW(
HKEY_LOCAL_MACHINE,
&subkey,
&value,
flags,
None,
Some(data.as_mut_ptr().cast()),
Some(&mut size),
)
};
if !status.is_ok() {
return None;
}
data.truncate(size as usize);
Some(data)
}