Merge pull request #6470 from fufesou/feat/topmost_window_exclude_from_capture

Feat/topmost window exclude from capture
This commit is contained in:
RustDesk 2023-11-20 12:46:23 +08:00 committed by GitHub
commit b535722421
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 648 additions and 478 deletions

View File

@ -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'],
}
}

View File

@ -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)) {

View File

@ -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();

View File

@ -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 (<domain>:<port>).\nIf you want to access a device on another server, please append the server address (<id>@<server_address>?key=<key_value>), for example,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nIf you want to access a device on a public server, please input \"<id>@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();
}

View File

@ -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 (<dominio>:<puerto>).\nSi quieres acceder a un dispositivo en otro servidor, por favor añade la ip del servidor (<id>@<dirección_servidor>?key=<clave_valor>), por ejemplo,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSi quieres acceder a un dispositivo en un servidor público, por favor, introduce \"<id>@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();

View File

@ -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 (<domain>:<port>).\nJika anda ingin mengakses perangkat lain yang berbeda server, tambahkan alamat server setelah penulisan ID(<id>@<server_address>?key=<key_value>), sebagai contoh,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJika anda ingin mengakses perangkat yang menggunakan server publik, masukkan \"<id>@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();

View File

@ -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 (<dominio>:<porta>).\nSe vuoi accedere as un dispositivo in un altro server, aggiungi l'indirizzo del server (<id>@<indirizzo_server >?key=<valore_chiave>), ad esempio\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nSe vuoi accedere as un dispositivo in un server pubblico, inserisci \"<id>@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();

View File

@ -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, 도메인과 포트(<domain>:<port>)를 입력할 수 있습니다.\n다른 서버에 있는 장치에 접속하려면 서버 주소(<id>@<server_address>?key=<key_value>)를 추가하세요"),
("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();

View File

@ -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 (<domēns>:<ports>).\nJa vēlaties piekļūt ierīcei citā serverī, lūdzu, pievienojiet servera adresi (<id>@<servera_adrese>?key=<atslēgas_vērtība>), piemēram,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJa vēlaties piekļūt ierīcei publiskajā serverī, lūdzu, ievadiet \"<id>@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();

View File

@ -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 (<adres_domenowy>:<port>).\nJeżeli chcesz uzyskać dostęp do urządzenia na innym serwerze, dołącz adres serwera (<id>@<adres_serwera>?key=<wartość_klucza>, np. \n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nJeżeli chcesz uzyskać dostęp do urządzenia na serwerze publicznym, wpisz \"<id>@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();

View File

@ -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Если необходимо получить доступ к устройству на другом сервере, добавьте адрес сервера (<id>@<адрес_сервера>?key=<ключ_значение>), например:\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЕсли необходимо получить доступ к устройству на общедоступном сервере, введите \"<id>@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();

View File

@ -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Якщо ви хочете отримати доступ до пристрою на іншому сервері, будь ласка, додайте адресу сервера (<id>@<адреса_сервера>?key=<значення_ключа>), наприклад,\n9123456234@192.168.16.1:21117?key=5Qbwsde3unUcJBtrx9ZkvUmwFNoExHzpryHuPUdqlWM=.\nЯкщо ви хочете отримати доступ до пристрою на публічному сервері, будь ласка, введіть \"<id>@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();

View File

@ -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"
} // 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);
}
}

View File

@ -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<String>
{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());
}

View File

@ -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<bool> {
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<Mutex<String>> = {
static ref PRIVACY_MODE: Arc<Mutex<Option<Box<dyn PrivacyMode>>>> = {
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<Mutex<Option<Box<dyn PrivacyMode>>>> = {
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<dyn PrivacyMode>;
pub type PrivacyModeCreator = fn(impl_key: &str) -> Box<dyn PrivacyMode>;
lazy_static::lazy_static! {
static ref PRIVACY_MODE_CREATOR: Arc<Mutex<HashMap<&'static str, PrivacyModeCreator>>> = {
#[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<ResultType<bool>>
// 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<ResultType<bool>>
}
}
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<ResultType<bool>>
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<String> {
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<i32> {
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)
}

View File

@ -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)
}

View File

@ -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<bool> {
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<PrivacyModeState>,
) -> 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<u16> = 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<u16> = 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<HWND> {
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),

View File

@ -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<bool> {
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<PrivacyModeState>,
) -> 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<u16> = 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<u16> = 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<HWND> {
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));
}
}

View File

@ -35,23 +35,13 @@ struct Display {
}
pub struct PrivacyModeImpl {
impl_key: String,
conn_id: i32,
displays: Vec<Display>,
virtual_displays: Vec<Display>,
virtual_displays_added: Vec<u32>,
}
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 {

View File

@ -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;

View File

@ -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<i32> = Mutex::new(0);
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
@ -79,16 +79,6 @@ pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) {
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<i32>,
@ -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<Ca
&name,
);
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
let privacy_mode_id = get_privacy_mode_conn_id().unwrap_or(INVALID_PRIVACY_MODE_CONN_ID);
#[cfg(not(windows))]
let capturer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
let mut capturer_privacy_mode_id = privacy_mode_id;
#[cfg(windows)]
{
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 is_process_consent_running()? {
capturer_privacy_mode_id = 0;
capturer_privacy_mode_id = INVALID_PRIVACY_MODE_CONN_ID;
}
}
}
@ -344,7 +337,7 @@ fn get_capturer(current: usize, portable_service_running: bool) -> ResultType<Ca
capturer_privacy_mode_id,
);
if privacy_mode_id != 0 {
if privacy_mode_id != INVALID_PRIVACY_MODE_CONN_ID {
if privacy_mode_id != capturer_privacy_mode_id {
log::info!("In privacy mode, but show UAC prompt window for now");
} else {
@ -658,9 +651,9 @@ fn get_recorder(
}
fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> 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(),