feat: add hardware detection and monitoring via WMI
parent
4269ddb312
commit
c7b4c9d33a
|
@ -7,6 +7,9 @@ edition = "2024"
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
env_logger = "0.11"
|
env_logger = "0.11"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
wmi = "0.13"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
|
|
||||||
[dependencies.windows]
|
[dependencies.windows]
|
||||||
version = "0.61.3"
|
version = "0.61.3"
|
||||||
|
|
|
@ -1,72 +1,117 @@
|
||||||
use crate::display::{DisplayManager, VirtualDisplayController};
|
use crate::display::{DisplayManager, VirtualDisplayController};
|
||||||
use crate::device::{VddHandle, DeviceController, DeviceStatusChecker};
|
use crate::device::{VddHandle, DeviceController, DeviceStatusChecker};
|
||||||
|
use crate::monitor::HardwareDetector;
|
||||||
use crate::config::{Config, VDD_ADAPTER_GUID, VDD_CLASS_GUID, VDD_HARDWARE_ID};
|
use crate::config::{Config, VDD_ADAPTER_GUID, VDD_CLASS_GUID, VDD_HARDWARE_ID};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
|
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use log::error;
|
use log::{error, info, warn};
|
||||||
|
|
||||||
pub struct VddService {
|
pub struct VddService {
|
||||||
handle: Arc<VddHandle>,
|
handle: Arc<VddHandle>,
|
||||||
device_controller: DeviceController,
|
device_controller: DeviceController,
|
||||||
display_manager: DisplayManager,
|
hardware_detector: Option<HardwareDetector>,
|
||||||
config: Config,
|
config: Config,
|
||||||
running: Arc<AtomicBool>,
|
running: Arc<AtomicBool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VddService {
|
impl VddService {
|
||||||
pub fn new() -> Result<Self> {
|
pub fn new() -> Result<Self> {
|
||||||
DeviceStatusChecker::check_driver_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID)?;
|
info!("Initializing VDD Service...");
|
||||||
|
|
||||||
|
// Check driver status first
|
||||||
|
DeviceStatusChecker::check_driver_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID)?;
|
||||||
|
info!("VDD driver status: OK");
|
||||||
|
|
||||||
|
// Open device handle
|
||||||
let handle = Arc::new(VddHandle::open(&VDD_ADAPTER_GUID)?);
|
let handle = Arc::new(VddHandle::open(&VDD_ADAPTER_GUID)?);
|
||||||
|
info!("VDD device handle opened successfully");
|
||||||
|
|
||||||
let config = Config::default();
|
let config = Config::default();
|
||||||
let device_controller = DeviceController::new(config.clone());
|
let device_controller = DeviceController::new(config.clone());
|
||||||
let virtual_display_controller = VirtualDisplayController::new(device_controller.clone());
|
|
||||||
let display_manager = DisplayManager::new(virtual_display_controller);
|
// Initialize hardware detector
|
||||||
|
let hardware_detector = match HardwareDetector::new() {
|
||||||
|
Ok(detector) => {
|
||||||
|
info!("Hardware detector initialized successfully");
|
||||||
|
if let Err(e) = detector.log_hardware_summary() {
|
||||||
|
warn!("Failed to log hardware summary: {}", e);
|
||||||
|
}
|
||||||
|
Some(detector)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
warn!("Failed to initialize hardware detector: {}", e);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
handle,
|
handle,
|
||||||
device_controller,
|
device_controller,
|
||||||
display_manager,
|
hardware_detector,
|
||||||
config,
|
config,
|
||||||
running: Arc::new(AtomicBool::new(false)),
|
running: Arc::new(AtomicBool::new(false)),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&mut self) -> Result<()> {
|
pub fn start(&mut self) -> Result<()> {
|
||||||
|
info!("Starting VDD Service...");
|
||||||
self.running.store(true, Ordering::SeqCst);
|
self.running.store(true, Ordering::SeqCst);
|
||||||
|
|
||||||
|
// Create display manager for the monitor thread
|
||||||
|
let virtual_display_controller = VirtualDisplayController::new(self.device_controller.clone());
|
||||||
|
let mut display_manager = DisplayManager::new(virtual_display_controller);
|
||||||
|
|
||||||
// Start monitor watching thread
|
// Start monitor watching thread
|
||||||
let handle_clone = Arc::clone(&self.handle);
|
let handle_clone = Arc::clone(&self.handle);
|
||||||
let running_clone = Arc::clone(&self.running);
|
let running_clone = Arc::clone(&self.running);
|
||||||
let config = self.config.clone();
|
let monitor_config = self.config.clone();
|
||||||
let mut display_manager = std::mem::replace(&mut self.display_manager,
|
|
||||||
DisplayManager::new(VirtualDisplayController::new(self.device_controller.clone())));
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
|
info!("Monitor detection thread started");
|
||||||
|
|
||||||
|
// Initial delay to let system stabilize
|
||||||
|
std::thread::sleep(Duration::from_millis(
|
||||||
|
monitor_config.display.virtual_display_settings.startup_delay_ms as u64
|
||||||
|
));
|
||||||
|
|
||||||
while running_clone.load(Ordering::SeqCst) {
|
while running_clone.load(Ordering::SeqCst) {
|
||||||
if let Err(e) = display_manager.update_displays(&handle_clone) {
|
if let Err(e) = display_manager.update_displays(&handle_clone) {
|
||||||
error!("Failed to update displays: {}", e);
|
error!("Failed to update displays: {}", e);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(config.monitor_check_interval_ms));
|
std::thread::sleep(Duration::from_millis(monitor_config.monitor_check_interval_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("Monitor detection thread stopped");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start update loop
|
info!("VDD Service started successfully");
|
||||||
|
|
||||||
|
// Start update loop in main thread
|
||||||
self.update_loop()
|
self.update_loop()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self) {
|
pub fn stop(&self) {
|
||||||
|
info!("Stopping VDD Service...");
|
||||||
self.running.store(false, Ordering::SeqCst);
|
self.running.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_hardware_info(&self) -> Option<crate::monitor::HardwareInfo> {
|
||||||
|
self.hardware_detector.as_ref()
|
||||||
|
.and_then(|detector| detector.get_hardware_info().ok())
|
||||||
|
}
|
||||||
|
|
||||||
fn update_loop(&self) -> Result<()> {
|
fn update_loop(&self) -> Result<()> {
|
||||||
|
info!("VDD update loop started");
|
||||||
|
|
||||||
while self.running.load(Ordering::SeqCst) {
|
while self.running.load(Ordering::SeqCst) {
|
||||||
if let Err(e) = self.device_controller.update(&self.handle) {
|
if let Err(e) = self.device_controller.update(&self.handle) {
|
||||||
error!("Failed to update VDD: {}", e);
|
error!("Failed to update VDD: {}", e);
|
||||||
}
|
}
|
||||||
std::thread::sleep(Duration::from_millis(self.config.vdd_update_interval_ms));
|
std::thread::sleep(Duration::from_millis(self.config.vdd_update_interval_ms));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("VDD update loop stopped");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,5 +119,6 @@ impl VddService {
|
||||||
impl Drop for VddService {
|
impl Drop for VddService {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.stop();
|
self.stop();
|
||||||
|
info!("VDD Service dropped");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct DisplayConfig {
|
||||||
|
pub auto_manage_virtual_display: bool,
|
||||||
|
pub prefer_wmi_detection: bool,
|
||||||
|
pub fallback_to_gdi: bool,
|
||||||
|
pub monitor_check_interval_ms: u64,
|
||||||
|
pub hardware_detection_timeout_ms: u32,
|
||||||
|
pub virtual_display_settings: VirtualDisplaySettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct VirtualDisplaySettings {
|
||||||
|
pub default_width: u32,
|
||||||
|
pub default_height: u32,
|
||||||
|
pub default_refresh_rate: u32,
|
||||||
|
pub auto_remove_on_physical_connect: bool,
|
||||||
|
pub startup_delay_ms: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DisplayConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
auto_manage_virtual_display: true,
|
||||||
|
prefer_wmi_detection: true,
|
||||||
|
fallback_to_gdi: true,
|
||||||
|
monitor_check_interval_ms: 1000,
|
||||||
|
hardware_detection_timeout_ms: 5000,
|
||||||
|
virtual_display_settings: VirtualDisplaySettings::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for VirtualDisplaySettings {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
default_width: 1920,
|
||||||
|
default_height: 1080,
|
||||||
|
default_refresh_rate: 60,
|
||||||
|
auto_remove_on_physical_connect: true,
|
||||||
|
startup_delay_ms: 1000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
use windows::core::GUID;
|
use windows::core::GUID;
|
||||||
|
|
||||||
|
mod display;
|
||||||
|
pub use display::{DisplayConfig, VirtualDisplaySettings};
|
||||||
|
|
||||||
// Constants from the C header file
|
// Constants from the C header file
|
||||||
pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0";
|
pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0";
|
||||||
pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0";
|
pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0";
|
||||||
|
@ -27,6 +30,7 @@ pub struct Config {
|
||||||
pub monitor_check_interval_ms: u64,
|
pub monitor_check_interval_ms: u64,
|
||||||
pub vdd_update_interval_ms: u64,
|
pub vdd_update_interval_ms: u64,
|
||||||
pub io_timeout_ms: u32,
|
pub io_timeout_ms: u32,
|
||||||
|
pub display: DisplayConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Config {
|
impl Default for Config {
|
||||||
|
@ -35,6 +39,7 @@ impl Default for Config {
|
||||||
monitor_check_interval_ms: 1000,
|
monitor_check_interval_ms: 1000,
|
||||||
vdd_update_interval_ms: 100,
|
vdd_update_interval_ms: 100,
|
||||||
io_timeout_ms: 5000,
|
io_timeout_ms: 5000,
|
||||||
|
display: DisplayConfig::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
40
src/main.rs
40
src/main.rs
|
@ -1,20 +1,52 @@
|
||||||
use parsec_vdd::app::VddService;
|
use parsec_vdd::app::VddService;
|
||||||
use log::error;
|
use log::{error, info};
|
||||||
use env_logger::Env;
|
use env_logger::Env;
|
||||||
|
use std::io::{self, Write};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
|
// Initialize logging with timestamp
|
||||||
|
env_logger::Builder::from_env(Env::default().default_filter_or("info"))
|
||||||
|
.format(|buf, record| {
|
||||||
|
writeln!(buf, "{} [{}] - {}",
|
||||||
|
chrono::Local::now().format("%Y-%m-%d %H:%M:%S"),
|
||||||
|
record.level(),
|
||||||
|
record.args()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.init();
|
||||||
|
|
||||||
|
info!("=== Parsec VDD Service Starting ===");
|
||||||
|
|
||||||
let mut service = match VddService::new() {
|
let mut service = match VddService::new() {
|
||||||
Ok(service) => service,
|
Ok(service) => {
|
||||||
|
info!("VDD Service initialized successfully");
|
||||||
|
service
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Failed to initialize VDD service: {}", e);
|
error!("Failed to initialize VDD service: {}", e);
|
||||||
|
error!("Please ensure:");
|
||||||
|
error!(" 1. Parsec VDD driver is installed");
|
||||||
|
error!(" 2. Running with administrator privileges");
|
||||||
|
error!(" 3. Windows version is supported");
|
||||||
|
|
||||||
|
println!("Press Enter to exit...");
|
||||||
|
let _ = io::stdin().read_line(&mut String::new());
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Log hardware information if available
|
||||||
|
if let Some(hardware) = service.get_hardware_info() {
|
||||||
|
info!("Detected {} video controllers and {} monitors",
|
||||||
|
hardware.video_controllers.len(),
|
||||||
|
hardware.monitors.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Starting service main loop...");
|
||||||
if let Err(e) = service.start() {
|
if let Err(e) = service.start() {
|
||||||
error!("Failed to start VDD service: {}", e);
|
error!("VDD service encountered an error: {}", e);
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
info!("VDD Service stopped normally");
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,14 @@ use windows::{
|
||||||
core::PWSTR,
|
core::PWSTR,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DisplayInfo {
|
||||||
|
pub device_name: String,
|
||||||
|
pub device_string: String,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub is_parsec_virtual: bool,
|
||||||
|
}
|
||||||
|
|
||||||
pub struct MonitorDetector;
|
pub struct MonitorDetector;
|
||||||
|
|
||||||
impl MonitorDetector {
|
impl MonitorDetector {
|
||||||
|
@ -47,4 +55,39 @@ impl MonitorDetector {
|
||||||
|
|
||||||
physical_monitors
|
physical_monitors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_all_displays() -> Vec<DisplayInfo> {
|
||||||
|
let mut displays = Vec::new();
|
||||||
|
let mut info = DISPLAY_DEVICEW {
|
||||||
|
cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,
|
||||||
|
DeviceName: [0; 32],
|
||||||
|
DeviceString: [0; 128],
|
||||||
|
StateFlags: DISPLAY_DEVICE_STATE_FLAGS(0),
|
||||||
|
DeviceID: [0; 128],
|
||||||
|
DeviceKey: [0; 128],
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut i = 0;
|
||||||
|
while unsafe { EnumDisplayDevicesW(PWSTR::null(), i, &mut info, 0).as_bool() } {
|
||||||
|
let device_name = String::from_utf16_lossy(&info.DeviceName)
|
||||||
|
.trim_end_matches('\0')
|
||||||
|
.to_string();
|
||||||
|
let device_string = String::from_utf16_lossy(&info.DeviceString)
|
||||||
|
.trim_end_matches('\0')
|
||||||
|
.to_string();
|
||||||
|
let is_active = (info.StateFlags & DISPLAY_DEVICE_ACTIVE).0 != 0;
|
||||||
|
let is_parsec_virtual = device_name.to_lowercase().contains("parsec");
|
||||||
|
|
||||||
|
displays.push(DisplayInfo {
|
||||||
|
device_name,
|
||||||
|
device_string,
|
||||||
|
is_active,
|
||||||
|
is_parsec_virtual,
|
||||||
|
});
|
||||||
|
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
displays
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
use crate::monitor::wmi::{WmiDeviceManager, VideoController, DesktopMonitor};
|
||||||
|
use crate::error::Result;
|
||||||
|
use log::{debug, info, warn};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HardwareInfo {
|
||||||
|
pub video_controllers: Vec<VideoControllerInfo>,
|
||||||
|
pub monitors: Vec<MonitorInfo>,
|
||||||
|
pub total_vram: u64,
|
||||||
|
pub has_dedicated_gpu: bool,
|
||||||
|
pub primary_adapter: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VideoControllerInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub device_id: String,
|
||||||
|
pub pnp_device_id: String,
|
||||||
|
pub driver_version: Option<String>,
|
||||||
|
pub adapter_ram: u64,
|
||||||
|
pub status: String,
|
||||||
|
pub is_primary: bool,
|
||||||
|
pub vendor: GpuVendor,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct MonitorInfo {
|
||||||
|
pub name: String,
|
||||||
|
pub device_id: String,
|
||||||
|
pub manufacturer: Option<String>,
|
||||||
|
pub width: Option<u32>,
|
||||||
|
pub height: Option<u32>,
|
||||||
|
pub is_physical: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum GpuVendor {
|
||||||
|
Nvidia,
|
||||||
|
Amd,
|
||||||
|
Intel,
|
||||||
|
Microsoft, // Basic Display Adapter
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct HardwareDetector {
|
||||||
|
wmi_manager: WmiDeviceManager,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HardwareDetector {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let wmi_manager = WmiDeviceManager::new()?;
|
||||||
|
Ok(Self { wmi_manager })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_hardware_info(&self) -> Result<HardwareInfo> {
|
||||||
|
let video_controllers = self.get_video_controller_info()?;
|
||||||
|
let monitors = self.get_monitor_info()?;
|
||||||
|
|
||||||
|
let total_vram = video_controllers.iter()
|
||||||
|
.map(|vc| vc.adapter_ram)
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
let has_dedicated_gpu = video_controllers.iter()
|
||||||
|
.any(|vc| vc.vendor != GpuVendor::Microsoft && vc.adapter_ram > 0);
|
||||||
|
|
||||||
|
let primary_adapter = video_controllers.iter()
|
||||||
|
.find(|vc| vc.is_primary)
|
||||||
|
.map(|vc| vc.name.clone());
|
||||||
|
|
||||||
|
Ok(HardwareInfo {
|
||||||
|
video_controllers,
|
||||||
|
monitors,
|
||||||
|
total_vram,
|
||||||
|
has_dedicated_gpu,
|
||||||
|
primary_adapter,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_video_controller_info(&self) -> Result<Vec<VideoControllerInfo>> {
|
||||||
|
let controllers = self.wmi_manager.get_video_controllers()?;
|
||||||
|
let mut controller_info = Vec::new();
|
||||||
|
|
||||||
|
for (index, controller) in controllers.into_iter().enumerate() {
|
||||||
|
if let (Some(name), Some(device_id)) = (&controller.name, &controller.device_id) {
|
||||||
|
let vendor = Self::detect_gpu_vendor(name);
|
||||||
|
let is_primary = index == 0; // First controller is typically primary
|
||||||
|
|
||||||
|
controller_info.push(VideoControllerInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
device_id: device_id.clone(),
|
||||||
|
pnp_device_id: controller.pnp_device_id.unwrap_or_default(),
|
||||||
|
driver_version: controller.driver_version,
|
||||||
|
adapter_ram: controller.adapter_ram.unwrap_or(0),
|
||||||
|
status: controller.status.unwrap_or_else(|| "Unknown".to_string()),
|
||||||
|
is_primary,
|
||||||
|
vendor,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found {} video controllers", controller_info.len());
|
||||||
|
Ok(controller_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_monitor_info(&self) -> Result<Vec<MonitorInfo>> {
|
||||||
|
let monitors = self.wmi_manager.get_desktop_monitors()?;
|
||||||
|
let mut monitor_info = Vec::new();
|
||||||
|
|
||||||
|
for monitor in monitors {
|
||||||
|
if let (Some(name), Some(device_id)) = (&monitor.name, &monitor.device_id) {
|
||||||
|
let is_physical = Self::is_physical_monitor(name);
|
||||||
|
|
||||||
|
monitor_info.push(MonitorInfo {
|
||||||
|
name: name.clone(),
|
||||||
|
device_id: device_id.clone(),
|
||||||
|
manufacturer: monitor.manufacturer,
|
||||||
|
width: monitor.screen_width,
|
||||||
|
height: monitor.screen_height,
|
||||||
|
is_physical,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found {} monitors", monitor_info.len());
|
||||||
|
Ok(monitor_info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_gpu_vendor(name: &str) -> GpuVendor {
|
||||||
|
let name_lower = name.to_lowercase();
|
||||||
|
|
||||||
|
if name_lower.contains("nvidia") || name_lower.contains("geforce") || name_lower.contains("quadro") {
|
||||||
|
GpuVendor::Nvidia
|
||||||
|
} else if name_lower.contains("amd") || name_lower.contains("radeon") || name_lower.contains("ati") {
|
||||||
|
GpuVendor::Amd
|
||||||
|
} else if name_lower.contains("intel") {
|
||||||
|
GpuVendor::Intel
|
||||||
|
} else if name_lower.contains("microsoft") || name_lower.contains("basic display") {
|
||||||
|
GpuVendor::Microsoft
|
||||||
|
} else {
|
||||||
|
GpuVendor::Unknown
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_physical_monitor(name: &str) -> bool {
|
||||||
|
let name_lower = name.to_lowercase();
|
||||||
|
!name_lower.contains("parsec") &&
|
||||||
|
!name_lower.contains("virtual") &&
|
||||||
|
!name_lower.contains("generic pnp") &&
|
||||||
|
!name_lower.contains("software")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_hardware_summary(&self) -> Result<()> {
|
||||||
|
let hardware = self.get_hardware_info()?;
|
||||||
|
|
||||||
|
info!("=== Hardware Summary ===");
|
||||||
|
info!("Video Controllers: {}", hardware.video_controllers.len());
|
||||||
|
info!("Total VRAM: {} MB", hardware.total_vram / (1024 * 1024));
|
||||||
|
info!("Has Dedicated GPU: {}", hardware.has_dedicated_gpu);
|
||||||
|
|
||||||
|
if let Some(primary) = &hardware.primary_adapter {
|
||||||
|
info!("Primary Adapter: {}", primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i, controller) in hardware.video_controllers.iter().enumerate() {
|
||||||
|
info!(" GPU {}: {} ({:?}) - {} MB VRAM",
|
||||||
|
i, controller.name, controller.vendor,
|
||||||
|
controller.adapter_ram / (1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
let physical_monitors: Vec<_> = hardware.monitors.iter()
|
||||||
|
.filter(|m| m.is_physical)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
info!("Physical Monitors: {}", physical_monitors.len());
|
||||||
|
for (i, monitor) in physical_monitors.iter().enumerate() {
|
||||||
|
let resolution = match (monitor.width, monitor.height) {
|
||||||
|
(Some(w), Some(h)) => format!("{}x{}", w, h),
|
||||||
|
_ => "Unknown".to_string(),
|
||||||
|
};
|
||||||
|
info!(" Monitor {}: {} ({})", i, monitor.name, resolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,7 @@
|
||||||
mod detector;
|
mod detector;
|
||||||
|
mod wmi;
|
||||||
|
mod hardware;
|
||||||
|
|
||||||
pub use detector::MonitorDetector;
|
pub use detector::{MonitorDetector, DisplayInfo};
|
||||||
|
pub use wmi::{WmiDeviceManager, VideoController, DesktopMonitor};
|
||||||
|
pub use hardware::{HardwareDetector, HardwareInfo, VideoControllerInfo, MonitorInfo, GpuVendor};
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
use crate::error::{Result, VddError};
|
||||||
|
use log::{debug, error, warn};
|
||||||
|
use std::time::Duration;
|
||||||
|
use wmi::{COMLibrary, WMIConnection, Variant};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct VideoController {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub device_id: Option<String>,
|
||||||
|
pub pnp_device_id: Option<String>,
|
||||||
|
pub driver_version: Option<String>,
|
||||||
|
pub adapter_ram: Option<u64>,
|
||||||
|
pub status: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DesktopMonitor {
|
||||||
|
pub name: Option<String>,
|
||||||
|
pub device_id: Option<String>,
|
||||||
|
pub manufacturer: Option<String>,
|
||||||
|
pub screen_width: Option<u32>,
|
||||||
|
pub screen_height: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct WmiDeviceManager {
|
||||||
|
wmi_con: WMIConnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WmiDeviceManager {
|
||||||
|
pub fn new() -> Result<Self> {
|
||||||
|
let com_lib = COMLibrary::new().map_err(|e| {
|
||||||
|
error!("Failed to initialize COM library: {}", e);
|
||||||
|
VddError::IoError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let wmi_con = WMIConnection::new(com_lib).map_err(|e| {
|
||||||
|
error!("Failed to connect to WMI: {}", e);
|
||||||
|
VddError::IoError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self { wmi_con })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_video_controllers(&self) -> Result<Vec<VideoController>> {
|
||||||
|
let results: Vec<HashMap<String, Variant>> = self.wmi_con
|
||||||
|
.raw_query("SELECT Name, DeviceID, PNPDeviceID, DriverVersion, AdapterRAM, Status FROM Win32_VideoController")
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("WMI query for video controllers failed: {}", e);
|
||||||
|
VddError::IoError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut controllers = Vec::new();
|
||||||
|
for result in results {
|
||||||
|
controllers.push(VideoController {
|
||||||
|
name: Self::extract_string(&result, "Name"),
|
||||||
|
device_id: Self::extract_string(&result, "DeviceID"),
|
||||||
|
pnp_device_id: Self::extract_string(&result, "PNPDeviceID"),
|
||||||
|
driver_version: Self::extract_string(&result, "DriverVersion"),
|
||||||
|
adapter_ram: Self::extract_u64(&result, "AdapterRAM"),
|
||||||
|
status: Self::extract_string(&result, "Status"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found {} video controllers via WMI", controllers.len());
|
||||||
|
Ok(controllers)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_desktop_monitors(&self) -> Result<Vec<DesktopMonitor>> {
|
||||||
|
let results: Vec<HashMap<String, Variant>> = self.wmi_con
|
||||||
|
.raw_query("SELECT Name, DeviceID, MonitorManufacturer, ScreenWidth, ScreenHeight FROM Win32_DesktopMonitor")
|
||||||
|
.map_err(|e| {
|
||||||
|
error!("WMI query for desktop monitors failed: {}", e);
|
||||||
|
VddError::IoError
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let mut monitors = Vec::new();
|
||||||
|
for result in results {
|
||||||
|
monitors.push(DesktopMonitor {
|
||||||
|
name: Self::extract_string(&result, "Name"),
|
||||||
|
device_id: Self::extract_string(&result, "DeviceID"),
|
||||||
|
manufacturer: Self::extract_string(&result, "MonitorManufacturer"),
|
||||||
|
screen_width: Self::extract_u32(&result, "ScreenWidth"),
|
||||||
|
screen_height: Self::extract_u32(&result, "ScreenHeight"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found {} desktop monitors via WMI", monitors.len());
|
||||||
|
Ok(monitors)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_string(map: &HashMap<String, Variant>, key: &str) -> Option<String> {
|
||||||
|
match map.get(key) {
|
||||||
|
Some(Variant::String(s)) => Some(s.clone()),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_u32(map: &HashMap<String, Variant>, key: &str) -> Option<u32> {
|
||||||
|
match map.get(key) {
|
||||||
|
Some(Variant::UI4(n)) => Some(*n),
|
||||||
|
Some(Variant::I4(n)) => Some(*n as u32),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_u64(map: &HashMap<String, Variant>, key: &str) -> Option<u64> {
|
||||||
|
match map.get(key) {
|
||||||
|
Some(Variant::UI8(n)) => Some(*n),
|
||||||
|
Some(Variant::I8(n)) => Some(*n as u64),
|
||||||
|
Some(Variant::UI4(n)) => Some(*n as u64),
|
||||||
|
Some(Variant::I4(n)) => Some(*n as u64),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue