diff --git a/Cargo.toml b/Cargo.toml index 4d6e419c..e57d4d35 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,9 +47,13 @@ wmi = "0.12.0" winreg = "0.10.1" windows = { version = "0.39.0", features = [ "Win32_Foundation", + "Win32_Graphics_Gdi", + "Win32_Graphics_Dxgi", "Win32_System_Power", "Win32_System_SystemInformation", - "Win32_System_WindowsProgramming" + "Win32_System_WindowsProgramming", + "Win32_UI_HiDpi", + "Win32_UI_WindowsAndMessaging", ]} [target.'cfg(not(target_os = "windows"))'.dependencies] diff --git a/src/windows/mod.rs b/src/windows/mod.rs index 74fd1686..a1a0a77e 100644 --- a/src/windows/mod.rs +++ b/src/windows/mod.rs @@ -7,7 +7,10 @@ use wmi::WMIResult; use wmi::{COMLibrary, Variant, WMIConnection}; use windows::{ - core::PSTR, Win32::System::Power::GetSystemPowerStatus, + core::{PCWSTR, PSTR}, + Win32::Graphics::Dxgi::{CreateDXGIFactory, IDXGIFactory}, + Win32::Graphics::Gdi::{EnumDisplayDevicesW, DISPLAY_DEVICEW}, + Win32::System::Power::GetSystemPowerStatus, Win32::System::Power::SYSTEM_POWER_STATUS, Win32::System::SystemInformation::GetComputerNameExA, Win32::System::SystemInformation::GetTickCount64, @@ -343,7 +346,268 @@ impl GeneralReadout for WindowsGeneralReadout { } fn gpus(&self) -> Result, ReadoutError> { - Err(ReadoutError::NotImplemented) + // Convert bytes to a string + fn bytes_to_string(value: usize) -> String { + if value / (1024 * 1024 * 1024) > 0 { + // Gigabytes + format!( + "{} GB", + ((value * 100) / (1024 * 1024 * 1024)) as f64 / 100.0, + ) + } else if value / (1024 * 1024) > 0 { + // Megabytes + format!("{} MB", ((value * 100) / (1024 * 1024)) as f64 / 100.0,) + } else if value / 1024 > 0 { + // Kilobytes + format!("{} KB", ((value * 100) / 1024) as f64 / 100.0,) + } else { + "".to_string() + } + } + + // Convert memory values to a human-readable string + fn memory_to_string( + dedicated_video_memory: usize, + dedicated_system_memory: usize, + shared_system_memory: usize, + ) -> String { + match ( + dedicated_video_memory, + dedicated_system_memory, + shared_system_memory, + ) { + (0, 0, 0) => "".to_string(), + (0, 0, _) => format!(" ({} Shared)", bytes_to_string(shared_system_memory)), + (0, _, 0) => { + format!(" ({} Dedicated)", bytes_to_string(dedicated_system_memory)) + } + (0, _, _) => format!( + " ({} Dedicated, {} Shared)", + bytes_to_string(dedicated_system_memory), + bytes_to_string(shared_system_memory) + ), + (_, 0, 0) => { + format!(" ({} Dedicated)", bytes_to_string(dedicated_video_memory)) + } + (_, 0, _) => format!( + " ({} Dedicated, {} Shared)", + bytes_to_string(dedicated_video_memory), + bytes_to_string(shared_system_memory) + ), + (_, _, 0) => format!( + " ({} Dedicated, {} Dedicated)", + bytes_to_string(dedicated_video_memory), + bytes_to_string(dedicated_system_memory) + ), + (_, _, _) => format!( + " ({} Dedicated, {} Shared)", + bytes_to_string(dedicated_video_memory + dedicated_system_memory), + bytes_to_string(shared_system_memory) + ), + } + } + + // Sources: + // https://github.com/Carterpersall/OxiFetch/blob/main/src/main.rs#L360 + // https://github.com/lptstr/winfetch/pull/155 + + // Create the Vector to store each GPU's name. + let mut output: Vec = Vec::new(); + + let hklm = RegKey::predef(HKEY_LOCAL_MACHINE); + + // Open the location where some DirectX information is stored + if let Ok(dx_key) = hklm.open_subkey("SOFTWARE\\Microsoft\\DirectX\\") { + // Get the parent key's LastSeen value + if let Ok(lastseen) = dx_key.get_value::("LastSeen") { + // Iterate over the parent key's subkeys and find the ones with the same LastSeen value + for key in dx_key.enum_keys() { + if key.is_err() { + continue; + } + let key = key.unwrap(); + + let sublastseen = match dx_key.open_subkey(&key) { + Ok(key) => match key.get_value::("LastSeen") { + Ok(key) => key, + Err(_) => continue, + }, + Err(_) => continue, + }; + + if sublastseen == lastseen { + // Get the GPU's name + let name = match dx_key.open_subkey(&key) { + Ok(key) => match key.get_value::("Description") { + Ok(key) => key, + Err(_) => continue, + }, + Err(_) => continue, + }; + + // Get the GPU's video memory + let dedicated_video_memory = match dx_key.open_subkey(&key) { + Ok(key) => match key.get_value::("DedicatedVideoMemory") { + Ok(key) => key as usize, + Err(_) => continue, + }, + Err(_) => continue, + }; + let dedicated_system_memory = match dx_key.open_subkey(&key) { + Ok(key) => match key.get_value::("DedicatedSystemMemory") { + Ok(key) => key as usize, + Err(_) => continue, + }, + Err(_) => continue, + }; + let shared_system_memory = match dx_key.open_subkey(&key) { + Ok(key) => match key.get_value::("SharedSystemMemory") { + Ok(key) => key as usize, + Err(_) => continue, + }, + Err(_) => continue, + }; + + let memory = memory_to_string( + dedicated_video_memory, + dedicated_system_memory, + shared_system_memory, + ); + + // Exclude the Microsoft Basic Render Driver + if name == "Microsoft Basic Render Driver" { + continue; + } + + // Add the GPU's name to the output vector + output.push(name + &memory); + } + } + }; + }; + + // Some systems have a DirectX key that lacks a LastSeen value, so a backup method is needed. + if !output.is_empty() { + return Ok(output); + } + + // Backup Implementation 1: Get GPUs by getting every display device + + let mut devices = Vec::new(); + let mut index = 0; + let mut status = true; + // Iterate over EnumDisplayDevicesW until it returns false + while status { + devices.push(DISPLAY_DEVICEW::default()); + devices[index].cb = std::mem::size_of::() as u32; + unsafe { + status = EnumDisplayDevicesW(PCWSTR::null(), index as u32, &mut devices[index], 0) + .as_bool(); + }; + index += 1; + } + // Remove the last element, which will be invalid + devices.pop(); + + // Iterate over each device + for device in devices { + // Convert [u16; 128] to a String and add to the HashSet + match ( + String::from_utf16(&device.DeviceString), + String::from_utf16(&device.DeviceKey), + ) { + (Ok(gpu), Ok(key)) => { + // Check if the key ends with "\0000", which is the first entry for that GPU + if key.trim_matches(char::from(0)).ends_with("\\0000") { + output.push(gpu.trim_matches(char::from(0)).to_string()); + } + } + (_, _) => continue, + } + } + + if !output.is_empty() { + // Convert the HashSet to a Vec and return it + return Ok(output); + } + + // Backup Implementation 2: Get GPUs using DXGI + // Sources: + // https://github.com/SHAREVOX/sharevox_core/blob/297c6c75ea9c6a88ee9002a7848592f7a97b4f9a/crates/voicevox_core/src/publish.rs#L529 + // https://github.com/LinusDierheimer/fastfetch/blob/b3da6b0e89c0decb9ea648e1d98a75fa6ac40225/src/detection/gpu/gpu_windows.cpp#L91 + + // Create a DXGI Factory + let mut factory = unsafe { CreateDXGIFactory::() }; + + if factory.is_ok() { + // Get the GPU names + let mut index = 0; + loop { + // Get the adapter at the current index + let adapter = match unsafe { factory.as_mut().unwrap().EnumAdapters(index) } { + Ok(adapter) => adapter, + Err(_) => break, + }; + + // Get the adapter's information + let adapter_info = match unsafe { adapter.GetDesc() } { + Ok(info) => info, + Err(_) => break, + }; + + // Get the name of the video adapter + + if let Ok(description) = String::from_utf16(&adapter_info.Description) { + if description.contains("Microsoft Basic Render Driver") { + index += 1; + continue; + } + + // GPU Video Memory + let dedicated_video_memory = adapter_info.DedicatedVideoMemory; + // System RAM not available to the CPU + let dedicated_system_memory = adapter_info.DedicatedSystemMemory; + // System RAM available to both the CPU and GPU + let shared_system_memory = adapter_info.SharedSystemMemory; + + let memory = memory_to_string( + dedicated_video_memory, + dedicated_system_memory, + shared_system_memory, + ); + + output.push(format!("{}{}", description.trim_end_matches('\0'), memory)); + } + + index += 1; + } + } + + if !output.is_empty() { + return Ok(output); + } + + // Backup Implementation 3: Use WMI to query Win32_VideoController + + // Create a WMI connection + let wmi_con = wmi_connection()?; + + // Query the WMI connection + let results: Vec> = + wmi_con.raw_query("SELECT Name FROM Win32_VideoController")?; + + // Get each GPU's name + for result in results { + if let Some(Variant::String(gpu)) = result.get("Name") { + output.push(gpu.to_string()); + } + } + + if !output.is_empty() { + return Ok(output); + } + + Err(ReadoutError::Other("Failed to find any GPUs.".to_string())) } }