diff --git a/.gitignore b/.gitignore index ea8c4bf..ccb5166 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +.vscode \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index eb5b2c5..b8c759b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -104,6 +104,17 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +[[package]] +name = "daemon" +version = "0.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e2112c4a5cd067d08601da91abd9de4c47cd157e8a80edf41d9382d07e9d51c" +dependencies = [ + "kernel32-sys", + "libc", + "winapi 0.3.9", +] + [[package]] name = "heck" version = "0.5.0" @@ -116,6 +127,22 @@ version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "libc" +version = "0.2.174" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" + [[package]] name = "log" version = "0.4.27" @@ -133,6 +160,7 @@ name = "parsec-vdd" version = "0.1.0" dependencies = [ "clap", + "daemon", "log", "windows", "windows-service", @@ -191,6 +219,40 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows" version = "0.61.3" diff --git a/Cargo.toml b/Cargo.toml index a862658..d7ff26b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] clap = { version = "4.5.40", features = ["derive"] } +daemon = "0.0.8" log = "0.4.27" windows-service = "0.8.0" diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..713ee76 --- /dev/null +++ b/readme.md @@ -0,0 +1,14 @@ +inspire [parsec-vdd](https://github.com/nomi-san/parsec-vdd), but I don't need any GUI, multiple monitor, and etc. +I just want a tool which can create a fallback virtual display device when my physical display device is not available. +Like what parsec do. + +So here is my first rust exercise project. + +Do expect there are some bugs and I will fix them when I have time. + + +### how to compile? +well, I wrote this shit on linux. +so compile with `cargo build --target x86_64-pc-windows-gnu --release` + +### how to use? diff --git a/src/app.rs b/src/app.rs index 7dfcd07..41c61b0 100644 --- a/src/app.rs +++ b/src/app.rs @@ -1,21 +1,17 @@ -use log::{error, info, trace}; +use log::{debug, error, info, trace}; +use parsec_vdd::{ + DeviceStatus, 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, +}; use std::sync::{ - atomic::{AtomicBool, Ordering}, - Arc, + Arc, + atomic::{AtomicBool, Ordering}, }; use windows::{ + Win32::Graphics::Gdi::{ + DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_STATE_FLAGS, DISPLAY_DEVICEW, EnumDisplayDevicesW, + }, 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 { @@ -49,15 +45,9 @@ impl App { 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 { + fn is_physical_monitor_connected() -> bool { // fetch windows monitor lists. - let mut monitors = Vec::new(); + let mut physical_monitors = Vec::new(); let mut info = DISPLAY_DEVICEW { cb: std::mem::size_of::() as u32, DeviceName: [0; 32], @@ -66,66 +56,101 @@ impl App { DeviceID: [0; 128], DeviceKey: [0; 128], }; - + let mut i = 0; - while unsafe { - EnumDisplayDevicesW( - PWSTR::null(), - i, - &mut info, - 0 - ).as_bool() - } { + 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; + debug!("Found monitor: {}", device_name); + // 排除 parsec 虚拟显示器,只统计物理显示器 + if !device_name.to_lowercase().contains("parsec") { + physical_monitors.push(device_name); } - monitors.push(device_name); } i += 1; } - trace!("Found {} monitors", monitors.len()); - for (i, monitor) in monitors.iter().enumerate() { - trace!("Monitor {}: {}", i, monitor); + trace!("Found {} physical monitors", physical_monitors.len()); + for (i, monitor) in physical_monitors.iter().enumerate() { + trace!("Physical Monitor {}: {}", i, monitor); } - - !monitors.is_empty() + + !physical_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(); + let has_physical_monitor = App::is_physical_monitor_connected(); + let vdd_is_running = self.index != -1; + + if !has_physical_monitor && !vdd_is_running { + // 没有物理显示器且虚拟显示器未运行,启动虚拟显示器 + info!("No physical monitors detected, starting virtual display"); + self.start_virtual_display(); + } else if has_physical_monitor && vdd_is_running { + // 有物理显示器且虚拟显示器正在运行,停止虚拟显示器 + info!("Physical monitor detected, stopping virtual display"); + self.stop_virtual_display(); } + + if !self.running.load(Ordering::SeqCst) { + break; + } + std::thread::sleep(std::time::Duration::from_millis(1000)); } } - pub fn start(&mut self) { + fn start_virtual_display(&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"); + error!("Failed to add virtual display"); return; } + info!("Virtual display added with index: {}", self.index); } + } + + fn stop_virtual_display(&mut self) { + if self.index != -1 { + vdd_remove_display(&self.handle, self.index); + self.index = -1; + info!("Virtual display removed"); + } + } + + pub fn start(&mut self) { self.running.store(true, Ordering::SeqCst); while self.running.load(Ordering::SeqCst) { - vdd_update(&self.handle); + if self.index != -1 { + vdd_update(&self.handle); + } + std::thread::sleep(std::time::Duration::from_millis(100)); + } + } + + pub fn keep_alive(&mut self) { + loop { + if !self.running.load(Ordering::SeqCst) { + std::thread::sleep(std::time::Duration::from_millis(1000)); + continue; + } 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); } } + + +impl Drop for App { + fn drop(&mut self) { + self.stop(); + self.stop_virtual_display(); + } +} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 31fe77e..05cc77a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ #[allow(non_snake_case)] use std::mem::{size_of, zeroed}; +use std::ptr; use windows::{ Win32::{ @@ -14,17 +15,17 @@ use windows::{ SetupDiGetDeviceRegistryPropertyA, }, Foundation::{ - CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE, - GetLastError, HANDLE, + CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE, GetLastError, 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, }, System::{ - IO::{DeviceIoControl, OVERLAPPED, GetOverlappedResultEx}, + IO::{DeviceIoControl, GetOverlappedResultEx, OVERLAPPED}, Registry::{REG_MULTI_SZ, REG_SZ}, - Threading::{CreateEventA}, + Threading::CreateEventA, }, }, core::{GUID, PCSTR}, @@ -42,9 +43,10 @@ impl DevInfo { } } } + impl Drop for DevInfo { fn drop(&mut self) { - if !self.0.is_invalid() { + if self.0 != HDEVINFO(INVALID_HANDLE_VALUE.0 as isize) { unsafe { let _ = SetupDiDestroyDeviceInfoList(self.0); } @@ -103,16 +105,16 @@ pub enum VddCtlCode { Version = 0x0022e010, } -// 简化的错误处理宏 -macro_rules! win_try { - ($expr:expr) => { - match $expr { - Ok(val) => val, - Err(_) => return None, - } - }; +// 错误处理类型 +#[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) { @@ -120,6 +122,11 @@ pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus 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(); @@ -203,9 +210,14 @@ fn get_device_status(dev_info_data: &SP_DEVINFO_DATA) -> DeviceStatus { 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 { + // 检查设备是否正常运行 + 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 @@ -213,8 +225,9 @@ fn get_device_status(dev_info_data: &SP_DEVINFO_DATA) -> DeviceStatus { n if n == CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService, n if n == CM_PROB_FAILED_POST_START => DeviceStatus::DriverError, _ => DeviceStatus::UnknownProblem, - }, - _ => DeviceStatus::Unknown, + } + } else { + DeviceStatus::Unknown } } } @@ -309,7 +322,7 @@ fn create_device_handle(device_path: &PCSTR) -> Option { /// Release the device handle. fn close_device_handle(handle: HANDLE) { - if !handle.is_invalid() { + if handle != INVALID_HANDLE_VALUE { unsafe { let _ = CloseHandle(handle); } @@ -318,9 +331,9 @@ fn close_device_handle(handle: 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 { - if vdd.0.is_invalid() { - return None; +fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> VddResult { + if vdd.0 == INVALID_HANDLE_VALUE { + return Err(VddError::InvalidHandle); } unsafe { @@ -331,7 +344,7 @@ fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Opt } let mut overlapped: OVERLAPPED = zeroed(); - let event = win_try!(CreateEventA(None, true, false, None)); + let event = CreateEventA(None, true, false, None).map_err(|_| VddError::IoError)?; overlapped.hEvent = event; let mut out_buffer = 0u32; @@ -348,43 +361,60 @@ fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Opt Some(&mut overlapped), ); - // 简化的异步处理 - if result.is_err() && GetLastError() == ERROR_IO_PENDING { - win_try!(GetOverlappedResultEx( + // 处理异步操作 + let final_result = if result.is_err() && GetLastError() == ERROR_IO_PENDING { + GetOverlappedResultEx( vdd.0, &overlapped, &mut bytes_transferred, - 5000, - false - )); - } + 5000, // 5秒超时 + false, + ) + .map_err(|_| VddError::Timeout) + } else { + result.map_err(|_| VddError::IoError) + }; let _ = CloseHandle(event); - Some(out_buffer) + + 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).map(|v| v as i32) + 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) { - vdd_io_control(vdd, VddCtlCode::Update, None); +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); + let result = vdd_io_control(vdd, VddCtlCode::Add, None).ok()?; vdd_update(vdd); // Ping immediately after adding - result.map(|v| v as i32) + Some(result as i32) } /// Remove/unplug a virtual display. -pub fn vdd_remove_display(vdd: &VddHandle, index: i32) { +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(); - vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data)); - vdd_update(vdd); // Ping immediately after removing + let result = vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data)).is_ok(); + if result { + vdd_update(vdd); // Ping immediately after removing + } + result } diff --git a/src/main.rs b/src/main.rs index da8e29f..67cf220 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,18 +1,10 @@ -#[macro_use] extern crate windows_service; mod app; use app::App; -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(); -} - fn main() { - service_dispatcher::start("parsec-vdd", ffi_service_main).unwrap(); + let mut app = App::new(); + app.watch_monitors(); } +