use super::*; #[cfg(target_os = "linux")] use crate::platform::linux::is_x11; #[cfg(windows)] use crate::virtual_display_manager; #[cfg(windows)] use hbb_common::get_version_number; use hbb_common::protobuf::MessageField; use scrap::Display; // https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call pub const NAME: &'static str = "display"; #[cfg(windows)] const DUMMY_DISPLAY_SIDE_MAX_SIZE: usize = 1024; struct ChangedResolution { original: (i32, i32), changed: (i32, i32), } lazy_static::lazy_static! { static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported(); static ref CHANGED_RESOLUTIONS: Arc>> = Default::default(); // Initial primary display index. // It should not be updated when displays changed. pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary(); static ref SYNC_DISPLAYS: Arc> = Default::default(); } #[derive(Default)] struct SyncDisplaysInfo { displays: Vec, is_synced: bool, } impl SyncDisplaysInfo { fn check_changed(&mut self, displays: Vec) { if self.displays.len() != displays.len() { self.displays = displays; self.is_synced = false; return; } for (i, d) in displays.iter().enumerate() { if d != &self.displays[i] { self.displays = displays; self.is_synced = false; return; } } } fn get_update_sync_displays(&mut self) -> Option> { if self.is_synced { return None; } self.is_synced = true; Some(self.displays.clone()) } } // This function is really useful, though a duplicate check if display changed. // The video server will then send the following messages to the client: // 1. the supported resolutions of the {idx} display // 2. the switch resolution message, so that the client can record the custom resolution. pub(super) fn check_display_changed( ndisplay: usize, idx: usize, (x, y, w, h): (i32, i32, usize, usize), ) -> Option { #[cfg(target_os = "linux")] { // wayland do not support changing display for now if !is_x11() { return None; } } let lock = SYNC_DISPLAYS.lock().unwrap(); // If plugging out a monitor && lock.displays.get(idx) is None. // 1. The client version < 1.2.4. The client side has to reconnect. // 2. The client version > 1.2.4, The client side can handle the case because sync peer info message will be sent. // But it is acceptable to for the user to reconnect manually, because the monitor is unplugged. let d = lock.displays.get(idx)?; if ndisplay != lock.displays.len() { return Some(d.clone()); } if !(d.x == x && d.y == y && d.width == w as i32 && d.height == h as i32) { Some(d.clone()) } else { None } } #[inline] pub fn set_last_changed_resolution(display_name: &str, original: (i32, i32), changed: (i32, i32)) { let mut lock = CHANGED_RESOLUTIONS.write().unwrap(); match lock.get_mut(display_name) { Some(res) => res.changed = changed, None => { lock.insert( display_name.to_owned(), ChangedResolution { original, changed }, ); } } } #[inline] #[cfg(not(any(target_os = "android", target_os = "ios")))] pub fn reset_resolutions() { for (name, res) in CHANGED_RESOLUTIONS.read().unwrap().iter() { let (w, h) = res.original; if let Err(e) = crate::platform::change_resolution(name, w as _, h as _) { log::error!( "Failed to reset resolution of display '{}' to ({},{}): {}", name, w, h, e ); } } // Can be cleared because reset resolutions is called when there is no client connected. CHANGED_RESOLUTIONS.write().unwrap().clear(); } #[inline] fn is_capturer_mag_supported() -> bool { #[cfg(windows)] return scrap::CapturerMag::is_supported(); #[cfg(not(windows))] false } #[inline] pub fn capture_cursor_embedded() -> bool { scrap::is_cursor_embedded() } #[inline] #[cfg(windows)] pub fn is_privacy_mode_mag_supported() -> bool { return *IS_CAPTURER_MAGNIFIER_SUPPORTED && get_version_number(&crate::VERSION) > get_version_number("1.1.9"); } pub fn new() -> GenericService { let svc = EmptyExtraFieldService::new(NAME.to_owned(), true); GenericService::run(&svc.clone(), run); svc.sp } fn displays_to_msg(displays: Vec) -> Message { let mut pi = PeerInfo { ..Default::default() }; pi.displays = displays.clone(); #[cfg(windows)] if crate::platform::is_installed() { let m = crate::virtual_display_manager::get_platform_additions(); pi.platform_additions = serde_json::to_string(&m).unwrap_or_default(); } // current_display should not be used in server. // It is set to 0 for compatibility with old clients. pi.current_display = 0; let mut msg_out = Message::new(); msg_out.set_peer_info(pi); msg_out } fn check_get_displays_changed_msg() -> Option { #[cfg(target_os = "linux")] { if !is_x11() { return get_displays_msg(); } } check_update_displays(&try_get_displays().ok()?); get_displays_msg() } pub fn check_displays_changed() -> ResultType<()> { #[cfg(target_os = "linux")] { // Currently, wayland need to call wayland::clear() before call Display::all(), otherwise it will cause // block, or even crash here, https://github.com/rustdesk/rustdesk/blob/0bb4d43e9ea9d9dfb9c46c8d27d1a97cd0ad6bea/libs/scrap/src/wayland/pipewire.rs#L235 if !is_x11() { return Ok(()); } } check_update_displays(&try_get_displays()?); Ok(()) } fn get_displays_msg() -> Option { let displays = SYNC_DISPLAYS.lock().unwrap().get_update_sync_displays()?; Some(displays_to_msg(displays)) } fn run(sp: EmptyExtraFieldService) -> ResultType<()> { while sp.ok() { sp.snapshot(|sps| { if sps.has_subscribes() { SYNC_DISPLAYS.lock().unwrap().is_synced = false; bail!("new subscriber"); } Ok(()) })?; if let Some(msg_out) = check_get_displays_changed_msg() { sp.send(msg_out); log::info!("Displays changed"); } std::thread::sleep(Duration::from_millis(300)); } Ok(()) } #[inline] pub(super) fn get_original_resolution( display_name: &str, w: usize, h: usize, ) -> MessageField { #[cfg(windows)] let is_rustdesk_virtual_display = crate::virtual_display_manager::rustdesk_idd::is_virtual_display(&display_name); #[cfg(not(windows))] let is_rustdesk_virtual_display = false; Some(if is_rustdesk_virtual_display { Resolution { width: 0, height: 0, ..Default::default() } } else { let changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap(); let (width, height) = match changed_resolutions.get(display_name) { Some(res) => { res.original /* The resolution change may not happen immediately, `changed` has been updated, but the actual resolution is old, it will be mistaken for a third-party change. if res.changed.0 != w as i32 || res.changed.1 != h as i32 { // If the resolution is changed by third process, remove the record in changed_resolutions. changed_resolutions.remove(display_name); (w as _, h as _) } else { res.original } */ } None => (w as _, h as _), }; Resolution { width, height, ..Default::default() } }) .into() } pub(super) fn get_sync_displays() -> Vec { SYNC_DISPLAYS.lock().unwrap().displays.clone() } pub(super) fn get_display_info(idx: usize) -> Option { SYNC_DISPLAYS.lock().unwrap().displays.get(idx).cloned() } // Display to DisplayInfo // The DisplayInfo is be sent to the peer. pub(super) fn check_update_displays(all: &Vec) { let displays = all .iter() .map(|d| { let display_name = d.name(); #[allow(unused_assignments)] #[allow(unused_mut)] let mut scale = 1.0; #[cfg(target_os = "macos")] { scale = d.scale(); } let original_resolution = get_original_resolution( &display_name, ((d.width() as f64) / scale).round() as usize, (d.height() as f64 / scale).round() as usize, ); DisplayInfo { x: d.origin().0 as _, y: d.origin().1 as _, width: d.width() as _, height: d.height() as _, name: display_name, online: d.is_online(), cursor_embedded: false, original_resolution, scale, ..Default::default() } }) .collect::>(); SYNC_DISPLAYS.lock().unwrap().check_changed(displays); } pub fn is_inited_msg() -> Option { #[cfg(target_os = "linux")] if !is_x11() { return super::wayland::is_inited(); } None } pub async fn update_get_sync_displays() -> ResultType> { #[cfg(target_os = "linux")] { if !is_x11() { return super::wayland::get_displays().await; } } check_update_displays(&try_get_displays()?); Ok(SYNC_DISPLAYS.lock().unwrap().displays.clone()) } #[inline] pub fn get_primary() -> usize { #[cfg(target_os = "linux")] { if !is_x11() { return match super::wayland::get_primary() { Ok(n) => n, Err(_) => 0, }; } } try_get_displays().map(|d| get_primary_2(&d)).unwrap_or(0) } #[inline] pub fn get_primary_2(all: &Vec) -> usize { all.iter().position(|d| d.is_primary()).unwrap_or(0) } #[inline] #[cfg(windows)] fn no_displays(displays: &Vec) -> bool { let display_len = displays.len(); if display_len == 0 { true } else if display_len == 1 { let display = &displays[0]; if display.width() > DUMMY_DISPLAY_SIDE_MAX_SIZE || display.height() > DUMMY_DISPLAY_SIDE_MAX_SIZE { return false; } let any_real = crate::platform::resolutions(&display.name()) .iter() .any(|r| { (r.height as usize) > DUMMY_DISPLAY_SIDE_MAX_SIZE || (r.width as usize) > DUMMY_DISPLAY_SIDE_MAX_SIZE }); !any_real } else { false } } #[inline] #[cfg(not(windows))] pub fn try_get_displays() -> ResultType> { Ok(Display::all()?) } #[inline] #[cfg(windows)] pub fn try_get_displays() -> ResultType> { try_get_displays_(false) } // We can't get full control of the virtual display if we use amyuni idd. // If we add a virtual display, we cannot remove it automatically. // So when using amyuni idd, we only add a virtual display for headless if it is required. // eg. when the client is connecting. #[inline] #[cfg(windows)] pub fn try_get_displays_add_amyuni_headless() -> ResultType> { try_get_displays_(true) } #[inline] #[cfg(windows)] pub fn try_get_displays_(add_amyuni_headless: bool) -> ResultType> { let mut displays = Display::all()?; // Do not add virtual display if the platform is not installed or the virtual display is not supported. if !crate::platform::is_installed() || !virtual_display_manager::is_virtual_display_supported() { return Ok(displays); } // Enable headless virtual display when // 1. `amyuni` idd is not used. // 2. `amyuni` idd is used and `add_amyuni_headless` is true. if virtual_display_manager::is_amyuni_idd() && !add_amyuni_headless { return Ok(displays); } // If is switching session, no displays may be detected. But it is not a real case. if displays.is_empty() && crate::platform::desktop_changed() { return Ok(displays); } 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() { log::error!("plug in headless failed {}", e); } else { displays = Display::all()?; } } Ok(displays) }