diff --git a/Cargo.lock b/Cargo.lock index 93f45d9..eb5b2c5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,16 +2,138 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "anstream" +version = "0.6.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "301af1932e46185686725e0fad2f8f2aa7da69dd70bf6ecc44d6b703844a3933" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "862ed96ca487e809f1c8e5a8447f6ee2cf102f846893800b20cebdf541fc6bbd" + +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8bdeb6047d8983be085bab0ba1472e6dc604e7041dbf6fcd5e71523014fae9" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "403f75924867bb1033c59fbf0797484329750cfbe3c4325cd33127941fabc882" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +[[package]] +name = "clap" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675" + +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" + +[[package]] +name = "log" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4895175b425cb1f87721b59f0f286c2092bd4af812243672510e1ac53e2e0ad" + [[package]] name = "parsec-vdd" version = "0.1.0" dependencies = [ + "clap", + "log", "windows", "windows-service", ] @@ -34,6 +156,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.103" @@ -51,6 +179,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "widestring" version = "1.2.0" diff --git a/Cargo.toml b/Cargo.toml index 4879930..a862658 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,8 @@ version = "0.1.0" edition = "2024" [dependencies] +clap = { version = "4.5.40", features = ["derive"] } +log = "0.4.27" windows-service = "0.8.0" [dependencies.windows] @@ -15,6 +17,6 @@ features = [ "Win32_Storage_FileSystem", "Win32_System_Threading", "Win32_System_Registry", - "Win32_System_SystemServices", - "Win32_System_Diagnostics_Debug", -] \ No newline at end of file + "Win32_Graphics_Gdi", + "Win32_Security", +] diff --git a/src/app.rs b/src/app.rs new file mode 100644 index 0000000..7dfcd07 --- /dev/null +++ b/src/app.rs @@ -0,0 +1,131 @@ +use log::{error, info, trace}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; +use windows::{ + core::PWSTR, + Win32::{ + Graphics::Gdi::{ + EnumDisplayDevicesW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, + DISPLAY_DEVICE_STATE_FLAGS, + }, + }, +}; +use parsec_vdd::{ + VDD_ADAPTER_GUID, VDD_CLASS_GUID, VDD_HARDWARE_ID, VddHandle, + open_device_handle, query_device_status, vdd_add_display, + vdd_remove_display, vdd_update, vdd_version, DeviceStatus, +}; + +pub struct App { + handle: Arc, + version: i32, + running: Arc, + index: i32, +} + +impl App { + pub fn new() -> Self { + let status = 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"); + } + }; + let mut app = App { + handle: handle.clone(), + version: 0, + running: Arc::new(AtomicBool::new(true)), + index: -1, + }; + if let Some(version) = vdd_version(&handle) { + app.version = version; + } + app + } + + // reorder monitors, and make parsec-vdd monitor have the highest id. + pub fn reorder(&self) -> u8 { + // TODO: implement reorder logic + 0 + } + + fn if_monitor_connected() -> bool { + // fetch windows monitor lists. + let mut 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(); + if device_name.to_lowercase().contains("parsec") { + i += 1; + continue; + } + monitors.push(device_name); + } + i += 1; + } + + trace!("Found {} monitors", monitors.len()); + for (i, monitor) in monitors.iter().enumerate() { + trace!("Monitor {}: {}", i, monitor); + } + + !monitors.is_empty() + } + pub fn watch_monitors(&mut self) { + loop { + if !self.if_monitor_connected() && !self.running.load(Ordering::SeqCst) { + self.start(); + break; + } else { + self.stop(); + } + std::thread::sleep(std::time::Duration::from_millis(1000)); + } + } + + pub fn start(&mut self) { + if self.index == -1 { + self.index = vdd_add_display(&self.handle).unwrap_or(-1); + if self.index == -1 { + error!("Failed to add display"); + return; + } + } + self.running.store(true, Ordering::SeqCst); + while self.running.load(Ordering::SeqCst) { + vdd_update(&self.handle); + std::thread::sleep(std::time::Duration::from_millis(100)); + } + vdd_remove_display(&self.handle, self.index); + info!("Removed display"); + } + + pub fn stop(&self) { + self.running.store(false, Ordering::SeqCst); + } +} diff --git a/src/lib.rs b/src/lib.rs index ebcb01a..31fe77e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,43 +1,52 @@ #[allow(non_snake_case)] - use std::mem::{size_of, zeroed}; use windows::{ - core::{GUID, PCSTR}, Win32::{ Devices::DeviceAndDriverInstallation::{ - CM_Get_DevNode_Status, CM_PROB_DISABLED, CM_PROB_DISABLED_SERVICE, - CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, CM_PROB_NEED_RESTART, - SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiEnumDeviceInterfaces, - SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA, - SetupDiGetDeviceRegistryPropertyA, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, - DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SPDRP_HARDWAREID, + 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, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, - INVALID_HANDLE_VALUE, ERROR_SUCCESS, + CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE, + GetLastError, HANDLE, }, 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, OVERLAPPED}, + IO::{DeviceIoControl, OVERLAPPED, GetOverlappedResultEx}, Registry::{REG_MULTI_SZ, REG_SZ}, - Threading::{CreateEventA, GetOverlappedResultEx}, + Threading::{CreateEventA}, }, }, + core::{GUID, PCSTR}, }; // 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.is_invalid() { unsafe { - SetupDiDestroyDeviceInfoList(self.0); + let _ = SetupDiDestroyDeviceInfoList(self.0); } } } @@ -76,13 +85,11 @@ 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_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_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; @@ -96,124 +103,131 @@ pub enum VddCtlCode { Version = 0x0022e010, } +// 简化的错误处理宏 +macro_rules! win_try { + ($expr:expr) => { + match $expr { + Ok(val) => val, + Err(_) => return None, + } + }; +} + /// 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, + }; + unsafe { - let dev_info = DevInfo(SetupDiGetClassDevsA( - Some(class_guid), - None, - None, - DIGCF_PRESENT, - )); + for device_index in 0.. { + let mut dev_info_data: SP_DEVINFO_DATA = zeroed(); + dev_info_data.cbSize = size_of::() as u32; - if dev_info.0.is_invalid() { - return DeviceStatus::Inaccessible; - } + if SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_err() { + break; + } - let mut dev_info_data: SP_DEVINFO_DATA = zeroed(); - dev_info_data.cbSize = size_of::() as u32; - let mut device_index = 0; - - // Loop through all devices in the class. - while SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_ok() { - let mut required_size = 0; - // First call: get the required buffer size for the hardware ID. - SetupDiGetDeviceRegistryPropertyA( - dev_info.0, - &dev_info_data, - SPDRP_HARDWAREID, - None, - None, - 0, - Some(&mut required_size), - ); - - if required_size > 0 { - let mut prop_buffer = vec![0u8; required_size as usize]; - let mut reg_data_type = 0; - - // Second call: get the actual hardware ID data. - if SetupDiGetDeviceRegistryPropertyA( - dev_info.0, - &dev_info_data, - SPDRP_HARDWAREID, - Some(&mut reg_data_type), - Some(prop_buffer.as_mut_slice()), - required_size, - None, - ) - .is_ok() - { - // The property is a list of null-terminated strings (REG_MULTI_SZ). - if reg_data_type == REG_SZ.0 || reg_data_type == REG_MULTI_SZ.0 { - // Safely parse the multi-string buffer. - let found_match = prop_buffer - .split(|&b| b == 0) // Split by null terminator - .filter(|s| !s.is_empty()) // Remove empty segments - .any(|s| s == &device_id[..device_id.len() - 1]); // Compare with non-null-terminated slice - - if found_match { - let mut dev_status = 0; - let mut dev_problem_num = 0; - - // Get the device's configuration manager status. - if CM_Get_DevNode_Status( - &mut dev_status, - &mut dev_problem_num, - dev_info_data.DevInst, - 0, - ) != 0 - { - return DeviceStatus::NotInstalled; - } - - // Map the status codes to our enum. - if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)) != 0 { - return DeviceStatus::Ok; - } else if (dev_status & DN_HAS_PROBLEM) != 0 { - return match dev_problem_num { - CM_PROB_NEED_RESTART => DeviceStatus::RestartRequired, - CM_PROB_DISABLED | CM_PROB_HARDWARE_DISABLED => { - DeviceStatus::Disabled - } - CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService, - CM_PROB_FAILED_POST_START => DeviceStatus::DriverError, - _ => DeviceStatus::UnknownProblem, - }; - } else { - return DeviceStatus::Unknown; - } - } - } + 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); } } - device_index += 1; + } + } + 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; } - // If the loop finishes without finding the device. - DeviceStatus::NotInstalled + 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; + } + + match dev_status { + s if (s & (DN_DRIVER_LOADED | DN_STARTED)).0 != 0 => DeviceStatus::Ok, + s if (s & 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, + }, + _ => 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 { - let dev_info = DevInfo(SetupDiGetClassDevsA( - Some(interface_guid), - None, - None, - DIGCF_PRESENT | DIGCF_DEVICEINTERFACE, - )); - - if dev_info.0.is_invalid() { - return None; - } - - let mut dev_interface_data: SP_DEVICE_INTERFACE_DATA = zeroed(); - dev_interface_data.cbSize = size_of::() as u32; - - // Loop through available device interfaces. 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, @@ -223,85 +237,88 @@ pub fn open_device_handle(interface_guid: &GUID) -> Option { ) .is_err() { - break; // No more interfaces. + break; } - let mut detail_size = 0; - // First call: get required size for the detail data. - SetupDiGetDeviceInterfaceDetailA( - dev_info.0, - &dev_interface_data, - None, - 0, - Some(&mut detail_size), - None, - ); - - if detail_size == 0 { - continue; - } - - // Allocate a buffer for the SP_DEVICE_INTERFACE_DETAIL_DATA_A struct. - // The struct has a flexible array member, so we allocate enough space for it. - 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; - - // Second call: get the actual detail data, which includes the device path. - if SetupDiGetDeviceInterfaceDetailA( - dev_info.0, - &dev_interface_data, - Some(detail), - detail_size, - None, - None, - ) - .is_ok() - { - // The device path is a C-style string inside the struct. - let device_path = PCSTR((*detail).DevicePath.as_ptr()); - - let handle = 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, - ); - - if handle.is_ok() { - // On success, wrap the handle in our RAII struct and return. - return Some(VddHandle(handle.unwrap())); + 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)); } } } } - // If the loop completes without finding a valid 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.is_invalid() { unsafe { - CloseHandle(handle); + 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]>, -) -> Option { +fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Option { if vdd.0.is_invalid() { return None; } @@ -314,7 +331,7 @@ fn vdd_io_control( } let mut overlapped: OVERLAPPED = zeroed(); - let event = CreateEventA(None, true, false, None).ok()?; + let event = win_try!(CreateEventA(None, true, false, None)); overlapped.hEvent = event; let mut out_buffer = 0u32; @@ -331,18 +348,18 @@ fn vdd_io_control( Some(&mut overlapped), ); - // Handle asynchronous IOCTL. - if result.is_err() && GetLastError() != ERROR_SUCCESS { - // Wait for the overlapped operation to complete with a 5-second timeout. - if GetOverlappedResultEx(vdd.0, &overlapped, &mut bytes_transferred, 5000, false) - .is_err() - { - CloseHandle(event); - return None; - } + // 简化的异步处理 + if result.is_err() && GetLastError() == ERROR_IO_PENDING { + win_try!(GetOverlappedResultEx( + vdd.0, + &overlapped, + &mut bytes_transferred, + 5000, + false + )); } - CloseHandle(event); + let _ = CloseHandle(event); Some(out_buffer) } } diff --git a/src/main.rs b/src/main.rs index eb33559..da8e29f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,114 +1,18 @@ -#[allow(non_snake_case)] +#[macro_use] +extern crate windows_service; -use parsec_vdd::{ - query_device_status, open_device_handle, vdd_version, vdd_update, - vdd_add_display, vdd_remove_display, DeviceStatus, VddHandle, - VDD_CLASS_GUID, VDD_HARDWARE_ID, VDD_ADAPTER_GUID, VDD_MAX_DISPLAYS -}; - use std::sync::atomic::{AtomicBool, Ordering}; - use std::sync::Arc; - use std::thread; - use std::time::Duration; +mod app; +use app::App; - // FFI import for _getch() to read a single character without waiting for Enter. - #[link(name = "msvcrt")] - extern "C" { - fn _getch() -> i32; - } +use std::ffi::OsString; +use windows_service::{service_dispatcher, define_windows_service}; + +define_windows_service!(ffi_service_main, service_main); + +fn service_main(_arguments: Vec) { + let _app = App::new(); +} -// Main application logic, translated from main.c fn main() { - // 1. Check driver status. - let status = query_device_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID); - - if status != DeviceStatus::Ok { - println!("Parsec VDD device is not OK, status: {:?}", status); - return; - } - println!("Parsec VDD device is OK."); - - // 2. Obtain device handle. - // The handle is wrapped in an Option and our RAII VddHandle struct. - let vdd = match open_device_handle(&VDD_ADAPTER_GUID) { - Some(handle) => Arc::new(handle), // Use Arc for sharing between threads - None => { - println!("Failed to obtain the device handle."); - return; - } - }; - println!("Successfully obtained device handle."); - if let Some(version) = vdd_version(&vdd) { - println!("Driver version: {}", version); - } - - // 3. Set up the updater thread. - let running = Arc::new(AtomicBool::new(true)); - let running_clone = Arc::clone(&running); - let vdd_clone = Arc::clone(&vdd); - - let updater_thread = thread::spawn(move || { - while running_clone.load(Ordering::SeqCst) { - vdd_update(&vdd_clone); - thread::sleep(Duration::from_millis(100)); - } - }); - - // 4. Main interaction loop. - let mut displays: Vec = Vec::new(); - println!("\nPress 'a' to add a virtual display."); - println!("Press 'r' to remove the last added display."); - println!("Press 'q' to quit (this will unplug all displays)."); - - while running.load(Ordering::SeqCst) { - // Use the imported _getch function. - let key = unsafe { _getch() as u8 as char }; - - match key { - 'q' | 'Q' => { - println!("\nQuitting..."); - running.store(false, Ordering::SeqCst); - } - 'a' | 'A' => { - if displays.len() < VDD_MAX_DISPLAYS { - if let Some(index) = vdd_add_display(&vdd) { - if index != -1 { - displays.push(index); - println!("Added a new virtual display, index: {}", index); - } else { - println!("Failed to add virtual display (driver returned -1)."); - } - } else { - println!("IOCTL call to add display failed."); - } - } else { - println!( - "Limit exceeded ({}), could not add more virtual displays.", - VDD_MAX_DISPLAYS - ); - } - } - 'r' | 'R' => { - if let Some(index) = displays.pop() { - vdd_remove_display(&vdd, index); - println!("Removed the last virtual display, index: {}", index); - } else { - println!("No added virtual displays to remove."); - } - } - _ => {} // Ignore other keys - } - } - - // 5. Cleanup. - println!("Removing all displays before exiting..."); - for index in displays { - vdd_remove_display(&vdd, index); - } - - // Wait for the updater thread to finish. - updater_thread.join().expect("Updater thread panicked"); - - println!("Cleanup complete. Exiting."); - // The VddHandle (wrapped in Arc) will be dropped automatically here, - // which calls CloseHandle, cleaning up the resource. -} \ No newline at end of file + service_dispatcher::start("parsec-vdd", ffi_service_main).unwrap(); +} diff --git a/src/service.rs b/src/service.rs deleted file mode 100644 index e51e63a..0000000 --- a/src/service.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[marco_use] -extern crate windows_service; - -use std::ffi::OsString; -use windows_service::service_dispatcher; - -define_windows_service!(ffi_service_main, service_main); - -fn service_main(arguments: Vec) { - -} \ No newline at end of file