Compare commits
No commits in common. "886e6b3770a475262b0858fe7ecf115f79de2f2d" and "91e9cb05c2d0234657a1363cd56a3ff27429099c" have entirely different histories.
886e6b3770
...
91e9cb05c2
|
@ -1,2 +1 @@
|
||||||
/target
|
/target
|
||||||
.vscode
|
|
|
@ -2,18 +2,140 @@
|
||||||
# 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]]
|
||||||
|
name = "bitflags"
|
||||||
|
version = "2.9.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap"
|
||||||
|
version = "4.5.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "40b6887a1d8685cebccf115538db5c0efe625ccac9696ad45c409d96566e910f"
|
||||||
|
dependencies = [
|
||||||
|
"clap_builder",
|
||||||
|
"clap_derive",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_builder"
|
||||||
|
version = "4.5.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0c66c08ce9f0c698cbce5c0279d0bb6ac936d8674174fe48f736533b964f59e"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"clap_lex",
|
||||||
|
"strsim",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_derive"
|
||||||
|
version = "4.5.40"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d2c7947ae4cc3d851207c1adb5b5e260ff0cca11446b1d6d1423788e442257ce"
|
||||||
|
dependencies = [
|
||||||
|
"heck",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "clap_lex"
|
||||||
|
version = "0.7.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b94f61472cee1439c0b966b47e3aca9ae07e45d070759512cd390ea2bebc6675"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "heck"
|
||||||
|
version = "0.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.27"
|
version = "0.4.27"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
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",
|
"log",
|
||||||
"windows",
|
"windows",
|
||||||
|
"windows-service",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -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,18 @@ 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]]
|
||||||
|
name = "widestring"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows"
|
name = "windows"
|
||||||
version = "0.61.3"
|
version = "0.61.3"
|
||||||
|
@ -144,6 +284,17 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-service"
|
||||||
|
version = "0.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "193cae8e647981c35bc947fdd57ba7928b1fa0d4a79305f6dd2dc55221ac35ac"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"widestring",
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-strings"
|
name = "windows-strings"
|
||||||
version = "0.4.2"
|
version = "0.4.2"
|
||||||
|
@ -153,6 +304,31 @@ dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-sys"
|
||||||
|
version = "0.59.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||||
|
dependencies = [
|
||||||
|
"windows-targets",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-targets"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||||
|
dependencies = [
|
||||||
|
"windows_aarch64_gnullvm",
|
||||||
|
"windows_aarch64_msvc",
|
||||||
|
"windows_i686_gnu",
|
||||||
|
"windows_i686_gnullvm",
|
||||||
|
"windows_i686_msvc",
|
||||||
|
"windows_x86_64_gnu",
|
||||||
|
"windows_x86_64_gnullvm",
|
||||||
|
"windows_x86_64_msvc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "windows-threading"
|
name = "windows-threading"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -161,3 +337,51 @@ checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-link",
|
"windows-link",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_aarch64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_i686_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnu"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_gnullvm"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows_x86_64_msvc"
|
||||||
|
version = "0.52.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||||
|
|
|
@ -4,7 +4,9 @@ version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
clap = { version = "4.5.40", features = ["derive"] }
|
||||||
log = "0.4.27"
|
log = "0.4.27"
|
||||||
|
windows-service = "0.8.0"
|
||||||
|
|
||||||
[dependencies.windows]
|
[dependencies.windows]
|
||||||
version = "0.61.3"
|
version = "0.61.3"
|
||||||
|
|
14
readme.md
14
readme.md
|
@ -1,14 +0,0 @@
|
||||||
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?
|
|
118
src/app.rs
118
src/app.rs
|
@ -1,16 +1,21 @@
|
||||||
use log::{debug, error, info, trace};
|
use log::{error, info, trace};
|
||||||
use parsec_vdd::{
|
|
||||||
open_device_handle, query_device_status, vdd_add_display, vdd_remove_display, vdd_update, vdd_version, DeviceStatus, VddHandle, VDD_ADAPTER_GUID, VDD_CLASS_GUID, VDD_HARDWARE_ID
|
|
||||||
};
|
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
Arc,
|
atomic::{AtomicBool, Ordering},
|
||||||
atomic::{AtomicBool, Ordering},
|
Arc,
|
||||||
};
|
};
|
||||||
use windows::{
|
use windows::{
|
||||||
Win32::Graphics::Gdi::{
|
|
||||||
DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_STATE_FLAGS, DISPLAY_DEVICEW, EnumDisplayDevicesW,
|
|
||||||
},
|
|
||||||
core::PWSTR,
|
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 {
|
pub struct App {
|
||||||
|
@ -44,9 +49,15 @@ impl App {
|
||||||
app
|
app
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_physical_monitor_connected() -> bool {
|
// 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.
|
// fetch windows monitor lists.
|
||||||
let mut physical_monitors = Vec::new();
|
let mut monitors = Vec::new();
|
||||||
let mut info = DISPLAY_DEVICEW {
|
let mut info = DISPLAY_DEVICEW {
|
||||||
cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,
|
cb: std::mem::size_of::<DISPLAY_DEVICEW>() as u32,
|
||||||
DeviceName: [0; 32],
|
DeviceName: [0; 32],
|
||||||
|
@ -57,99 +68,64 @@ impl App {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut i = 0;
|
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 {
|
if (info.StateFlags & DISPLAY_DEVICE_ACTIVE).0 != 0 {
|
||||||
let device_name = String::from_utf16_lossy(&info.DeviceName)
|
let device_name = String::from_utf16_lossy(&info.DeviceName)
|
||||||
.trim_end_matches('\0')
|
.trim_end_matches('\0')
|
||||||
.to_string();
|
.to_string();
|
||||||
debug!("Found monitor: {}", device_name);
|
if device_name.to_lowercase().contains("parsec") {
|
||||||
// 排除 parsec 虚拟显示器,只统计物理显示器
|
i += 1;
|
||||||
if !device_name.to_lowercase().contains("parsec") {
|
continue;
|
||||||
physical_monitors.push(device_name);
|
|
||||||
}
|
}
|
||||||
|
monitors.push(device_name);
|
||||||
}
|
}
|
||||||
i += 1;
|
i += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
trace!("Found {} physical monitors", physical_monitors.len());
|
trace!("Found {} monitors", monitors.len());
|
||||||
for (i, monitor) in physical_monitors.iter().enumerate() {
|
for (i, monitor) in monitors.iter().enumerate() {
|
||||||
trace!("Physical Monitor {}: {}", i, monitor);
|
trace!("Monitor {}: {}", i, monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
!physical_monitors.is_empty()
|
!monitors.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn watch_monitors(&mut self) {
|
pub fn watch_monitors(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
let has_physical_monitor = App::is_physical_monitor_connected();
|
if !self.if_monitor_connected() && !self.running.load(Ordering::SeqCst) {
|
||||||
let vdd_is_running = self.index != -1;
|
self.start();
|
||||||
|
|
||||||
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;
|
break;
|
||||||
|
} else {
|
||||||
|
self.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::thread::sleep(std::time::Duration::from_millis(1000));
|
std::thread::sleep(std::time::Duration::from_millis(1000));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start_virtual_display(&mut self) {
|
pub fn start(&mut self) {
|
||||||
if self.index == -1 {
|
if self.index == -1 {
|
||||||
self.index = vdd_add_display(&self.handle).unwrap_or(-1);
|
self.index = vdd_add_display(&self.handle).unwrap_or(-1);
|
||||||
if self.index == -1 {
|
if self.index == -1 {
|
||||||
error!("Failed to add virtual display");
|
error!("Failed to add display");
|
||||||
return;
|
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);
|
self.running.store(true, Ordering::SeqCst);
|
||||||
while self.running.load(Ordering::SeqCst) {
|
while self.running.load(Ordering::SeqCst) {
|
||||||
if self.index != -1 {
|
vdd_update(&self.handle);
|
||||||
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));
|
std::thread::sleep(std::time::Duration::from_millis(100));
|
||||||
}
|
}
|
||||||
|
vdd_remove_display(&self.handle, self.index);
|
||||||
|
info!("Removed display");
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(&self) {
|
pub fn stop(&self) {
|
||||||
self.running.store(false, Ordering::SeqCst);
|
self.running.store(false, Ordering::SeqCst);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Drop for App {
|
|
||||||
fn drop(&mut self) {
|
|
||||||
self.stop();
|
|
||||||
self.stop_virtual_display();
|
|
||||||
}
|
|
||||||
}
|
|
107
src/lib.rs
107
src/lib.rs
|
@ -14,17 +14,17 @@ use windows::{
|
||||||
SetupDiGetDeviceRegistryPropertyA,
|
SetupDiGetDeviceRegistryPropertyA,
|
||||||
},
|
},
|
||||||
Foundation::{
|
Foundation::{
|
||||||
CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE, GetLastError, HANDLE,
|
CloseHandle, ERROR_IO_PENDING, GENERIC_READ, GENERIC_WRITE,
|
||||||
INVALID_HANDLE_VALUE,
|
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, GetOverlappedResultEx, OVERLAPPED},
|
IO::{DeviceIoControl, OVERLAPPED, GetOverlappedResultEx},
|
||||||
Registry::{REG_MULTI_SZ, REG_SZ},
|
Registry::{REG_MULTI_SZ, REG_SZ},
|
||||||
Threading::CreateEventA,
|
Threading::{CreateEventA},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
core::{GUID, PCSTR},
|
core::{GUID, PCSTR},
|
||||||
|
@ -42,10 +42,9 @@ impl DevInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for DevInfo {
|
impl Drop for DevInfo {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
if self.0 != HDEVINFO(INVALID_HANDLE_VALUE.0 as isize) {
|
if !self.0.is_invalid() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = SetupDiDestroyDeviceInfoList(self.0);
|
let _ = SetupDiDestroyDeviceInfoList(self.0);
|
||||||
}
|
}
|
||||||
|
@ -104,16 +103,16 @@ pub enum VddCtlCode {
|
||||||
Version = 0x0022e010,
|
Version = 0x0022e010,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 错误处理类型
|
// 简化的错误处理宏
|
||||||
#[derive(Debug)]
|
macro_rules! win_try {
|
||||||
pub enum VddError {
|
($expr:expr) => {
|
||||||
DeviceNotFound,
|
match $expr {
|
||||||
InvalidHandle,
|
Ok(val) => val,
|
||||||
IoError,
|
Err(_) => return None,
|
||||||
Timeout,
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
type VddResult<T> = Result<T, VddError>;
|
|
||||||
/// 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) {
|
let dev_info = match DevInfo::new(Some(class_guid), DIGCF_PRESENT) {
|
||||||
|
@ -121,11 +120,6 @@ pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus
|
||||||
None => return DeviceStatus::Inaccessible,
|
None => return DeviceStatus::Inaccessible,
|
||||||
};
|
};
|
||||||
|
|
||||||
// 确保 device_id 以 null 结尾
|
|
||||||
if device_id.is_empty() || device_id[device_id.len() - 1] != 0 {
|
|
||||||
return DeviceStatus::Unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
for device_index in 0.. {
|
for device_index in 0.. {
|
||||||
let mut dev_info_data: SP_DEVINFO_DATA = zeroed();
|
let mut dev_info_data: SP_DEVINFO_DATA = zeroed();
|
||||||
|
@ -209,14 +203,9 @@ fn get_device_status(dev_info_data: &SP_DEVINFO_DATA) -> DeviceStatus {
|
||||||
return DeviceStatus::NotInstalled;
|
return DeviceStatus::NotInstalled;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查设备是否正常运行
|
match dev_status {
|
||||||
if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)).0 == (DN_DRIVER_LOADED | DN_STARTED).0 {
|
s if (s & (DN_DRIVER_LOADED | DN_STARTED)).0 != 0 => DeviceStatus::Ok,
|
||||||
return DeviceStatus::Ok;
|
s if (s & DN_HAS_PROBLEM).0 != 0 => match dev_problem_num {
|
||||||
}
|
|
||||||
|
|
||||||
// 检查是否有问题
|
|
||||||
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_NEED_RESTART => DeviceStatus::RestartRequired,
|
||||||
n if n == CM_PROB_DISABLED || n == CM_PROB_HARDWARE_DISABLED => {
|
n if n == CM_PROB_DISABLED || n == CM_PROB_HARDWARE_DISABLED => {
|
||||||
DeviceStatus::Disabled
|
DeviceStatus::Disabled
|
||||||
|
@ -224,9 +213,8 @@ 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_DISABLED_SERVICE => DeviceStatus::DisabledService,
|
||||||
n if n == CM_PROB_FAILED_POST_START => DeviceStatus::DriverError,
|
n if n == CM_PROB_FAILED_POST_START => DeviceStatus::DriverError,
|
||||||
_ => DeviceStatus::UnknownProblem,
|
_ => DeviceStatus::UnknownProblem,
|
||||||
}
|
},
|
||||||
} else {
|
_ => DeviceStatus::Unknown,
|
||||||
DeviceStatus::Unknown
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -321,7 +309,7 @@ fn create_device_handle(device_path: &PCSTR) -> Option<HANDLE> {
|
||||||
|
|
||||||
/// Release the device handle.
|
/// Release the device handle.
|
||||||
fn close_device_handle(handle: HANDLE) {
|
fn close_device_handle(handle: HANDLE) {
|
||||||
if handle != INVALID_HANDLE_VALUE {
|
if !handle.is_invalid() {
|
||||||
unsafe {
|
unsafe {
|
||||||
let _ = CloseHandle(handle);
|
let _ = CloseHandle(handle);
|
||||||
}
|
}
|
||||||
|
@ -330,9 +318,9 @@ fn close_device_handle(handle: 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(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> VddResult<u32> {
|
fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Option<u32> {
|
||||||
if vdd.0 == INVALID_HANDLE_VALUE {
|
if vdd.0.is_invalid() {
|
||||||
return Err(VddError::InvalidHandle);
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -343,7 +331,7 @@ fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Vdd
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut overlapped: OVERLAPPED = zeroed();
|
let mut overlapped: OVERLAPPED = zeroed();
|
||||||
let event = CreateEventA(None, true, false, None).map_err(|_| VddError::IoError)?;
|
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;
|
||||||
|
@ -360,60 +348,43 @@ fn vdd_io_control(vdd: &VddHandle, code: VddCtlCode, data: Option<&[u8]>) -> Vdd
|
||||||
Some(&mut overlapped),
|
Some(&mut overlapped),
|
||||||
);
|
);
|
||||||
|
|
||||||
// 处理异步操作
|
// 简化的异步处理
|
||||||
let final_result = if result.is_err() && GetLastError() == ERROR_IO_PENDING {
|
if result.is_err() && GetLastError() == ERROR_IO_PENDING {
|
||||||
GetOverlappedResultEx(
|
win_try!(GetOverlappedResultEx(
|
||||||
vdd.0,
|
vdd.0,
|
||||||
&overlapped,
|
&overlapped,
|
||||||
&mut bytes_transferred,
|
&mut bytes_transferred,
|
||||||
5000, // 5秒超时
|
5000,
|
||||||
false,
|
false
|
||||||
)
|
));
|
||||||
.map_err(|_| VddError::Timeout)
|
}
|
||||||
} else {
|
|
||||||
result.map_err(|_| VddError::IoError)
|
|
||||||
};
|
|
||||||
|
|
||||||
let _ = CloseHandle(event);
|
let _ = CloseHandle(event);
|
||||||
|
Some(out_buffer)
|
||||||
match final_result {
|
|
||||||
Ok(_) => Ok(out_buffer),
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Query VDD minor version.
|
/// Query VDD minor version.
|
||||||
pub fn vdd_version(vdd: &VddHandle) -> Option<i32> {
|
pub fn vdd_version(vdd: &VddHandle) -> Option<i32> {
|
||||||
vdd_io_control(vdd, VddCtlCode::Version, None)
|
vdd_io_control(vdd, VddCtlCode::Version, None).map(|v| v as i32)
|
||||||
.ok()
|
|
||||||
.map(|v| v as i32)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Update/ping to VDD to keep displays alive.
|
/// Update/ping to VDD to keep displays alive.
|
||||||
pub fn vdd_update(vdd: &VddHandle) -> bool {
|
pub fn vdd_update(vdd: &VddHandle) {
|
||||||
vdd_io_control(vdd, VddCtlCode::Update, None).is_ok()
|
vdd_io_control(vdd, VddCtlCode::Update, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Add/plug a virtual display. Returns the index of the added display.
|
/// Add/plug a virtual display. Returns the index of the added display.
|
||||||
pub fn vdd_add_display(vdd: &VddHandle) -> Option<i32> {
|
pub fn vdd_add_display(vdd: &VddHandle) -> Option<i32> {
|
||||||
let result = vdd_io_control(vdd, VddCtlCode::Add, None).ok()?;
|
let result = vdd_io_control(vdd, VddCtlCode::Add, None);
|
||||||
vdd_update(vdd); // Ping immediately after adding
|
vdd_update(vdd); // Ping immediately after adding
|
||||||
Some(result as i32)
|
result.map(|v| v as i32)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove/unplug a virtual display.
|
/// Remove/unplug a virtual display.
|
||||||
pub fn vdd_remove_display(vdd: &VddHandle, index: i32) -> bool {
|
pub fn vdd_remove_display(vdd: &VddHandle, index: i32) {
|
||||||
// 验证索引范围
|
|
||||||
if index < 0 || index >= VDD_MAX_DISPLAYS as i32 {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// The driver expects the index as a 16-bit big-endian integer.
|
// The driver expects the index as a 16-bit big-endian integer.
|
||||||
let index_data = (index as u16).to_be_bytes();
|
let index_data = (index as u16).to_be_bytes();
|
||||||
let result = vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data)).is_ok();
|
vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data));
|
||||||
if result {
|
vdd_update(vdd); // Ping immediately after removing
|
||||||
vdd_update(vdd); // Ping immediately after removing
|
|
||||||
}
|
|
||||||
result
|
|
||||||
}
|
}
|
||||||
|
|
16
src/main.rs
16
src/main.rs
|
@ -1,8 +1,18 @@
|
||||||
|
#[macro_use]
|
||||||
|
extern crate windows_service;
|
||||||
|
|
||||||
mod app;
|
mod app;
|
||||||
use app::App;
|
use app::App;
|
||||||
|
|
||||||
fn main() {
|
use std::ffi::OsString;
|
||||||
let mut app = App::new();
|
use windows_service::{service_dispatcher, define_windows_service};
|
||||||
app.watch_monitors();
|
|
||||||
|
define_windows_service!(ffi_service_main, service_main);
|
||||||
|
|
||||||
|
fn service_main(_arguments: Vec<OsString>) {
|
||||||
|
let _app = App::new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
service_dispatcher::start("parsec-vdd", ffi_service_main).unwrap();
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue