refactor: restructure codebase with better separation of concerns
parent
8cac59d8bb
commit
4269ddb312
|
@ -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
|
||||
# strip = true
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
mod service;
|
||||
|
||||
pub use service::VddService;
|
|
@ -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<VddHandle>,
|
||||
device_controller: DeviceController,
|
||||
display_manager: DisplayManager,
|
||||
config: Config,
|
||||
running: Arc<AtomicBool>,
|
||||
}
|
||||
|
||||
impl VddService {
|
||||
pub fn new() -> Result<Self> {
|
||||
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();
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<i32> {
|
||||
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<i32> {
|
||||
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<u32> {
|
||||
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::<u32>() 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(),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<Self> {
|
||||
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<Self> {
|
||||
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<VddHandle> {
|
||||
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::<SP_DEVICE_INTERFACE_DATA>() 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<PCSTR> {
|
||||
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::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>() 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<HANDLE> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
mod handle;
|
||||
mod status;
|
||||
mod control;
|
||||
|
||||
pub use handle::VddHandle;
|
||||
pub use status::DeviceStatusChecker;
|
||||
pub use control::DeviceController;
|
|
@ -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<Self> {
|
||||
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::<SP_DEVINFO_DATA>() 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<Vec<Vec<u8>>> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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<VirtualDisplay>,
|
||||
}
|
||||
|
||||
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(())
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
mod virtual_display;
|
||||
mod manager;
|
||||
|
||||
pub use virtual_display::{VirtualDisplay, VirtualDisplayController};
|
||||
pub use manager::DisplayManager;
|
|
@ -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<VirtualDisplay> {
|
||||
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())
|
||||
}
|
||||
}
|
|
@ -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<T> = std::result::Result<T, VddError>;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
|
||||
#[repr(i32)]
|
||||
pub enum DeviceStatus {
|
||||
Ok = 0,
|
||||
Inaccessible,
|
||||
Unknown,
|
||||
UnknownProblem,
|
||||
Disabled,
|
||||
DriverError,
|
||||
RestartRequired,
|
||||
DisabledService,
|
||||
NotInstalled,
|
||||
}
|
469
src/lib.rs
469
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<Self> {
|
||||
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<T> = Result<T, VddError>;
|
||||
/// 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::<SP_DEVINFO_DATA>() 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<Vec<Vec<u8>>> {
|
||||
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<VddHandle> {
|
||||
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::<SP_DEVICE_INTERFACE_DATA>() 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<PCSTR> {
|
||||
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::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>() 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<HANDLE> {
|
||||
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<u32> {
|
||||
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::<u32>() 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<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) -> 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<i32> {
|
||||
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::<DISPLAY_DEVICEW>() as u32,
|
||||
DeviceName: [0; 32],
|
||||
DeviceString: [0; 128],
|
||||
StateFlags: DISPLAY_DEVICE_STATE_FLAGS(0),
|
||||
DeviceID: [0; 128],
|
||||
DeviceKey: [0; 128],
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
while unsafe { EnumDisplayDevicesW(PWSTR::null(), i, &mut info, 0).as_bool() } {
|
||||
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;
|
||||
|
|
57
src/main.rs
57
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> {
|
||||
let mut physical_monitors = Vec::new();
|
||||
let mut info = DISPLAY_DEVICEW {
|
||||
cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,
|
||||
DeviceName: [0; 32],
|
||||
DeviceString: [0; 128],
|
||||
StateFlags: DISPLAY_DEVICE_STATE_FLAGS(0),
|
||||
DeviceID: [0; 128],
|
||||
DeviceKey: [0; 128],
|
||||
};
|
||||
|
||||
let mut i = 0;
|
||||
while unsafe { EnumDisplayDevicesW(PWSTR::null(), i, &mut info, 0).as_bool() } {
|
||||
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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
mod detector;
|
||||
|
||||
pub use detector::MonitorDetector;
|
Loading…
Reference in New Issue