diff --git a/Cargo.toml b/Cargo.toml index ecb69e0..19badfc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,8 @@ edition = "2024" [dependencies] log = "0.4.27" +env_logger = "0.11" +thiserror = "1.0" [dependencies.windows] version = "0.61.3" @@ -23,4 +25,4 @@ features = [ lto = true codegen-units = 1 # panic = 'abort' -# strip = true \ No newline at end of file +# strip = true diff --git a/src/app/mod.rs b/src/app/mod.rs new file mode 100644 index 0000000..b5cecfe --- /dev/null +++ b/src/app/mod.rs @@ -0,0 +1,3 @@ +mod service; + +pub use service::VddService; diff --git a/src/app/service.rs b/src/app/service.rs new file mode 100644 index 0000000..c104b59 --- /dev/null +++ b/src/app/service.rs @@ -0,0 +1,78 @@ +use crate::display::{DisplayManager, VirtualDisplayController}; +use crate::device::{VddHandle, DeviceController, DeviceStatusChecker}; +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; + +pub struct VddService { + handle: Arc, + device_controller: DeviceController, + display_manager: DisplayManager, + config: Config, + running: Arc, +} + +impl VddService { + pub fn new() -> Result { + DeviceStatusChecker::check_driver_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID)?; + + let handle = Arc::new(VddHandle::open(&VDD_ADAPTER_GUID)?); + 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); + + Ok(Self { + handle, + device_controller, + display_manager, + config, + running: Arc::new(AtomicBool::new(false)), + }) + } + + pub fn start(&mut self) -> Result<()> { + self.running.store(true, Ordering::SeqCst); + + // 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()))); + + std::thread::spawn(move || { + 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)); + } + }); + + // Start update loop + self.update_loop() + } + + pub fn stop(&self) { + self.running.store(false, Ordering::SeqCst); + } + + fn update_loop(&self) -> Result<()> { + 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)); + } + Ok(()) + } +} + +impl Drop for VddService { + fn drop(&mut self) { + self.stop(); + } +} diff --git a/src/config/mod.rs b/src/config/mod.rs new file mode 100644 index 0000000..e88c0a2 --- /dev/null +++ b/src/config/mod.rs @@ -0,0 +1,40 @@ +use windows::core::GUID; + +// Constants from the C header file +pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0"; +pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0"; + +// {00b41627-04c4-429e-a26e-0265cf50c8fa} +pub const VDD_ADAPTER_GUID: GUID = GUID::from_u128(0x00b41627_04c4_429e_a26e_0265cf50c8fa); +pub const VDD_ADAPTER_NAME: &[u8] = b"Parsec Virtual Display Adapter\0"; + +// {4d36e968-e325-11ce-bfc1-08002be10318} +pub const VDD_CLASS_GUID: GUID = GUID::from_u128(0x4d36e968_e325_11ce_bfc1_08002be10318); +pub const VDD_HARDWARE_ID: &[u8] = b"Root\\Parsec\\VDA\0"; + +pub const VDD_MAX_DISPLAYS: usize = 8; + +#[repr(u32)] +pub enum VddCtlCode { + Add = 0x0022e004, + Remove = 0x0022a008, + Update = 0x0022a00c, + Version = 0x0022e010, +} + +#[derive(Clone)] +pub struct Config { + pub monitor_check_interval_ms: u64, + pub vdd_update_interval_ms: u64, + pub io_timeout_ms: u32, +} + +impl Default for Config { + fn default() -> Self { + Self { + monitor_check_interval_ms: 1000, + vdd_update_interval_ms: 100, + io_timeout_ms: 5000, + } + } +} diff --git a/src/device/control.rs b/src/device/control.rs new file mode 100644 index 0000000..eaa23e7 --- /dev/null +++ b/src/device/control.rs @@ -0,0 +1,112 @@ +use std::mem::{size_of, zeroed}; +use windows::{ + Win32::{ + Foundation::{CloseHandle, ERROR_IO_PENDING, GetLastError, HANDLE, INVALID_HANDLE_VALUE}, + System::{ + IO::{DeviceIoControl, GetOverlappedResultEx, OVERLAPPED}, + Threading::CreateEventA, + }, + }, +}; +use crate::device::handle::VddHandle; +use crate::config::{VddCtlCode, Config, VDD_MAX_DISPLAYS}; +use crate::error::{Result, VddError}; + +pub struct DeviceController { + config: Config, +} + +impl DeviceController { + pub fn new(config: Config) -> Self { + Self { config } + } + + pub fn get_version(&self, handle: &VddHandle) -> Result { + self.io_control(handle, VddCtlCode::Version, None) + .map(|v| v as i32) + } + + pub fn update(&self, handle: &VddHandle) -> Result<()> { + self.io_control(handle, VddCtlCode::Update, None) + .map(|_| ()) + } + + pub fn add_display(&self, handle: &VddHandle) -> Result { + let result = self.io_control(handle, VddCtlCode::Add, None)?; + self.update(handle)?; // Ping immediately after adding + Ok(result as i32) + } + + pub fn remove_display(&self, handle: &VddHandle, index: i32) -> Result<()> { + if index < 0 || index >= VDD_MAX_DISPLAYS as i32 { + return Err(VddError::InvalidDisplayIndex(index, VDD_MAX_DISPLAYS)); + } + + let index_data = (index as u16).to_be_bytes(); + self.io_control(handle, VddCtlCode::Remove, Some(&index_data))?; + self.update(handle)?; // Ping immediately after removing + Ok(()) + } + + fn io_control(&self, handle: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Result { + let raw_handle = handle.raw_handle(); + if raw_handle == INVALID_HANDLE_VALUE { + return Err(VddError::InvalidHandle); + } + + unsafe { + let mut in_buffer = [0u8; 32]; + if let Some(d) = data { + let len = d.len().min(in_buffer.len()); + in_buffer[..len].copy_from_slice(&d[..len]); + } + + let mut overlapped: OVERLAPPED = zeroed(); + let event = CreateEventA(None, true, false, None).map_err(|_| VddError::IoError)?; + overlapped.hEvent = event; + + let mut out_buffer = 0u32; + let mut bytes_transferred = 0; + + let result = DeviceIoControl( + raw_handle, + code as u32, + Some(in_buffer.as_ptr() as *const _), + in_buffer.len() as u32, + Some(&mut out_buffer as *mut _ as *mut _), + size_of::() as u32, + None, + Some(&mut overlapped), + ); + + // Handle asynchronous operation + let final_result = if result.is_err() && GetLastError() == ERROR_IO_PENDING { + GetOverlappedResultEx( + raw_handle, + &overlapped, + &mut bytes_transferred, + self.config.io_timeout_ms, + false, + ) + .map_err(|_| VddError::Timeout) + } else { + result.map_err(|_| VddError::IoError) + }; + + let _ = CloseHandle(event); + + match final_result { + Ok(_) => Ok(out_buffer), + Err(e) => Err(e), + } + } + } +} + +impl Clone for DeviceController { + fn clone(&self) -> Self { + Self { + config: self.config.clone(), + } + } +} diff --git a/src/device/handle.rs b/src/device/handle.rs new file mode 100644 index 0000000..7de1edf --- /dev/null +++ b/src/device/handle.rs @@ -0,0 +1,159 @@ +use std::mem::zeroed; +use windows::{ + Win32::{ + Devices::DeviceAndDriverInstallation::{ + DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, HDEVINFO, SETUP_DI_GET_CLASS_DEVS_FLAGS, + SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, + SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInterfaces, + SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA, + }, + Foundation::{ + CloseHandle, GENERIC_READ, GENERIC_WRITE, HANDLE, INVALID_HANDLE_VALUE, + }, + Storage::FileSystem::{ + CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, + FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }, + }, + core::{GUID, PCSTR}, +}; +use crate::error::{Result, VddError}; +use std::mem::size_of; + +pub struct VddHandle(HANDLE); + +unsafe impl Send for VddHandle {} +unsafe impl Sync for VddHandle {} + +impl VddHandle { + pub fn open(interface_guid: &GUID) -> Result { + open_device_handle(interface_guid) + .ok_or(VddError::DeviceNotFound) + } + + pub(crate) fn raw_handle(&self) -> HANDLE { + self.0 + } +} + +impl Drop for VddHandle { + fn drop(&mut self) { + close_device_handle(self.0); + } +} + +// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called. +struct DevInfo(HDEVINFO); + +impl DevInfo { + fn new(class_guid: Option<&GUID>, flags: SETUP_DI_GET_CLASS_DEVS_FLAGS) -> Option { + unsafe { + let class_guid_ptr = class_guid.map(|g| g as *const GUID); + let handle = SetupDiGetClassDevsA(class_guid_ptr, None, None, flags).ok()?; + Some(DevInfo(handle)) + } + } +} + +impl Drop for DevInfo { + fn drop(&mut self) { + if self.0 != HDEVINFO(INVALID_HANDLE_VALUE.0 as isize) { + unsafe { + let _ = SetupDiDestroyDeviceInfoList(self.0); + } + } + } +} + +pub fn open_device_handle(interface_guid: &GUID) -> Option { + let dev_info = DevInfo::new(Some(interface_guid), DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)?; + + unsafe { + for i in 0.. { + let mut dev_interface_data: SP_DEVICE_INTERFACE_DATA = zeroed(); + dev_interface_data.cbSize = size_of::() as u32; + + if SetupDiEnumDeviceInterfaces( + dev_info.0, + None, + interface_guid, + i, + &mut dev_interface_data, + ) + .is_err() + { + break; + } + + if let Some(device_path) = get_device_path(&dev_info, &dev_interface_data) { + if let Some(handle) = create_device_handle(&device_path) { + return Some(VddHandle(handle)); + } + } + } + } + None +} + +fn get_device_path( + dev_info: &DevInfo, + dev_interface_data: &SP_DEVICE_INTERFACE_DATA, +) -> Option { + unsafe { + let mut detail_size = 0; + let _ = SetupDiGetDeviceInterfaceDetailA( + dev_info.0, + dev_interface_data, + None, + 0, + Some(&mut detail_size), + None, + ); + + if detail_size == 0 { + return None; + } + + let mut detail_buffer = vec![0u8; detail_size as usize]; + let detail = detail_buffer.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_A; + (*detail).cbSize = size_of::() as u32; + + SetupDiGetDeviceInterfaceDetailA( + dev_info.0, + dev_interface_data, + Some(detail), + detail_size, + None, + None, + ) + .ok()?; + + Some(PCSTR((*detail).DevicePath.as_ptr() as *const u8)) + } +} + +fn create_device_handle(device_path: &PCSTR) -> Option { + unsafe { + CreateFileA( + *device_path, + (GENERIC_READ | GENERIC_WRITE).0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL + | FILE_FLAG_NO_BUFFERING + | FILE_FLAG_OVERLAPPED + | FILE_FLAG_WRITE_THROUGH, + None, + ) + .ok() + } +} + +fn close_device_handle(handle: HANDLE) { + if handle != INVALID_HANDLE_VALUE { + unsafe { + let _ = CloseHandle(handle); + } + } +} diff --git a/src/device/mod.rs b/src/device/mod.rs new file mode 100644 index 0000000..53ec710 --- /dev/null +++ b/src/device/mod.rs @@ -0,0 +1,7 @@ +mod handle; +mod status; +mod control; + +pub use handle::VddHandle; +pub use status::DeviceStatusChecker; +pub use control::DeviceController; diff --git a/src/device/status.rs b/src/device/status.rs new file mode 100644 index 0000000..65fe9fd --- /dev/null +++ b/src/device/status.rs @@ -0,0 +1,165 @@ +use std::mem::{size_of, zeroed}; +use windows::{ + Win32::{ + Devices::DeviceAndDriverInstallation::{ + CM_DEVNODE_STATUS_FLAGS, CM_Get_DevNode_Status, CM_PROB, CM_PROB_DISABLED, + CM_PROB_DISABLED_SERVICE, CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, + CM_PROB_NEED_RESTART, CONFIGRET, DIGCF_PRESENT, DN_DRIVER_LOADED, DN_HAS_PROBLEM, + DN_STARTED, HDEVINFO, SP_DEVINFO_DATA, SPDRP_HARDWAREID, SetupDiEnumDeviceInfo, + SetupDiGetClassDevsA, SetupDiGetDeviceRegistryPropertyA, + }, + Foundation::INVALID_HANDLE_VALUE, + System::Registry::{REG_MULTI_SZ, REG_SZ}, + }, + core::GUID, +}; +use crate::error::{DeviceStatus, Result, VddError}; + +pub struct DeviceStatusChecker; + +impl DeviceStatusChecker { + pub fn check_driver_status(class_guid: &GUID, device_id: &[u8]) -> Result<()> { + let status = query_device_status(class_guid, device_id); + match status { + DeviceStatus::Ok => Ok(()), + other => Err(VddError::DeviceStatus(other)), + } + } +} + +// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called. +struct DevInfo(HDEVINFO); + +impl DevInfo { + fn new(class_guid: Option<&GUID>, flags: u32) -> Option { + unsafe { + let class_guid_ptr = class_guid.map(|g| g as *const GUID); + let handle = SetupDiGetClassDevsA(class_guid_ptr, None, None, flags.into()).ok()?; + Some(DevInfo(handle)) + } + } +} + +impl Drop for DevInfo { + fn drop(&mut self) { + if self.0 != HDEVINFO(INVALID_HANDLE_VALUE.0 as isize) { + unsafe { + let _ = windows::Win32::Devices::DeviceAndDriverInstallation::SetupDiDestroyDeviceInfoList(self.0); + } + } + } +} + +pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus { + let dev_info = match DevInfo::new(Some(class_guid), DIGCF_PRESENT) { + Some(info) => info, + None => return DeviceStatus::Inaccessible, + }; + + // Ensure device_id is null-terminated + if device_id.is_empty() || device_id[device_id.len() - 1] != 0 { + return DeviceStatus::Unknown; + } + + unsafe { + for device_index in 0.. { + let mut dev_info_data: SP_DEVINFO_DATA = zeroed(); + dev_info_data.cbSize = size_of::() as u32; + + if SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_err() { + break; + } + + if let Some(hardware_ids) = get_device_hardware_ids(&dev_info, &dev_info_data) { + let target_id = &device_id[..device_id.len() - 1]; + if hardware_ids.iter().any(|id| id.as_slice() == target_id) { + return get_device_status(&dev_info_data); + } + } + } + } + DeviceStatus::NotInstalled +} + +fn get_device_hardware_ids( + dev_info: &DevInfo, + dev_info_data: &SP_DEVINFO_DATA, +) -> Option>> { + unsafe { + let mut required_size = 0; + let _ = SetupDiGetDeviceRegistryPropertyA( + dev_info.0, + dev_info_data, + SPDRP_HARDWAREID, + None, + None, + Some(&mut required_size), + ); + + if required_size == 0 { + return None; + } + + let mut prop_buffer = vec![0u8; required_size as usize]; + let mut reg_data_type = 0; + + SetupDiGetDeviceRegistryPropertyA( + dev_info.0, + dev_info_data, + SPDRP_HARDWAREID, + Some(&mut reg_data_type), + Some(prop_buffer.as_mut_slice()), + Some(&mut required_size), + ) + .ok()?; + + if reg_data_type == REG_SZ.0 || reg_data_type == REG_MULTI_SZ.0 { + Some( + prop_buffer + .split(|&b| b == 0) + .filter(|s| !s.is_empty()) + .map(|s| s.to_vec()) + .collect(), + ) + } else { + None + } + } +} + +fn get_device_status(dev_info_data: &SP_DEVINFO_DATA) -> DeviceStatus { + unsafe { + let mut dev_status = CM_DEVNODE_STATUS_FLAGS::default(); + let mut dev_problem_num = CM_PROB::default(); + + if CM_Get_DevNode_Status( + &mut dev_status, + &mut dev_problem_num, + dev_info_data.DevInst, + 0, + ) != CONFIGRET(0) + { + return DeviceStatus::NotInstalled; + } + + // Check if device is running normally + if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)).0 == (DN_DRIVER_LOADED | DN_STARTED).0 { + return DeviceStatus::Ok; + } + + // Check for problems + if (dev_status & DN_HAS_PROBLEM).0 != 0 { + match dev_problem_num { + n if n == CM_PROB_NEED_RESTART => DeviceStatus::RestartRequired, + n if n == CM_PROB_DISABLED || n == CM_PROB_HARDWARE_DISABLED => { + DeviceStatus::Disabled + } + n if n == CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService, + n if n == CM_PROB_FAILED_POST_START => DeviceStatus::DriverError, + _ => DeviceStatus::UnknownProblem, + } + } else { + DeviceStatus::Unknown + } + } +} diff --git a/src/display/manager.rs b/src/display/manager.rs new file mode 100644 index 0000000..a78f184 --- /dev/null +++ b/src/display/manager.rs @@ -0,0 +1,55 @@ +use crate::display::virtual_display::{VirtualDisplay, VirtualDisplayController}; +use crate::monitor::MonitorDetector; +use crate::device::VddHandle; +use crate::error::Result; +use log::info; + +pub struct DisplayManager { + virtual_display_controller: VirtualDisplayController, + current_display: Option, +} + +impl DisplayManager { + pub fn new(virtual_display_controller: VirtualDisplayController) -> Self { + Self { + virtual_display_controller, + current_display: None, + } + } + + pub fn update_displays(&mut self, handle: &VddHandle) -> Result<()> { + let has_physical = MonitorDetector::has_physical_monitors(); + let has_virtual = self.current_display.is_some(); + + match (has_physical, has_virtual) { + (false, false) => { + info!("No physical monitors detected, starting virtual display"); + self.start_virtual_display(handle)?; + } + (true, true) => { + info!("Physical monitor detected, stopping virtual display"); + self.stop_virtual_display(handle)?; + } + _ => {} // No change needed + } + + Ok(()) + } + + fn start_virtual_display(&mut self, handle: &VddHandle) -> Result<()> { + if self.current_display.is_none() { + let display = self.virtual_display_controller.create_display(handle)?; + info!("Virtual display added with index: {}", display.index()); + self.current_display = Some(display); + } + Ok(()) + } + + fn stop_virtual_display(&mut self, handle: &VddHandle) -> Result<()> { + if let Some(display) = self.current_display.take() { + self.virtual_display_controller.remove_display(handle, display)?; + info!("Virtual display removed"); + } + Ok(()) + } +} diff --git a/src/display/mod.rs b/src/display/mod.rs new file mode 100644 index 0000000..6043a56 --- /dev/null +++ b/src/display/mod.rs @@ -0,0 +1,5 @@ +mod virtual_display; +mod manager; + +pub use virtual_display::{VirtualDisplay, VirtualDisplayController}; +pub use manager::DisplayManager; diff --git a/src/display/virtual_display.rs b/src/display/virtual_display.rs new file mode 100644 index 0000000..81000f5 --- /dev/null +++ b/src/display/virtual_display.rs @@ -0,0 +1,36 @@ +use crate::device::{VddHandle, DeviceController}; +use crate::error::Result; + +#[derive(Debug)] +pub struct VirtualDisplay { + index: i32, +} + +impl VirtualDisplay { + pub fn new(index: i32) -> Self { + Self { index } + } + + pub fn index(&self) -> i32 { + self.index + } +} + +pub struct VirtualDisplayController { + device_controller: DeviceController, +} + +impl VirtualDisplayController { + pub fn new(device_controller: DeviceController) -> Self { + Self { device_controller } + } + + pub fn create_display(&self, handle: &VddHandle) -> Result { + let index = self.device_controller.add_display(handle)?; + Ok(VirtualDisplay::new(index)) + } + + pub fn remove_display(&self, handle: &VddHandle, display: VirtualDisplay) -> Result<()> { + self.device_controller.remove_display(handle, display.index()) + } +} diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..457a787 --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,37 @@ +use crate::config::VDD_MAX_DISPLAYS; + +#[derive(Debug, thiserror::Error)] +pub enum VddError { + #[error("Device not found")] + DeviceNotFound, + #[error("Invalid handle")] + InvalidHandle, + #[error("I/O operation failed")] + IoError, + #[error("Operation timed out")] + Timeout, + #[error("Device status error: {0:?}")] + DeviceStatus(DeviceStatus), + #[error("Failed to add virtual display")] + AddDisplayFailed, + #[error("Failed to remove virtual display")] + RemoveDisplayFailed, + #[error("Invalid display index: {0} (max: {1})")] + InvalidDisplayIndex(i32, usize), +} + +pub type Result = std::result::Result; + +#[derive(Debug, PartialEq, Eq, Copy, Clone)] +#[repr(i32)] +pub enum DeviceStatus { + Ok = 0, + Inaccessible, + Unknown, + UnknownProblem, + Disabled, + DriverError, + RestartRequired, + DisabledService, + NotInstalled, +} diff --git a/src/lib.rs b/src/lib.rs index f70821a..b338115 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,460 +1,11 @@ -#[allow(non_snake_case)] -use std::mem::{size_of, zeroed}; -use log::{trace,debug,info}; -use windows::{ - Win32::{ - Devices::DeviceAndDriverInstallation::{ - CM_DEVNODE_STATUS_FLAGS, CM_Get_DevNode_Status, CM_PROB, CM_PROB_DISABLED, - CM_PROB_DISABLED_SERVICE, CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, - CM_PROB_NEED_RESTART, CONFIGRET, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, - DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SETUP_DI_GET_CLASS_DEVS_FLAGS, - SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, SP_DEVINFO_DATA, - SPDRP_HARDWAREID, SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, - SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA, - SetupDiGetDeviceRegistryPropertyA, - }, - Foundation::{ - CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE, GetLastError, HANDLE, - INVALID_HANDLE_VALUE, - }, - Graphics::Gdi::{ - DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_STATE_FLAGS, DISPLAY_DEVICEW, EnumDisplayDevicesW, - }, - Storage::FileSystem::{ - CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, - FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, - }, - System::{ - IO::{DeviceIoControl, GetOverlappedResultEx, OVERLAPPED}, - Registry::{REG_MULTI_SZ, REG_SZ}, - Threading::CreateEventA, - }, - }, - core::{GUID, PCSTR, PWSTR}, -}; +pub mod app; +pub mod device; +pub mod display; +pub mod monitor; +pub mod error; +pub mod config; -// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called. -struct DevInfo(HDEVINFO); - -impl DevInfo { - fn new(class_guid: Option<&GUID>, flags: SETUP_DI_GET_CLASS_DEVS_FLAGS) -> Option { - unsafe { - let class_guid_ptr = class_guid.map(|g| g as *const GUID); - let handle = SetupDiGetClassDevsA(class_guid_ptr, None, None, flags).ok()?; - Some(DevInfo(handle)) - } - } -} - -impl Drop for DevInfo { - fn drop(&mut self) { - if self.0 != HDEVINFO(INVALID_HANDLE_VALUE.0 as isize) { - unsafe { - let _ = SetupDiDestroyDeviceInfoList(self.0); - } - } - } -} - -// RAII wrapper for HANDLE to ensure CloseHandle is always called. -// We expose this as the public handle type. -pub struct VddHandle(HANDLE); - -unsafe impl Send for VddHandle {} -unsafe impl Sync for VddHandle {} - -impl Drop for VddHandle { - fn drop(&mut self) { - close_device_handle(self.0); - } -} - -// Device Status Enum -// Derived traits allow for easy comparison, printing, and copying. -#[derive(Debug, PartialEq, Eq, Copy, Clone)] -#[repr(i32)] -pub enum DeviceStatus { - Ok = 0, - Inaccessible, - Unknown, - UnknownProblem, - Disabled, - DriverError, - RestartRequired, - DisabledService, - NotInstalled, -} - -// Constants from the C header file. -// GUIDs are created from their string representations. -// C-style strings are represented as null-terminated byte slices. -pub const VDD_DISPLAY_ID: &[u8] = b"PSCCDD0\0"; -pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0"; - -// {00b41627-04c4-429e-a26e-0265cf50c8fa} -pub const VDD_ADAPTER_GUID: GUID = GUID::from_u128(0x00b41627_04c4_429e_a26e_0265cf50c8fa); -pub const VDD_ADAPTER_NAME: &[u8] = b"Parsec Virtual Display Adapter\0"; - -// {4d36e968-e325-11ce-bfc1-08002be10318} -pub const VDD_CLASS_GUID: GUID = GUID::from_u128(0x4d36e968_e325_11ce_bfc1_08002be10318); -pub const VDD_HARDWARE_ID: &[u8] = b"Root\\Parsec\\VDA\0"; - -pub const VDD_MAX_DISPLAYS: usize = 8; - -// IOCTL codes -#[repr(u32)] -pub enum VddCtlCode { - Add = 0x0022e004, - Remove = 0x0022a008, - Update = 0x0022a00c, - Version = 0x0022e010, -} - -// 错误处理类型 -#[derive(Debug)] -pub enum VddError { - DeviceNotFound, - InvalidHandle, - IoError, - Timeout, -} - -type VddResult = Result; -/// Query the driver status. -pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus { - let dev_info = match DevInfo::new(Some(class_guid), DIGCF_PRESENT) { - Some(info) => info, - None => return DeviceStatus::Inaccessible, - }; - - // 确保 device_id 以 null 结尾 - if device_id.is_empty() || device_id[device_id.len() - 1] != 0 { - return DeviceStatus::Unknown; - } - - unsafe { - for device_index in 0.. { - let mut dev_info_data: SP_DEVINFO_DATA = zeroed(); - dev_info_data.cbSize = size_of::() as u32; - - if SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_err() { - break; - } - - if let Some(hardware_ids) = get_device_hardware_ids(&dev_info, &dev_info_data) { - let target_id = &device_id[..device_id.len() - 1]; - if hardware_ids.iter().any(|id| id.as_slice() == target_id) { - return get_device_status(&dev_info_data); - } - } - } - } - DeviceStatus::NotInstalled -} - -// 提取硬件ID获取逻辑 -fn get_device_hardware_ids( - dev_info: &DevInfo, - dev_info_data: &SP_DEVINFO_DATA, -) -> Option>> { - unsafe { - let mut required_size = 0; - let _ = SetupDiGetDeviceRegistryPropertyA( - dev_info.0, - dev_info_data, - SPDRP_HARDWAREID, - None, - None, - Some(&mut required_size), - ); - - if required_size == 0 { - return None; - } - - let mut prop_buffer = vec![0u8; required_size as usize]; - let mut reg_data_type = 0; - - SetupDiGetDeviceRegistryPropertyA( - dev_info.0, - dev_info_data, - SPDRP_HARDWAREID, - Some(&mut reg_data_type), - Some(prop_buffer.as_mut_slice()), - Some(&mut required_size), - ) - .ok()?; - - if reg_data_type == REG_SZ.0 || reg_data_type == REG_MULTI_SZ.0 { - Some( - prop_buffer - .split(|&b| b == 0) - .filter(|s| !s.is_empty()) - .map(|s| s.to_vec()) - .collect(), - ) - } else { - None - } - } -} - -// 提取设备状态获取逻辑 -fn get_device_status(dev_info_data: &SP_DEVINFO_DATA) -> DeviceStatus { - unsafe { - let mut dev_status = CM_DEVNODE_STATUS_FLAGS::default(); - let mut dev_problem_num = CM_PROB::default(); - - if CM_Get_DevNode_Status( - &mut dev_status, - &mut dev_problem_num, - dev_info_data.DevInst, - 0, - ) != CONFIGRET(0) - { - return DeviceStatus::NotInstalled; - } - - // 检查设备是否正常运行 - if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)).0 == (DN_DRIVER_LOADED | DN_STARTED).0 { - return DeviceStatus::Ok; - } - - // 检查是否有问题 - if (dev_status & DN_HAS_PROBLEM).0 != 0 { - match dev_problem_num { - n if n == CM_PROB_NEED_RESTART => DeviceStatus::RestartRequired, - n if n == CM_PROB_DISABLED || n == CM_PROB_HARDWARE_DISABLED => { - DeviceStatus::Disabled - } - n if n == CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService, - n if n == CM_PROB_FAILED_POST_START => DeviceStatus::DriverError, - _ => DeviceStatus::UnknownProblem, - } - } else { - DeviceStatus::Unknown - } - } -} - -/// Obtain the device handle. Returns None if it fails. -pub fn open_device_handle(interface_guid: &GUID) -> Option { - let dev_info = DevInfo::new(Some(interface_guid), DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)?; - - unsafe { - for i in 0.. { - let mut dev_interface_data: SP_DEVICE_INTERFACE_DATA = zeroed(); - dev_interface_data.cbSize = size_of::() as u32; - - if SetupDiEnumDeviceInterfaces( - dev_info.0, - None, - interface_guid, - i, - &mut dev_interface_data, - ) - .is_err() - { - break; - } - - if let Some(device_path) = get_device_path(&dev_info, &dev_interface_data) { - if let Some(handle) = create_device_handle(&device_path) { - return Some(VddHandle(handle)); - } - } - } - } - None -} - -// 提取设备路径获取逻辑 -fn get_device_path( - dev_info: &DevInfo, - dev_interface_data: &SP_DEVICE_INTERFACE_DATA, -) -> Option { - unsafe { - let mut detail_size = 0; - let _ = SetupDiGetDeviceInterfaceDetailA( - dev_info.0, - dev_interface_data, - None, - 0, - Some(&mut detail_size), - None, - ); - - if detail_size == 0 { - return None; - } - - let mut detail_buffer = vec![0u8; detail_size as usize]; - let detail = detail_buffer.as_mut_ptr() as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_A; - (*detail).cbSize = size_of::() as u32; - - SetupDiGetDeviceInterfaceDetailA( - dev_info.0, - dev_interface_data, - Some(detail), - detail_size, - None, - None, - ) - .ok()?; - - Some(PCSTR((*detail).DevicePath.as_ptr() as *const u8)) - } -} - -// 提取文件句柄创建逻辑 -fn create_device_handle(device_path: &PCSTR) -> Option { - unsafe { - CreateFileA( - *device_path, - (GENERIC_READ | GENERIC_WRITE).0, - FILE_SHARE_READ | FILE_SHARE_WRITE, - None, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL - | FILE_FLAG_NO_BUFFERING - | FILE_FLAG_OVERLAPPED - | FILE_FLAG_WRITE_THROUGH, - None, - ) - .ok() - } -} - -/// Release the device handle. -fn close_device_handle(handle: HANDLE) { - if handle != INVALID_HANDLE_VALUE { - unsafe { - let _ = CloseHandle(handle); - } - } -} - -/// Generic DeviceIoControl for all IoControl codes. -/// Returns the output buffer value from the driver, or None on failure. -fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> VddResult { - if vdd.0 == INVALID_HANDLE_VALUE { - return Err(VddError::InvalidHandle); - } - - unsafe { - let mut in_buffer = [0u8; 32]; - if let Some(d) = data { - let len = d.len().min(in_buffer.len()); - in_buffer[..len].copy_from_slice(&d[..len]); - } - - let mut overlapped: OVERLAPPED = zeroed(); - let event = CreateEventA(None, true, false, None).map_err(|_| VddError::IoError)?; - overlapped.hEvent = event; - - let mut out_buffer = 0u32; - let mut bytes_transferred = 0; - - let result = DeviceIoControl( - vdd.0, - code as u32, - Some(in_buffer.as_ptr() as *const _), - in_buffer.len() as u32, - Some(&mut out_buffer as *mut _ as *mut _), - size_of::() as u32, - None, - Some(&mut overlapped), - ); - - // 处理异步操作 - let final_result = if result.is_err() && GetLastError() == ERROR_IO_PENDING { - GetOverlappedResultEx( - vdd.0, - &overlapped, - &mut bytes_transferred, - 5000, // 5秒超时 - false, - ) - .map_err(|_| VddError::Timeout) - } else { - result.map_err(|_| VddError::IoError) - }; - - let _ = CloseHandle(event); - - match final_result { - Ok(_) => Ok(out_buffer), - Err(e) => Err(e), - } - } -} - -/// Query VDD minor version. -pub fn vdd_version(vdd: &VddHandle) -> Option { - vdd_io_control(vdd, VddCtlCode::Version, None) - .ok() - .map(|v| v as i32) -} - -/// Update/ping to VDD to keep displays alive. -pub fn vdd_update(vdd: &VddHandle) -> bool { - vdd_io_control(vdd, VddCtlCode::Update, None).is_ok() -} - -/// Add/plug a virtual display. Returns the index of the added display. -pub fn vdd_add_display(vdd: &VddHandle) -> Option { - let result = vdd_io_control(vdd, VddCtlCode::Add, None).ok()?; - vdd_update(vdd); // Ping immediately after adding - Some(result as i32) -} - -/// Remove/unplug a virtual display. -pub fn vdd_remove_display(vdd: &VddHandle, index: i32) -> bool { - // 验证索引范围 - if index < 0 || index >= VDD_MAX_DISPLAYS as i32 { - return false; - } - - // The driver expects the index as a 16-bit big-endian integer. - let index_data = (index as u16).to_be_bytes(); - let result = vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data)).is_ok(); - if result { - vdd_update(vdd); // Ping immediately after removing - } - result -} - -pub fn is_physical_monitor_connected() -> bool { - // fetch windows monitor lists. - let mut physical_monitors = 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() } { - if (info.StateFlags & DISPLAY_DEVICE_ACTIVE).0 != 0 { - let device_name = String::from_utf16_lossy(&info.DeviceName) - .trim_end_matches('\0') - .to_string(); - debug!("Found monitor: {}", device_name); - // 排除 parsec 虚拟显示器,只统计物理显示器 - if !device_name.to_lowercase().contains("parsec") { - physical_monitors.push(device_name); - } - } - i += 1; - } - - trace!("Found {} physical monitors", physical_monitors.len()); - for (i, monitor) in physical_monitors.iter().enumerate() { - trace!("Physical Monitor {}: {}", i, monitor); - } - - !physical_monitors.is_empty() -} +// Re-export main types for public API +pub use app::VddService; +pub use error::{VddError, Result, DeviceStatus}; +pub use device::VddHandle; diff --git a/src/main.rs b/src/main.rs index c72369e..4916e5f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,47 +1,20 @@ -use std::sync::{ - Arc, - atomic::{AtomicI32, Ordering}, -}; +use parsec_vdd::app::VddService; +use log::error; +use env_logger::Env; -use parsec_vdd::{ - DeviceStatus, VDD_ADAPTER_GUID, VDD_CLASS_GUID, VDD_HARDWARE_ID, is_physical_monitor_connected, - open_device_handle, query_device_status, vdd_add_display, vdd_remove_display, vdd_update, -}; - -static INDEX: AtomicI32 = AtomicI32::new(-1); fn main() { - let status: DeviceStatus = query_device_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID); - if status != DeviceStatus::Ok { - panic!("Failed to query device status"); - } - let handle = match open_device_handle(&VDD_ADAPTER_GUID) { - Some(handle) => Arc::new(handle), - None => panic!("Failed to open device handle"), + env_logger::Builder::from_env(Env::default().default_filter_or("info")).init(); + + let mut service = match VddService::new() { + Ok(service) => service, + Err(e) => { + error!("Failed to initialize VDD service: {}", e); + std::process::exit(1); + } }; - let handle_clone = Arc::clone(&handle); - std::thread::spawn(move || { - loop { - vdd_update(&handle_clone); - std::thread::sleep(std::time::Duration::from_millis(100)); - } - }); - - loop { - let vdd_is_running = INDEX.load(Ordering::SeqCst) != -1; - let has_physicial_monitor = is_physical_monitor_connected(); - if !has_physicial_monitor && !vdd_is_running { - INDEX.store( - match vdd_add_display(&handle) { - Some(i) => i, - None => -1, - }, - Ordering::SeqCst, - ); - } else if has_physicial_monitor && vdd_is_running { - vdd_remove_display(&handle, INDEX.load(Ordering::SeqCst)); - INDEX.store(-1, Ordering::SeqCst); - } - std::thread::sleep(std::time::Duration::from_millis(1000)); + if let Err(e) = service.start() { + error!("Failed to start VDD service: {}", e); + std::process::exit(1); } -} \ No newline at end of file +} diff --git a/src/monitor/detector.rs b/src/monitor/detector.rs new file mode 100644 index 0000000..e34eee1 --- /dev/null +++ b/src/monitor/detector.rs @@ -0,0 +1,50 @@ +use log::{debug, trace}; +use windows::{ + Win32::Graphics::Gdi::{ + DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_STATE_FLAGS, DISPLAY_DEVICEW, EnumDisplayDevicesW, + }, + core::PWSTR, +}; + +pub struct MonitorDetector; + +impl MonitorDetector { + pub fn has_physical_monitors() -> bool { + let monitors = Self::get_physical_monitors(); + !monitors.is_empty() + } + + pub fn get_physical_monitors() -> Vec { + let mut physical_monitors = 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() } { + if (info.StateFlags & DISPLAY_DEVICE_ACTIVE).0 != 0 { + let device_name = String::from_utf16_lossy(&info.DeviceName) + .trim_end_matches('\0') + .to_string(); + debug!("Found monitor: {}", device_name); + // Exclude parsec virtual displays, only count physical displays + if !device_name.to_lowercase().contains("parsec") { + physical_monitors.push(device_name); + } + } + i += 1; + } + + trace!("Found {} physical monitors", physical_monitors.len()); + for (i, monitor) in physical_monitors.iter().enumerate() { + trace!("Physical Monitor {}: {}", i, monitor); + } + + physical_monitors + } +} diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs new file mode 100644 index 0000000..ffeca01 --- /dev/null +++ b/src/monitor/mod.rs @@ -0,0 +1,3 @@ +mod detector; + +pub use detector::MonitorDetector;