use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType}; use std::sync::atomic; // This string is defined here. // https://github.com/fufesou/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 pub const RUSTDESK_IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; pub const AMYUNI_IDD_DEVICE_STRING: &'static str = "USB Mobile Monitor Virtual Display\0"; const IDD_IMPL: &str = IDD_IMPL_AMYUNI; const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd"; const IDD_IMPL_AMYUNI: &str = "amyuni_idd"; const IS_CAN_PLUG_OUT_ALL_NOT_SET: i8 = 0; const IS_CAN_PLUG_OUT_ALL_YES: i8 = 1; const IS_CAN_PLUG_OUT_ALL_NO: i8 = 2; static IS_CAN_PLUG_OUT_ALL: atomic::AtomicI8 = atomic::AtomicI8::new(IS_CAN_PLUG_OUT_ALL_NOT_SET); pub fn is_can_plug_out_all() -> bool { IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) != IS_CAN_PLUG_OUT_ALL_NO } // No need to consider concurrency here. pub fn set_can_plug_out_all(v: bool) { if IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) == IS_CAN_PLUG_OUT_ALL_NOT_SET { IS_CAN_PLUG_OUT_ALL.store( if v { IS_CAN_PLUG_OUT_ALL_YES } else { IS_CAN_PLUG_OUT_ALL_NO }, atomic::Ordering::Relaxed, ); } } pub fn is_amyuni_idd() -> bool { IDD_IMPL == IDD_IMPL_AMYUNI } pub fn get_cur_device_string() -> &'static str { match IDD_IMPL { IDD_IMPL_RUSTDESK => RUSTDESK_IDD_DEVICE_STRING, IDD_IMPL_AMYUNI => AMYUNI_IDD_DEVICE_STRING, _ => "", } } pub fn is_virtual_display_supported() -> bool { #[cfg(target_os = "windows")] { is_windows_version_or_greater(10, 0, 19041, 0, 0) } #[cfg(not(target_os = "windows"))] { false } } pub fn plug_in_headless() -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_headless(), IDD_IMPL_AMYUNI => amyuni_idd::plug_in_headless(), _ => bail!("Unsupported virtual display implementation."), } } pub fn get_platform_additions() -> serde_json::Map { let mut map = serde_json::Map::new(); if !crate::platform::windows::is_self_service_running() { return map; } map.insert("idd_impl".into(), serde_json::json!(IDD_IMPL)); match IDD_IMPL { IDD_IMPL_RUSTDESK => { let virtual_displays = rustdesk_idd::get_virtual_displays(); if !virtual_displays.is_empty() { map.insert( "rustdesk_virtual_displays".into(), serde_json::json!(virtual_displays), ); } } IDD_IMPL_AMYUNI => { let c = amyuni_idd::get_monitor_count(); if c > 0 { map.insert("amyuni_virtual_displays".into(), serde_json::json!(c)); } } _ => {} } map } #[inline] pub fn plug_in_monitor(idx: u32, modes: Vec) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_index_modes(idx, modes), IDD_IMPL_AMYUNI => amyuni_idd::plug_in_monitor(), _ => bail!("Unsupported virtual display implementation."), } } pub fn plug_out_monitor(index: i32) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => { let indices = if index == -1 { rustdesk_idd::get_virtual_displays() } else { vec![index as _] }; rustdesk_idd::plug_out_peer_request(&indices) } IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index), _ => bail!("Unsupported virtual display implementation."), } } pub fn plug_in_peer_request(modes: Vec>) -> ResultType> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_in_peer_request(modes), IDD_IMPL_AMYUNI => { amyuni_idd::plug_in_monitor()?; Ok(vec![0]) } _ => bail!("Unsupported virtual display implementation."), } } pub fn plug_out_monitor_indices(indices: &[u32]) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices), IDD_IMPL_AMYUNI => { for _idx in indices.iter() { amyuni_idd::plug_out_monitor(0)?; } Ok(()) } _ => bail!("Unsupported virtual display implementation."), } } pub fn reset_all() -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::reset_all(), IDD_IMPL_AMYUNI => crate::privacy_mode::turn_off_privacy(0, None).unwrap_or(Ok(())), _ => bail!("Unsupported virtual display implementation."), } } pub mod rustdesk_idd { use super::windows; use hbb_common::{allow_err, bail, lazy_static, log, ResultType}; use std::{ collections::{HashMap, HashSet}, sync::{Arc, Mutex}, }; // virtual display index range: 0 - 2 are reserved for headless and other special uses. const VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS: u32 = 0; const VIRTUAL_DISPLAY_START_FOR_PEER: u32 = 1; const VIRTUAL_DISPLAY_MAX_COUNT: u32 = 5; lazy_static::lazy_static! { static ref VIRTUAL_DISPLAY_MANAGER: Arc> = Arc::new(Mutex::new(VirtualDisplayManager::default())); } #[derive(Default)] struct VirtualDisplayManager { headless_index_name: Option<(u32, String)>, peer_index_name: HashMap, is_driver_installed: bool, } impl VirtualDisplayManager { fn prepare_driver(&mut self) -> ResultType<()> { if !self.is_driver_installed { self.install_update_driver()?; } Ok(()) } fn install_update_driver(&mut self) -> ResultType<()> { if let Err(e) = virtual_display::create_device() { if !e.to_string().contains("Device is already created") { bail!("Create device failed {}", e); } } // Reboot is not required for this case. let mut _reboot_required = false; virtual_display::install_update_driver(&mut _reboot_required)?; self.is_driver_installed = true; Ok(()) } fn plug_in_monitor(index: u32, modes: &[virtual_display::MonitorMode]) -> ResultType<()> { if let Err(e) = virtual_display::plug_in_monitor(index) { bail!("Plug in monitor failed {}", e); } if let Err(e) = virtual_display::update_monitor_modes(index, &modes) { log::error!("Update monitor modes failed {}", e); } Ok(()) } } pub fn install_update_driver() -> ResultType<()> { VIRTUAL_DISPLAY_MANAGER .lock() .unwrap() .install_update_driver() } #[inline] fn get_device_names() -> Vec { windows::get_device_names(Some(super::RUSTDESK_IDD_DEVICE_STRING)) } pub fn plug_in_headless() -> ResultType<()> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); manager.prepare_driver()?; let modes = [virtual_display::MonitorMode { width: 1920, height: 1080, sync: 60, }]; let device_names = get_device_names().into_iter().collect(); VirtualDisplayManager::plug_in_monitor(VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, &modes)?; let device_name = get_new_device_name(&device_names); manager.headless_index_name = Some((VIRTUAL_DISPLAY_INDEX_FOR_HEADLESS, device_name)); Ok(()) } pub fn plug_out_headless() -> bool { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); if let Some((index, _)) = manager.headless_index_name.take() { if let Err(e) = virtual_display::plug_out_monitor(index) { log::error!("Plug out monitor failed {}", e); } true } else { false } } fn get_new_device_name(device_names: &HashSet) -> String { for _ in 0..3 { let device_names_af: HashSet = get_device_names().into_iter().collect(); let diff_names: Vec<_> = device_names_af.difference(&device_names).collect(); if diff_names.len() == 1 { return diff_names[0].clone(); } else if diff_names.len() > 1 { log::error!( "Failed to get diff device names after plugin virtual display, more than one diff names: {:?}", &diff_names ); return "".to_string(); } // Sleep is needed here to wait for the virtual display to be ready. std::thread::sleep(std::time::Duration::from_millis(50)); } log::error!("Failed to get diff device names after plugin virtual display",); "".to_string() } pub fn get_virtual_displays() -> Vec { VIRTUAL_DISPLAY_MANAGER .lock() .unwrap() .peer_index_name .keys() .cloned() .collect() } pub fn plug_in_index_modes( idx: u32, mut modes: Vec, ) -> ResultType<()> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); manager.prepare_driver()?; if !manager.peer_index_name.contains_key(&idx) { let device_names = get_device_names().into_iter().collect(); if modes.is_empty() { modes.push(virtual_display::MonitorMode { width: 1920, height: 1080, sync: 60, }); } match VirtualDisplayManager::plug_in_monitor(idx, modes.as_slice()) { Ok(_) => { let device_name = get_new_device_name(&device_names); manager.peer_index_name.insert(idx, device_name); } Err(e) => { log::error!("Plug in monitor failed {}", e); } } } Ok(()) } pub fn reset_all() -> ResultType<()> { if super::is_virtual_display_supported() { return Ok(()); } if let Err(e) = plug_out_peer_request(&get_virtual_displays()) { log::error!("Failed to plug out virtual displays: {}", e); } let _ = plug_out_headless(); Ok(()) } pub fn plug_in_peer_request( modes: Vec>, ) -> ResultType> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); manager.prepare_driver()?; let mut indices: Vec = Vec::new(); for m in modes.iter() { for idx in VIRTUAL_DISPLAY_START_FOR_PEER..VIRTUAL_DISPLAY_MAX_COUNT { if !manager.peer_index_name.contains_key(&idx) { let device_names = get_device_names().into_iter().collect(); match VirtualDisplayManager::plug_in_monitor(idx, m) { Ok(_) => { let device_name = get_new_device_name(&device_names); manager.peer_index_name.insert(idx, device_name); indices.push(idx); } Err(e) => { log::error!("Plug in monitor failed {}", e); } } break; } } } Ok(indices) } pub fn plug_out_peer_request(indices: &[u32]) -> ResultType<()> { let mut manager = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); for idx in indices.iter() { if manager.peer_index_name.contains_key(idx) { allow_err!(virtual_display::plug_out_monitor(*idx)); manager.peer_index_name.remove(idx); } } Ok(()) } pub fn is_virtual_display(name: &str) -> bool { let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); if let Some((_, device_name)) = &lock.headless_index_name { if windows::is_device_name(device_name, name) { return true; } } for (_, v) in lock.peer_index_name.iter() { if windows::is_device_name(v, name) { return true; } } false } fn change_resolution(index: u32, w: u32, h: u32) -> bool { let modes = [virtual_display::MonitorMode { width: w, height: h, sync: 60, }]; match virtual_display::update_monitor_modes(index, &modes) { Ok(_) => true, Err(e) => { log::error!("Update monitor {} modes {:?} failed: {}", index, &modes, e); false } } } pub fn change_resolution_if_is_virtual_display(name: &str, w: u32, h: u32) -> Option { let lock = VIRTUAL_DISPLAY_MANAGER.lock().unwrap(); if let Some((index, device_name)) = &lock.headless_index_name { if windows::is_device_name(device_name, name) { return Some(change_resolution(*index, w, h)); } } for (k, v) in lock.peer_index_name.iter() { if windows::is_device_name(v, name) { return Some(change_resolution(*k, w, h)); } } None } } pub mod amyuni_idd { use super::windows; use crate::platform::win_device; use hbb_common::{bail, lazy_static, log, tokio::time::Instant, ResultType}; use std::{ ops::Sub, ptr::null_mut, sync::{Arc, Mutex}, time::Duration, }; use winapi::{ shared::{guiddef::GUID, winerror::ERROR_NO_MORE_ITEMS}, um::shellapi::ShellExecuteA, }; const INF_PATH: &str = r#"usbmmidd_v2\usbmmIdd.inf"#; const INTERFACE_GUID: GUID = GUID { Data1: 0xb5ffd75f, Data2: 0xda40, Data3: 0x4353, Data4: [0x8f, 0xf8, 0xb6, 0xda, 0xf6, 0xf1, 0xd8, 0xca], }; const HARDWARE_ID: &str = "usbmmidd"; const PLUG_MONITOR_IO_CONTROL_CDOE: u32 = 2307084; const INSTALLER_EXE_FILE: &str = "deviceinstaller64.exe"; lazy_static::lazy_static! { static ref LOCK: Arc> = Default::default(); static ref LAST_PLUG_IN_HEADLESS_TIME: Arc> = Arc::new(Mutex::new(Instant::now().sub(Duration::from_secs(100)))); } fn get_deviceinstaller64_work_dir() -> ResultType>> { let cur_exe = std::env::current_exe()?; let Some(cur_dir) = cur_exe.parent() else { bail!("Cannot get parent of current exe file."); }; let work_dir = cur_dir.join("usbmmidd_v2"); if !work_dir.exists() { return Ok(None); } let exe_path = work_dir.join(INSTALLER_EXE_FILE); if !exe_path.exists() { return Ok(None); } let Some(work_dir) = work_dir.to_str() else { bail!("Cannot convert work_dir to string."); }; let mut work_dir2 = work_dir.as_bytes().to_vec(); work_dir2.push(0); Ok(Some(work_dir2)) } pub fn uninstall_driver() -> ResultType<()> { if let Ok(Some(work_dir)) = get_deviceinstaller64_work_dir() { if crate::platform::windows::is_x64() { log::info!("Uninstalling driver by deviceinstaller64.exe"); install_if_x86_on_x64(&work_dir, "remove usbmmidd")?; return Ok(()); } } log::info!("Uninstalling driver by SetupAPI"); let mut reboot_required = false; let _ = unsafe { win_device::uninstall_driver(HARDWARE_ID, &mut reboot_required)? }; Ok(()) } // SetupDiCallClassInstaller() will always fail if current_exe() is built as x86 and running on x64. // So we need to call another x64 version exe to install and uninstall the driver. fn install_if_x86_on_x64(work_dir: &[u8], args: &str) -> ResultType<()> { const SW_HIDE: i32 = 0; let mut args = args.bytes().collect::>(); args.push(0); let mut exe_file = INSTALLER_EXE_FILE.bytes().collect::>(); exe_file.push(0); let hi = unsafe { ShellExecuteA( null_mut(), "open\0".as_ptr() as _, exe_file.as_ptr() as _, args.as_ptr() as _, work_dir.as_ptr() as _, SW_HIDE, ) as i32 }; if hi <= 32 { log::error!("Failed to run deviceinstaller: {}", hi); bail!("Failed to run deviceinstaller.") } Ok(()) } // If the driver is installed by "deviceinstaller64.exe", the driver will be installed asynchronously. // The caller must wait some time before using the driver. fn check_install_driver(is_async: &mut bool) -> ResultType<()> { let _l = LOCK.lock().unwrap(); let drivers = windows::get_display_drivers(); if drivers .iter() .any(|(s, c)| s == super::AMYUNI_IDD_DEVICE_STRING && *c == 0) { *is_async = false; return Ok(()); } if let Ok(Some(work_dir)) = get_deviceinstaller64_work_dir() { if crate::platform::windows::is_x64() { log::info!("Installing driver by deviceinstaller64.exe"); install_if_x86_on_x64(&work_dir, "install usbmmidd.inf usbmmidd")?; *is_async = true; return Ok(()); } } let exe_file = std::env::current_exe()?; let Some(cur_dir) = exe_file.parent() else { bail!("Cannot get parent of current exe file"); }; let inf_path = cur_dir.join(INF_PATH); if !inf_path.exists() { bail!("Driver inf file not found."); } let inf_path = inf_path.to_string_lossy().to_string(); log::info!("Installing driver by SetupAPI"); let mut reboot_required = false; let _ = unsafe { win_device::install_driver(&inf_path, HARDWARE_ID, &mut reboot_required)? }; *is_async = false; Ok(()) } #[inline] fn plug_monitor_(add: bool) -> Result<(), win_device::DeviceError> { let cmd = if add { 0x10 } else { 0x00 }; let cmd = [cmd, 0x00, 0x00, 0x00]; unsafe { win_device::device_io_control(&INTERFACE_GUID, PLUG_MONITOR_IO_CONTROL_CDOE, &cmd, 0)?; } Ok(()) } // `std::thread::sleep()` with a timeout is acceptable here. // Because user can wait for a while to plug in a monitor. fn plug_in_monitor_(add: bool, is_driver_async_installed: bool) -> ResultType<()> { let timeout = Duration::from_secs(3); let now = Instant::now(); loop { match plug_monitor_(add) { Ok(_) => { break; } Err(e) => { if is_driver_async_installed { if let win_device::DeviceError::WinApiLastErr(_, e2) = &e { if e2.raw_os_error() == Some(ERROR_NO_MORE_ITEMS as _) { if now.elapsed() < timeout { std::thread::sleep(Duration::from_millis(100)); continue; } } } } return Err(e.into()); } } } Ok(()) } pub fn plug_in_headless() -> ResultType<()> { let mut tm = LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap(); if tm.elapsed() < Duration::from_secs(3) { bail!("Plugging in too frequently."); } *tm = Instant::now(); drop(tm); let mut is_async = false; if let Err(e) = check_install_driver(&mut is_async) { log::error!("Failed to install driver: {}", e); bail!("Failed to install driver."); } plug_in_monitor_(true, is_async) } pub fn plug_in_monitor() -> ResultType<()> { let mut is_async = false; if let Err(e) = check_install_driver(&mut is_async) { log::error!("Failed to install driver: {}", e); bail!("Failed to install driver."); } if get_monitor_count() == 4 { bail!("There are already 4 monitors plugged in."); } plug_in_monitor_(true, is_async) } pub fn plug_out_monitor(index: i32) -> ResultType<()> { let all_count = windows::get_device_names(None).len(); let amyuni_count = get_monitor_count(); let mut to_plug_out_count = match all_count { 0 => return Ok(()), 1 => { if amyuni_count == 0 { bail!("No virtual displays to plug out.") } else { if super::is_can_plug_out_all() { 1 } else { bail!("This only virtual display cannot be pulled out.") } } } _ => { if all_count == amyuni_count { if super::is_can_plug_out_all() { all_count } else { all_count - 1 } } else { amyuni_count } } }; if to_plug_out_count != 0 && index != -1 { to_plug_out_count = 1; } for _i in 0..to_plug_out_count { let _ = plug_monitor_(false); } Ok(()) } #[inline] pub fn get_monitor_count() -> usize { windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)).len() } #[inline] pub fn is_my_display(name: &str) -> bool { windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)) .iter() .any(|s| windows::is_device_name(s, name)) } } mod windows { use std::ptr::null_mut; use winapi::{ shared::{ devguid::GUID_DEVCLASS_DISPLAY, minwindef::{DWORD, FALSE}, ntdef::ULONG, }, um::{ cfgmgr32::{CM_Get_DevNode_Status, CR_SUCCESS}, cguid::GUID_NULL, setupapi::{ SetupDiEnumDeviceInfo, SetupDiGetClassDevsW, SetupDiGetDeviceRegistryPropertyW, SP_DEVINFO_DATA, }, wingdi::{ DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_MIRRORING_DRIVER, }, winnt::HANDLE, winuser::{EnumDisplayDevicesW, EnumDisplaySettingsExW, ENUM_CURRENT_SETTINGS}, }, }; const DIGCF_PRESENT: DWORD = 0x00000002; const SPDRP_DEVICEDESC: DWORD = 0x00000000; const INVALID_HANDLE_VALUE: HANDLE = -1isize as HANDLE; #[inline] pub(super) fn is_device_name(device_name: &str, name: &str) -> bool { if name.len() == device_name.len() { name == device_name } else if name.len() > device_name.len() { false } else { &device_name[..name.len()] == name && device_name.as_bytes()[name.len() as usize] == 0 } } pub(super) fn get_device_names(device_string: Option<&str>) -> Vec { let mut device_names = Vec::new(); let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::zeroed() }; dd.cb = std::mem::size_of::() as DWORD; let mut i_dev_num = 0; loop { let result = unsafe { EnumDisplayDevicesW(null_mut(), i_dev_num, &mut dd, 0) }; if result == 0 { break; } i_dev_num += 1; if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) || (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0 { continue; } let mut dm: DEVMODEW = unsafe { std::mem::zeroed() }; dm.dmSize = std::mem::size_of::() as _; dm.dmDriverExtra = 0; let ok = unsafe { EnumDisplaySettingsExW( dd.DeviceName.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm as _, 0, ) }; if ok == FALSE { continue; } if dm.dmPelsHeight == 0 || dm.dmPelsWidth == 0 { continue; } if let (Ok(device_name), Ok(ds)) = ( String::from_utf16(&dd.DeviceName), String::from_utf16(&dd.DeviceString), ) { if let Some(s) = device_string { if ds.len() >= s.len() && &ds[..s.len()] == s { device_names.push(device_name); } } else { device_names.push(device_name); } } } device_names } pub(super) fn get_display_drivers() -> Vec<(String, u32)> { let mut display_drivers: Vec<(String, u32)> = Vec::new(); let device_info_set = unsafe { SetupDiGetClassDevsW( &GUID_DEVCLASS_DISPLAY, null_mut(), null_mut(), DIGCF_PRESENT, ) }; if device_info_set == INVALID_HANDLE_VALUE { println!( "Failed to get device information set. Error: {}", std::io::Error::last_os_error() ); return display_drivers; } let mut device_info_data = SP_DEVINFO_DATA { cbSize: std::mem::size_of::() as u32, ClassGuid: GUID_NULL, DevInst: 0, Reserved: 0, }; let mut device_index = 0; loop { let result = unsafe { SetupDiEnumDeviceInfo(device_info_set, device_index, &mut device_info_data) }; if result == 0 { break; } let mut data_type: DWORD = 0; let mut required_size: DWORD = 0; // Get the required buffer size for the driver description let mut buffer; unsafe { SetupDiGetDeviceRegistryPropertyW( device_info_set, &mut device_info_data, SPDRP_DEVICEDESC, &mut data_type, null_mut(), 0, &mut required_size, ); buffer = vec![0; required_size as usize / 2]; SetupDiGetDeviceRegistryPropertyW( device_info_set, &mut device_info_data, SPDRP_DEVICEDESC, &mut data_type, buffer.as_mut_ptr() as *mut u8, required_size, null_mut(), ); } let Ok(driver_description) = String::from_utf16(&buffer) else { println!("Failed to convert driver description to string"); device_index += 1; continue; }; let mut status: ULONG = 0; let mut problem_number: ULONG = 0; // Get the device status and problem number let config_ret = unsafe { CM_Get_DevNode_Status( &mut status, &mut problem_number, device_info_data.DevInst, 0, ) }; if config_ret != CR_SUCCESS { println!( "Failed to get device status. Error: {}", std::io::Error::last_os_error() ); device_index += 1; continue; } display_drivers.push((driver_description, problem_number)); device_index += 1; } display_drivers } }