fix, change display resolution

Signed-off-by: dignow <linlong1265@gmail.com>
This commit is contained in:
dignow 2023-10-17 23:31:50 +08:00
parent 63591941b8
commit c4f09b5598
4 changed files with 148 additions and 143 deletions

View File

@ -2282,7 +2282,7 @@ class PeerInfo with ChangeNotifier {
if (currentDisplay == kAllDisplayValue) { if (currentDisplay == kAllDisplayValue) {
return null; return null;
} }
if (currentDisplay > 0 && currentDisplay < displays.length) { if (currentDisplay >= 0 && currentDisplay < displays.length) {
return displays[currentDisplay]; return displays[currentDisplay];
} else { } else {
return null; return null;

View File

@ -611,7 +611,7 @@ impl Connection {
_ => {}, _ => {},
} }
} }
Some(message::Union::PeerInfo(_)) => { Some(message::Union::PeerInfo(..)) => {
conn.refresh_video_display(None); conn.refresh_video_display(None);
} }
_ => {} _ => {}
@ -1132,11 +1132,13 @@ impl Connection {
self.send(msg_out).await; self.send(msg_out).await;
} }
match super::display_service::get_displays().await { match super::display_service::update_get_sync_displays().await {
Err(err) => { Err(err) => {
res.set_error(format!("{}", err)); res.set_error(format!("{}", err));
} }
Ok(displays) => { Ok(displays) => {
// For compatibility with old versions, we need to send the displays to the peer.
// But the displays may be updated later, before creating the video capturer.
pi.displays = displays.clone(); pi.displays = displays.clone();
pi.current_display = self.display_idx as _; pi.current_display = self.display_idx as _;
res.set_peer_info(pi); res.set_peer_info(pi);
@ -2139,13 +2141,6 @@ impl Connection {
..Default::default() ..Default::default()
}); });
} }
// send display changed message
if let Some(msg_out) =
video_service::make_display_changed_msg(self.display_idx, None)
{
self.send(msg_out).await;
}
} }
} }
} }

View File

