use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_DISPLAYS}; use crate::virtual_display_manager; use hbb_common::{allow_err, bail, config::Config, log, ResultType}; use std::ops::{Deref, DerefMut}; use virtual_display::MonitorMode; use winapi::{ shared::{ minwindef::{DWORD, FALSE}, ntdef::{NULL, WCHAR}, }, um::{ errhandlingapi::GetLastError, wingdi::{ DEVMODEW, DISPLAY_DEVICEW, DISPLAY_DEVICE_ACTIVE, DISPLAY_DEVICE_ATTACHED_TO_DESKTOP, DISPLAY_DEVICE_MIRRORING_DRIVER, DISPLAY_DEVICE_PRIMARY_DEVICE, DM_POSITION, }, winuser::{ ChangeDisplaySettingsExW, EnumDisplayDevicesW, EnumDisplaySettingsExW, EnumDisplaySettingsW, CDS_NORESET, CDS_RESET, CDS_SET_PRIMARY, CDS_UPDATEREGISTRY, DISP_CHANGE_SUCCESSFUL, EDD_GET_DEVICE_INTERFACE_NAME, ENUM_CURRENT_SETTINGS, ENUM_REGISTRY_SETTINGS, }, }, }; pub(super) const PRIVACY_MODE_IMPL: &str = "privacy_mode_impl_virtual_display"; const IDD_DEVICE_STRING: &'static str = "RustDeskIddDriver Device\0"; const CONFIG_KEY_REG_RECOVERY: &str = "reg_recovery"; struct Display { dm: DEVMODEW, name: [WCHAR; 32], primary: bool, } pub struct PrivacyModeImpl { impl_key: String, conn_id: i32, displays: Vec, virtual_displays: Vec, virtual_displays_added: Vec, } struct TurnOnGuard<'a> { privacy_mode: &'a mut PrivacyModeImpl, succeeded: bool, } impl<'a> Deref for TurnOnGuard<'a> { type Target = PrivacyModeImpl; fn deref(&self) -> &Self::Target { self.privacy_mode } } impl<'a> DerefMut for TurnOnGuard<'a> { fn deref_mut(&mut self) -> &mut Self::Target { self.privacy_mode } } impl<'a> Drop for TurnOnGuard<'a> { fn drop(&mut self) { if !self.succeeded { self.privacy_mode .turn_off_privacy(INVALID_PRIVACY_MODE_CONN_ID, None) .ok(); } } } 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(); self.virtual_displays.clear(); let mut i: DWORD = 0; loop { #[allow(invalid_value)] let mut dd: DISPLAY_DEVICEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; dd.cb = std::mem::size_of::() as _; let ok = unsafe { EnumDisplayDevicesW(std::ptr::null(), i, &mut dd as _, 0) }; if ok == FALSE { break; } i += 1; if 0 == (dd.StateFlags & DISPLAY_DEVICE_ACTIVE) || (dd.StateFlags & DISPLAY_DEVICE_MIRRORING_DRIVER) > 0 { continue; } #[allow(invalid_value)] let mut dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; dm.dmSize = std::mem::size_of::() as _; dm.dmDriverExtra = 0; unsafe { if FALSE == EnumDisplaySettingsExW( dd.DeviceName.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm as _, 0, ) { if FALSE == EnumDisplaySettingsExW( dd.DeviceName.as_ptr(), ENUM_REGISTRY_SETTINGS, &mut dm as _, 0, ) { continue; } } } let primary = (dd.StateFlags & DISPLAY_DEVICE_PRIMARY_DEVICE) > 0; let display = Display { dm, name: dd.DeviceName, primary, }; if let Ok(s) = std::string::String::from_utf16(&dd.DeviceString) { if &s[..IDD_DEVICE_STRING.len()] == IDD_DEVICE_STRING { self.virtual_displays.push(display); continue; } } self.displays.push(display); } } fn restore(&mut self) { Self::restore_displays(&self.displays); Self::restore_displays(&self.virtual_displays); allow_err!(Self::commit_change_display(0)); self.restore_plug_out_monitor(); } fn restore_plug_out_monitor(&mut self) { let _ = virtual_display_manager::plug_out_peer_request(&self.virtual_displays_added); self.virtual_displays_added.clear(); } fn restore_displays(displays: &[Display]) { for display in displays { unsafe { let mut dm = display.dm.clone(); let flags = if display.primary { CDS_NORESET | CDS_UPDATEREGISTRY | CDS_SET_PRIMARY } else { CDS_NORESET | CDS_UPDATEREGISTRY }; ChangeDisplaySettingsExW( display.name.as_ptr(), &mut dm, std::ptr::null_mut(), flags, std::ptr::null_mut(), ); } } } fn set_primary_display(&mut self) -> ResultType<()> { let display = &self.virtual_displays[0]; #[allow(invalid_value)] let mut new_primary_dm: DEVMODEW = unsafe { std::mem::MaybeUninit::uninit().assume_init() }; new_primary_dm.dmSize = std::mem::size_of::() as _; new_primary_dm.dmDriverExtra = 0; unsafe { if FALSE == EnumDisplaySettingsW( display.name.as_ptr(), ENUM_CURRENT_SETTINGS, &mut new_primary_dm, ) { bail!( "Failed EnumDisplaySettingsW, device name: {:?}, error code: {}", std::string::String::from_utf16(&display.name), GetLastError() ); } let mut i: DWORD = 0; loop { let mut flags = CDS_UPDATEREGISTRY | CDS_NORESET; #[allow(invalid_value)] let mut dd: DISPLAY_DEVICEW = std::mem::MaybeUninit::uninit().assume_init(); dd.cb = std::mem::size_of::() as _; if FALSE == EnumDisplayDevicesW(NULL as _, i, &mut dd, EDD_GET_DEVICE_INTERFACE_NAME) { break; } i += 1; if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0 { continue; } if dd.DeviceName == display.name { flags |= CDS_SET_PRIMARY; } #[allow(invalid_value)] let mut dm: DEVMODEW = std::mem::MaybeUninit::uninit().assume_init(); dm.dmSize = std::mem::size_of::() as _; dm.dmDriverExtra = 0; if FALSE == EnumDisplaySettingsW(dd.DeviceName.as_ptr(), ENUM_CURRENT_SETTINGS, &mut dm) { bail!( "Failed EnumDisplaySettingsW, device name: {:?}, error code: {}", std::string::String::from_utf16(&dd.DeviceName), GetLastError() ); } dm.u1.s2_mut().dmPosition.x -= new_primary_dm.u1.s2().dmPosition.x; dm.u1.s2_mut().dmPosition.y -= new_primary_dm.u1.s2().dmPosition.y; dm.dmFields |= DM_POSITION; let rc = ChangeDisplaySettingsExW( dd.DeviceName.as_ptr(), &mut dm, NULL as _, flags, NULL, ); if rc != DISP_CHANGE_SUCCESSFUL { log::error!( "Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}", std::string::String::from_utf16(&dd.DeviceName), flags, rc ); bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc); } } } Ok(()) } fn disable_physical_displays(&self) -> ResultType<()> { for display in &self.displays { let mut dm = display.dm.clone(); unsafe { dm.u1.s2_mut().dmPosition.x = 10000; dm.u1.s2_mut().dmPosition.y = 10000; dm.dmPelsHeight = 0; dm.dmPelsWidth = 0; let flags = CDS_UPDATEREGISTRY | CDS_NORESET; let rc = ChangeDisplaySettingsExW( display.name.as_ptr(), &mut dm, NULL as _, flags, NULL as _, ); if rc != DISP_CHANGE_SUCCESSFUL { log::error!( "Failed ChangeDisplaySettingsEx, device name: {:?}, flags: {}, ret: {}", std::string::String::from_utf16(&display.name), flags, rc ); bail!("Failed ChangeDisplaySettingsEx, ret: {}", rc); } } } Ok(()) } #[inline] fn default_display_modes() -> Vec { vec![MonitorMode { width: 1920, height: 1080, sync: 60, }] } pub fn ensure_virtual_display(&mut self) -> ResultType<()> { if self.virtual_displays.is_empty() { let displays = virtual_display_manager::plug_in_peer_request(vec![Self::default_display_modes()])?; self.virtual_displays_added.extend(displays); self.set_displays(); } Ok(()) } #[inline] fn commit_change_display(flags: DWORD) -> ResultType<()> { unsafe { // use winapi::{ // shared::windef::HDESK, // um::{ // processthreadsapi::GetCurrentThreadId, // winnt::MAXIMUM_ALLOWED, // winuser::{CloseDesktop, GetThreadDesktop, OpenInputDesktop, SetThreadDesktop}, // }, // }; // let mut desk_input: HDESK = NULL as _; // let desk_current: HDESK = GetThreadDesktop(GetCurrentThreadId()); // if !desk_current.is_null() { // desk_input = OpenInputDesktop(0, FALSE, MAXIMUM_ALLOWED); // if desk_input.is_null() { // SetThreadDesktop(desk_input); // } // } let ret = ChangeDisplaySettingsExW(NULL as _, NULL as _, NULL as _, flags, NULL as _); if ret != DISP_CHANGE_SUCCESSFUL { bail!("Failed ChangeDisplaySettingsEx, ret: {}", ret); } // if !desk_current.is_null() { // SetThreadDesktop(desk_current); // } // if !desk_input.is_null() { // CloseDesktop(desk_input); // } } Ok(()) } } 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); } self.set_displays(); if self.displays.is_empty() { log::debug!("No displays"); bail!(NO_DISPLAYS); } let mut guard = TurnOnGuard { privacy_mode: self, succeeded: false, }; guard.ensure_virtual_display()?; if guard.virtual_displays.is_empty() { log::debug!("No virtual displays"); bail!("No virtual displays"); } let reg_connectivity_1 = reg_display_settings::read_reg_connectivity()?; guard.set_primary_display()?; guard.disable_physical_displays()?; Self::commit_change_display(CDS_RESET)?; let reg_connectivity_2 = reg_display_settings::read_reg_connectivity()?; if let Some(reg_recovery) = reg_display_settings::diff_recent_connectivity(reg_connectivity_1, reg_connectivity_2) { Config::set_option( CONFIG_KEY_REG_RECOVERY.to_owned(), serde_json::to_string(®_recovery)?, ); } else { reset_config_reg_connectivity(); }; // OpenInputDesktop and block the others' input ? guard.conn_id = conn_id; guard.succeeded = true; allow_err!(super::win_input::hook()); 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()?; self.restore(); restore_reg_connectivity(); 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 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)); } } } #[inline] fn reset_config_reg_connectivity() { Config::set_option(CONFIG_KEY_REG_RECOVERY.to_owned(), "".to_owned()); } pub fn restore_reg_connectivity() { let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY); if config_recovery_value.is_empty() { return; } if let Ok(reg_recovery) = serde_json::from_str::(&config_recovery_value) { if let Err(e) = reg_display_settings::restore_reg_connectivity(reg_recovery) { log::error!("Failed restore_reg_connectivity, error: {}", e); } } reset_config_reg_connectivity(); } mod reg_display_settings { use hbb_common::ResultType; use serde_derive::{Deserialize, Serialize}; use std::collections::HashMap; use winreg::{enums::*, RegValue}; const REG_GRAPHICS_DRIVERS_PATH: &str = "SYSTEM\\CurrentControlSet\\Control\\GraphicsDrivers"; const REG_CONNECTIVITY_PATH: &str = "Connectivity"; #[derive(Serialize, Deserialize, Debug)] pub(super) struct RegRecovery { path: String, key: String, old: (Vec, isize), new: (Vec, isize), } pub(super) fn read_reg_connectivity() -> ResultType>> { let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); let reg_connectivity = hklm.open_subkey_with_flags( format!("{}\\{}", REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH), KEY_READ, )?; let mut map_connectivity = HashMap::new(); for key in reg_connectivity.enum_keys() { let key = key?; let mut map_item = HashMap::new(); let reg_item = reg_connectivity.open_subkey_with_flags(&key, KEY_READ)?; for value in reg_item.enum_values() { let (name, value) = value?; map_item.insert(name, value); } map_connectivity.insert(key, map_item); } Ok(map_connectivity) } pub(super) fn diff_recent_connectivity( map1: HashMap>, map2: HashMap>, ) -> Option { for (subkey, map_item2) in map2 { if let Some(map_item1) = map1.get(&subkey) { let key = "Recent"; if let Some(value1) = map_item1.get(key) { if let Some(value2) = map_item2.get(key) { if value1 != value2 { return Some(RegRecovery { path: format!( "{}\\{}\\{}", REG_GRAPHICS_DRIVERS_PATH, REG_CONNECTIVITY_PATH, subkey ), key: key.to_owned(), old: (value1.bytes.clone(), value1.vtype.clone() as isize), new: (value2.bytes.clone(), value2.vtype.clone() as isize), }); } } } } } None } pub(super) fn restore_reg_connectivity(reg_recovery: RegRecovery) -> ResultType<()> { let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE); let reg_item = hklm.open_subkey_with_flags(®_recovery.path, KEY_READ | KEY_WRITE)?; let cur_reg_value = reg_item.get_raw_value(®_recovery.key)?; let new_reg_value = RegValue { bytes: reg_recovery.new.0, vtype: isize_to_reg_type(reg_recovery.new.1), }; if cur_reg_value != new_reg_value { return Ok(()); } let reg_value = RegValue { bytes: reg_recovery.old.0, vtype: isize_to_reg_type(reg_recovery.old.1), }; reg_item.set_raw_value(®_recovery.key, ®_value)?; Ok(()) } #[inline] fn isize_to_reg_type(i: isize) -> RegType { match i { 0 => RegType::REG_NONE, 1 => RegType::REG_SZ, 2 => RegType::REG_EXPAND_SZ, 3 => RegType::REG_BINARY, 4 => RegType::REG_DWORD, 5 => RegType::REG_DWORD_BIG_ENDIAN, 6 => RegType::REG_LINK, 7 => RegType::REG_MULTI_SZ, 8 => RegType::REG_RESOURCE_LIST, 9 => RegType::REG_FULL_RESOURCE_DESCRIPTOR, 10 => RegType::REG_RESOURCE_REQUIREMENTS_LIST, 11 => RegType::REG_QWORD, _ => RegType::REG_NONE, } } }