2023-10-08 21:44:54 +08:00
use super ::* ;
2024-08-22 09:35:50 +08:00
use crate ::common ::SimpleCallOnReturn ;
2023-10-21 13:10:51 +08:00
#[ cfg(target_os = " linux " ) ]
use crate ::platform ::linux ::is_x11 ;
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2023-10-08 21:44:54 +08:00
use crate ::virtual_display_manager ;
#[ cfg(windows) ]
use hbb_common ::get_version_number ;
use hbb_common ::protobuf ::MessageField ;
use scrap ::Display ;
2024-08-22 09:35:50 +08:00
use std ::sync ::atomic ::{ AtomicBool , Ordering } ;
2023-10-17 23:31:50 +08:00
// https://github.com/rustdesk/rustdesk/discussions/6042, avoiding dbus call
2023-10-08 21:44:54 +08:00
pub const NAME : & 'static str = " display " ;
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2023-10-27 16:19:42 +08:00
const DUMMY_DISPLAY_SIDE_MAX_SIZE : usize = 1024 ;
2023-10-08 21:44:54 +08:00
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 < RwLock < HashMap < String , ChangedResolution > > > = Default ::default ( ) ;
// Initial primary display index.
2024-03-14 21:36:55 +08:00
// It should not be updated when displays changed.
2023-10-08 21:44:54 +08:00
pub static ref PRIMARY_DISPLAY_IDX : usize = get_primary ( ) ;
2023-10-17 23:31:50 +08:00
static ref SYNC_DISPLAYS : Arc < Mutex < SyncDisplaysInfo > > = Default ::default ( ) ;
}
2024-08-22 09:35:50 +08:00
// https://github.com/rustdesk/rustdesk/pull/8537
static TEMP_IGNORE_DISPLAYS_CHANGED : AtomicBool = AtomicBool ::new ( false ) ;
2023-10-17 23:31:50 +08:00
#[ 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 ;
2024-08-22 09:35:50 +08:00
if ! TEMP_IGNORE_DISPLAYS_CHANGED . load ( Ordering ::Relaxed ) {
self . is_synced = false ;
}
2023-10-17 23:31:50 +08:00
return ;
}
for ( i , d ) in displays . iter ( ) . enumerate ( ) {
if d ! = & self . displays [ i ] {
self . displays = displays ;
2024-08-22 09:35:50 +08:00
if ! TEMP_IGNORE_DISPLAYS_CHANGED . load ( Ordering ::Relaxed ) {
self . is_synced = false ;
}
2023-10-17 23:31:50 +08:00
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 ( ) )
}
}
2024-08-22 09:35:50 +08:00
pub fn temp_ignore_displays_changed ( ) -> SimpleCallOnReturn {
TEMP_IGNORE_DISPLAYS_CHANGED . store ( true , std ::sync ::atomic ::Ordering ::Relaxed ) ;
SimpleCallOnReturn {
b : true ,
f : Box ::new ( move | | {
// Wait for a while to make sure check_display_changed() is called
// after video service has sending its `SwitchDisplay` message(`try_broadcast_display_changed()`).
std ::thread ::sleep ( Duration ::from_millis ( 1000 ) ) ;
TEMP_IGNORE_DISPLAYS_CHANGED . store ( false , Ordering ::Relaxed ) ;
// Trigger the display changed message.
SYNC_DISPLAYS . lock ( ) . unwrap ( ) . is_synced = false ;
} ) ,
}
}
2023-10-18 09:59:02 +08:00
// This function is really useful, though a duplicate check if display changed.
2023-10-18 11:35:28 +08:00
// 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.
2023-10-17 23:31:50 +08:00
pub ( super ) fn check_display_changed (
2023-10-18 09:59:02 +08:00
ndisplay : usize ,
2023-10-17 23:31:50 +08:00
idx : usize ,
( x , y , w , h ) : ( i32 , i32 , usize , usize ) ,
) -> Option < DisplayInfo > {
#[ cfg(target_os = " linux " ) ]
{
// wayland do not support changing display for now
2023-10-21 13:10:51 +08:00
if ! is_x11 ( ) {
2023-10-17 23:31:50 +08:00
return None ;
}
}
let lock = SYNC_DISPLAYS . lock ( ) . unwrap ( ) ;
2023-10-18 09:59:02 +08:00
// If plugging out a monitor && lock.displays.get(idx) is None.
// 1. The client version < 1.2.4. The client side has to reconnect.
2024-03-27 01:08:56 +08:00
// 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.
2023-10-17 23:31:50 +08:00
let d = lock . displays . get ( idx ) ? ;
2023-10-18 09:59:02 +08:00
if ndisplay ! = lock . displays . len ( ) {
return Some ( d . clone ( ) ) ;
}
2023-10-17 23:31:50 +08:00
if ! ( d . x = = x & & d . y = = y & & d . width = = w as i32 & & d . height = = h as i32 ) {
Some ( d . clone ( ) )
} else {
None
}
2023-10-08 21:44:54 +08:00
}
#[ 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
) ;
}
}
2024-04-01 17:20:19 +08:00
// Can be cleared because reset resolutions is called when there is no client connected.
CHANGED_RESOLUTIONS . write ( ) . unwrap ( ) . clear ( ) ;
2023-10-08 21:44:54 +08:00
}
#[ 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 ]
2023-11-14 12:11:38 +08:00
#[ cfg(windows) ]
pub fn is_privacy_mode_mag_supported ( ) -> bool {
2023-10-08 21:44:54 +08:00
return * IS_CAPTURER_MAGNIFIER_SUPPORTED
& & get_version_number ( & crate ::VERSION ) > get_version_number ( " 1.1.9 " ) ;
}
pub fn new ( ) -> GenericService {
2023-10-17 23:31:50 +08:00
let svc = EmptyExtraFieldService ::new ( NAME . to_owned ( ) , true ) ;
GenericService ::run ( & svc . clone ( ) , run ) ;
2023-10-08 21:44:54 +08:00
svc . sp
}
2023-10-17 23:31:50 +08:00
fn displays_to_msg ( displays : Vec < DisplayInfo > ) -> Message {
let mut pi = PeerInfo {
.. Default ::default ( )
} ;
pi . displays = displays . clone ( ) ;
2023-10-27 16:19:42 +08:00
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2023-10-27 16:19:42 +08:00
if crate ::platform ::is_installed ( ) {
2024-04-19 11:31:52 +08:00
let m = crate ::virtual_display_manager ::get_platform_additions ( ) ;
pi . platform_additions = serde_json ::to_string ( & m ) . unwrap_or_default ( ) ;
2023-10-27 16:19:42 +08:00
}
2023-10-18 09:59:02 +08:00
// current_display should not be used in server.
// It is set to 0 for compatibility with old clients.
2023-10-17 23:31:50 +08:00
pi . current_display = 0 ;
let mut msg_out = Message ::new ( ) ;
msg_out . set_peer_info ( pi ) ;
msg_out
}
2023-10-08 21:44:54 +08:00
2023-10-17 23:31:50 +08:00
fn check_get_displays_changed_msg ( ) -> Option < Message > {
2023-12-06 20:55:37 +05:30
#[ cfg(target_os = " linux " ) ]
{
if ! is_x11 ( ) {
return get_displays_msg ( ) ;
}
}
2023-11-13 17:31:55 +08:00
check_update_displays ( & try_get_displays ( ) . ok ( ) ? ) ;
2023-12-06 20:55:37 +05:30
get_displays_msg ( )
}
2024-01-02 16:58:10 +08:00
pub fn check_displays_changed ( ) -> ResultType < ( ) > {
2024-07-09 18:01:30 +08:00
#[ cfg(target_os = " linux " ) ]
{
2024-07-09 22:10:39 +08:00
// Currently, wayland need to call wayland::clear() before call Display::all(), otherwise it will cause
2024-07-09 18:01:30 +08:00
// block, or even crash here, https://github.com/rustdesk/rustdesk/blob/0bb4d43e9ea9d9dfb9c46c8d27d1a97cd0ad6bea/libs/scrap/src/wayland/pipewire.rs#L235
if ! is_x11 ( ) {
return Ok ( ( ) ) ;
}
}
2024-01-02 16:58:10 +08:00
check_update_displays ( & try_get_displays ( ) ? ) ;
Ok ( ( ) )
}
2023-12-06 20:55:37 +05:30
fn get_displays_msg ( ) -> Option < Message > {
2023-10-17 23:31:50 +08:00
let displays = SYNC_DISPLAYS . lock ( ) . unwrap ( ) . get_update_sync_displays ( ) ? ;
Some ( displays_to_msg ( displays ) )
2023-10-08 21:44:54 +08:00
}
2023-10-17 23:31:50 +08:00
fn run ( sp : EmptyExtraFieldService ) -> ResultType < ( ) > {
while sp . ok ( ) {
sp . snapshot ( | sps | {
2024-08-22 09:35:50 +08:00
if ! TEMP_IGNORE_DISPLAYS_CHANGED . load ( Ordering ::Relaxed ) {
if sps . has_subscribes ( ) {
SYNC_DISPLAYS . lock ( ) . unwrap ( ) . is_synced = false ;
bail! ( " new subscriber " ) ;
}
2023-10-17 23:31:50 +08:00
}
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 ) ) ;
}
2023-10-08 21:44:54 +08:00
Ok ( ( ) )
}
#[ inline ]
pub ( super ) fn get_original_resolution (
display_name : & str ,
w : usize ,
h : usize ,
) -> MessageField < Resolution > {
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2024-04-19 11:31:52 +08:00
let is_rustdesk_virtual_display =
crate ::virtual_display_manager ::rustdesk_idd ::is_virtual_display ( & display_name ) ;
2024-05-06 15:26:21 +08:00
#[ cfg(not(windows)) ]
2024-04-19 11:31:52 +08:00
let is_rustdesk_virtual_display = false ;
Some ( if is_rustdesk_virtual_display {
2023-10-08 21:44:54 +08:00
Resolution {
width : 0 ,
height : 0 ,
.. Default ::default ( )
}
} else {
2024-04-01 17:20:19 +08:00
let changed_resolutions = CHANGED_RESOLUTIONS . write ( ) . unwrap ( ) ;
2023-10-08 21:44:54 +08:00
let ( width , height ) = match changed_resolutions . get ( display_name ) {
Some ( res ) = > {
2024-04-01 17:20:19 +08:00
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 .
2023-10-08 21:44:54 +08:00
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
}
2024-04-01 17:20:19 +08:00
* /
2023-10-08 21:44:54 +08:00
}
None = > ( w as _ , h as _ ) ,
} ;
Resolution {
width ,
height ,
.. Default ::default ( )
}
} )
. into ( )
}
2023-10-18 08:07:00 +08:00
pub ( super ) fn get_sync_displays ( ) -> Vec < DisplayInfo > {
SYNC_DISPLAYS . lock ( ) . unwrap ( ) . displays . clone ( )
}
2023-10-18 10:45:46 +08:00
pub ( super ) fn get_display_info ( idx : usize ) -> Option < DisplayInfo > {
SYNC_DISPLAYS . lock ( ) . unwrap ( ) . displays . get ( idx ) . cloned ( )
}
2023-10-17 23:31:50 +08:00
// Display to DisplayInfo
// The DisplayInfo is be sent to the peer.
2023-10-18 08:07:00 +08:00
pub ( super ) fn check_update_displays ( all : & Vec < Display > ) {
2023-10-17 23:31:50 +08:00
let displays = all
. iter ( )
2023-10-08 21:44:54 +08:00
. map ( | d | {
let display_name = d . name ( ) ;
2024-02-27 22:28:23 +08:00
#[ 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 ,
) ;
2023-10-08 21:44:54 +08:00
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 ,
2024-02-27 22:28:23 +08:00
scale ,
2023-10-08 21:44:54 +08:00
.. Default ::default ( )
}
} )
2023-10-17 23:31:50 +08:00
. collect ::< Vec < DisplayInfo > > ( ) ;
SYNC_DISPLAYS . lock ( ) . unwrap ( ) . check_changed ( displays ) ;
2023-10-08 21:44:54 +08:00
}
pub fn is_inited_msg ( ) -> Option < Message > {
#[ cfg(target_os = " linux " ) ]
2023-10-21 13:10:51 +08:00
if ! is_x11 ( ) {
2023-10-08 21:44:54 +08:00
return super ::wayland ::is_inited ( ) ;
}
None
}
2024-09-12 14:44:40 +08:00
pub async fn update_get_sync_displays_on_login ( ) -> ResultType < Vec < DisplayInfo > > {
2023-10-08 21:44:54 +08:00
#[ cfg(target_os = " linux " ) ]
{
2023-10-21 13:10:51 +08:00
if ! is_x11 ( ) {
2023-10-08 21:44:54 +08:00
return super ::wayland ::get_displays ( ) . await ;
}
}
2024-09-12 14:44:40 +08:00
#[ cfg(not(windows)) ]
let displays = display_service ::try_get_displays ( ) ;
#[ cfg(windows) ]
let displays = display_service ::try_get_displays_add_amyuni_headless ( ) ;
check_update_displays ( & displays ? ) ;
2023-10-17 23:31:50 +08:00
Ok ( SYNC_DISPLAYS . lock ( ) . unwrap ( ) . displays . clone ( ) )
2023-10-08 21:44:54 +08:00
}
#[ inline ]
pub fn get_primary ( ) -> usize {
#[ cfg(target_os = " linux " ) ]
{
2023-10-21 13:10:51 +08:00
if ! is_x11 ( ) {
2023-10-08 21:44:54 +08:00
return match super ::wayland ::get_primary ( ) {
Ok ( n ) = > n ,
Err ( _ ) = > 0 ,
} ;
}
}
2023-11-13 17:31:55 +08:00
try_get_displays ( ) . map ( | d | get_primary_2 ( & d ) ) . unwrap_or ( 0 )
2023-10-08 21:44:54 +08:00
}
#[ inline ]
pub fn get_primary_2 ( all : & Vec < Display > ) -> usize {
all . iter ( ) . position ( | d | d . is_primary ( ) ) . unwrap_or ( 0 )
}
#[ inline ]
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2023-10-08 21:44:54 +08:00
fn no_displays ( displays : & Vec < Display > ) -> bool {
let display_len = displays . len ( ) ;
if display_len = = 0 {
true
} else if display_len = = 1 {
let display = & displays [ 0 ] ;
2023-10-27 16:19:42 +08:00
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
2023-10-08 21:44:54 +08:00
} else {
false
}
}
#[ inline ]
2024-05-06 15:26:21 +08:00
#[ cfg(not(windows)) ]
2023-11-13 17:31:55 +08:00
pub fn try_get_displays ( ) -> ResultType < Vec < Display > > {
Ok ( Display ::all ( ) ? )
2023-10-08 21:44:54 +08:00
}
2024-05-06 14:01:59 +08:00
#[ inline ]
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2023-11-13 17:31:55 +08:00
pub fn try_get_displays ( ) -> ResultType < Vec < Display > > {
2024-05-06 14:01:59 +08:00
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 ]
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2024-05-06 14:01:59 +08:00
pub fn try_get_displays_add_amyuni_headless ( ) -> ResultType < Vec < Display > > {
try_get_displays_ ( true )
}
#[ inline ]
2024-05-06 15:26:21 +08:00
#[ cfg(windows) ]
2024-05-06 14:01:59 +08:00
pub fn try_get_displays_ ( add_amyuni_headless : bool ) -> ResultType < Vec < Display > > {
2023-11-13 17:31:55 +08:00
let mut displays = Display ::all ( ) ? ;
2024-05-06 14:01:59 +08:00
// Do not add virtual display if the platform is not installed or the virtual display is not supported.
2024-07-09 18:01:30 +08:00
if ! crate ::platform ::is_installed ( ) | | ! virtual_display_manager ::is_virtual_display_supported ( )
{
2024-05-06 14:01:59 +08:00
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 ) ;
}
2024-07-18 22:23:45 +08:00
// The following code causes a bug.
// The virtual display cannot be added when there's no session(eg. when exiting from RDP).
// Because `crate::platform::desktop_changed()` always returns true at that time.
//
// The code only solves a rare case:
// 1. The control side is connecting.
// 2. The windows session is switching, no displays are detected, but they're there.
// Then the controlled side plugs in a virtual display for "headless".
//
// No need to do the following check. But the code is kept here for marking the issue.
// If there're someones reporting the issue, we may add a better check by waiting for a while. (switching session).
// But I don't think it's good to add the timeout check without any issue.
//
// If is switching session, no displays may be detected.
// if displays.is_empty() && crate::platform::desktop_changed() {
// return Ok(displays);
// }
2024-05-06 14:01:59 +08:00
2024-04-19 11:31:52 +08:00
let no_displays_v = no_displays ( & displays ) ;
2024-05-06 14:01:59 +08:00
if no_displays_v {
2023-10-08 21:44:54 +08:00
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 {
2023-11-13 17:31:55 +08:00
displays = Display ::all ( ) ? ;
2023-10-08 21:44:54 +08:00
}
}
Ok ( displays )
}