@ -5,6 +5,12 @@ use crate::virtual_display_manager;
use hbb_common::get_version_number; use hbb_common::get_version_number;
use hbb_common::protobuf::MessageField; use hbb_common::protobuf::MessageField;
use scrap::Display; use scrap::Display;
#[cfg(target_os = "linux")]
use std::sync::atomic::{AtomicBool, Ordering};
// https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call
#[cfg(target_os = "linux")]
pub(super) static IS_X11: AtomicBool = AtomicBool::new(false);
pub const NAME: &'static str = "display"; pub const NAME: &'static str = "display";
@ -19,6 +25,59 @@ lazy_static::lazy_static! {
// Initial primary display index. // Initial primary display index.
// It should only be updated when the rustdesk server is started, and should not be updated when displays changed. // It should only be updated when the rustdesk server is started, and should not be updated when displays changed.
pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary(); pub static ref PRIMARY_DISPLAY_IDX: usize = get_primary();
static ref SYNC_DISPLAYS: Arc<Mutex<SyncDisplaysInfo>> = Default::default();
}
#[derive(Default)]
struct SyncDisplaysInfo {
displays: Vec<DisplayInfo>,
is_synced: bool,
}
impl SyncDisplaysInfo {
fn check_changed(&mut self, displays: Vec<DisplayInfo>) {
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<Vec<DisplayInfo>> {
if self.is_synced {
return None;
}
self.is_synced = true;
Some(self.displays.clone())
}
}
pub(super) fn check_display_changed(
idx: usize,
(x, y, w, h): (i32, i32, usize, usize),
) -> Option<DisplayInfo> {
#[cfg(target_os = "linux")]
{
// wayland do not support changing display for now
if !IS_X11.load(Ordering::SeqCst) {
return None;
}
}
let lock = SYNC_DISPLAYS.lock().unwrap();
let d = lock.displays.get(idx)?;
if !(d.x == x && d.y == y && d.width == w as i32 && d.height == h as i32) {
Some(d.clone())
} else {
None
}
} }
#[inline] #[inline]
@ -74,45 +133,27 @@ pub fn is_privacy_mode_supported() -> bool {
return false; return false;
} }
#[derive(Default)]
struct StateDisplay {
synced_displays: Vec<DisplayInfo>,
}
impl super::service::Reset for StateDisplay {
fn reset(&mut self) {
self.synced_displays.clear();
}
}
pub fn new() -> GenericService { pub fn new() -> GenericService {
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false); let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
GenericService::repeat::<StateDisplay, _, _>(&svc.clone(), 300, run); GenericService::run(&svc.clone(), run);
svc.sp svc.sp
} }
fn check_get_displays_changed_msg(last_synced_displays: &mut Vec<DisplayInfo>) -> Option<Message> { fn displays_to_msg(displays: Vec<DisplayInfo>) -> Message {
let displays = try_get_displays().ok()?; let mut pi = PeerInfo {
if displays.len() == last_synced_displays.len() { ..Default::default()
return None; };
} pi.displays = displays.clone();
pi.current_display = 0;
let mut msg_out = Message::new();
msg_out.set_peer_info(pi);
msg_out
}
// Display to DisplayInfo fn check_get_displays_changed_msg() -> Option<Message> {
let displays = to_display_info(&displays); check_update_displays(try_get_displays().ok()?);
if last_synced_displays.len() == 0 { let displays = SYNC_DISPLAYS.lock().unwrap().get_update_sync_displays()?;
*last_synced_displays = displays; Some(displays_to_msg(displays))
None
} else {
let mut pi = PeerInfo {
..Default::default()
};
pi.displays = displays.clone();
pi.current_display = 0;
let mut msg_out = Message::new();
msg_out.set_peer_info(pi);
*last_synced_displays = displays;
Some(msg_out)
}
} }
#[cfg(all(windows, feature = "virtual_display_driver"))] #[cfg(all(windows, feature = "virtual_display_driver"))]
@ -120,11 +161,28 @@ pub fn try_plug_out_virtual_display() {
let _res = virtual_display_manager::plug_out_headless(); let _res = virtual_display_manager::plug_out_headless();
} }
fn run(sp: EmptyExtraFieldService, state: &mut StateDisplay) -> ResultType<()> { fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
if let Some(msg_out) = check_get_displays_changed_msg(&mut state.synced_displays) { #[cfg(target_os = "linux")]
sp.send(msg_out); {
log::info!("Displays changed"); IS_X11.store(scrap::is_x11(), Ordering::SeqCst);
} }
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(()) Ok(())
} }
@ -167,8 +225,11 @@ pub(super) fn get_original_resolution(
.into() .into()
} }
pub fn to_display_info(all: &Vec<Display>) -> Vec<DisplayInfo> { // Display to DisplayInfo
all.iter() // The DisplayInfo is be sent to the peer.
fn check_update_displays(all: Vec<Display>) {
let displays = all
.iter()
.map(|d| { .map(|d| {
let display_name = d.name(); let display_name = d.name();
let original_resolution = get_original_resolution(&display_name, d.width(), d.height()); let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
@ -184,32 +245,34 @@ pub fn to_display_info(all: &Vec<Display>) -> Vec<DisplayInfo> {
..Default::default() ..Default::default()
} }
}) })
.collect::<Vec<DisplayInfo>>() .collect::<Vec<DisplayInfo>>();
SYNC_DISPLAYS.lock().unwrap().check_changed(displays);
} }
pub fn is_inited_msg() -> Option<Message> { pub fn is_inited_msg() -> Option<Message> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
if !scrap::is_x11() { if !IS_X11.load(Ordering::SeqCst) {
return super::wayland::is_inited(); return super::wayland::is_inited();
} }
None None
} }
pub async fn get_displays() -> ResultType<Vec<DisplayInfo>> { pub async fn update_get_sync_displays() -> ResultType<Vec<DisplayInfo>> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
if !scrap::is_x11() { if !IS_X11.load(Ordering::SeqCst) {
return super::wayland::get_displays().await; return super::wayland::get_displays().await;
} }
} }
Ok(to_display_info(&try_get_displays()?)) check_update_displays(try_get_displays()?);
Ok(SYNC_DISPLAYS.lock().unwrap().displays.clone())
} }
#[inline] #[inline]
pub fn get_primary() -> usize { pub fn get_primary() -> usize {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
{ {
if !scrap::is_x11() { if !IS_X11.load(Ordering::SeqCst) {
return match super::wayland::get_primary() { return match super::wayland::get_primary() {
Ok(n) => n, Ok(n) => n,
Err(_) => 0, Err(_) => 0,

View File

@ -18,7 +18,11 @@
// to-do: // to-do:
// https://slhck.info/video/2017/03/01/rate-control.html // https://slhck.info/video/2017/03/01/rate-control.html
use super::{service::ServiceTmpl, video_qos::VideoQoS, *}; #[cfg(target_os = "linux")]
use super::display_service::IS_X11;
use super::{display_service::check_display_changed, service::ServiceTmpl, video_qos::VideoQoS, *};
#[cfg(target_os = "linux")]
use crate::common::SimpleCallOnReturn;
#[cfg(windows)] #[cfg(windows)]
use crate::{platform::windows::is_process_consent_running, privacy_win_mag}; use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
use hbb_common::{ use hbb_common::{
@ -38,7 +42,7 @@ use scrap::{
CodecName, Display, TraitCapturer, CodecName, Display, TraitCapturer,
}; };
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::Ordering;
#[cfg(windows)] #[cfg(windows)]
use std::sync::Once; use std::sync::Once;
use std::{ use std::{
@ -49,7 +53,6 @@ use std::{
}; };
pub const NAME: &'static str = "video"; pub const NAME: &'static str = "video";
pub const OPTION_DISPLAY_CHANGED: &'static str = "changed";
pub const OPTION_REFRESH: &'static str = "refresh"; pub const OPTION_REFRESH: &'static str = "refresh";
lazy_static::lazy_static! { lazy_static::lazy_static! {
@ -63,10 +66,6 @@ lazy_static::lazy_static! {
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default(); pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
} }
// https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call
#[cfg(target_os = "linux")]
static IS_X11: AtomicBool = AtomicBool::new(false);
#[inline] #[inline]
pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) { pub fn notify_video_frame_fetched(conn_id: i32, frame_tm: Option<Instant>) {
FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok(); FRAME_FETCHED_NOTIFIER.0.send((conn_id, frame_tm)).ok();
@ -165,35 +164,6 @@ pub fn new(idx: usize) -> GenericService {
vs.sp vs.sp
} }
fn check_display_changed(
last_n: usize,
last_current: usize,
last_width: usize,
last_height: usize,
) -> bool {
#[cfg(target_os = "linux")]
{
// wayland do not support changing display for now
if !IS_X11.load(Ordering::SeqCst) {
return false;
}
}
let displays = match try_get_displays() {
Ok(d) => d,
_ => return false,
};
let n = displays.len();
if n != last_n {
return true;
};
match displays.get(last_current) {
Some(d) => d.width() != last_width || d.height() != last_height,
None => true,
}
}
// Capturer object is expensive, avoiding to create it frequently. // Capturer object is expensive, avoiding to create it frequently.
fn create_capturer( fn create_capturer(
privacy_mode_id: i32, privacy_mode_id: i32,
@ -327,7 +297,6 @@ pub(super) struct CapturerInfo {
pub origin: (i32, i32), pub origin: (i32, i32),
pub width: usize, pub width: usize,
pub height: usize, pub height: usize,
pub ndisplay: usize,
pub current: usize, pub current: usize,
pub privacy_mode_id: i32, pub privacy_mode_id: i32,
pub _capturer_privacy_mode_id: i32, pub _capturer_privacy_mode_id: i32,
@ -419,7 +388,6 @@ fn get_capturer(
origin, origin,
width, width,
height, height,
ndisplay,
current, current,
privacy_mode_id, privacy_mode_id,
_capturer_privacy_mode_id: capturer_privacy_mode_id, _capturer_privacy_mode_id: capturer_privacy_mode_id,
@ -431,16 +399,21 @@ fn run(vs: VideoService) -> ResultType<()> {
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let _wake_lock = get_wake_lock(); let _wake_lock = get_wake_lock();
#[cfg(target_os = "linux")] // Wayland only support one video capturer for now. It is ok to call ensure_inited() here.
{ //
IS_X11.store(scrap::is_x11(), Ordering::SeqCst);
}
// ensure_inited() is needed because clear() may be called. // ensure_inited() is needed because clear() may be called.
// to-do: wayland ensure_inited should pass current display index. // to-do: wayland ensure_inited should pass current display index.
// But for now, we do not support multi-screen capture on wayland. // But for now, we do not support multi-screen capture on wayland.
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
super::wayland::ensure_inited()?; super::wayland::ensure_inited()?;
#[cfg(target_os = "linux")]
let wayland_call_on_ret = SimpleCallOnReturn {
b: true,
f: Box::new(|| {
super::wayland::clear();
}),
};
#[cfg(windows)] #[cfg(windows)]
let last_portable_service_running = crate::portable_service::client::running(); let last_portable_service_running = crate::portable_service::client::running();
#[cfg(not(windows))] #[cfg(not(windows))]
@ -471,16 +444,6 @@ fn run(vs: VideoService) -> ResultType<()> {
c.set_use_yuv(encoder.use_yuv()); c.set_use_yuv(encoder.use_yuv());
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate()); VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
if sp.is_option_true(OPTION_DISPLAY_CHANGED) {
log::debug!("Broadcasting display changed");
broadcast_display_changed(
display_idx,
&sp,
Some((c.name.clone(), c.origin.clone(), c.width, c.height)),
);
sp.set_option_bool(OPTION_DISPLAY_CHANGED, false);
}
if sp.is_option_true(OPTION_REFRESH) { if sp.is_option_true(OPTION_REFRESH) {
sp.set_option_bool(OPTION_REFRESH, false); sp.set_option_bool(OPTION_REFRESH, false);
} }
@ -518,7 +481,7 @@ fn run(vs: VideoService) -> ResultType<()> {
} }
drop(video_qos); drop(video_qos);
if sp.is_option_true(OPTION_DISPLAY_CHANGED) || sp.is_option_true(OPTION_REFRESH) { if sp.is_option_true(OPTION_REFRESH) {
bail!("SWITCH"); bail!("SWITCH");
} }
if codec_name != Encoder::negotiated_codec() { if codec_name != Encoder::negotiated_codec() {
@ -540,12 +503,11 @@ fn run(vs: VideoService) -> ResultType<()> {
let now = time::Instant::now(); let now = time::Instant::now();
if last_check_displays.elapsed().as_millis() > 1000 { if last_check_displays.elapsed().as_millis() > 1000 {
last_check_displays = now; last_check_displays = now;
if let Some(display) =
// Capturer on macos does not return Err event the solution is changed. check_display_changed(c.current, (c.origin.0, c.origin.1, c.width, c.height))
#[cfg(target_os = "macos")] {
if check_display_changed(c.ndisplay, c.current, c.width, c.height) { log::info!("Display {} changed", display);
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true); broadcast_display_changed(display_idx, &sp, &c.name, display);
log::info!("Displays changed");
bail!("SWITCH"); bail!("SWITCH");
} }
} }
@ -624,21 +586,12 @@ fn run(vs: VideoService) -> ResultType<()> {
} }
} }
Err(err) => { Err(err) => {
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
log::info!("Displays changed");
#[cfg(target_os = "linux")]
super::wayland::clear();
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true);
bail!("SWITCH");
}
#[cfg(windows)] #[cfg(windows)]
if !c.is_gdi() { if !c.is_gdi() {
c.set_gdi(); c.set_gdi();
log::info!("dxgi error, fall back to gdi: {:?}", err); log::info!("dxgi error, fall back to gdi: {:?}", err);
continue; continue;
} }
return Err(err.into()); return Err(err.into());
} }
_ => { _ => {
@ -671,9 +624,6 @@ fn run(vs: VideoService) -> ResultType<()> {
} }
} }
#[cfg(target_os = "linux")]
super::wayland::clear();
Ok(()) Ok(())
} }
@ -892,9 +842,10 @@ fn get_wake_lock() -> crate::platform::WakeLock {
fn broadcast_display_changed( fn broadcast_display_changed(
display_idx: usize, display_idx: usize,
sp: &GenericService, sp: &GenericService,
display_meta: Option<(String, (i32, i32), usize, usize)>, name: &str,
display: DisplayInfo,
) { ) {
if let Some(msg_out) = make_display_changed_msg(display_idx, display_meta) { if let Some(msg_out) = make_display_changed_msg(display_idx, name, display) {
sp.send(msg_out); sp.send(msg_out);
} }
} }
@ -913,34 +864,30 @@ fn get_display_info_simple_meta(display_idx: usize) -> Option<(String, (i32, i32
} }
} }
pub fn make_display_changed_msg( fn make_display_changed_msg(
display_idx: usize, display_idx: usize,
display_meta: Option<(String, (i32, i32), usize, usize)>, name: &str,
display: DisplayInfo,
) -> Option<Message> { ) -> Option<Message> {
let mut misc = Misc::new(); let mut misc = Misc::new();
let (name, origin, width, height) = match display_meta {
Some(d) => d,
None => get_display_info_simple_meta(display_idx)?,
};
let original_resolution = display_service::get_original_resolution(&name, width, height);
misc.set_switch_display(SwitchDisplay { misc.set_switch_display(SwitchDisplay {
display: display_idx as _, display: display_idx as _,
x: origin.0, x: display.x,
y: origin.1, y: display.y,
width: width as _, width: display.width,
height: height as _, height: display.height,
cursor_embedded: display_service::capture_cursor_embedded(), cursor_embedded: display_service::capture_cursor_embedded(),
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
resolutions: Some(SupportedResolutions { resolutions: Some(SupportedResolutions {
resolutions: if name.is_empty() { resolutions: if name.is_empty() {
vec![] vec![]
} else { } else {
crate::platform::resolutions(&name) crate::platform::resolutions(name)
}, },
..SupportedResolutions::default() ..SupportedResolutions::default()
}) })
.into(), .into(),
original_resolution, original_resolution: display.original_resolution,
..Default::default() ..Default::default()
}); });
let mut msg_out = Message::new(); let mut msg_out = Message::new();