diff --git a/Cargo.toml b/Cargo.toml index 19badfc..d17b837 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/app/service.rs b/src/app/service.rs index c104b59..839142b 100644 --- a/src/app/service.rs +++ b/src/app/service.rs @@ -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, device_controller: DeviceController, - display_manager: DisplayManager, + hardware_detector: Option, config: Config, running: Arc, } impl VddService { pub fn new() -> Result { - 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 { + 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"); } } diff --git a/src/config/display.rs b/src/config/display.rs new file mode 100644 index 0000000..c861743 --- /dev/null +++ b/src/config/display.rs @@ -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, + } + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs index e88c0a2..ff7baed 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -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(), } } } diff --git a/src/main.rs b/src/main.rs index 4916e5f..99e493e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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"); } diff --git a/src/monitor/detector.rs b/src/monitor/detector.rs index e34eee1..8d70343 100644 --- a/src/monitor/detector.rs +++ b/src/monitor/detector.rs @@ -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 { + let mut displays = Vec::new(); + let mut info = DISPLAY_DEVICEW { + cb: std::mem::size_of::() 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 + } } diff --git a/src/monitor/hardware.rs b/src/monitor/hardware.rs new file mode 100644 index 0000000..b62f14c --- /dev/null +++ b/src/monitor/hardware.rs @@ -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, + pub monitors: Vec, + pub total_vram: u64, + pub has_dedicated_gpu: bool, + pub primary_adapter: Option, +} + +#[derive(Debug, Clone)] +pub struct VideoControllerInfo { + pub name: String, + pub device_id: String, + pub pnp_device_id: String, + pub driver_version: Option, + 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, + pub width: Option, + pub height: Option, + 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 { + let wmi_manager = WmiDeviceManager::new()?; + Ok(Self { wmi_manager }) + } + + pub fn get_hardware_info(&self) -> Result { + 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> { + 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> { + 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(()) + } +} diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs index ffeca01..00f1cc5 100644 --- a/src/monitor/mod.rs +++ b/src/monitor/mod.rs @@ -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}; diff --git a/src/monitor/wmi.rs b/src/monitor/wmi.rs new file mode 100644 index 0000000..add7ca8 --- /dev/null +++ b/src/monitor/wmi.rs @@ -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, + pub device_id: Option, + pub pnp_device_id: Option, + pub driver_version: Option, + pub adapter_ram: Option, + pub status: Option, +} + +#[derive(Debug, Clone)] +pub struct DesktopMonitor { + pub name: Option, + pub device_id: Option, + pub manufacturer: Option, + pub screen_width: Option, + pub screen_height: Option, +} + +pub struct WmiDeviceManager { + wmi_con: WMIConnection, +} + +impl WmiDeviceManager { + pub fn new() -> Result { + 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> { + let results: Vec> = 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> { + let results: Vec> = 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, key: &str) -> Option { + match map.get(key) { + Some(Variant::String(s)) => Some(s.clone()), + _ => None, + } + } + + fn extract_u32(map: &HashMap, key: &str) -> Option { + match map.get(key) { + Some(Variant::UI4(n)) => Some(*n), + Some(Variant::I4(n)) => Some(*n as u32), + _ => None, + } + } + + fn extract_u64(map: &HashMap, key: &str) -> Option { + 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, + } + } +}