From 6625aca9942601fcc97049419f2bfd82d9575f91 Mon Sep 17 00:00:00 2001 From: fufesou <13586388+fufesou@users.noreply.github.com> Date: Sun, 11 Aug 2024 19:26:41 +0800 Subject: [PATCH] fix: win, virtual display (#9023) 1. Default resolution 1920x1080. 2. Restore on conn & disconn. Signed-off-by: fufesou --- src/client/io_loop.rs | 19 ++++ src/flutter.rs | 48 ++++++++++ src/flutter_ffi.rs | 1 + src/platform/windows.rs | 104 ++++++++++++++++++++ src/privacy_mode/win_virtual_display.rs | 121 +++--------------------- src/server/connection.rs | 2 +- src/server/display_service.rs | 1 - src/virtual_display_manager.rs | 77 ++++++++------- 8 files changed, 229 insertions(+), 144 deletions(-) diff --git a/src/client/io_loop.rs b/src/client/io_loop.rs index ea2afff1d..ce32b0d26 100644 --- a/src/client/io_loop.rs +++ b/src/client/io_loop.rs @@ -937,6 +937,24 @@ impl Remote { } } + async fn send_toggle_virtual_display_msg(&self, peer: &mut Stream) { + let lc = self.handler.lc.read().unwrap(); + let displays = lc.get_option("virtual-display"); + for d in displays.split(',') { + if let Ok(index) = d.parse::() { + let mut misc = Misc::new(); + misc.set_toggle_virtual_display(ToggleVirtualDisplay { + display: index, + on: true, + ..Default::default() + }); + let mut msg_out = Message::new(); + msg_out.set_misc(misc); + allow_err!(peer.send(&msg_out).await); + } + } + } + async fn send_toggle_privacy_mode_msg(&self, peer: &mut Stream) { let lc = self.handler.lc.read().unwrap(); if lc.version >= hbb_common::get_version_number("1.2.4") @@ -1073,6 +1091,7 @@ impl Remote { self.handler.close_success(); self.handler.adapt_size(); self.send_opts_after_login(peer).await; + self.send_toggle_virtual_display_msg(peer).await; self.send_toggle_privacy_mode_msg(peer).await; } let incoming_format = CodecFormat::from(&vf); diff --git a/src/flutter.rs b/src/flutter.rs index e60063357..d408202a9 100644 --- a/src/flutter.rs +++ b/src/flutter.rs @@ -1780,6 +1780,54 @@ pub fn try_sync_peer_option( } } +pub(super) fn session_update_virtual_display(session: &FlutterSession, index: i32, on: bool) { + let virtual_display_key = "virtual-display"; + let displays = session.get_option(virtual_display_key.to_owned()); + if !on { + if index == -1 { + if !displays.is_empty() { + session.set_option(virtual_display_key.to_owned(), "".to_owned()); + } + } else { + let mut vdisplays = displays.split(',').collect::>(); + let len = vdisplays.len(); + if index == 0 { + // 0 means we cann't toggle the virtual display by index. + vdisplays.remove(vdisplays.len() - 1); + } else { + if let Some(i) = vdisplays.iter().position(|&x| x == index.to_string()) { + vdisplays.remove(i); + } + } + if vdisplays.len() != len { + session.set_option( + virtual_display_key.to_owned(), + vdisplays.join(",").to_owned(), + ); + } + } + } else { + let mut vdisplays = displays + .split(',') + .map(|x| x.to_string()) + .collect::>(); + let len = vdisplays.len(); + if index == 0 { + vdisplays.push(index.to_string()); + } else { + if !vdisplays.iter().any(|x| *x == index.to_string()) { + vdisplays.push(index.to_string()); + } + } + if vdisplays.len() != len { + session.set_option( + virtual_display_key.to_owned(), + vdisplays.join(",").to_owned(), + ); + } + } +} + // sessions mod is used to avoid the big lock of sessions' map. pub mod sessions { use std::collections::HashSet; diff --git a/src/flutter_ffi.rs b/src/flutter_ffi.rs index 7fb324b3f..d64823b8d 100644 --- a/src/flutter_ffi.rs +++ b/src/flutter_ffi.rs @@ -1568,6 +1568,7 @@ pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) { pub fn session_toggle_virtual_display(session_id: SessionID, index: i32, on: bool) { if let Some(session) = sessions::get_session_by_session_id(&session_id) { session.toggle_virtual_display(index, on); + flutter::session_update_virtual_display(&session, index, on); } } diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 51aee97a3..e11f7d110 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -2595,3 +2595,107 @@ pub fn try_set_window_foreground(window: HWND) { } } } + +pub 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 struct RegRecovery { + path: String, + key: String, + old: (Vec, isize), + new: (Vec, isize), + } + + pub 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 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 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, + } + } +} diff --git a/src/privacy_mode/win_virtual_display.rs b/src/privacy_mode/win_virtual_display.rs index 69fff6310..7e322ebbf 100644 --- a/src/privacy_mode/win_virtual_display.rs +++ b/src/privacy_mode/win_virtual_display.rs @@ -1,5 +1,5 @@ use super::{PrivacyMode, PrivacyModeState, INVALID_PRIVACY_MODE_CONN_ID, NO_PHYSICAL_DISPLAYS}; -use crate::virtual_display_manager; +use crate::{platform::windows::reg_display_settings, virtual_display_manager}; use hbb_common::{allow_err, bail, config::Config, log, ResultType}; use std::{ io::Error, @@ -150,7 +150,8 @@ impl PrivacyModeImpl { } fn restore_plug_out_monitor(&mut self) { - let _ = virtual_display_manager::plug_out_monitor_indices(&self.virtual_displays_added); + let _ = + virtual_display_manager::plug_out_monitor_indices(&self.virtual_displays_added, true); self.virtual_displays_added.clear(); } @@ -296,7 +297,7 @@ impl PrivacyModeImpl { // No physical displays, no need to use the privacy mode. if self.displays.is_empty() { - virtual_display_manager::plug_out_monitor_indices(&displays)?; + virtual_display_manager::plug_out_monitor_indices(&displays, false)?; bail!(NO_PHYSICAL_DISPLAYS); } @@ -414,8 +415,14 @@ impl PrivacyMode for PrivacyModeImpl { ) -> ResultType<()> { self.check_off_conn_id(conn_id)?; super::win_input::unhook()?; - self.restore_plug_out_monitor(); + let virtual_display_added = self.virtual_displays_added.len() > 0; + if virtual_display_added { + self.restore_plug_out_monitor(); + } restore_reg_connectivity(false); + if !virtual_display_added { + Self::commit_change_display(CDS_RESET)?; + } if self.conn_id != INVALID_PRIVACY_MODE_CONN_ID { if let Some(state) = state { @@ -462,7 +469,7 @@ pub fn restore_reg_connectivity(plug_out_monitors: bool) { return; } if plug_out_monitors { - let _ = virtual_display_manager::plug_out_monitor(-1); + let _ = virtual_display_manager::plug_out_monitor(-1, true); } if let Ok(reg_recovery) = serde_json::from_str::(&config_recovery_value) @@ -473,107 +480,3 @@ pub fn restore_reg_connectivity(plug_out_monitors: bool) { } 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, - } - } -} diff --git a/src/server/connection.rs b/src/server/connection.rs index b1af36e47..bc8c4ef0e 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -2670,7 +2670,7 @@ impl Connection { } } } else { - if let Err(e) = virtual_display_manager::plug_out_monitor(t.display) { + if let Err(e) = virtual_display_manager::plug_out_monitor(t.display, false) { log::error!("Failed to plug out virtual display {}: {}", t.display, e); self.send(make_msg(format!( "Failed to plug out virtual displays: {}", diff --git a/src/server/display_service.rs b/src/server/display_service.rs index b4abdecfa..7260b9c7a 100644 --- a/src/server/display_service.rs +++ b/src/server/display_service.rs @@ -433,7 +433,6 @@ pub fn try_get_displays_(add_amyuni_headless: bool) -> ResultType> // } let no_displays_v = no_displays(&displays); - virtual_display_manager::set_can_plug_out_all(!no_displays_v); if no_displays_v { log::debug!("no displays, create virtual display"); if let Err(e) = virtual_display_manager::plug_in_headless() { diff --git a/src/virtual_display_manager.rs b/src/virtual_display_manager.rs index 7807bd3d5..138087c75 100644 --- a/src/virtual_display_manager.rs +++ b/src/virtual_display_manager.rs @@ -1,5 +1,4 @@ use hbb_common::{bail, platform::windows::is_windows_version_or_greater, ResultType}; -use std::sync::atomic; // This string is defined here. // https://github.com/rustdesk-org/RustDeskIddDriver/blob/b370aad3f50028b039aad211df60c8051c4a64d6/RustDeskIddDriver/RustDeskIddDriver.inf#LL73C1-L73C40 @@ -10,29 +9,6 @@ const IDD_IMPL: &str = IDD_IMPL_AMYUNI; const IDD_IMPL_RUSTDESK: &str = "rustdesk_idd"; const IDD_IMPL_AMYUNI: &str = "amyuni_idd"; -const IS_CAN_PLUG_OUT_ALL_NOT_SET: i8 = 0; -const IS_CAN_PLUG_OUT_ALL_YES: i8 = 1; -const IS_CAN_PLUG_OUT_ALL_NO: i8 = 2; -static IS_CAN_PLUG_OUT_ALL: atomic::AtomicI8 = atomic::AtomicI8::new(IS_CAN_PLUG_OUT_ALL_NOT_SET); - -pub fn is_can_plug_out_all() -> bool { - IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) != IS_CAN_PLUG_OUT_ALL_NO -} - -// No need to consider concurrency here. -pub fn set_can_plug_out_all(v: bool) { - if IS_CAN_PLUG_OUT_ALL.load(atomic::Ordering::Relaxed) == IS_CAN_PLUG_OUT_ALL_NOT_SET { - IS_CAN_PLUG_OUT_ALL.store( - if v { - IS_CAN_PLUG_OUT_ALL_YES - } else { - IS_CAN_PLUG_OUT_ALL_NO - }, - atomic::Ordering::Relaxed, - ); - } -} - pub fn is_amyuni_idd() -> bool { IDD_IMPL == IDD_IMPL_AMYUNI } @@ -100,7 +76,7 @@ pub fn plug_in_monitor(idx: u32, modes: Vec) -> Re } } -pub fn plug_out_monitor(index: i32) -> ResultType<()> { +pub fn plug_out_monitor(index: i32, force_all: bool) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => { let indices = if index == -1 { @@ -110,7 +86,7 @@ pub fn plug_out_monitor(index: i32) -> ResultType<()> { }; rustdesk_idd::plug_out_peer_request(&indices) } - IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index), + IDD_IMPL_AMYUNI => amyuni_idd::plug_out_monitor(index, force_all), _ => bail!("Unsupported virtual display implementation."), } } @@ -126,12 +102,12 @@ pub fn plug_in_peer_request(modes: Vec>) -> Re } } -pub fn plug_out_monitor_indices(indices: &[u32]) -> ResultType<()> { +pub fn plug_out_monitor_indices(indices: &[u32], force_all: bool) -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::plug_out_peer_request(indices), IDD_IMPL_AMYUNI => { for _idx in indices.iter() { - amyuni_idd::plug_out_monitor(0)?; + amyuni_idd::plug_out_monitor(0, force_all)?; } Ok(()) } @@ -142,7 +118,7 @@ pub fn plug_out_monitor_indices(indices: &[u32]) -> ResultType<()> { pub fn reset_all() -> ResultType<()> { match IDD_IMPL { IDD_IMPL_RUSTDESK => rustdesk_idd::reset_all(), - IDD_IMPL_AMYUNI => crate::privacy_mode::turn_off_privacy(0, None).unwrap_or(Ok(())), + IDD_IMPL_AMYUNI => amyuni_idd::reset_all(), _ => bail!("Unsupported virtual display implementation."), } } @@ -402,7 +378,7 @@ pub mod rustdesk_idd { pub mod amyuni_idd { use super::windows; - use crate::platform::win_device; + use crate::platform::{reg_display_settings, win_device}; use hbb_common::{bail, lazy_static, log, tokio::time::Instant, ResultType}; use std::{ ptr::null_mut, @@ -532,6 +508,13 @@ pub mod amyuni_idd { Ok(()) } + pub fn reset_all() -> ResultType<()> { + let _ = crate::privacy_mode::turn_off_privacy(0, None); + let _ = plug_out_monitor(-1, true); + *LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap() = None; + Ok(()) + } + #[inline] fn plug_monitor_(add: bool) -> Result<(), win_device::DeviceError> { let cmd = if add { 0x10 } else { 0x00 }; @@ -547,6 +530,7 @@ pub mod amyuni_idd { fn plug_in_monitor_(add: bool, is_driver_async_installed: bool) -> ResultType<()> { let timeout = Duration::from_secs(3); let now = Instant::now(); + let reg_connectivity_old = reg_display_settings::read_reg_connectivity(); loop { match plug_monitor_(add) { Ok(_) => { @@ -567,9 +551,36 @@ pub mod amyuni_idd { } } } + // Workaround for the issue that we can't set the default the resolution. + if let Ok(old_connectivity_old) = reg_connectivity_old { + std::thread::spawn(move || { + try_reset_resolution_on_first_plug_in(old_connectivity_old.len(), 1920, 1080); + }); + } + Ok(()) } + fn try_reset_resolution_on_first_plug_in( + old_connectivity_len: usize, + width: usize, + height: usize, + ) { + for _ in 0..10 { + std::thread::sleep(Duration::from_millis(300)); + if let Ok(reg_connectivity_new) = reg_display_settings::read_reg_connectivity() { + if reg_connectivity_new.len() != old_connectivity_len { + for name in + windows::get_device_names(Some(super::AMYUNI_IDD_DEVICE_STRING)).iter() + { + crate::platform::change_resolution(&name, width, height).ok(); + } + break; + } + } + } + } + pub fn plug_in_headless() -> ResultType<()> { let mut tm = LAST_PLUG_IN_HEADLESS_TIME.lock().unwrap(); if let Some(tm) = &mut *tm { @@ -603,7 +614,7 @@ pub mod amyuni_idd { plug_in_monitor_(true, is_async) } - pub fn plug_out_monitor(index: i32) -> ResultType<()> { + pub fn plug_out_monitor(index: i32, force_all: bool) -> ResultType<()> { let all_count = windows::get_device_names(None).len(); let amyuni_count = get_monitor_count(); let mut to_plug_out_count = match all_count { @@ -612,7 +623,7 @@ pub mod amyuni_idd { if amyuni_count == 0 { bail!("No virtual displays to plug out.") } else { - if super::is_can_plug_out_all() { + if force_all { 1 } else { bail!("This only virtual display cannot be pulled out.") @@ -621,7 +632,7 @@ pub mod amyuni_idd { } _ => { if all_count == amyuni_count { - if super::is_can_plug_out_all() { + if force_all { all_count } else { all_count - 1