Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Implement gpus #149

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
6 changes: 5 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
268 changes: 266 additions & 2 deletions src/windows/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -343,7 +346,268 @@ impl GeneralReadout for WindowsGeneralReadout {
}

fn gpus(&self) -> Result<Vec<String>, 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<String> = 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::<u64, _>("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::<u64, _>("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::<String, _>("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::<u64, _>("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::<u64, _>("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::<u64, _>("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::<DISPLAY_DEVICEW>() 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::<IDXGIFactory>() };

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<HashMap<String, Variant>> =
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()))
}
}

Expand Down