diff --git a/build.py b/build.py index 741c1c095..2a79fc1d6 100755 --- a/build.py +++ b/build.py @@ -57,9 +57,9 @@ def parse_rc_features(feature): }, 'PrivacyMode': { 'platform': ['windows'], - 'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.2' - '/TempTopMostWindow_x64_pic_en.zip', - 'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.2/checksum_md5', + 'zip_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3' + '/TempTopMostWindow_x64.zip', + 'checksum_url': 'https://github.com/fufesou/RustDeskTempTopMostWindow/releases/download/v0.3/checksum_md5', 'include': ['WindowInjection.dll'], } } diff --git a/libs/portable/src/main.rs b/libs/portable/src/main.rs index dc6f7bc84..6e3648c4e 100644 --- a/libs/portable/src/main.rs +++ b/libs/portable/src/main.rs @@ -94,11 +94,11 @@ mod windows { // Used for privacy mode(magnifier impl). pub const RUNTIME_BROKER_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; - pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; + pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; pub(super) fn copy_runtime_broker(dir: &PathBuf) { let src = RUNTIME_BROKER_EXE; - let tgt = WIN_MAG_INJECTED_PROCESS_EXE; + let tgt = WIN_TOPMOST_INJECTED_PROCESS_EXE; let target_file = dir.join(tgt); if target_file.exists() { if let (Ok(src_file), Ok(tgt_file)) = (fs::read(src), fs::read(&target_file)) { diff --git a/src/lang/cn.rs b/src/lang/cn.rs index a838701d5..be59faecb 100644 --- a/src/lang/cn.rs +++ b/src/lang/cn.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "真彩模式(4:4:4)"), ("Enable blocking user input", "允许阻止用户输入"), ("id_input_tip", ""), - ("privacy_mode_impl_mag_tip", "模式 1 (不推荐)"), - ("privacy_mode_impl_virtual_display_tip", "模式 2 (推荐)"), + ("privacy_mode_impl_mag_tip", "模式 1"), + ("privacy_mode_impl_virtual_display_tip", "模式 2"), ("Enter privacy mode", "进入隐私模式"), ("Exit privacy mode", "退出隐私模式"), ].iter().cloned().collect(); diff --git a/src/lang/en.rs b/src/lang/en.rs index 3afb27bc6..a5f494b74 100644 --- a/src/lang/en.rs +++ b/src/lang/en.rs @@ -203,7 +203,7 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("elevated_switch_display_msg", "Switch to the primary display because multiple displays are not supported in elevated mode."), ("selinux_tip", "SELinux is enabled on your device, which may prevent RustDesk from running properly as controlled side."), ("id_input_tip", "You can input an ID, a direct IP, or a domain with a port (:).\nIf you want to access a device on another server, please append the server address (@?key=), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"@public\", the key is not needed for public server"), - ("privacy_mode_impl_mag_tip", "Mode 1 (deprecated)"), - ("privacy_mode_impl_virtual_display_tip", "Mode 2 (recommended)"), + ("privacy_mode_impl_mag_tip", "Mode 1"), + ("privacy_mode_impl_virtual_display_tip", "Mode 2"), ].iter().cloned().collect(); } diff --git a/src/lang/es.rs b/src/lang/es.rs index 5692ce9d3..a2dcd53ba 100644 --- a/src/lang/es.rs +++ b/src/lang/es.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Color real (4:4:4)"), ("Enable blocking user input", "Habilitar el bloqueo de la entrada del usuario"), ("id_input_tip", "Puedes introducir una ID, una IP directa o un dominio con un puerto (:).\nSi quieres acceder a un dispositivo en otro servidor, por favor añade la ip del servidor (@?key=), por ejemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi quieres acceder a un dispositivo en un servidor público, por favor, introduce \"@public\", la clave no es necesaria para un servidor público."), - ("privacy_mode_impl_mag_tip", "Modo 1 (obsoleto)"), - ("privacy_mode_impl_virtual_display_tip", "Modo 2 (recomendado)"), + ("privacy_mode_impl_mag_tip", "Modo 1"), + ("privacy_mode_impl_virtual_display_tip", "Modo 2"), ("Enter privacy mode", "Entrar al modo privado"), ("Exit privacy mode", "Salir del modo privado"), ].iter().cloned().collect(); diff --git a/src/lang/id.rs b/src/lang/id.rs index 7e81282fc..3455ddab4 100644 --- a/src/lang/id.rs +++ b/src/lang/id.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", ""), ("Enable blocking user input", "Aktifkan pemblokiran input pengguna"), ("id_input_tip", "Anda bisa memasukkan ID, IP langsung, atau domain dengan port kostum yang sudah ditentukan (:).\nJika anda ingin mengakses perangkat lain yang berbeda server, tambahkan alamat server setelah penulisan ID(@?key=), sebagai contoh,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJika anda ingin mengakses perangkat yang menggunakan server publik, masukkan \"@public\", server public tidak memerlukan key khusus"), - ("privacy_mode_impl_mag_tip", "Mode 1 (deprecated)"), - ("privacy_mode_impl_virtual_display_tip", "Mode 2 (direkomendasikan)"), + ("privacy_mode_impl_mag_tip", "Mode 1"), + ("privacy_mode_impl_virtual_display_tip", "Mode 2"), ("Enter privacy mode", "Masuk mode privasi"), ("Exit privacy mode", "Keluar mode privasi"), ].iter().cloned().collect(); diff --git a/src/lang/it.rs b/src/lang/it.rs index 99613c4d2..70fbe1c4e 100644 --- a/src/lang/it.rs +++ b/src/lang/it.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Colore reale (4:4:4)"), ("Enable blocking user input", "Abilita blocco input utente"), ("id_input_tip", "Puoi inserire un ID, un IP diretto o un dominio con una porta (:).\nSe vuoi accedere as un dispositivo in un altro server, aggiungi l'indirizzo del server (@?key=), ad esempio\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere as un dispositivo in un server pubblico, inserisci \"@public\", per il server pubblico la chiave non è necessaria"), - ("privacy_mode_impl_mag_tip", "Modo 1 (deprecato)"), - ("privacy_mode_impl_virtual_display_tip", "Modo 2 (consigliato)"), + ("privacy_mode_impl_mag_tip", "Modo 1"), + ("privacy_mode_impl_virtual_display_tip", "Modo 2"), ("Enter privacy mode", "Entra in modalità privacy"), ("Exit privacy mode", "Esci dalla modalità privacy"), ].iter().cloned().collect(); diff --git a/src/lang/ko.rs b/src/lang/ko.rs index 8d8981575..26a666d53 100644 --- a/src/lang/ko.rs +++ b/src/lang/ko.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "트루컬러(4:4:4)"), ("Enable blocking user input", "사용자 입력 차단 허용"), ("id_input_tip", "입력된 ID, IP, 도메인과 포트(:)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(@?key=)를 추가하세요"), - ("privacy_mode_impl_mag_tip", "모드 1(더 이상 사용되지 않음)"), - ("privacy_mode_impl_virtual_display_tip", "모드 2(권장)"), + ("privacy_mode_impl_mag_tip", "모드 1"), + ("privacy_mode_impl_virtual_display_tip", "모드 2"), ("Enter privacy mode", "개인정보 보호 모드 사용"), ("Exit privacy mode", "개인정보 보호 모드 종료"), ].iter().cloned().collect(); diff --git a/src/lang/lv.rs b/src/lang/lv.rs index 63b941d00..100d701fb 100644 --- a/src/lang/lv.rs +++ b/src/lang/lv.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Īstā krāsa (4:4:4)"), ("Enable blocking user input", "Iespējot lietotāja ievades bloķēšanu"), ("id_input_tip", "Varat ievadīt ID, tiešo IP vai domēnu ar portu (:).\nJa vēlaties piekļūt ierīcei citā serverī, lūdzu, pievienojiet servera adresi (@?key=), piemēram,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJa vēlaties piekļūt ierīcei publiskajā serverī, lūdzu, ievadiet \"@public\", publiskajam serverim atslēga nav nepieciešama"), - ("privacy_mode_impl_mag_tip", "1. režīms (novecojis)"), - ("privacy_mode_impl_virtual_display_tip", "2. režīms (ieteicams)"), + ("privacy_mode_impl_mag_tip", "1. režīms"), + ("privacy_mode_impl_virtual_display_tip", "2. režīms"), ("Enter privacy mode", "Ieiet privātuma režīmā"), ("Exit privacy mode", "Iziet no privātuma režīma"), ].iter().cloned().collect(); diff --git a/src/lang/pl.rs b/src/lang/pl.rs index 13ed8942d..aa91e8e9a 100644 --- a/src/lang/pl.rs +++ b/src/lang/pl.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "True color (4:4:4)"), ("Enable blocking user input", "Zablokuj wprowadzanie danych przez użytkownika"), ("id_input_tip", "Możesz wprowadzić identyfikator, bezpośredni adres IP lub domenę z portem (:).\nJeżeli chcesz uzyskać dostęp do urządzenia na innym serwerze, dołącz adres serwera (@?key=, np. \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeżeli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wpisz \"@public\", klucz nie jest potrzebny dla serwera publicznego."), - ("privacy_mode_impl_mag_tip", "Tryb 1 (przestarzały)"), - ("privacy_mode_impl_virtual_display_tip", "Tryb 2 (zalecany)"), + ("privacy_mode_impl_mag_tip", "Tryb 1"), + ("privacy_mode_impl_virtual_display_tip", "Tryb 2"), ("Enter privacy mode", "Wejdź w tryb prywatności"), ("Exit privacy mode", "Wyjdź z trybu prywatności"), ].iter().cloned().collect(); diff --git a/src/lang/ru.rs b/src/lang/ru.rs index 9e184cb89..abca158c9 100644 --- a/src/lang/ru.rs +++ b/src/lang/ru.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "True color (4:4:4)"), ("Enable blocking user input", "Блокировать ввод пользователя"), ("id_input_tip", "Можно ввести идентификатор, прямой IP-адрес или домен с портом (<домен>:<порт>).\nЕсли необходимо получить доступ к устройству на другом сервере, добавьте адрес сервера (@<адрес_сервера>?key=<ключ_значение>), например:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли необходимо получить доступ к устройству на общедоступном сервере, введите \"@public\", ключ для публичного сервера не требуется."), - ("privacy_mode_impl_mag_tip", "Режим 1 (устаревший)"), - ("privacy_mode_impl_virtual_display_tip", "Режим 2 (рекомендуемый)"), + ("privacy_mode_impl_mag_tip", "Режим 1"), + ("privacy_mode_impl_virtual_display_tip", "Режим 2"), ("Enter privacy mode", "Включить режим конфиденциальности"), ("Exit privacy mode", "Отключить режим конфиденциальности"), ].iter().cloned().collect(); diff --git a/src/lang/ua.rs b/src/lang/ua.rs index e1d301119..b0b193f94 100644 --- a/src/lang/ua.rs +++ b/src/lang/ua.rs @@ -568,8 +568,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> = ("True color (4:4:4)", "Спражній колір (4:4:4)"), ("Enable blocking user input", "Блокувати введення для користувача"), ("id_input_tip", "Ви можете ввести ID, безпосередню IP, або ж домен з портом (<домен>:<порт>).\nЯкщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"@public\", ключ для публічного сервера не потрібен."), - ("privacy_mode_impl_mag_tip", "Режим 1 (застарілий)"), - ("privacy_mode_impl_virtual_display_tip", "Режим 2 (рекомендований)"), + ("privacy_mode_impl_mag_tip", "Режим 1"), + ("privacy_mode_impl_virtual_display_tip", "Режим 2"), ("Enter privacy mode", "Увійти в режим конфіденційності"), ("Exit privacy mode", "Вийти з режиму конфіденційності"), ].iter().cloned().collect(); diff --git a/src/platform/windows.cc b/src/platform/windows.cc index 4ffa6eeeb..25372b218 100644 --- a/src/platform/windows.cc +++ b/src/platform/windows.cc @@ -628,8 +628,48 @@ extern "C" return bSystem; } - void alloc_console_and_redirect() { + void alloc_console_and_redirect() + { AllocConsole(); freopen("CONOUT$", "w", stdout); } -} // end of extern "C" \ No newline at end of file +} // end of extern "C" + +extern "C" +{ + // https://learn.microsoft.com/en-us/windows/win32/sysinfo/targeting-your-application-at-windows-8-1 + // https://github.com/nodejs/node-convergence-archive/blob/e11fe0c2777561827cdb7207d46b0917ef3c42a7/deps/uv/src/win/util.c#L780 + BOOL IsWindowsVersionOrGreater(DWORD os_major, + DWORD os_minor, + DWORD build_number, + WORD service_pack_major, + WORD service_pack_minor) + { + OSVERSIONINFOEX osvi; + DWORDLONG condition_mask = 0; + int op = VER_GREATER_EQUAL; + + /* Initialize the OSVERSIONINFOEX structure. */ + ZeroMemory(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + osvi.dwMajorVersion = os_major; + osvi.dwMinorVersion = os_minor; + osvi.dwBuildNumber = build_number; + osvi.wServicePackMajor = service_pack_major; + osvi.wServicePackMinor = service_pack_minor; + + /* Initialize the condition mask. */ + VER_SET_CONDITION(condition_mask, VER_MAJORVERSION, op); + VER_SET_CONDITION(condition_mask, VER_MINORVERSION, op); + VER_SET_CONDITION(condition_mask, VER_BUILDNUMBER, op); + VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMAJOR, op); + VER_SET_CONDITION(condition_mask, VER_SERVICEPACKMINOR, op); + + /* Perform the test. */ + return VerifyVersionInfo( + &osvi, + VER_MAJORVERSION | VER_MINORVERSION | VER_BUILDNUMBER | + VER_SERVICEPACKMAJOR | VER_SERVICEPACKMINOR, + condition_mask); + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 02c5490fd..4e88ea99c 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -3,7 +3,7 @@ use crate::common::PORTABLE_APPNAME_RUNTIME_ENV_KEY; use crate::{ ipc, license::*, - privacy_mode::win_mag::{self, WIN_MAG_INJECTED_PROCESS_EXE}, + privacy_mode::win_topmost_window::{self, WIN_TOPMOST_INJECTED_PROCESS_EXE}, }; use hbb_common::{ allow_err, @@ -460,6 +460,13 @@ extern "C" { fn is_win_down() -> BOOL; fn is_local_system() -> BOOL; fn alloc_console_and_redirect(); + fn IsWindowsVersionOrGreater( + os_major: DWORD, + os_minor: DWORD, + build_number: DWORD, + service_pack_major: WORD, + service_pack_minor: WORD, + ) -> BOOL; } extern "system" { @@ -848,8 +855,8 @@ fn get_default_install_path() -> String { } pub fn check_update_broker_process() -> ResultType<()> { - let process_exe = win_mag::INJECTED_PROCESS_EXE; - let origin_process_exe = win_mag::ORIGIN_PROCESS_EXE; + let process_exe = win_topmost_window::INJECTED_PROCESS_EXE; + let origin_process_exe = win_topmost_window::ORIGIN_PROCESS_EXE; let exe_file = std::env::current_exe()?; let Some(cur_dir) = exe_file.parent() else { @@ -926,8 +933,8 @@ pub fn copy_exe_cmd(src_exe: &str, exe: &str, path: &str) -> ResultType {main_exe} copy /Y \"{ORIGIN_PROCESS_EXE}\" \"{path}\\{broker_exe}\" ", - ORIGIN_PROCESS_EXE = win_mag::ORIGIN_PROCESS_EXE, - broker_exe = win_mag::INJECTED_PROCESS_EXE, + ORIGIN_PROCESS_EXE = win_topmost_window::ORIGIN_PROCESS_EXE, + broker_exe = win_topmost_window::INJECTED_PROCESS_EXE, )) } @@ -1157,7 +1164,7 @@ fn get_before_uninstall(kill_self: bool) -> String { reg delete HKEY_CLASSES_ROOT\\{ext} /f netsh advfirewall firewall delete rule name=\"{app_name} Service\" ", - broker_exe = WIN_MAG_INJECTED_PROCESS_EXE, + broker_exe = WIN_TOPMOST_INJECTED_PROCESS_EXE, ) } @@ -1283,6 +1290,25 @@ pub fn block_input(v: bool) -> (bool, String) { } } +#[inline] +pub fn is_windows_version_or_greater( + os_major: u32, + os_minor: u32, + build_number: u32, + service_pack_major: u32, + service_pack_minor: u32, +) -> bool { + unsafe { + IsWindowsVersionOrGreater( + os_major as _, + os_minor as _, + build_number as _, + service_pack_major as _, + service_pack_minor as _, + ) == TRUE + } +} + pub fn add_recent_document(path: &str) { extern "C" { fn AddRecentDocument(path: *const u16); @@ -2164,7 +2190,7 @@ pub fn uninstall_service(show_new_window: bool) -> bool { taskkill /F /IM {app_name}.exe{filter} ", app_name = crate::get_app_name(), - broker_exe = WIN_MAG_INJECTED_PROCESS_EXE, + broker_exe = WIN_TOPMOST_INJECTED_PROCESS_EXE, ); if let Err(err) = run_cmds(cmds, false, "uninstall") { Config::set_option("stop-service".into(), "".into()); @@ -2279,7 +2305,10 @@ fn run_after_run_cmds(silent: bool) { pub fn try_kill_broker() { allow_err!(std::process::Command::new("cmd") .arg("/c") - .arg(&format!("taskkill /F /IM {}", WIN_MAG_INJECTED_PROCESS_EXE)) + .arg(&format!( + "taskkill /F /IM {}", + WIN_TOPMOST_INJECTED_PROCESS_EXE + )) .creation_flags(winapi::um::winbase::CREATE_NO_WINDOW) .spawn()); } diff --git a/src/privacy_mode.rs b/src/privacy_mode.rs index e8ec1fb53..d0781d993 100644 --- a/src/privacy_mode.rs +++ b/src/privacy_mode.rs @@ -15,11 +15,14 @@ use std::{ sync::{Arc, Mutex}, }; +#[cfg(windows)] +pub mod win_exclude_from_capture; #[cfg(windows)] mod win_input; - #[cfg(windows)] pub mod win_mag; +#[cfg(windows)] +pub mod win_topmost_window; #[cfg(all(windows, feature = "virtual_display_driver"))] mod win_virtual_display; @@ -34,6 +37,9 @@ pub const NO_DISPLAYS: &'static str = "No displays"; #[cfg(windows)] pub const PRIVACY_MODE_IMPL_WIN_MAG: &str = win_mag::PRIVACY_MODE_IMPL; +#[cfg(windows)] +pub const PRIVACY_MODE_IMPL_WIN_EXCLUDE_FROM_CAPTURE: &str = + win_exclude_from_capture::PRIVACY_MODE_IMPL; #[cfg(all(windows, feature = "virtual_display_driver"))] pub const PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY: &str = win_virtual_display::PRIVACY_MODE_IMPL; @@ -55,6 +61,8 @@ pub trait PrivacyMode: Sync + Send { fn pre_conn_id(&self) -> i32; + fn get_impl_key(&self) -> &str; + #[inline] fn check_on_conn_id(&self, conn_id: i32) -> ResultType { let pre_conn_id = self.pre_conn_id(); @@ -84,21 +92,25 @@ lazy_static::lazy_static! { pub static ref DEFAULT_PRIVACY_MODE_IMPL: String = { #[cfg(windows)] { - if display_service::is_privacy_mode_mag_supported() { - PRIVACY_MODE_IMPL_WIN_MAG + if win_exclude_from_capture::is_supported() { + PRIVACY_MODE_IMPL_WIN_EXCLUDE_FROM_CAPTURE } else { - #[cfg(feature = "virtual_display_driver")] - { - if is_installed() { - PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY - } else { + if display_service::is_privacy_mode_mag_supported() { + PRIVACY_MODE_IMPL_WIN_MAG + } else { + #[cfg(feature = "virtual_display_driver")] + { + if is_installed() { + PRIVACY_MODE_IMPL_WIN_VIRTUAL_DISPLAY + } else { + "" + } + } + #[cfg(not(feature = "virtual_display_driver"))] + { "" } } - #[cfg(not(feature = "virtual_display_driver"))] - { - "" - } }.to_owned() } #[cfg(not(windows))] @@ -107,24 +119,21 @@ lazy_static::lazy_static! { } }; - static ref CUR_PRIVACY_MODE_IMPL: Arc> = { + static ref PRIVACY_MODE: Arc>>> = { let mut cur_impl = get_option("privacy-mode-impl-key".to_owned()); if !get_supported_privacy_mode_impl().iter().any(|(k, _)| k == &cur_impl) { cur_impl = DEFAULT_PRIVACY_MODE_IMPL.to_owned(); } - Arc::new(Mutex::new(cur_impl)) - }; - static ref PRIVACY_MODE: Arc>>> = { - let cur_impl = (*CUR_PRIVACY_MODE_IMPL.lock().unwrap()).clone(); + let privacy_mode = match PRIVACY_MODE_CREATOR.lock().unwrap().get(&(&cur_impl as &str)) { - Some(creator) => Some(creator()), + Some(creator) => Some(creator(&cur_impl)), None => None, }; Arc::new(Mutex::new(privacy_mode)) }; } -pub type PrivacyModeCreator = fn() -> Box; +pub type PrivacyModeCreator = fn(impl_key: &str) -> Box; lazy_static::lazy_static! { static ref PRIVACY_MODE_CREATOR: Arc>> = { #[cfg(not(windows))] @@ -133,13 +142,19 @@ lazy_static::lazy_static! { let mut map: HashMap<&'static str, PrivacyModeCreator> = HashMap::new(); #[cfg(windows)] { - map.insert(win_mag::PRIVACY_MODE_IMPL, || { - Box::new(win_mag::PrivacyModeImpl::default()) + if win_exclude_from_capture::is_supported() { + map.insert(win_exclude_from_capture::PRIVACY_MODE_IMPL, |impl_key: &str| { + Box::new(win_exclude_from_capture::PrivacyModeImpl::new(impl_key)) }); + } else { + map.insert(win_mag::PRIVACY_MODE_IMPL, |impl_key: &str| { + Box::new(win_mag::PrivacyModeImpl::new(impl_key)) + }); + } #[cfg(feature = "virtual_display_driver")] - map.insert(win_virtual_display::PRIVACY_MODE_IMPL, || { - Box::new(win_virtual_display::PrivacyModeImpl::default()) + map.insert(win_virtual_display::PRIVACY_MODE_IMPL, |impl_key: &str| { + Box::new(win_virtual_display::PrivacyModeImpl::new(impl_key)) }); } Arc::new(Mutex::new(map)) @@ -158,13 +173,15 @@ pub fn clear() -> Option<()> { #[inline] pub fn switch(impl_key: &str) { - let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap(); - if *cur_impl_lock == impl_key { - return; + let mut privacy_mode_lock = PRIVACY_MODE.lock().unwrap(); + if let Some(privacy_mode) = privacy_mode_lock.as_ref() { + if privacy_mode.get_impl_key() == impl_key { + return; + } } + if let Some(creator) = PRIVACY_MODE_CREATOR.lock().unwrap().get(impl_key) { - *PRIVACY_MODE.lock().unwrap() = Some(creator()); - *cur_impl_lock = impl_key.to_owned(); + *privacy_mode_lock = Some(creator(impl_key)); } } @@ -192,13 +209,15 @@ pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> // Check or switch privacy mode implementation let impl_key = get_supported_impl(impl_key); - let mut cur_impl_lock = CUR_PRIVACY_MODE_IMPL.lock().unwrap(); + let mut cur_impl_key = "".to_string(); if let Some(privacy_mode) = privacy_mode_lock.as_ref() { + cur_impl_key = privacy_mode.get_impl_key().to_string(); let check_on_conn_id = privacy_mode.check_on_conn_id(conn_id); match check_on_conn_id.as_ref() { Ok(true) => { - if *cur_impl_lock == impl_key { + if cur_impl_key == impl_key { + // Same peer, same implementation. return Some(Ok(true)); } else { // Same peer, switch to new implementation. @@ -209,7 +228,7 @@ pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> } } - if *cur_impl_lock != impl_key { + if cur_impl_key != impl_key { if let Some(creator) = PRIVACY_MODE_CREATOR .lock() .unwrap() @@ -219,8 +238,7 @@ pub fn turn_on_privacy(impl_key: &str, conn_id: i32) -> Option> privacy_mode.clear(); } - *privacy_mode_lock = Some(creator()); - *cur_impl_lock = impl_key.to_owned(); + *privacy_mode_lock = Some(creator(&impl_key)); } else { return Some(Err(anyhow!("Unsupported privacy mode: {}", impl_key))); } @@ -269,9 +287,18 @@ pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> { #[cfg(target_os = "windows")] { let mut vec_impls = Vec::new(); - if display_service::is_privacy_mode_mag_supported() { - vec_impls.push((PRIVACY_MODE_IMPL_WIN_MAG, "privacy_mode_impl_mag_tip")); + + if win_exclude_from_capture::is_supported() { + vec_impls.push(( + PRIVACY_MODE_IMPL_WIN_EXCLUDE_FROM_CAPTURE, + "privacy_mode_impl_mag_tip", + )); + } else { + if display_service::is_privacy_mode_mag_supported() { + vec_impls.push((PRIVACY_MODE_IMPL_WIN_MAG, "privacy_mode_impl_mag_tip")); + } } + #[cfg(feature = "virtual_display_driver")] if is_installed() { vec_impls.push(( @@ -279,6 +306,7 @@ pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> { "privacy_mode_impl_virtual_display_tip", )); } + vec_impls } #[cfg(not(target_os = "windows"))] @@ -287,9 +315,23 @@ pub fn get_supported_privacy_mode_impl() -> Vec<(&'static str, &'static str)> { } } +#[inline] +pub fn get_cur_impl_key() -> Option { + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map(|pm| pm.get_impl_key().to_owned()) +} + #[inline] pub fn is_current_privacy_mode_impl(impl_key: &str) -> bool { - *CUR_PRIVACY_MODE_IMPL.lock().unwrap() == impl_key + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map(|pm| pm.get_impl_key() == impl_key) + .unwrap_or(false) } #[inline] @@ -309,6 +351,7 @@ pub fn check_privacy_mode_err( display_idx: usize, timeout_millis: u64, ) -> String { + // win magnifier implementation requires a test of creating a capturer. if is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) { crate::video_service::test_create_capturer(privacy_mode_id, display_idx, timeout_millis) } else { @@ -320,3 +363,22 @@ pub fn check_privacy_mode_err( pub fn is_privacy_mode_supported() -> bool { !DEFAULT_PRIVACY_MODE_IMPL.is_empty() } + +#[inline] +pub fn get_privacy_mode_conn_id() -> Option { + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map(|pm| pm.pre_conn_id()) +} + +#[inline] +pub fn is_in_privacy_mode() -> bool { + PRIVACY_MODE + .lock() + .unwrap() + .as_ref() + .map(|pm| pm.pre_conn_id() != INVALID_PRIVACY_MODE_CONN_ID) + .unwrap_or(false) +} diff --git a/src/privacy_mode/win_exclude_from_capture.rs b/src/privacy_mode/win_exclude_from_capture.rs new file mode 100644 index 000000000..0bb81f693 --- /dev/null +++ b/src/privacy_mode/win_exclude_from_capture.rs @@ -0,0 +1,9 @@ +pub use super::win_topmost_window::PrivacyModeImpl; + +pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_exclude_from_capture"; + +pub(super) fn is_supported() -> bool { + // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setwindowdisplayaffinity + // https://en.wikipedia.org/wiki/Windows_10_version_history + crate::platform::windows::is_windows_version_or_greater(10, 0, 19041, 0, 0) +} diff --git a/src/privacy_mode/win_mag.rs b/src/privacy_mode/win_mag.rs index 1df92a32d..a93dce350 100644 --- a/src/privacy_mode/win_mag.rs +++ b/src/privacy_mode/win_mag.rs @@ -1,370 +1,11 @@ -use super::{PrivacyMode, INVALID_PRIVACY_MODE_CONN_ID}; -use crate::{platform::windows::get_user_token, privacy_mode::PrivacyModeState}; -use hbb_common::{allow_err, bail, log, ResultType}; -use std::{ - ffi::CString, - time::{Duration, Instant}, -}; -use winapi::{ - shared::{ - minwindef::FALSE, - ntdef::{HANDLE, NULL}, - windef::HWND, - }, - um::{ - errhandlingapi::GetLastError, - handleapi::CloseHandle, - libloaderapi::{GetModuleHandleA, GetProcAddress}, - memoryapi::{VirtualAllocEx, WriteProcessMemory}, - processthreadsapi::{ - CreateProcessAsUserW, QueueUserAPC, ResumeThread, TerminateProcess, - PROCESS_INFORMATION, STARTUPINFOW, - }, - winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, - winnt::{MEM_COMMIT, PAGE_READWRITE}, - winuser::*, - }, -}; +use super::win_topmost_window::PRIVACY_WINDOW_NAME; +use hbb_common::{bail, log, ResultType}; +use std::time::Instant; + +pub use super::win_topmost_window::PrivacyModeImpl; pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag"; -pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; -pub const WIN_MAG_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; -pub const INJECTED_PROCESS_EXE: &'static str = WIN_MAG_INJECTED_PROCESS_EXE; -const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow"; - -struct WindowHandlers { - hthread: u64, - hprocess: u64, -} - -impl Drop for WindowHandlers { - fn drop(&mut self) { - self.reset(); - } -} - -impl WindowHandlers { - fn reset(&mut self) { - unsafe { - if self.hprocess != 0 { - let _res = TerminateProcess(self.hprocess as _, 0); - CloseHandle(self.hprocess as _); - } - self.hprocess = 0; - if self.hthread != 0 { - CloseHandle(self.hthread as _); - } - self.hthread = 0; - } - } - - fn is_default(&self) -> bool { - self.hthread == 0 && self.hprocess == 0 - } -} - -pub struct PrivacyModeImpl { - conn_id: i32, - handlers: WindowHandlers, -} - -impl Default for PrivacyModeImpl { - fn default() -> Self { - Self { - conn_id: INVALID_PRIVACY_MODE_CONN_ID, - handlers: WindowHandlers { - hthread: 0, - hprocess: 0, - }, - } - } -} - -impl PrivacyMode for PrivacyModeImpl { - fn init(&self) -> ResultType<()> { - Ok(()) - } - - fn clear(&mut self) { - allow_err!(self.turn_off_privacy(self.conn_id, None)); - } - - fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType { - if self.check_on_conn_id(conn_id)? { - log::debug!("Privacy mode of conn {} is already on", conn_id); - return Ok(true); - } - - let exe_file = std::env::current_exe()?; - if let Some(cur_dir) = exe_file.parent() { - if !cur_dir.join("WindowInjection.dll").exists() { - return Ok(false); - } - } else { - bail!( - "Invalid exe parent for {}", - exe_file.to_string_lossy().as_ref() - ); - } - - if self.handlers.is_default() { - log::info!("turn_on_privacy, dll not found when started, try start"); - self.start()?; - std::thread::sleep(std::time::Duration::from_millis(1_000)); - } - - let hwnd = wait_find_privacy_hwnd(0)?; - if hwnd.is_null() { - bail!("No privacy window created"); - } - super::win_input::hook()?; - unsafe { - ShowWindow(hwnd as _, SW_SHOW); - } - self.conn_id = conn_id; - Ok(true) - } - - fn turn_off_privacy( - &mut self, - conn_id: i32, - state: Option, - ) -> ResultType<()> { - self.check_off_conn_id(conn_id)?; - super::win_input::unhook()?; - - unsafe { - let hwnd = wait_find_privacy_hwnd(0)?; - if !hwnd.is_null() { - ShowWindow(hwnd, SW_HIDE); - } - } - - if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { - if let Some(state) = state { - allow_err!(super::set_privacy_mode_state( - conn_id, - state, - PRIVACY_MODE_IMPL.to_string(), - 1_000 - )); - } - self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned(); - } - - Ok(()) - } - - #[inline] - fn pre_conn_id(&self) -> i32 { - self.conn_id - } -} - -impl PrivacyModeImpl { - pub fn start(&mut self) -> ResultType<()> { - if self.handlers.hprocess != 0 { - return Ok(()); - } - - log::info!("Start privacy mode window broker, check_update_broker_process"); - if let Err(e) = crate::platform::windows::check_update_broker_process() { - log::warn!( - "Failed to check update broker process. Privacy mode may not work properly. {}", - e - ); - } - - let exe_file = std::env::current_exe()?; - let Some(cur_dir) = exe_file.parent() else { - bail!("Cannot get parent of current exe file"); - }; - - let dll_file = cur_dir.join("WindowInjection.dll"); - if !dll_file.exists() { - bail!( - "Failed to find required file {}", - dll_file.to_string_lossy().as_ref() - ); - } - - let hwnd = wait_find_privacy_hwnd(1_000)?; - if !hwnd.is_null() { - log::info!("Privacy window is ready"); - return Ok(()); - } - - // let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string(); - let cmdline = cur_dir - .join(INJECTED_PROCESS_EXE) - .to_string_lossy() - .to_string(); - - unsafe { - let cmd_utf16: Vec = cmdline.encode_utf16().chain(Some(0).into_iter()).collect(); - - let mut start_info = STARTUPINFOW { - cb: 0, - lpReserved: NULL as _, - lpDesktop: NULL as _, - lpTitle: NULL as _, - dwX: 0, - dwY: 0, - dwXSize: 0, - dwYSize: 0, - dwXCountChars: 0, - dwYCountChars: 0, - dwFillAttribute: 0, - dwFlags: 0, - wShowWindow: 0, - cbReserved2: 0, - lpReserved2: NULL as _, - hStdInput: NULL as _, - hStdOutput: NULL as _, - hStdError: NULL as _, - }; - let mut proc_info = PROCESS_INFORMATION { - hProcess: NULL as _, - hThread: NULL as _, - dwProcessId: 0, - dwThreadId: 0, - }; - - let session_id = WTSGetActiveConsoleSessionId(); - let token = get_user_token(session_id, true); - if token.is_null() { - bail!("Failed to get token of current user"); - } - - let create_res = CreateProcessAsUserW( - token, - NULL as _, - cmd_utf16.as_ptr() as _, - NULL as _, - NULL as _, - FALSE, - CREATE_SUSPENDED | DETACHED_PROCESS, - NULL, - NULL as _, - &mut start_info, - &mut proc_info, - ); - CloseHandle(token); - if 0 == create_res { - bail!( - "Failed to create privacy window process {}, code {}", - cmdline, - GetLastError() - ); - }; - - inject_dll( - proc_info.hProcess, - proc_info.hThread, - dll_file.to_string_lossy().as_ref(), - )?; - - if 0xffffffff == ResumeThread(proc_info.hThread) { - // CloseHandle - CloseHandle(proc_info.hThread); - CloseHandle(proc_info.hProcess); - - bail!( - "Failed to create privacy window process, {}", - GetLastError() - ); - } - - self.handlers.hthread = proc_info.hThread as _; - self.handlers.hprocess = proc_info.hProcess as _; - - let hwnd = wait_find_privacy_hwnd(1_000)?; - if hwnd.is_null() { - bail!("Failed to get hwnd after started"); - } - } - - Ok(()) - } - - #[inline] - pub fn stop(&mut self) { - self.handlers.reset(); - } -} - -impl Drop for PrivacyModeImpl { - fn drop(&mut self) { - if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { - allow_err!(self.turn_off_privacy(self.conn_id, None)); - } - } -} - -unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> { - let dll_file_utf16: Vec = dll_file.encode_utf16().chain(Some(0).into_iter()).collect(); - - let buf = VirtualAllocEx( - hproc, - NULL as _, - dll_file_utf16.len() * 2, - MEM_COMMIT, - PAGE_READWRITE, - ); - if buf.is_null() { - bail!("Failed VirtualAllocEx"); - } - - let mut written: usize = 0; - if 0 == WriteProcessMemory( - hproc, - buf, - dll_file_utf16.as_ptr() as _, - dll_file_utf16.len() * 2, - &mut written, - ) { - bail!("Failed WriteProcessMemory"); - } - - let kernel32_modulename = CString::new("kernel32")?; - let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _); - if hmodule.is_null() { - bail!("Failed GetModuleHandleA"); - } - - let load_librarya_name = CString::new("LoadLibraryW")?; - let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _); - if load_librarya.is_null() { - bail!("Failed GetProcAddress of LoadLibraryW"); - } - - if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) { - bail!("Failed QueueUserAPC"); - } - - Ok(()) -} - -fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { - let tm_begin = Instant::now(); - let wndname = CString::new(PRIVACY_WINDOW_NAME)?; - loop { - unsafe { - let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); - if !hwnd.is_null() { - return Ok(hwnd); - } - } - - if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { - return Ok(NULL as _); - } - - std::thread::sleep(Duration::from_millis(100)); - } -} - pub fn create_capturer( privacy_mode_id: i32, origin: (i32, i32), diff --git a/src/privacy_mode/win_topmost_window.rs b/src/privacy_mode/win_topmost_window.rs new file mode 100644 index 000000000..687f59155 --- /dev/null +++ b/src/privacy_mode/win_topmost_window.rs @@ -0,0 +1,379 @@ +use super::{PrivacyMode, INVALID_PRIVACY_MODE_CONN_ID}; +use crate::{platform::windows::get_user_token, privacy_mode::PrivacyModeState}; +use hbb_common::{allow_err, bail, log, ResultType}; +use std::{ + ffi::CString, + time::{Duration, Instant}, +}; +use winapi::{ + shared::{ + minwindef::FALSE, + ntdef::{HANDLE, NULL}, + windef::HWND, + }, + um::{ + errhandlingapi::GetLastError, + handleapi::CloseHandle, + libloaderapi::{GetModuleHandleA, GetProcAddress}, + memoryapi::{VirtualAllocEx, WriteProcessMemory}, + processthreadsapi::{ + CreateProcessAsUserW, QueueUserAPC, ResumeThread, TerminateProcess, + PROCESS_INFORMATION, STARTUPINFOW, + }, + winbase::{WTSGetActiveConsoleSessionId, CREATE_SUSPENDED, DETACHED_PROCESS}, + winnt::{MEM_COMMIT, PAGE_READWRITE}, + winuser::*, + }, +}; + +pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_mag"; + +pub const ORIGIN_PROCESS_EXE: &'static str = "C:\\Windows\\System32\\RuntimeBroker.exe"; +pub const WIN_TOPMOST_INJECTED_PROCESS_EXE: &'static str = "RuntimeBroker_rustdesk.exe"; +pub const INJECTED_PROCESS_EXE: &'static str = WIN_TOPMOST_INJECTED_PROCESS_EXE; +pub(super) const PRIVACY_WINDOW_NAME: &'static str = "RustDeskPrivacyWindow"; + +struct WindowHandlers { + hthread: u64, + hprocess: u64, +} + +impl Drop for WindowHandlers { + fn drop(&mut self) { + self.reset(); + } +} + +impl WindowHandlers { + fn reset(&mut self) { + unsafe { + if self.hprocess != 0 { + let _res = TerminateProcess(self.hprocess as _, 0); + CloseHandle(self.hprocess as _); + } + self.hprocess = 0; + if self.hthread != 0 { + CloseHandle(self.hthread as _); + } + self.hthread = 0; + } + } + + fn is_default(&self) -> bool { + self.hthread == 0 && self.hprocess == 0 + } +} + +pub struct PrivacyModeImpl { + impl_key: String, + conn_id: i32, + handlers: WindowHandlers, + hwnd: u64, +} + +impl PrivacyMode for PrivacyModeImpl { + fn init(&self) -> ResultType<()> { + Ok(()) + } + + fn clear(&mut self) { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + + fn turn_on_privacy(&mut self, conn_id: i32) -> ResultType { + if self.check_on_conn_id(conn_id)? { + log::debug!("Privacy mode of conn {} is already on", conn_id); + return Ok(true); + } + + let exe_file = std::env::current_exe()?; + if let Some(cur_dir) = exe_file.parent() { + if !cur_dir.join("WindowInjection.dll").exists() { + return Ok(false); + } + } else { + bail!( + "Invalid exe parent for {}", + exe_file.to_string_lossy().as_ref() + ); + } + + if self.handlers.is_default() { + log::info!("turn_on_privacy, dll not found when started, try start"); + self.start()?; + std::thread::sleep(std::time::Duration::from_millis(1_000)); + } + + let hwnd = wait_find_privacy_hwnd(0)?; + if hwnd.is_null() { + bail!("No privacy window created"); + } + super::win_input::hook()?; + unsafe { + ShowWindow(hwnd as _, SW_SHOW); + } + self.conn_id = conn_id; + self.hwnd = hwnd as _; + Ok(true) + } + + fn turn_off_privacy( + &mut self, + conn_id: i32, + state: Option, + ) -> ResultType<()> { + self.check_off_conn_id(conn_id)?; + super::win_input::unhook()?; + + unsafe { + let hwnd = wait_find_privacy_hwnd(0)?; + if !hwnd.is_null() { + ShowWindow(hwnd, SW_HIDE); + } + } + + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + if let Some(state) = state { + allow_err!(super::set_privacy_mode_state( + conn_id, + state, + PRIVACY_MODE_IMPL.to_string(), + 1_000 + )); + } + self.conn_id = INVALID_PRIVACY_MODE_CONN_ID.to_owned(); + } + + Ok(()) + } + + #[inline] + fn pre_conn_id(&self) -> i32 { + self.conn_id + } + + #[inline] + fn get_impl_key(&self) -> &str { + &self.impl_key + } +} + +impl PrivacyModeImpl { + pub fn new(impl_key: &str) -> Self { + Self { + impl_key: impl_key.to_owned(), + conn_id: INVALID_PRIVACY_MODE_CONN_ID, + handlers: WindowHandlers { + hthread: 0, + hprocess: 0, + }, + hwnd: 0, + } + } + + #[inline] + pub fn get_hwnd(&self) -> u64 { + self.hwnd + } + + pub fn start(&mut self) -> ResultType<()> { + if self.handlers.hprocess != 0 { + return Ok(()); + } + + log::info!("Start privacy mode window broker, check_update_broker_process"); + if let Err(e) = crate::platform::windows::check_update_broker_process() { + log::warn!( + "Failed to check update broker process. Privacy mode may not work properly. {}", + e + ); + } + + let exe_file = std::env::current_exe()?; + let Some(cur_dir) = exe_file.parent() else { + bail!("Cannot get parent of current exe file"); + }; + + let dll_file = cur_dir.join("WindowInjection.dll"); + if !dll_file.exists() { + bail!( + "Failed to find required file {}", + dll_file.to_string_lossy().as_ref() + ); + } + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if !hwnd.is_null() { + log::info!("Privacy window is ready"); + return Ok(()); + } + + // let cmdline = cur_dir.join("MiniBroker.exe").to_string_lossy().to_string(); + let cmdline = cur_dir + .join(INJECTED_PROCESS_EXE) + .to_string_lossy() + .to_string(); + + unsafe { + let cmd_utf16: Vec = cmdline.encode_utf16().chain(Some(0).into_iter()).collect(); + + let mut start_info = STARTUPINFOW { + cb: 0, + lpReserved: NULL as _, + lpDesktop: NULL as _, + lpTitle: NULL as _, + dwX: 0, + dwY: 0, + dwXSize: 0, + dwYSize: 0, + dwXCountChars: 0, + dwYCountChars: 0, + dwFillAttribute: 0, + dwFlags: 0, + wShowWindow: 0, + cbReserved2: 0, + lpReserved2: NULL as _, + hStdInput: NULL as _, + hStdOutput: NULL as _, + hStdError: NULL as _, + }; + let mut proc_info = PROCESS_INFORMATION { + hProcess: NULL as _, + hThread: NULL as _, + dwProcessId: 0, + dwThreadId: 0, + }; + + let session_id = WTSGetActiveConsoleSessionId(); + let token = get_user_token(session_id, true); + if token.is_null() { + bail!("Failed to get token of current user"); + } + + let create_res = CreateProcessAsUserW( + token, + NULL as _, + cmd_utf16.as_ptr() as _, + NULL as _, + NULL as _, + FALSE, + CREATE_SUSPENDED | DETACHED_PROCESS, + NULL, + NULL as _, + &mut start_info, + &mut proc_info, + ); + CloseHandle(token); + if 0 == create_res { + bail!( + "Failed to create privacy window process {}, code {}", + cmdline, + GetLastError() + ); + }; + + inject_dll( + proc_info.hProcess, + proc_info.hThread, + dll_file.to_string_lossy().as_ref(), + )?; + + if 0xffffffff == ResumeThread(proc_info.hThread) { + // CloseHandle + CloseHandle(proc_info.hThread); + CloseHandle(proc_info.hProcess); + + bail!( + "Failed to create privacy window process, {}", + GetLastError() + ); + } + + self.handlers.hthread = proc_info.hThread as _; + self.handlers.hprocess = proc_info.hProcess as _; + + let hwnd = wait_find_privacy_hwnd(1_000)?; + if hwnd.is_null() { + bail!("Failed to get hwnd after started"); + } + } + + Ok(()) + } + + #[inline] + pub fn stop(&mut self) { + self.handlers.reset(); + } +} + +impl Drop for PrivacyModeImpl { + fn drop(&mut self) { + if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { + allow_err!(self.turn_off_privacy(self.conn_id, None)); + } + } +} + +unsafe fn inject_dll<'a>(hproc: HANDLE, hthread: HANDLE, dll_file: &'a str) -> ResultType<()> { + let dll_file_utf16: Vec = dll_file.encode_utf16().chain(Some(0).into_iter()).collect(); + + let buf = VirtualAllocEx( + hproc, + NULL as _, + dll_file_utf16.len() * 2, + MEM_COMMIT, + PAGE_READWRITE, + ); + if buf.is_null() { + bail!("Failed VirtualAllocEx"); + } + + let mut written: usize = 0; + if 0 == WriteProcessMemory( + hproc, + buf, + dll_file_utf16.as_ptr() as _, + dll_file_utf16.len() * 2, + &mut written, + ) { + bail!("Failed WriteProcessMemory"); + } + + let kernel32_modulename = CString::new("kernel32")?; + let hmodule = GetModuleHandleA(kernel32_modulename.as_ptr() as _); + if hmodule.is_null() { + bail!("Failed GetModuleHandleA"); + } + + let load_librarya_name = CString::new("LoadLibraryW")?; + let load_librarya = GetProcAddress(hmodule, load_librarya_name.as_ptr() as _); + if load_librarya.is_null() { + bail!("Failed GetProcAddress of LoadLibraryW"); + } + + if 0 == QueueUserAPC(Some(std::mem::transmute(load_librarya)), hthread, buf as _) { + bail!("Failed QueueUserAPC"); + } + + Ok(()) +} + +pub(super) fn wait_find_privacy_hwnd(msecs: u128) -> ResultType { + let tm_begin = Instant::now(); + let wndname = CString::new(PRIVACY_WINDOW_NAME)?; + loop { + unsafe { + let hwnd = FindWindowA(NULL as _, wndname.as_ptr() as _); + if !hwnd.is_null() { + return Ok(hwnd); + } + } + + if msecs == 0 || tm_begin.elapsed().as_millis() > msecs { + return Ok(NULL as _); + } + + std::thread::sleep(Duration::from_millis(100)); + } +} diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index 1d3ffa30e..8183d3275 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -35,23 +35,13 @@ struct Display { } pub struct PrivacyModeImpl { + impl_key: String, conn_id: i32, displays: Vec, virtual_displays: Vec, virtual_displays_added: Vec, } -impl Default for PrivacyModeImpl { - fn default() -> Self { - Self { - conn_id: INVALID_PRIVACY_MODE_CONN_ID, - displays: Vec::new(), - virtual_displays: Vec::new(), - virtual_displays_added: Vec::new(), - } - } -} - struct TurnOnGuard<'a> { privacy_mode: &'a mut PrivacyModeImpl, succeeded: bool, @@ -82,6 +72,16 @@ impl<'a> Drop for TurnOnGuard<'a> { } impl PrivacyModeImpl { + pub fn new(impl_key: &str) -> Self { + Self { + impl_key: impl_key.to_owned(), + conn_id: INVALID_PRIVACY_MODE_CONN_ID, + displays: Vec::new(), + virtual_displays: Vec::new(), + virtual_displays_added: Vec::new(), + } + } + // mainly from https://github.com/fufesou/rustdesk/blob/44c3a52ca8502cf53b58b59db130611778d34dbe/libs/scrap/src/dxgi/mod.rs#L365 fn set_displays(&mut self) { self.displays.clear(); @@ -431,6 +431,11 @@ impl PrivacyMode for PrivacyModeImpl { fn pre_conn_id(&self) -> i32 { self.conn_id } + + #[inline] + fn get_impl_key(&self) -> &str { + &self.impl_key + } } impl Drop for PrivacyModeImpl { diff --git a/src/server/connection.rs b/src/server/connection.rs index 419592821..12e52afba 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -508,21 +508,18 @@ impl Connection { ipc::Data::PrivacyModeState((_, state, impl_key)) => { let msg_out = match state { privacy_mode::PrivacyModeState::OffSucceeded => { - video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffSucceeded, impl_key, ) } privacy_mode::PrivacyModeState::OffByPeer => { - video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffByPeer, impl_key, ) } privacy_mode::PrivacyModeState::OffUnknown => { - video_service::set_privacy_mode_conn_id(0); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOffUnknown, impl_key, @@ -682,10 +679,10 @@ impl Connection { } } - let video_privacy_conn_id = video_service::get_privacy_mode_conn_id(); - if video_privacy_conn_id == id { - video_service::set_privacy_mode_conn_id(0); - let _ = Self::turn_off_privacy_to_msg(id); + if let Some(video_privacy_conn_id) = privacy_mode::get_privacy_mode_conn_id() { + if video_privacy_conn_id == id { + let _ = Self::turn_off_privacy_to_msg(id); + } } #[cfg(all(feature = "flutter", feature = "plugin_framework"))] #[cfg(not(any(target_os = "android", target_os = "ios")))] @@ -880,7 +877,7 @@ impl Connection { } async fn check_privacy_mode_on(&mut self) -> bool { - if video_service::get_privacy_mode_conn_id() > 0 { + if privacy_mode::is_in_privacy_mode() { self.send_login_error("Someone turns on privacy mode, exit") .await; false @@ -2610,7 +2607,23 @@ impl Connection { impl_key, ) } else { - match privacy_mode::turn_on_privacy(&impl_key, self.inner.id) { + let is_pre_privacy_on = privacy_mode::is_in_privacy_mode(); + let pre_impl_key = privacy_mode::get_cur_impl_key(); + let turn_on_res = privacy_mode::turn_on_privacy(&impl_key, self.inner.id); + + if is_pre_privacy_on { + if let Some(pre_impl_key) = pre_impl_key { + if !privacy_mode::is_current_privacy_mode_impl(&pre_impl_key) { + let off_msg = crate::common::make_privacy_mode_msg( + back_notification::PrivacyModeState::PrvOffSucceeded, + pre_impl_key, + ); + self.send(off_msg).await; + } + } + } + + match turn_on_res { Some(Ok(res)) => { if res { let err_msg = privacy_mode::check_privacy_mode_err( @@ -2619,7 +2632,6 @@ impl Connection { 5_000, ); if err_msg.is_empty() { - video_service::set_privacy_mode_conn_id(self.inner.id); crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOnSucceeded, impl_key, @@ -2629,7 +2641,6 @@ impl Connection { "Check privacy mode failed: {}, turn off privacy mode.", &err_msg ); - video_service::set_privacy_mode_conn_id(0); let _ = Self::turn_off_privacy_to_msg(self.inner.id); crate::common::make_privacy_mode_msg_with_details( back_notification::PrivacyModeState::PrvOnFailed, @@ -2646,8 +2657,10 @@ impl Connection { } Some(Err(e)) => { log::error!("Failed to turn on privacy mode. {}", e); - if video_service::get_privacy_mode_conn_id() == 0 { - let _ = Self::turn_off_privacy_to_msg(0); + if !privacy_mode::is_in_privacy_mode() { + let _ = Self::turn_off_privacy_to_msg( + privacy_mode::INVALID_PRIVACY_MODE_CONN_ID, + ); } crate::common::make_privacy_mode_msg_with_details( back_notification::PrivacyModeState::PrvOnFailed, @@ -2674,7 +2687,6 @@ impl Connection { impl_key, ) } else { - video_service::set_privacy_mode_conn_id(0); Self::turn_off_privacy_to_msg(self.inner.id) }; self.send(msg_out).await; diff --git a/src/server/video_service.rs b/src/server/video_service.rs index 509db74b0..c36c6b90d 100644 --- a/src/server/video_service.rs +++ b/src/server/video_service.rs @@ -28,6 +28,7 @@ use super::{ use crate::common::SimpleCallOnReturn; #[cfg(target_os = "linux")] use crate::platform::linux::is_x11; +use crate::privacy_mode::{get_privacy_mode_conn_id, INVALID_PRIVACY_MODE_CONN_ID}; #[cfg(windows)] use crate::{ platform::windows::is_process_consent_running, @@ -68,7 +69,6 @@ lazy_static::lazy_static! { let (tx, rx) = unbounded_channel(); (tx, Arc::new(TokioMutex::new(rx))) }; - static ref PRIVACY_MODE_CONN_ID: Mutex = Mutex::new(0); pub static ref VIDEO_QOS: Arc> = Default::default(); pub static ref IS_UAC_RUNNING: Arc> = Default::default(); pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc> = Default::default(); @@ -79,16 +79,6 @@ pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option) { FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok(); } -#[inline] -pub fn set_privacy_mode_conn_id(conn_id: i32) { - *PRIVACY_MODE_CONN_ID.lock().unwrap() = conn_id -} - -#[inline] -pub fn get_privacy_mode_conn_id() -> i32 { - *PRIVACY_MODE_CONN_ID.lock().unwrap() -} - struct VideoFrameController { cur: Instant, send_conn_ids: HashSet, @@ -251,7 +241,9 @@ pub fn test_create_capturer( // Note: This function is extremely expensive, do not call it frequently. #[cfg(windows)] fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> ResultType<()> { - if capturer_privacy_mode_id != 0 && is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) { + if capturer_privacy_mode_id != INVALID_PRIVACY_MODE_CONN_ID + && is_current_privacy_mode_impl(PRIVACY_MODE_IMPL_WIN_MAG) + { if !is_installed() { if privacy_mode_id != capturer_privacy_mode_id { if !is_process_consent_running()? { @@ -323,18 +315,19 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType ResultType ResultType<()> { - let privacy_mode_id_2 = *PRIVACY_MODE_CONN_ID.lock().unwrap(); + let privacy_mode_id_2 = get_privacy_mode_conn_id().unwrap_or(INVALID_PRIVACY_MODE_CONN_ID); if privacy_mode_id != privacy_mode_id_2 { - if privacy_mode_id_2 != 0 { + if privacy_mode_id_2 != INVALID_PRIVACY_MODE_CONN_ID { let msg_out = crate::common::make_privacy_mode_msg( back_notification::PrivacyModeState::PrvOnByOther, "".to_owned(),