feat: add hardware detection and monitoring via WMI

main
jianghanxin (aider) 2025-07-01 14:21:06 +08:00
parent 4269ddb312
commit c7b4c9d33a
9 changed files with 496 additions and 16 deletions

View File

@ -7,6 +7,9 @@ edition = "2024"
log = "0.4.27"
env_logger = "0.11"
thiserror = "1.0"
wmi = "0.13"
serde = { version = "1.0", features = ["derive"] }
chrono = { version = "0.4", features = ["serde"] }
[dependencies.windows]
version = "0.61.3"

View File

@ -1,72 +1,117 @@
use crate::display::{DisplayManager, VirtualDisplayController};
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::error::Result;
use std::sync::{Arc, atomic::{AtomicBool, Ordering}};
use std::time::Duration;
use log::error;
use log::{error, info, warn};
pub struct VddService {
handle: Arc<VddHandle>,
device_controller: DeviceController,
display_manager: DisplayManager,
hardware_detector: Option<HardwareDetector>,
config: Config,
running: Arc<AtomicBool>,
}
impl VddService {
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)?);
info!("VDD device handle opened successfully");
let config = Config::default();
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 {
handle,
device_controller,
display_manager,
hardware_detector,
config,
running: Arc::new(AtomicBool::new(false)),
})
}
pub fn start(&mut self) -> Result<()> {
info!("Starting VDD Service...");
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
let handle_clone = Arc::clone(&self.handle);
let running_clone = Arc::clone(&self.running);
let config = self.config.clone();
let mut display_manager = std::mem::replace(&mut self.display_manager,
DisplayManager::new(VirtualDisplayController::new(self.device_controller.clone())));
let monitor_config = self.config.clone();
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) {
if let Err(e) = display_manager.update_displays(&handle_clone) {
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()
}
pub fn stop(&self) {
info!("Stopping VDD Service...");
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<()> {
info!("VDD update loop started");
while self.running.load(Ordering::SeqCst) {
if let Err(e) = self.device_controller.update(&self.handle) {
error!("Failed to update VDD: {}", e);
}
std::thread::sleep(Duration::from_millis(self.config.vdd_update_interval_ms));
}
info!("VDD update loop stopped");
Ok(())
}
}
@ -74,5 +119,6 @@ impl VddService {
impl Drop for VddService {
fn drop(&mut self) {
self.stop();
info!("VDD Service dropped");
}
}

45
src/config/display.rs Normal file
View File

@ -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,
}
}
}

View File

@ -1,5 +1,8 @@
use windows::core::GUID;
mod display;
pub use display::{DisplayConfig, VirtualDisplaySettings};
// Constants from the C header file
pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0";
pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0";
@ -27,6 +30,7 @@ pub struct Config {
pub monitor_check_interval_ms: u64,
pub vdd_update_interval_ms: u64,
pub io_timeout_ms: u32,
pub display: DisplayConfig,
}
impl Default for Config {
@ -35,6 +39,7 @@ impl Default for Config {
monitor_check_interval_ms: 1000,
vdd_update_interval_ms: 100,
io_timeout_ms: 5000,
display: DisplayConfig::default(),
}
}
}

View File

@ -1,20 +1,52 @@
use parsec_vdd::app::VddService;
use log::error;
use log::{error, info};
use env_logger::Env;
use std::io::{self, Write};
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() {
Ok(service) => service,
Ok(service) => {
info!("VDD Service initialized successfully");
service
}
Err(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);
}
};
// 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() {
error!("Failed to start VDD service: {}", e);
error!("VDD service encountered an error: {}", e);
std::process::exit(1);
}
info!("VDD Service stopped normally");
}

View File

@ -6,6 +6,14 @@ use windows::{
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;
impl MonitorDetector {
@ -47,4 +55,39 @@ impl MonitorDetector {
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
}
}

186
src/monitor/hardware.rs Normal file
View File

@ -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(())
}
}

View File

@ -1,3 +1,7 @@
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};

116
src/monitor/wmi.rs Normal file
View File

@ -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,
}
}
}