From df591cc371d42e012e270f3b52c3a162bfdfc2df Mon Sep 17 00:00:00 2001 From: maye Date: Tue, 17 Jun 2025 11:50:10 +0800 Subject: [PATCH] init --- .gitignore | 1 + Cargo.lock | 253 +++++++++++++++++++++++++++++++++ Cargo.toml | 20 +++ src/lib.rs | 373 +++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 114 +++++++++++++++ src/service.rs | 11 ++ 6 files changed, 772 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 src/lib.rs create mode 100644 src/main.rs create mode 100644 src/service.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/target diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..93f45d9 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,253 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "bitflags" +version = "2.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" + +[[package]] +name = "parsec-vdd" +version = "0.1.0" +dependencies = [ + "windows", + "windows-service", +] + +[[package]] +name = "proc-macro2" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4307e30089d6fd6aff212f2da3a1f9e32f3223b1f010fb09b7c95f90f3ca1e8" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" + +[[package]] +name = "widestring" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd7cf3379ca1aac9eea11fba24fd7e315d621f8dfe35c8d7d2be8b793726e07d" + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core", + "windows-future", + "windows-link", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core", + "windows-link", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a47fddd13af08290e67f4acabf4b459f647552718f683a7b415d290ac744a836" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-interface" +version = "0.59.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9211b69f8dcdfa817bfd14bf1c97c9188afa36f4750130fcdf3f400eca9fa8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core", + "windows-link", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "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]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "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]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "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" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..4879930 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "parsec-vdd" +version = "0.1.0" +edition = "2024" + +[dependencies] +windows-service = "0.8.0" + +[dependencies.windows] +version = "0.61.3" +features = [ + "Win32_Devices_DeviceAndDriverInstallation", + "Win32_Foundation", + "Win32_System_IO", + "Win32_Storage_FileSystem", + "Win32_System_Threading", + "Win32_System_Registry", + "Win32_System_SystemServices", + "Win32_System_Diagnostics_Debug", +] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..ebcb01a --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,373 @@ +#[allow(non_snake_case)] + +use std::mem::{size_of, zeroed}; + +use windows::{ + core::{GUID, PCSTR}, + Win32::{ + Devices::DeviceAndDriverInstallation::{ + CM_Get_DevNode_Status, CM_PROB_DISABLED, CM_PROB_DISABLED_SERVICE, + CM_PROB_FAILED_POST_START, CM_PROB_HARDWARE_DISABLED, CM_PROB_NEED_RESTART, + SetupDiDestroyDeviceInfoList, SetupDiEnumDeviceInfo, SetupDiEnumDeviceInterfaces, + SetupDiGetClassDevsA, SetupDiGetDeviceInterfaceDetailA, + SetupDiGetDeviceRegistryPropertyA, DIGCF_DEVICEINTERFACE, DIGCF_PRESENT, + DN_DRIVER_LOADED, DN_HAS_PROBLEM, DN_STARTED, HDEVINFO, SPDRP_HARDWAREID, + SP_DEVICE_INTERFACE_DATA, SP_DEVICE_INTERFACE_DETAIL_DATA_A, SP_DEVINFO_DATA, + }, + Foundation::{ + CloseHandle, GetLastError, GENERIC_READ, GENERIC_WRITE, HANDLE, + INVALID_HANDLE_VALUE, ERROR_SUCCESS, + }, + Storage::FileSystem::{ + CreateFileA, FILE_ATTRIBUTE_NORMAL, FILE_FLAG_NO_BUFFERING, FILE_FLAG_OVERLAPPED, + FILE_FLAG_WRITE_THROUGH, FILE_SHARE_READ, FILE_SHARE_WRITE, OPEN_EXISTING, + }, + System::{ + IO::{DeviceIoControl, OVERLAPPED}, + Registry::{REG_MULTI_SZ, REG_SZ}, + Threading::{CreateEventA, GetOverlappedResultEx}, + }, + }, +}; + +// RAII wrapper for HDEVINFO to ensure SetupDiDestroyDeviceInfoList is always called. +struct DevInfo(HDEVINFO); + +impl Drop for DevInfo { + fn drop(&mut self) { + if !self.0.is_invalid() { + unsafe { + 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); + +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, +} + +/// Query the driver status. +pub fn query_device_status(class_guid: &GUID, device_id: &[u8]) -> DeviceStatus { + unsafe { + let dev_info = DevInfo(SetupDiGetClassDevsA( + Some(class_guid), + None, + None, + DIGCF_PRESENT, + )); + + if dev_info.0.is_invalid() { + return DeviceStatus::Inaccessible; + } + + let mut dev_info_data: SP_DEVINFO_DATA = zeroed(); + dev_info_data.cbSize = size_of::() as u32; + let mut device_index = 0; + + // Loop through all devices in the class. + while SetupDiEnumDeviceInfo(dev_info.0, device_index, &mut dev_info_data).is_ok() { + let mut required_size = 0; + // First call: get the required buffer size for the hardware ID. + SetupDiGetDeviceRegistryPropertyA( + dev_info.0, + &dev_info_data, + SPDRP_HARDWAREID, + None, + None, + 0, + Some(&mut required_size), + ); + + if required_size > 0 { + let mut prop_buffer = vec![0u8; required_size as usize]; + let mut reg_data_type = 0; + + // Second call: get the actual hardware ID data. + if SetupDiGetDeviceRegistryPropertyA( + dev_info.0, + &dev_info_data, + SPDRP_HARDWAREID, + Some(&mut reg_data_type), + Some(prop_buffer.as_mut_slice()), + required_size, + None, + ) + .is_ok() + { + // The property is a list of null-terminated strings (REG_MULTI_SZ). + if reg_data_type == REG_SZ.0 || reg_data_type == REG_MULTI_SZ.0 { + // Safely parse the multi-string buffer. + let found_match = prop_buffer + .split(|&b| b == 0) // Split by null terminator + .filter(|s| !s.is_empty()) // Remove empty segments + .any(|s| s == &device_id[..device_id.len() - 1]); // Compare with non-null-terminated slice + + if found_match { + let mut dev_status = 0; + let mut dev_problem_num = 0; + + // Get the device's configuration manager status. + if CM_Get_DevNode_Status( + &mut dev_status, + &mut dev_problem_num, + dev_info_data.DevInst, + 0, + ) != 0 + { + return DeviceStatus::NotInstalled; + } + + // Map the status codes to our enum. + if (dev_status & (DN_DRIVER_LOADED | DN_STARTED)) != 0 { + return DeviceStatus::Ok; + } else if (dev_status & DN_HAS_PROBLEM) != 0 { + return match dev_problem_num { + CM_PROB_NEED_RESTART => DeviceStatus::RestartRequired, + CM_PROB_DISABLED | CM_PROB_HARDWARE_DISABLED => { + DeviceStatus::Disabled + } + CM_PROB_DISABLED_SERVICE => DeviceStatus::DisabledService, + CM_PROB_FAILED_POST_START => DeviceStatus::DriverError, + _ => DeviceStatus::UnknownProblem, + }; + } else { + return DeviceStatus::Unknown; + } + } + } + } + } + device_index += 1; + } + + // If the loop finishes without finding the device. + DeviceStatus::NotInstalled + } +} + +/// Obtain the device handle. Returns None if it fails. +pub fn open_device_handle(interface_guid: &GUID) -> Option { + unsafe { + let dev_info = DevInfo(SetupDiGetClassDevsA( + Some(interface_guid), + None, + None, + DIGCF_PRESENT | DIGCF_DEVICEINTERFACE, + )); + + if dev_info.0.is_invalid() { + return None; + } + + let mut dev_interface_data: SP_DEVICE_INTERFACE_DATA = zeroed(); + dev_interface_data.cbSize = size_of::() as u32; + + // Loop through available device interfaces. + for i in 0.. { + if SetupDiEnumDeviceInterfaces( + dev_info.0, + None, + interface_guid, + i, + &mut dev_interface_data, + ) + .is_err() + { + break; // No more interfaces. + } + + let mut detail_size = 0; + // First call: get required size for the detail data. + SetupDiGetDeviceInterfaceDetailA( + dev_info.0, + &dev_interface_data, + None, + 0, + Some(&mut detail_size), + None, + ); + + if detail_size == 0 { + continue; + } + + // Allocate a buffer for the SP_DEVICE_INTERFACE_DETAIL_DATA_A struct. + // The struct has a flexible array member, so we allocate enough space for it. + let mut detail_buffer = vec![0u8; detail_size as usize]; + let detail = detail_buffer.as_mut_ptr() + as *mut SP_DEVICE_INTERFACE_DETAIL_DATA_A; + (*detail).cbSize = size_of::() as u32; + + // Second call: get the actual detail data, which includes the device path. + if SetupDiGetDeviceInterfaceDetailA( + dev_info.0, + &dev_interface_data, + Some(detail), + detail_size, + None, + None, + ) + .is_ok() + { + // The device path is a C-style string inside the struct. + let device_path = PCSTR((*detail).DevicePath.as_ptr()); + + let handle = CreateFileA( + device_path, + (GENERIC_READ | GENERIC_WRITE).0, + FILE_SHARE_READ | FILE_SHARE_WRITE, + None, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL + | FILE_FLAG_NO_BUFFERING + | FILE_FLAG_OVERLAPPED + | FILE_FLAG_WRITE_THROUGH, + None, + ); + + if handle.is_ok() { + // On success, wrap the handle in our RAII struct and return. + return Some(VddHandle(handle.unwrap())); + } + } + } + } + // If the loop completes without finding a valid handle. + None +} + +/// Release the device handle. +fn close_device_handle(handle: HANDLE) { + if !handle.is_invalid() { + unsafe { + CloseHandle(handle); + } + } +} + +/// Generic DeviceIoControl for all IoControl codes. +/// Returns the output buffer value from the driver, or None on failure. +fn vdd_io_control( + vdd: &VddHandle, + code: VddCtlCode, + data: Option<&[u8]>, +) -> Option { + if vdd.0.is_invalid() { + return None; + } + + 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).ok()?; + 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::() as u32, + None, + Some(&mut overlapped), + ); + + // Handle asynchronous IOCTL. + if result.is_err() && GetLastError() != ERROR_SUCCESS { + // Wait for the overlapped operation to complete with a 5-second timeout. + if GetOverlappedResultEx(vdd.0, &overlapped, &mut bytes_transferred, 5000, false) + .is_err() + { + CloseHandle(event); + return None; + } + } + + CloseHandle(event); + Some(out_buffer) + } +} + +/// Query VDD minor version. +pub fn vdd_version(vdd: &VddHandle) -> Option { + vdd_io_control(vdd, VddCtlCode::Version, None).map(|v| v as i32) +} + +/// Update/ping to VDD to keep displays alive. +pub fn vdd_update(vdd: &VddHandle) { + vdd_io_control(vdd, VddCtlCode::Update, None); +} + +/// Add/plug a virtual display. Returns the index of the added display. +pub fn vdd_add_display(vdd: &VddHandle) -> Option { + let result = vdd_io_control(vdd, VddCtlCode::Add, None); + vdd_update(vdd); // Ping immediately after adding + result.map(|v| v as i32) +} + +/// Remove/unplug a virtual display. +pub fn vdd_remove_display(vdd: &VddHandle, index: i32) { + // The driver expects the index as a 16-bit big-endian integer. + let index_data = (index as u16).to_be_bytes(); + vdd_io_control(vdd, VddCtlCode::Remove, Some(&index_data)); + vdd_update(vdd); // Ping immediately after removing +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..eb33559 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,114 @@ +#[allow(non_snake_case)] + +use parsec_vdd::{ + query_device_status, open_device_handle, vdd_version, vdd_update, + vdd_add_display, vdd_remove_display, DeviceStatus, VddHandle, + VDD_CLASS_GUID, VDD_HARDWARE_ID, VDD_ADAPTER_GUID, VDD_MAX_DISPLAYS +}; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::sync::Arc; + use std::thread; + use std::time::Duration; + + // FFI import for _getch() to read a single character without waiting for Enter. + #[link(name = "msvcrt")] + extern "C" { + fn _getch() -> i32; + } + +// Main application logic, translated from main.c +fn main() { + // 1. Check driver status. + let status = query_device_status(&VDD_CLASS_GUID, VDD_HARDWARE_ID); + + if status != DeviceStatus::Ok { + println!("Parsec VDD device is not OK, status: {:?}", status); + return; + } + println!("Parsec VDD device is OK."); + + // 2. Obtain device handle. + // The handle is wrapped in an Option and our RAII VddHandle struct. + let vdd = match open_device_handle(&VDD_ADAPTER_GUID) { + Some(handle) => Arc::new(handle), // Use Arc for sharing between threads + None => { + println!("Failed to obtain the device handle."); + return; + } + }; + println!("Successfully obtained device handle."); + if let Some(version) = vdd_version(&vdd) { + println!("Driver version: {}", version); + } + + // 3. Set up the updater thread. + let running = Arc::new(AtomicBool::new(true)); + let running_clone = Arc::clone(&running); + let vdd_clone = Arc::clone(&vdd); + + let updater_thread = thread::spawn(move || { + while running_clone.load(Ordering::SeqCst) { + vdd_update(&vdd_clone); + thread::sleep(Duration::from_millis(100)); + } + }); + + // 4. Main interaction loop. + let mut displays: Vec = Vec::new(); + println!("\nPress 'a' to add a virtual display."); + println!("Press 'r' to remove the last added display."); + println!("Press 'q' to quit (this will unplug all displays)."); + + while running.load(Ordering::SeqCst) { + // Use the imported _getch function. + let key = unsafe { _getch() as u8 as char }; + + match key { + 'q' | 'Q' => { + println!("\nQuitting..."); + running.store(false, Ordering::SeqCst); + } + 'a' | 'A' => { + if displays.len() < VDD_MAX_DISPLAYS { + if let Some(index) = vdd_add_display(&vdd) { + if index != -1 { + displays.push(index); + println!("Added a new virtual display, index: {}", index); + } else { + println!("Failed to add virtual display (driver returned -1)."); + } + } else { + println!("IOCTL call to add display failed."); + } + } else { + println!( + "Limit exceeded ({}), could not add more virtual displays.", + VDD_MAX_DISPLAYS + ); + } + } + 'r' | 'R' => { + if let Some(index) = displays.pop() { + vdd_remove_display(&vdd, index); + println!("Removed the last virtual display, index: {}", index); + } else { + println!("No added virtual displays to remove."); + } + } + _ => {} // Ignore other keys + } + } + + // 5. Cleanup. + println!("Removing all displays before exiting..."); + for index in displays { + vdd_remove_display(&vdd, index); + } + + // Wait for the updater thread to finish. + updater_thread.join().expect("Updater thread panicked"); + + println!("Cleanup complete. Exiting."); + // The VddHandle (wrapped in Arc) will be dropped automatically here, + // which calls CloseHandle, cleaning up the resource. +} \ No newline at end of file diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..e51e63a --- /dev/null +++ b/src/service.rs @@ -0,0 +1,11 @@ +#[marco_use] +extern crate windows_service; + +use std::ffi::OsString; +use windows_service::service_dispatcher; + +define_windows_service!(ffi_service_main, service_main); + +fn service_main(arguments: Vec) { + +} \ No newline at end of file