fix: win, privacy mode 2 (#12123)

* fix: win, privacy mode 2

Signed-off-by: fufesou <linlong1266@gmail.com>

* Update src/privacy_mode/win_virtual_display.rs

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Signed-off-by: fufesou <linlong1266@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
fufesou
2025-06-19 18:39:15 +08:00
committed by GitHub
parent 590ecc43ff
commit d6ba063655
4 changed files with 92 additions and 26 deletions

View File

@@ -345,7 +345,7 @@ pub fn core_main() -> Option<Vec<String>> {
hbb_common::allow_err!(crate::run_me(vec!["--tray"]));
}
#[cfg(windows)]
crate::privacy_mode::restore_reg_connectivity(true);
crate::privacy_mode::restore_reg_connectivity(true, false);
#[cfg(any(target_os = "linux", target_os = "windows"))]
{
crate::start_server(true, false);

View File

@@ -3010,16 +3010,21 @@ pub mod reg_display_settings {
None
}
pub fn restore_reg_connectivity(reg_recovery: RegRecovery) -> ResultType<()> {
pub fn restore_reg_connectivity(reg_recovery: RegRecovery, force: bool) -> ResultType<()> {
let hklm = winreg::RegKey::predef(HKEY_LOCAL_MACHINE);
let reg_item = hklm.open_subkey_with_flags(&reg_recovery.path, KEY_READ | KEY_WRITE)?;
let cur_reg_value = reg_item.get_raw_value(&reg_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(());
if !force {
let cur_reg_value = reg_item.get_raw_value(&reg_recovery.key)?;
let new_reg_value = RegValue {
bytes: reg_recovery.new.0,
vtype: isize_to_reg_type(reg_recovery.new.1),
};
// Compare if the current value is the same as the new value.
// If they are not the same, the registry value has been changed by other processes.
// So we do not restore the registry value.
if cur_reg_value != new_reg_value {
return Ok(());
}
}
let reg_value = RegValue {
bytes: reg_recovery.old.0,

View File

@@ -219,9 +219,10 @@ async fn turn_on_privacy_async(impl_key: String, conn_id: i32) -> Option<ResultT
let res = turn_on_privacy_sync(&impl_key, conn_id);
let _ = tx.send(res);
});
// Wait at most 5 seconds for the result.
// Wait at most 7.5 seconds for the result.
// Because it may take a long time to turn on the privacy mode with amyuni idd.
match hbb_common::timeout(5000, rx).await {
// Some laptops may take time to plug in a virtual display.
match hbb_common::timeout(7500, rx).await {
Ok(res) => match res {
Ok(res) => res,
Err(e) => Some(Err(anyhow!(e.to_string()))),

View File

@@ -172,6 +172,7 @@ impl PrivacyModeImpl {
}
fn set_primary_display(&mut self) -> ResultType<String> {
// Multiple virtual displays with different origins are tested.
let display = &self.virtual_displays[0];
let display_name = std::string::String::from_utf16(&display.name)?;
@@ -194,9 +195,32 @@ impl PrivacyModeImpl {
);
}
// Windows 24H2 requires the virtual display to be set first.
// No idea why, maybe the same issue: https://developercommunity.visualstudio.com/t/Windows-11-Enterprise-24H2-using-WinApi/10851936?sort=newest
let flags = CDS_UPDATEREGISTRY | CDS_NORESET;
let offx = new_primary_dm.u1.s2().dmPosition.x;
let offy = new_primary_dm.u1.s2().dmPosition.y;
new_primary_dm.u1.s2_mut().dmPosition.x = 0;
new_primary_dm.u1.s2_mut().dmPosition.y = 0;
new_primary_dm.dmFields |= DM_POSITION;
let rc = ChangeDisplaySettingsExW(
display.name.as_ptr(),
&mut new_primary_dm,
NULL as _,
flags | CDS_SET_PRIMARY,
NULL,
);
if rc != DISP_CHANGE_SUCCESSFUL {
let err = Self::change_display_settings_ex_err_msg(rc);
log::error!(
"Failed ChangeDisplaySettingsEx, the virtual display, {}",
&err
);
bail!("Failed ChangeDisplaySettingsEx, {}", err);
}
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::<DISPLAY_DEVICEW>() as _;
@@ -209,9 +233,9 @@ impl PrivacyModeImpl {
if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0 {
continue;
}
// Skip the virtual display.
if dd.DeviceName == display.name {
flags |= CDS_SET_PRIMARY;
continue;
}
#[allow(invalid_value)]
@@ -228,8 +252,8 @@ impl PrivacyModeImpl {
);
}
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.u1.s2_mut().dmPosition.x -= offx;
dm.u1.s2_mut().dmPosition.y -= offy;
dm.dmFields |= DM_POSITION;
let rc = ChangeDisplaySettingsExW(
dd.DeviceName.as_ptr(),
@@ -263,6 +287,9 @@ impl PrivacyModeImpl {
Ok(display_name)
}
// NOTE: We can't detect if the other virtual displays are physical displays or not.
// We can only use `DeviceString` == `virtual_display_manager::get_cur_device_string()` to detect if the display is a virtual display.
// The other virtual displays can't be restored after exiting the privacy mode on Windows 24H2.
fn disable_physical_displays(&self) -> ResultType<()> {
for display in &self.displays {
let mut dm = display.dm.clone();
@@ -303,21 +330,34 @@ impl PrivacyModeImpl {
}]
}
pub fn ensure_virtual_display(&mut self) -> ResultType<()> {
// This function will wait at most 6 seconds for the virtual displays to be ready.
// It's ok to wait, because:
// 1. A new thread is created to handle the async privacy mode.
// 2. The user is usually not in a hurry to turn on the privacy mode.
pub fn ensure_virtual_display(&mut self, is_async_mode: bool) -> ResultType<()> {
if self.virtual_displays.is_empty() {
let displays =
virtual_display_manager::plug_in_peer_request(vec![Self::default_display_modes()])?;
if virtual_display_manager::is_amyuni_idd() {
thread::sleep(Duration::from_secs(3));
if is_async_mode {
thread::sleep(Duration::from_secs(1));
}
self.set_displays();
// No physical displays, no need to use the privacy mode.
if self.displays.is_empty() {
virtual_display_manager::plug_out_monitor_indices(&displays, false, false)?;
bail!(NO_PHYSICAL_DISPLAYS);
}
if is_async_mode {
let now = std::time::Instant::now();
while self.virtual_displays.is_empty()
&& now.elapsed() < Duration::from_millis(5000)
{
thread::sleep(Duration::from_millis(500));
self.set_displays();
}
}
self.virtual_displays_added.extend(displays);
}
@@ -364,9 +404,22 @@ impl PrivacyModeImpl {
Self::restore_displays(&self.displays);
Self::restore_displays(&self.virtual_displays);
allow_err!(Self::commit_change_display(0));
self.restore_plug_out_monitor();
self.displays.clear();
self.virtual_displays.clear();
let is_virtual_display_added = self.virtual_displays_added.len() > 0;
if is_virtual_display_added {
self.restore_plug_out_monitor();
} else {
// https://github.com/rustdesk/rustdesk/pull/12114#issuecomment-2983054370
// No virtual displays added, we need to change the display combination to force the display settings to be reloaded.
// This function changes the user behavior of the virtual displays.
// But it makes the privacy mode more stable.
// No need to restore the virtual displays. It's easy to notice that the virtual displays are plugged out.
let _ = virtual_display_manager::plug_out_monitor(-1, true, false);
// We can't replug the virtual dislays here.
// TODO: plug out + plug in the virtual displays (`IDD_IMPL_AMYUNI`) in a short time makes the server side crash.
}
}
fn restore_displays(displays: &[Display]) {
@@ -418,12 +471,13 @@ impl PrivacyMode for PrivacyModeImpl {
bail!(NO_PHYSICAL_DISPLAYS);
}
let is_async_mode = self.is_async_privacy_mode();
let mut guard = TurnOnGuard {
privacy_mode: self,
succeeded: false,
};
guard.ensure_virtual_display()?;
guard.ensure_virtual_display(is_async_mode)?;
if guard.virtual_displays.is_empty() {
log::debug!("No virtual displays");
bail!("No virtual displays.");
@@ -434,7 +488,11 @@ impl PrivacyMode for PrivacyModeImpl {
guard.disable_physical_displays()?;
Self::commit_change_display(CDS_RESET)?;
// Explicitly set the resolution(virtual display) to 1920x1080.
allow_err!(crate::platform::change_resolution(&primary_display_name, 1920, 1080));
allow_err!(crate::platform::change_resolution(
&primary_display_name,
1920,
1080
));
let reg_connectivity_2 = reg_display_settings::read_reg_connectivity()?;
if let Some(reg_recovery) =
@@ -466,7 +524,9 @@ impl PrivacyMode for PrivacyModeImpl {
super::win_input::unhook()?;
let _tmp_ignore_changed_holder = crate::display_service::temp_ignore_displays_changed();
self.restore();
restore_reg_connectivity(false);
// We need to force restore the registry connectivity.
// This is because the registry connection may be changed by `self.restore()`, but will not be fully restored.
restore_reg_connectivity(false, true);
if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID {
if let Some(state) = state {
@@ -507,7 +567,7 @@ fn reset_config_reg_connectivity() {
Config::set_option(CONFIG_KEY_REG_RECOVERY.to_owned(), "".to_owned());
}
pub fn restore_reg_connectivity(plug_out_monitors: bool) {
pub fn restore_reg_connectivity(plug_out_monitors: bool, force: bool) {
let config_recovery_value = Config::get_option(CONFIG_KEY_REG_RECOVERY);
if config_recovery_value.is_empty() {
return;
@@ -518,7 +578,7 @@ pub fn restore_reg_connectivity(plug_out_monitors: bool) {
if let Ok(reg_recovery) =
serde_json::from_str::<reg_display_settings::RegRecovery>(&config_recovery_value)
{
if let Err(e) = reg_display_settings::restore_reg_connectivity(reg_recovery) {
if let Err(e) = reg_display_settings::restore_reg_connectivity(reg_recovery, force) {
log::error!("Failed restore_reg_connectivity, error: {}", e);
}
}