diff --git a/.github/workflows/flutter-build.yml b/.github/workflows/flutter-build.yml index f6123e4a2..0168420f9 100644 --- a/.github/workflows/flutter-build.yml +++ b/.github/workflows/flutter-build.yml @@ -264,7 +264,10 @@ jobs: echo "output_folder=./Release" >> $GITHUB_OUTPUT curl -LJ -o ./usbmmidd_v2.zip https://github.com/rustdesk-org/rdev/releases/download/usbmmidd_v2/usbmmidd_v2.zip unzip usbmmidd_v2.zip - rm -rf ./usbmmidd_v2/x64 ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/deviceinstaller64.exe ./usbmmidd_v2/usbmmidd.bat + # Do not remove x64 files, because the user may run the 32bit version on a 64bit system. + # Do not remove ./usbmmidd_v2/deviceinstaller64.exe, as x86 exe cannot install and uninstall drivers when running on x64, + # we need the x64 exe to install and uninstall the driver. + rm -rf ./usbmmidd_v2/deviceinstaller.exe ./usbmmidd_v2/usbmmidd.bat mv ./usbmmidd_v2 ./Release || true - name: find Runner.res diff --git a/res/msi/CustomActions/CustomActions.cpp b/res/msi/CustomActions/CustomActions.cpp index bea7c0525..c21035b8b 100644 --- a/res/msi/CustomActions/CustomActions.cpp +++ b/res/msi/CustomActions/CustomActions.cpp @@ -598,6 +598,19 @@ UINT __stdcall RemoveAmyuniIdd( HRESULT hr = S_OK; DWORD er = ERROR_SUCCESS; + int nResult = 0; + LPWSTR installFolder = NULL; + LPWSTR pwz = NULL; + LPWSTR pwzData = NULL; + + WCHAR workDir[1024] = L""; + DWORD fileAttributes = 0; + HINSTANCE hi = 0; + + SYSTEM_INFO si; + LPCWSTR exe = L"deviceinstaller64.exe"; + WCHAR exePath[1024] = L""; + BOOL rebootRequired = FALSE; hr = WcaInitialize(hInstall, "RemoveAmyuniIdd"); @@ -605,7 +618,49 @@ UINT __stdcall RemoveAmyuniIdd( UninstallDriver(L"usbmmidd", rebootRequired); + // Only for x86 app on x64 + GetNativeSystemInfo(&si); + if (si.wProcessorArchitecture != PROCESSOR_ARCHITECTURE_AMD64) { + goto LExit; + } + + hr = WcaGetProperty(L"CustomActionData", &pwzData); + ExitOnFailure(hr, "failed to get CustomActionData"); + + pwz = pwzData; + hr = WcaReadStringFromCaData(&pwz, &installFolder); + ExitOnFailure(hr, "failed to read database key from custom action data: %ls", pwz); + + hr = StringCchPrintfW(workDir, 1024, L"%lsusbmmidd_v2", installFolder); + ExitOnFailure(hr, "Failed to compose a resource identifier string"); + fileAttributes = GetFileAttributesW(workDir); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) { + WcaLog(LOGMSG_STANDARD, "Amyuni idd dir \"%ls\" is not found, %d", workDir, fileAttributes); + goto LExit; + } + + hr = StringCchPrintfW(exePath, 1024, L"%ls\\%ls", workDir, exe); + ExitOnFailure(hr, "Failed to compose a resource identifier string"); + fileAttributes = GetFileAttributesW(exePath); + if (fileAttributes == INVALID_FILE_ATTRIBUTES) { + goto LExit; + } + + WcaLog(LOGMSG_STANDARD, "Remove amyuni idd %ls in %ls", exe, workDir); + hi = ShellExecuteW(NULL, L"open", exe, L"remove usbmmidd", workDir, SW_HIDE); + // https://learn.microsoft.com/en-us/windows/win32/api/shellapi/nf-shellapi-shellexecutew + if ((int)hi <= 32) { + WcaLog(LOGMSG_STANDARD, "Failed to remove amyuni idd : %d, last error: %d", (int)hi, GetLastError()); + } + else { + WcaLog(LOGMSG_STANDARD, "Amyuni idd is removed"); + } + LExit: + if (pwzData) { + ReleaseStr(pwzData); + } + er = SUCCEEDED(hr) ? ERROR_SUCCESS : ERROR_INSTALL_FAILURE; return WcaFinalize(er); } diff --git a/res/msi/Package/Components/RustDesk.wxs b/res/msi/Package/Components/RustDesk.wxs index a79f870b8..31dbbc66d 100644 --- a/res/msi/Package/Components/RustDesk.wxs +++ b/res/msi/Package/Components/RustDesk.wxs @@ -29,6 +29,7 @@ + @@ -73,6 +74,7 @@ + diff --git a/src/platform/win_device.rs b/src/platform/win_device.rs index f010c7f8a..265006502 100644 --- a/src/platform/win_device.rs +++ b/src/platform/win_device.rs @@ -46,16 +46,20 @@ pub enum DeviceError { Raw(String), } +impl DeviceError { + #[inline] + fn new_api_last_err(api: &str) -> Self { + Self::WinApiLastErr(api.to_string(), io::Error::last_os_error()) + } +} + struct DeviceInfo(HDEVINFO); impl DeviceInfo { fn setup_di_create_device_info_list(class_guid: &mut GUID) -> Result { let dev_info = unsafe { SetupDiCreateDeviceInfoList(class_guid, null_mut()) }; if dev_info == null_mut() { - return Err(DeviceError::WinApiLastErr( - "SetupDiCreateDeviceInfoList".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiCreateDeviceInfoList")); } Ok(Self(dev_info)) @@ -77,10 +81,7 @@ impl DeviceInfo { ) }; if dev_info == null_mut() { - return Err(DeviceError::WinApiLastErr( - "SetupDiGetClassDevsExW".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiGetClassDevsExW")); } Ok(Self(dev_info)) } @@ -133,10 +134,7 @@ pub unsafe fn install_driver( null_mut(), ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiGetINFClassW".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiGetINFClassW")); } let dev_info = DeviceInfo::setup_di_create_device_info_list(&mut class_guid)?; @@ -157,10 +155,7 @@ pub unsafe fn install_driver( &mut dev_info_data, ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiCreateDeviceInfoW".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiCreateDeviceInfoW")); } if SetupDiSetDeviceRegistryPropertyW( @@ -171,17 +166,13 @@ pub unsafe fn install_driver( (hardware_id.len() * 2) as _, ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiSetDeviceRegistryPropertyW".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "SetupDiSetDeviceRegistryPropertyW", )); } if SetupDiCallClassInstaller(DIF_REGISTERDEVICE, *dev_info, &mut dev_info_data) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiCallClassInstaller".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiCallClassInstaller")); } let mut reboot_required_ = FALSE; @@ -193,9 +184,8 @@ pub unsafe fn install_driver( &mut reboot_required_, ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "UpdateDriverForPlugAndPlayDevicesW".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "UpdateDriverForPlugAndPlayDevicesW", )); } *reboot_required = reboot_required_ == TRUE; @@ -219,9 +209,8 @@ unsafe fn is_same_hardware_id( null_mut(), ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiGetDeviceRegistryPropertyW".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "SetupDiGetDeviceRegistryPropertyW", )); } @@ -245,9 +234,8 @@ pub unsafe fn uninstall_driver( RemoteMachineName: [0; SP_MAX_MACHINENAME_LENGTH], }; if SetupDiGetDeviceInfoListDetailW(*dev_info, &mut device_info_list_detail) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiGetDeviceInfoListDetailW".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "SetupDiGetDeviceInfoListDetailW", )); } @@ -300,17 +288,13 @@ pub unsafe fn uninstall_driver( std::mem::size_of::() as _, ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiSetClassInstallParams".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "SetupDiSetClassInstallParams", )); } if SetupDiCallClassInstaller(DIF_REMOVE, *dev_info, &mut devinfo_data) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiCallClassInstaller".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiCallClassInstaller")); } let mut device_params = SP_DEVINSTALL_PARAMS_W { @@ -371,10 +355,7 @@ pub unsafe fn device_io_control( ); CloseHandle(h_device); if result == FALSE { - return Err(DeviceError::WinApiLastErr( - "DeviceIoControl".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("DeviceIoControl")); } if outbuf_max_len > 0 { outbuf.set_len(bytes_returned as _); @@ -403,10 +384,7 @@ unsafe fn get_device_path(interface_guid: &GUID) -> Result, DeviceError &mut device_interface_data, ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiEnumDeviceInterfaces".to_string(), - io::Error::last_os_error(), - )); + return Err(DeviceError::new_api_last_err("SetupDiEnumDeviceInterfaces")); } let mut required_length = 0; @@ -444,14 +422,14 @@ unsafe fn get_device_path(interface_guid: &GUID) -> Result, DeviceError null_mut(), ) == FALSE { - return Err(DeviceError::WinApiLastErr( - "SetupDiGetDeviceInterfaceDetailW".to_string(), - io::Error::last_os_error(), + return Err(DeviceError::new_api_last_err( + "SetupDiGetDeviceInterfaceDetailW", )); } let mut path = Vec::new(); - let device_path_ptr = std::ptr::addr_of!((*device_interface_detail_data).DevicePath) as *const u16; + let device_path_ptr = + std::ptr::addr_of!((*device_interface_detail_data).DevicePath) as *const u16; let steps = device_path_ptr as usize - vec_data.as_ptr() as usize; for i in 0..(predicted_length - steps as u32) / 2 { if *device_path_ptr.offset(i as _) == 0 { @@ -465,7 +443,6 @@ unsafe fn get_device_path(interface_guid: &GUID) -> Result, DeviceError unsafe fn open_device_handle(interface_guid: &GUID) -> Result { let device_path = get_device_path(interface_guid)?; - println!("device_path: {:?}", String::from_utf16_lossy(&device_path)); let h_device = CreateFileW( device_path.as_ptr(), GENERIC_READ | GENERIC_WRITE, @@ -476,10 +453,7 @@ unsafe fn open_device_handle(interface_guid: &GUID) -> Result String { if exist \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" del /f /q \"%PROGRAMDATA%\\Microsoft\\Windows\\Start Menu\\Programs\\Startup\\{app_name} Tray.lnk\" ", before_uninstall=get_before_uninstall(kill_self), - uninstall_amyuni_idd=get_uninstall_amyuni_idd(&path), + uninstall_amyuni_idd=get_uninstall_amyuni_idd(), app_name = crate::get_app_name(), ) } @@ -2369,7 +2370,7 @@ impl Drop for WallPaperRemover { } } -fn get_uninstall_amyuni_idd(path: &str) -> String { +fn get_uninstall_amyuni_idd() -> String { match std::env::current_exe() { Ok(path) => format!("\"{}\" --uninstall-amyuni-idd", path.to_str().unwrap_or("")), Err(e) => { @@ -2390,3 +2391,13 @@ pub fn is_service_running(service_name: &str) -> bool { is_service_running_w(service_name.as_ptr() as _) } } + +pub fn is_x64() -> bool { + const PROCESSOR_ARCHITECTURE_AMD64: u16 = 9; + + let mut sys_info = SYSTEM_INFO::default(); + unsafe { + GetNativeSystemInfo(&mut sys_info as _); + } + unsafe { sys_info.u.s().wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 } +} diff --git a/src/privacy_mode.rs b/src/privacy_mode.rs index 859eef123..c062fbff8 100644 --- a/src/privacy_mode.rs +++ b/src/privacy_mode.rs @@ -6,7 +6,12 @@ use crate::{ display_service, ipc::{connect, Data}, }; -use hbb_common::{anyhow::anyhow, bail, lazy_static, tokio, ResultType}; +use hbb_common::{ + anyhow::anyhow, + bail, lazy_static, + tokio::{self, sync::oneshot}, + ResultType, +}; use serde_derive::{Deserialize, Serialize}; use std::{ collections::HashMap, @@ -51,6 +56,8 @@ pub enum PrivacyModeState { } pub trait PrivacyMode: Sync + Send { + fn is_async_privacy_mode(&self) -> bool; + fn init(&self) -> ResultType<()>; fn clear(&mut self); fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType; @@ -200,7 +207,40 @@ fn get_supported_impl(impl_key: &str) -> String { cur_impl } -pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> { +pub async fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> { + if is_async_privacy_mode() { + turn_on_privacy_async(impl_key.to_string(), conn_id).await + } else { + turn_on_privacy_sync(impl_key, conn_id) + } +} + +#[inline] +fn is_async_privacy_mode() -> bool { + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map_or(false, |m| m.is_async_privacy_mode()) +} + +#[inline] +async fn turn_on_privacy_async(impl_key: String, conn_id: i32) -> Option> { + let (tx, rx) = oneshot::channel(); + std::thread::spawn(move || { + let res = turn_on_privacy_sync(&impl_key, conn_id); + let _ = tx.send(res); + }); + match hbb_common::timeout(5000, rx).await { + Ok(res) => match res { + Ok(res) => res, + Err(e) => Some(Err(anyhow!(e.to_string()))), + }, + Err(e) => Some(Err(anyhow!(e.to_string()))), + } +} + +fn turn_on_privacy_sync(impl_key: &str, conn_id: i32) -> Option> { // Check if privacy mode is already on or occupied by another one let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap(); diff --git a/src/privacy_mode/win_topmost_window.rs b/src/privacy_mode/win_topmost_window.rs index 7fd27b60b..fdfcfcba6 100644 --- a/src/privacy_mode/win_topmost_window.rs +++ b/src/privacy_mode/win_topmost_window.rs @@ -72,6 +72,10 @@ pub struct PrivacyModeImpl { } impl PrivacyMode for PrivacyModeImpl { + fn is_async_privacy_mode(&self) -> bool { + false + } + fn init(&self) -> ResultType<()> { Ok(()) } diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index 7e7543d67..04a9d776c 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -360,6 +360,10 @@ impl PrivacyModeImpl { } impl PrivacyMode for PrivacyModeImpl { + fn is_async_privacy_mode(&self) -> bool { + virtual_display_manager::is_amyuni_idd() + } + fn init(&self) -> ResultType<()> { Ok(()) } diff --git a/src/server/connection.rs b/src/server/connection.rs index afd090a92..9cd9221c9 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -2883,7 +2883,7 @@ impl Connection { } } - let turn_on_res = privacy_mode::turn_on_privacy(&impl_key, self.inner.id); + let turn_on_res = privacy_mode::turn_on_privacy(&impl_key, self.inner.id).await; match turn_on_res { Some(Ok(res)) => { if res { diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index 4358e6561..72a39a987 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -403,9 +403,16 @@ pub mod rustdesk_idd { pub mod amyuni_idd { use super::windows; use crate::platform::win_device; - use hbb_common::{bail, lazy_static, log, ResultType}; - use std::sync::{Arc, Mutex}; - use winapi::shared::guiddef::GUID; + use hbb_common::{bail, lazy_static, log, tokio::time::Instant, ResultType}; + use std::{ + 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 { @@ -416,49 +423,116 @@ pub mod amyuni_idd { }; 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(); } + 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; - unsafe { - win_device::uninstall_driver(HARDWARE_ID, &mut reboot_required)?; + 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(()) } - fn check_install_driver() -> ResultType<()> { + // 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; - unsafe { - win_device::install_driver(&inf_path, HARDWARE_ID, &mut reboot_required)?; - } + let _ = + unsafe { win_device::install_driver(&inf_path, HARDWARE_ID, &mut reboot_required)? }; + *is_async = false; Ok(()) } #[inline] - fn plug_in_monitor_(add: bool) -> ResultType<()> { + fn plug_monitor_(add: bool) -> Result<(), win_device::DeviceError> { let cmd = if add { 0x10 } else { 0x00 }; let cmd = [cmd, 0x00, 0x00, 0x00]; unsafe { @@ -467,21 +541,51 @@ pub mod amyuni_idd { 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<()> { if get_monitor_count() > 0 { return Ok(()); } - if let Err(e) = check_install_driver() { + 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) + plug_in_monitor_(true, is_async) } pub fn plug_in_monitor() -> ResultType<()> { - if let Err(e) = check_install_driver() { + 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."); } @@ -490,7 +594,7 @@ pub mod amyuni_idd { bail!("There are already 4 monitors plugged in."); } - plug_in_monitor_(true) + plug_in_monitor_(true, is_async) } pub fn plug_out_monitor(index: i32) -> ResultType<()> { @@ -525,7 +629,7 @@ pub mod amyuni_idd { to_plug_out_count = 1; } for _i in 0..to_plug_out_count { - let _ = plug_in_monitor_(false); + let _ = plug_monitor_(false); } Ok(()) }