init
parent
df591cc371
commit
91e9cb05c2
|
@ -2,16 +2,138 @@
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
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]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
version = "2.9.1"
|
version = "2.9.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
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]]
|
[[package]]
|
||||||
name = "parsec-vdd"
|
name = "parsec-vdd"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"clap",
|
||||||
|
"log",
|
||||||
"windows",
|
"windows",
|
||||||
"windows-service",
|
"windows-service",
|
||||||
]
|
]
|
||||||
|
@ -34,6 +156,12 @@ dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "strsim"
|
||||||
|
version = "0.11.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.103"
|
version = "2.0.103"
|
||||||
|
@ -51,6 +179,12 @@ version = "1.0.18"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "widestring"
|
name = "widestring"
|
||||||
version = "1.2.0"
|
version = "1.2.0"
|
||||||
|
|
|
@ -4,6 +4,8 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.40", features = ["derive"] }
|
||||||
|
log = "0.4.27"
|
||||||
windows-service = "0.8.0"
|
windows-service = "0.8.0"
|
||||||
|
|
||||||
[dependencies.windows]
|
[dependencies.windows]
|
||||||
|
@ -15,6 +17,6 @@ features = [
|
||||||
"Win32_Storage_FileSystem",
|
"Win32_Storage_FileSystem",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
"Win32_System_Registry",
|
"Win32_System_Registry",
|
||||||
"Win32_System_SystemServices",
|
"Win32_Graphics_Gdi",
|
||||||
"Win32_System_Diagnostics_Debug",
|
"Win32_Security",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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<VddHandle>,
|
||||||
|
version: i32,
|
||||||
|
running: Arc<AtomicBool>,
|
||||||
|
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::<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();
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
397
src/lib.rs
397
src/lib.rs
|
@ -1,43 +1,52 @@
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
|
||||||
use std::mem::{size_of, zeroed};
|
use std::mem::{size_of, zeroed};
|
||||||
|
|
||||||
use windows::{
|
use windows::{
|
||||||
core::{GUID, PCSTR},
|
|
||||||
Win32::{
|
Win32::{
|
||||||
Devices::DeviceAndDriverInstallation::{
|
Devices::DeviceAndDriverInstallation::{
|
||||||
CM_Get_DevNode_Status, CM_PROB_DISABLED, CM_PROB_DISABLED_SERVICE,
|
CM_DEVNODE_STATUS_FLAGS, CM_Get_DevNode_Status, CM_PROB, CM_PROB_DISABLED,
|
||||||
CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, CM_PROB_NEED_RESTART,
|
CM_PROB_DISABLED_SERVICE, CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED,
|
||||||
SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiEnumDeviceInterfaces,
|
CM_PROB_NEED_RESTART, CONFIGRET, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
|
||||||
SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA,
|
DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SETUP_DI_GET_CLASS_DEVS_FLAGS,
|
||||||
SetupDiGetDeviceRegistryPropertyA, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT,
|
|
||||||
DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SPDRP_HARDWAREID,
|
|
||||||
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, SP_DEVINFO_DATA,
|
SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, SP_DEVINFO_DATA,
|
||||||
|
SPDRP_HARDWAREID, SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo,
|
||||||
|
SetupDiEnumDeviceInterfaces, SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA,
|
||||||
|
SetupDiGetDeviceRegistryPropertyA,
|
||||||
},
|
},
|
||||||
Foundation::{
|
Foundation::{
|
||||||
CloseHandle, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE,
|
CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE,
|
||||||
INVALID_HANDLE_VALUE, ERROR_SUCCESS,
|
GetLastError, HANDLE,
|
||||||
},
|
},
|
||||||
Storage::FileSystem::{
|
Storage::FileSystem::{
|
||||||
CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED,
|
CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED,
|
||||||
FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
|
FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING,
|
||||||
},
|
},
|
||||||
System::{
|
System::{
|
||||||
IO::{DeviceIoControl, OVERLAPPED},
|
IO::{DeviceIoControl, OVERLAPPED, GetOverlappedResultEx},
|
||||||
Registry::{REG_MULTI_SZ, REG_SZ},
|
Registry::{REG_MULTI_SZ, REG_SZ},
|
||||||
Threading::{CreateEventA, GetOverlappedResultEx},
|
Threading::{CreateEventA},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
core::{GUID, PCSTR},
|
||||||
};
|
};
|
||||||
|
|
||||||
// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called.
|
// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called.
|
||||||
struct DevInfo(HDEVINFO);
|
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 {
|
impl Drop for DevInfo {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if !self.0.is_invalid() {
|
if !self.0.is_invalid() {
|
||||||
unsafe {
|
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";
|
pub const VDD_DISPLAY_NAME: &[u8] = b"ParsecVDA\0";
|
||||||
|
|
||||||
// {00b41627-04c4-429e-a26e-0265cf50c8fa}
|
// {00b41627-04c4-429e-a26e-0265cf50c8fa}
|
||||||
pub const VDD_ADAPTER_GUID: GUID =
|
pub const VDD_ADAPTER_GUID: GUID = GUID::from_u128(0x00b41627_04c4_429e_a26e_0265cf50c8fa);
|
||||||
GUID::from_u128(0x00b41627_04c4_429e_a26e_0265cf50c8fa);
|
|
||||||
pub const VDD_ADAPTER_NAME: &[u8] = b"Parsec Virtual Display Adapter\0";
|
pub const VDD_ADAPTER_NAME: &[u8] = b"Parsec Virtual Display Adapter\0";
|
||||||
|
|
||||||
// {4d36e968-e325-11ce-bfc1-08002be10318}
|
// {4d36e968-e325-11ce-bfc1-08002be10318}
|
||||||
pub const VDD_CLASS_GUID: GUID =
|
pub const VDD_CLASS_GUID: GUID = GUID::from_u128(0x4d36e968_e325_11ce_bfc1_08002be10318);
|
||||||
GUID::from_u128(0x4d36e968_e325_11ce_bfc1_08002be10318);
|
|
||||||
pub const VDD_HARDWARE_ID: &[u8] = b"Root\\Parsec\\VDA\0";
|
pub const VDD_HARDWARE_ID: &[u8] = b"Root\\Parsec\\VDA\0";
|
||||||
|
|
||||||
pub const VDD_MAX_DISPLAYS: usize = 8;
|
pub const VDD_MAX_DISPLAYS: usize = 8;
|
||||||
|
@ -96,124 +103,131 @@ pub enum VddCtlCode {
|
||||||
Version = 0x0022e010,
|
Version = 0x0022e010,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 简化的错误处理宏
|
||||||
|
macro_rules! win_try {
|
||||||
|
($expr:expr) => {
|
||||||
|
match $expr {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => return None,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// Query the driver status.
|
/// Query the driver status.
|
||||||
pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus {
|
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 {
|
unsafe {
|
||||||
let dev_info = DevInfo(SetupDiGetClassDevsA(
|
for device_index in 0.. {
|
||||||
Some(class_guid),
|
let mut dev_info_data: SP_DEVINFO_DATA = zeroed();
|
||||||
None,
|
dev_info_data.cbSize = size_of::<SP_DEVINFO_DATA>() as u32;
|
||||||
None,
|
|
||||||
DIGCF_PRESENT,
|
|
||||||
));
|
|
||||||
|
|
||||||
if dev_info.0.is_invalid() {
|
if SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_err() {
|
||||||
return DeviceStatus::Inaccessible;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dev_info_data: SP_DEVINFO_DATA = zeroed();
|
if let Some(hardware_ids) = get_device_hardware_ids(&dev_info, &dev_info_data) {
|
||||||
dev_info_data.cbSize = size_of::<SP_DEVINFO_DATA>() as u32;
|
let target_id = &device_id[..device_id.len() - 1];
|
||||||
let mut device_index = 0;
|
if hardware_ids.iter().any(|id| id.as_slice() == target_id) {
|
||||||
|
return get_device_status(&dev_info_data);
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
device_index += 1;
|
}
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the loop finishes without finding the device.
|
let mut prop_buffer = vec![0u8; required_size as usize];
|
||||||
DeviceStatus::NotInstalled
|
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.
|
/// Obtain the device handle. Returns None if it fails.
|
||||||
pub fn open_device_handle(interface_guid: &GUID) -> Option<VddHandle> {
|
pub fn open_device_handle(interface_guid: &GUID) -> Option<VddHandle> {
|
||||||
|
let dev_info = DevInfo::new(Some(interface_guid), DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)?;
|
||||||
|
|
||||||
unsafe {
|
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::<SP_DEVICE_INTERFACE_DATA>() as u32;
|
|
||||||
|
|
||||||
// Loop through available device interfaces.
|
|
||||||
for i in 0.. {
|
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(
|
if SetupDiEnumDeviceInterfaces(
|
||||||
dev_info.0,
|
dev_info.0,
|
||||||
None,
|
None,
|
||||||
|
@ -223,85 +237,88 @@ pub fn open_device_handle(interface_guid: &GUID) -> Option<VddHandle> {
|
||||||
)
|
)
|
||||||
.is_err()
|
.is_err()
|
||||||
{
|
{
|
||||||
break; // No more interfaces.
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut detail_size = 0;
|
if let Some(device_path) = get_device_path(&dev_info, &dev_interface_data) {
|
||||||
// First call: get required size for the detail data.
|
if let Some(handle) = create_device_handle(&device_path) {
|
||||||
SetupDiGetDeviceInterfaceDetailA(
|
return Some(VddHandle(handle));
|
||||||
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::<SP_DEVICE_INTERFACE_DETAIL_DATA_A>() 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 the loop completes without finding a valid handle.
|
|
||||||
None
|
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.
|
/// Release the device handle.
|
||||||
fn close_device_handle(handle: HANDLE) {
|
fn close_device_handle(handle: HANDLE) {
|
||||||
if !handle.is_invalid() {
|
if !handle.is_invalid() {
|
||||||
unsafe {
|
unsafe {
|
||||||
CloseHandle(handle);
|
let _ = CloseHandle(handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generic DeviceIoControl for all IoControl codes.
|
/// Generic DeviceIoControl for all IoControl codes.
|
||||||
/// Returns the output buffer value from the driver, or None on failure.
|
/// Returns the output buffer value from the driver, or None on failure.
|
||||||
fn vdd_io_control(
|
fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Option<u32> {
|
||||||
vdd: &VddHandle,
|
|
||||||
code: VddCtlCode,
|
|
||||||
data: Option<&[u8]>,
|
|
||||||
) -> Option<u32> {
|
|
||||||
if vdd.0.is_invalid() {
|
if vdd.0.is_invalid() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
@ -314,7 +331,7 @@ fn vdd_io_control(
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut overlapped: OVERLAPPED = zeroed();
|
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;
|
overlapped.hEvent = event;
|
||||||
|
|
||||||
let mut out_buffer = 0u32;
|
let mut out_buffer = 0u32;
|
||||||
|
@ -331,18 +348,18 @@ fn vdd_io_control(
|
||||||
Some(&mut overlapped),
|
Some(&mut overlapped),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Handle asynchronous IOCTL.
|
// 简化的异步处理
|
||||||
if result.is_err() && GetLastError() != ERROR_SUCCESS {
|
if result.is_err() && GetLastError() == ERROR_IO_PENDING {
|
||||||
// Wait for the overlapped operation to complete with a 5-second timeout.
|
win_try!(GetOverlappedResultEx(
|
||||||
if GetOverlappedResultEx(vdd.0, &overlapped, &mut bytes_transferred, 5000, false)
|
vdd.0,
|
||||||
.is_err()
|
&overlapped,
|
||||||
{
|
&mut bytes_transferred,
|
||||||
CloseHandle(event);
|
5000,
|
||||||
return None;
|
false
|
||||||
}
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
CloseHandle(event);
|
let _ = CloseHandle(event);
|
||||||
Some(out_buffer)
|
Some(out_buffer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
124
src/main.rs
124
src/main.rs
|
@ -1,114 +1,18 @@
|
||||||
#[allow(non_snake_case)]
|
#[macro_use]
|
||||||
|
extern crate windows_service;
|
||||||
|
|
||||||
use parsec_vdd::{
|
mod app;
|
||||||
query_device_status, open_device_handle, vdd_version, vdd_update,
|
use app::App;
|
||||||
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;
|
|
||||||
|
|
||||||
// FFI import for _getch() to read a single character without waiting for Enter.
|
use std::ffi::OsString;
|
||||||
#[link(name = "msvcrt")]
|
use windows_service::{service_dispatcher, define_windows_service};
|
||||||
extern "C" {
|
|
||||||
fn _getch() -> i32;
|
define_windows_service!(ffi_service_main, service_main);
|
||||||
}
|
|
||||||
|
fn service_main(_arguments: Vec<OsString>) {
|
||||||
|
let _app = App::new();
|
||||||
|
}
|
||||||
|
|
||||||
// Main application logic, translated from main.c
|
|
||||||
fn main() {
|
fn main() {
|
||||||
// 1. Check driver status.
|
service_dispatcher::start("parsec-vdd", ffi_service_main).unwrap();
|
||||||
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<i32> = 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.
|
|
||||||
}
|
|
||||||
|
|
|
@ -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<OsString>) {
|
|
||||||
|
|
||||||
}
|
|
Loading…
Reference in New Issue