Merge branch 'master' into patch-9

This commit is contained in:
RustDesk
2023-10-14 22:23:51 +08:00
committed by GitHub
91 changed files with 3068 additions and 1347 deletions

View File

@@ -3,10 +3,7 @@ use std::{
net::SocketAddr,
ops::Deref,
str::FromStr,
sync::{
atomic::{AtomicUsize, Ordering},
mpsc, Arc, Mutex, RwLock,
},
sync::{mpsc, Arc, Mutex, RwLock},
};
pub use async_trait::async_trait;
@@ -60,6 +57,7 @@ use scrap::{
use crate::{
common::input::{MOUSE_BUTTON_LEFT, MOUSE_BUTTON_RIGHT, MOUSE_TYPE_DOWN, MOUSE_TYPE_UP},
is_keyboard_mode_supported,
ui_session_interface::{InvokeUiSession, Session},
};
#[cfg(not(feature = "flutter"))]
@@ -675,9 +673,12 @@ impl Client {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn try_stop_clipboard(_self_uuid: &uuid::Uuid) {
fn try_stop_clipboard(_self_id: &str) {
#[cfg(feature = "flutter")]
if crate::flutter::sessions::other_sessions_running(_self_uuid) {
if crate::flutter::sessions::other_sessions_running(
_self_id.to_string(),
ConnType::DEFAULT_CONN,
) {
return;
}
TEXT_CLIPBOARD_STATE.lock().unwrap().running = false;
@@ -1206,6 +1207,17 @@ impl LoginConfigHandler {
self.save_config(config);
}
/// Save reverse mouse wheel ("", "Y") to the current config.
///
/// # Arguments
///
/// * `value` - The "displays_as_individual_windows" value ("", "Y").
pub fn save_displays_as_individual_windows(&mut self, value: String) {
let mut config = self.load_config();
config.displays_as_individual_windows = value;
self.save_config(config);
}
/// Save scroll style to the current config.
///
/// # Arguments
@@ -1523,6 +1535,15 @@ impl LoginConfigHandler {
msg_out
}
/// Create a [`Message`] for refreshing video.
pub fn refresh_display(display: usize) -> Message {
let mut misc = Misc::new();
misc.set_refresh_video_display(display as _);
let mut msg_out = Message::new();
msg_out.set_misc(misc);
msg_out
}
/// Create a [`Message`] for saving custom image quality.
///
/// # Arguments
@@ -1789,89 +1810,158 @@ impl LoginConfigHandler {
/// Media data.
pub enum MediaData {
VideoQueue,
VideoQueue(usize),
VideoFrame(Box<VideoFrame>),
AudioFrame(Box<AudioFrame>),
AudioFormat(AudioFormat),
Reset,
RecordScreen(bool, i32, i32, String),
Reset(usize),
RecordScreen(bool, usize, i32, i32, String),
}
pub type MediaSender = mpsc::Sender<MediaData>;
struct VideoHandlerController {
handler: VideoHandler,
count: u128,
duration: std::time::Duration,
skip_beginning: u32,
}
/// Start video and audio thread.
/// Return two [`MediaSender`], they should be given to the media producer.
///
/// # Arguments
///
/// * `video_callback` - The callback for video frame. Being called when a video frame is ready.
pub fn start_video_audio_threads<F>(
pub fn start_video_audio_threads<F, T>(
session: Session<T>,
video_callback: F,
) -> (
MediaSender,
MediaSender,
Arc<ArrayQueue<VideoFrame>>,
Arc<AtomicUsize>,
Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
Arc<RwLock<HashMap<usize, usize>>>,
)
where
F: 'static + FnMut(&mut scrap::ImageRgb) + Send,
F: 'static + FnMut(usize, &mut scrap::ImageRgb) + Send,
T: InvokeUiSession,
{
let (video_sender, video_receiver) = mpsc::channel::<MediaData>();
let video_queue = Arc::new(ArrayQueue::<VideoFrame>::new(VIDEO_QUEUE_SIZE));
let video_queue_cloned = video_queue.clone();
let video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>> = Default::default();
let video_queue_map_cloned = video_queue_map.clone();
let mut video_callback = video_callback;
let mut duration = std::time::Duration::ZERO;
let mut count = 0;
let fps = Arc::new(AtomicUsize::new(0));
let decode_fps = fps.clone();
let mut skip_beginning = 0;
let fps_map = Arc::new(RwLock::new(HashMap::new()));
let decode_fps_map = fps_map.clone();
std::thread::spawn(move || {
#[cfg(windows)]
sync_cpu_usage();
let mut video_handler = VideoHandler::new();
let mut handler_controller_map = Vec::new();
// let mut count = Vec::new();
// let mut duration = std::time::Duration::ZERO;
// let mut skip_beginning = Vec::new();
loop {
if let Ok(data) = video_receiver.recv() {
match data {
MediaData::VideoFrame(_) | MediaData::VideoQueue => {
let vf = if let MediaData::VideoFrame(vf) = data {
*vf
} else {
if let Some(vf) = video_queue.pop() {
vf
} else {
MediaData::VideoFrame(_) | MediaData::VideoQueue(_) => {
let vf = match data {
MediaData::VideoFrame(vf) => *vf,
MediaData::VideoQueue(display) => {
if let Some(video_queue) =
video_queue_map.read().unwrap().get(&display)
{
if let Some(vf) = video_queue.pop() {
vf
} else {
continue;
}
} else {
continue;
}
}
_ => {
// unreachable!();
continue;
}
};
let display = vf.display as usize;
let start = std::time::Instant::now();
if let Ok(true) = video_handler.handle_frame(vf) {
video_callback(&mut video_handler.rgb);
// fps calculation
// The first frame will be very slow
if skip_beginning < 5 {
skip_beginning += 1;
continue;
if handler_controller_map.len() <= display {
for _i in handler_controller_map.len()..=display {
handler_controller_map.push(VideoHandlerController {
handler: VideoHandler::new(),
count: 0,
duration: std::time::Duration::ZERO,
skip_beginning: 0,
});
}
duration += start.elapsed();
count += 1;
if count % 10 == 0 {
fps.store(
(count * 1000 / duration.as_millis()) as usize,
Ordering::Relaxed,
);
}
// Clear to get real-time fps
if count > 150 {
count = 0;
duration = Duration::ZERO;
}
if let Some(handler_controller) = handler_controller_map.get_mut(display) {
match handler_controller.handler.handle_frame(vf) {
Ok(true) => {
video_callback(display, &mut handler_controller.handler.rgb);
// fps calculation
// The first frame will be very slow
if handler_controller.skip_beginning < 5 {
handler_controller.skip_beginning += 1;
continue;
}
handler_controller.duration += start.elapsed();
handler_controller.count += 1;
if handler_controller.count % 10 == 0 {
fps_map.write().unwrap().insert(
display,
(handler_controller.count * 1000
/ handler_controller.duration.as_millis())
as usize,
);
}
// Clear to get real-time fps
if handler_controller.count > 150 {
handler_controller.count = 0;
handler_controller.duration = Duration::ZERO;
}
}
Err(e) => {
// This is a simple workaround.
//
// I only see the following error:
// FailedCall("errcode=1 scrap::common::vpxcodec:libs\\scrap\\src\\common\\vpxcodec.rs:433:9")
// When switching from all displays to one display, the error occurs.
// eg:
// 1. Connect to a device with two displays (A and B).
// 2. Switch to display A. The error occurs.
// 3. If the error does not occur. Switch from A to display B. The error occurs.
//
// to-do: fix the error
log::error!("handle video frame error, {}", e);
session.refresh_video(display as _);
}
_ => {}
}
}
}
MediaData::Reset => {
video_handler.reset();
MediaData::Reset(display) => {
if let Some(handler_controler) = handler_controller_map.get_mut(display) {
handler_controler.handler.reset();
}
}
MediaData::RecordScreen(start, w, h, id) => {
video_handler.record_screen(start, w, h, id)
MediaData::RecordScreen(start, display, w, h, id) => {
if handler_controller_map.len() == 1 {
// Compatible with the sciter version(single ui session).
// For the sciter version, there're no multi-ui-sessions for one connection.
// The display is always 0, video_handler_controllers.len() is always 1. So we use the first video handler.
handler_controller_map[0]
.handler
.record_screen(start, w, h, id);
} else {
if let Some(handler_controler) = handler_controller_map.get_mut(display)
{
handler_controler.handler.record_screen(start, w, h, id);
}
}
}
_ => {}
}
@@ -1882,7 +1972,12 @@ where
log::info!("Video decoder loop exits");
});
let audio_sender = start_audio_thread();
return (video_sender, audio_sender, video_queue_cloned, decode_fps);
return (
video_sender,
audio_sender,
video_queue_map_cloned,
decode_fps_map,
);
}
/// Start an audio thread
@@ -2500,7 +2595,7 @@ pub enum Data {
SetConfirmOverrideFile((i32, i32, bool, bool, bool)),
AddJob((i32, String, String, i32, bool, bool)),
ResumeJob((i32, bool)),
RecordScreen(bool, i32, i32, String),
RecordScreen(bool, usize, i32, i32, String),
ElevateDirect,
ElevateWithLogon(String, String),
NewVoiceCall,

View File

@@ -1,3 +1,4 @@
use std::collections::HashMap;
use hbb_common::{
get_time,
message_proto::{Message, VoiceCallRequest, VoiceCallResponse},
@@ -7,7 +8,7 @@ use scrap::CodecFormat;
#[derive(Debug, Default)]
pub struct QualityStatus {
pub speed: Option<String>,
pub fps: Option<i32>,
pub fps: HashMap<usize, i32>,
pub delay: Option<i32>,
pub target_bitrate: Option<i32>,
pub codec_format: Option<CodecFormat>,

View File

@@ -1,33 +1,41 @@
use std::collections::HashMap;
use std::num::NonZeroI64;
use std::sync::{
atomic::{AtomicUsize, Ordering},
Arc,
use std::{
collections::HashMap,
num::NonZeroI64,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, RwLock,
},
};
#[cfg(windows)]
use clipboard::{cliprdr::CliprdrClientContext, empty_clipboard, ContextSend};
use crossbeam_queue::ArrayQueue;
use hbb_common::config::{PeerConfig, TransferSerde};
use hbb_common::fs::{
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
RemoveJobMeta,
};
use hbb_common::message_proto::permission_info::Permission;
use hbb_common::protobuf::Message as _;
use hbb_common::rendezvous_proto::ConnType;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::sleep;
#[cfg(not(target_os = "ios"))]
use hbb_common::tokio::sync::mpsc::error::TryRecvError;
#[cfg(windows)]
use hbb_common::tokio::sync::Mutex as TokioMutex;
use hbb_common::tokio::{
self,
sync::mpsc,
time::{self, Duration, Instant, Interval},
use hbb_common::{
allow_err,
config::{PeerConfig, TransferSerde},
fs,
fs::{
can_enable_overwrite_detection, get_job, get_string, new_send_confirm, DigestCheckResult,
RemoveJobMeta,
},
get_time, log,
message_proto::permission_info::Permission,
message_proto::*,
protobuf::Message as _,
rendezvous_proto::ConnType,
tokio::{
self,
sync::mpsc,
time::{self, Duration, Instant, Interval},
},
Stream,
};
use hbb_common::{allow_err, fs, get_time, log, message_proto::*, Stream};
use scrap::CodecFormat;
use crate::client::{
@@ -43,7 +51,7 @@ use crate::{client::Data, client::Interface};
pub struct Remote<T: InvokeUiSession> {
handler: Session<T>,
video_queue: Arc<ArrayQueue<VideoFrame>>,
video_queue_map: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
video_sender: MediaSender,
audio_sender: MediaSender,
receiver: mpsc::UnboundedReceiver<Data>,
@@ -61,27 +69,27 @@ pub struct Remote<T: InvokeUiSession> {
#[cfg(windows)]
client_conn_id: i32, // used for file clipboard
data_count: Arc<AtomicUsize>,
frame_count: Arc<AtomicUsize>,
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
video_format: CodecFormat,
elevation_requested: bool,
fps_control: FpsControl,
decode_fps: Arc<AtomicUsize>,
fps_control_map: HashMap<usize, FpsControl>,
decode_fps_map: Arc<RwLock<HashMap<usize, usize>>>,
}
impl<T: InvokeUiSession> Remote<T> {
pub fn new(
handler: Session<T>,
video_queue: Arc<ArrayQueue<VideoFrame>>,
video_queue: Arc<RwLock<HashMap<usize, ArrayQueue<VideoFrame>>>>,
video_sender: MediaSender,
audio_sender: MediaSender,
receiver: mpsc::UnboundedReceiver<Data>,
sender: mpsc::UnboundedSender<Data>,
frame_count: Arc<AtomicUsize>,
decode_fps: Arc<AtomicUsize>,
frame_count_map: Arc<RwLock<HashMap<usize, usize>>>,
decode_fps: Arc<RwLock<HashMap<usize, usize>>>,
) -> Self {
Self {
handler,
video_queue,
video_queue_map: video_queue,
video_sender,
audio_sender,
receiver,
@@ -96,13 +104,13 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(windows)]
client_conn_id: 0,
data_count: Arc::new(AtomicUsize::new(0)),
frame_count,
frame_count_map,
video_format: CodecFormat::Unknown,
stop_voice_call_sender: None,
voice_call_request_timestamp: None,
elevation_requested: false,
fps_control: Default::default(),
decode_fps,
fps_control_map: Default::default(),
decode_fps_map: decode_fps,
}
}
@@ -152,7 +160,7 @@ impl<T: InvokeUiSession> Remote<T> {
|| self.handler.is_rdp();
if !is_conn_not_default {
(self.client_conn_id, rx_clip_client_lock) =
clipboard::get_rx_cliprdr_client(&self.handler.session_id);
clipboard::get_rx_cliprdr_client(&self.handler.id);
};
}
#[cfg(windows)]
@@ -229,12 +237,18 @@ impl<T: InvokeUiSession> Remote<T> {
let mut speed = self.data_count.swap(0, Ordering::Relaxed);
speed = speed * 1000 / elapsed as usize;
let speed = format!("{:.2}kB/s", speed as f32 / 1024 as f32);
let mut fps = self.frame_count.swap(0, Ordering::Relaxed) as _;
// Correcting the inaccuracy of status_timer
fps = fps * 1000 / elapsed as i32;
let mut frame_count_map_write = self.frame_count_map.write().unwrap();
let frame_count_map = frame_count_map_write.clone();
frame_count_map_write.values_mut().for_each(|v| *v = 0);
drop(frame_count_map_write);
let fps = frame_count_map.iter().map(|(k, v)| {
// Correcting the inaccuracy of status_timer
(k.clone(), (*v as i32) * 1000 / elapsed as i32)
}).collect::<HashMap<usize, i32>>();
self.handler.update_quality_status(QualityStatus {
speed:Some(speed),
fps:Some(fps),
speed: Some(speed),
fps,
..Default::default()
});
}
@@ -260,7 +274,7 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if _set_disconnected_ok {
Client::try_stop_clipboard(&self.handler.session_id);
Client::try_stop_clipboard(&self.handler.id);
}
#[cfg(windows)]
@@ -760,10 +774,10 @@ impl<T: InvokeUiSession> Remote<T> {
}
}
}
Data::RecordScreen(start, w, h, id) => {
Data::RecordScreen(start, display, w, h, id) => {
let _ = self
.video_sender
.send(MediaData::RecordScreen(start, w, h, id));
.send(MediaData::RecordScreen(start, display, w, h, id));
}
Data::ElevateDirect => {
let mut request = ElevationRequest::new();
@@ -904,89 +918,100 @@ impl<T: InvokeUiSession> Remote<T> {
None => false,
}
}
#[inline]
fn fps_control(&mut self, direct: bool) {
let len = self.video_queue.len();
let ctl = &mut self.fps_control;
// Current full speed decoding fps
let decode_fps = self.decode_fps.load(std::sync::atomic::Ordering::Relaxed);
if decode_fps == 0 {
return;
}
let limited_fps = if direct {
decode_fps * 9 / 10 // 30 got 27
} else {
decode_fps * 4 / 5 // 30 got 24
};
// send full speed fps
let version = self.handler.lc.read().unwrap().version;
let max_encode_speed = 144 * 10 / 9;
if version >= hbb_common::get_version_number("1.2.1")
&& (ctl.last_full_speed_fps.is_none() // First time
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
{
let mut misc = Misc::new();
misc.set_full_speed_fps(decode_fps as _);
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_full_speed_fps = Some(decode_fps as _);
}
// decrease judgement
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
let should_decrease = len >= debounce // exceed debounce
&& len > ctl.last_queue_size + 5 // still caching
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
let decode_fps_read = self.decode_fps_map.read().unwrap();
for (display, decode_fps) in decode_fps_read.iter() {
let video_queue_map_read = self.video_queue_map.read().unwrap();
let Some(video_queue) = video_queue_map_read.get(display) else {
continue;
};
// increase judgement
if len <= 1 {
ctl.idle_counter += 1;
} else {
ctl.idle_counter = 0;
}
let mut should_increase = false;
if let Some(last_custom_fps) = ctl.last_custom_fps {
// ever set
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
should_increase = true;
if !self.fps_control_map.contains_key(display) {
self.fps_control_map.insert(*display, FpsControl::default());
}
}
if should_decrease || should_increase {
// limited_fps to ensure decoding is faster than encoding
let mut custom_fps = limited_fps as i32;
if custom_fps < 1 {
custom_fps = 1;
}
// send custom fps
let mut misc = Misc::new();
if version > hbb_common::get_version_number("1.2.1") {
// avoid confusion with custom image quality fps
misc.set_auto_adjust_fps(custom_fps as _);
let Some(ctl) = self.fps_control_map.get_mut(display) else {
return;
};
let len = video_queue.len();
let decode_fps = *decode_fps;
let limited_fps = if direct {
decode_fps * 9 / 10 // 30 got 27
} else {
misc.set_option(OptionMessage {
custom_fps,
..Default::default()
});
decode_fps * 4 / 5 // 30 got 24
};
// send full speed fps
let version = self.handler.lc.read().unwrap().version;
let max_encode_speed = 144 * 10 / 9;
if version >= hbb_common::get_version_number("1.2.1")
&& (ctl.last_full_speed_fps.is_none() // First time
|| ((ctl.last_full_speed_fps.unwrap_or_default() - decode_fps as i32).abs() >= 5 // diff 5
&& !(decode_fps > max_encode_speed // already exceed max encoding speed
&& ctl.last_full_speed_fps.unwrap_or_default() > max_encode_speed as i32)))
{
let mut misc = Misc::new();
misc.set_full_speed_fps(decode_fps as _);
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_full_speed_fps = Some(decode_fps as _);
}
// decrease judgement
let debounce = if decode_fps > 10 { decode_fps / 2 } else { 5 }; // 500ms
let should_decrease = len >= debounce // exceed debounce
&& len > ctl.last_queue_size + 5 // still caching
&& !ctl.last_custom_fps.unwrap_or(i32::MAX) < limited_fps as i32; // NOT already set a smaller one
// increase judgement
if len <= 1 {
ctl.idle_counter += 1;
} else {
ctl.idle_counter = 0;
}
let mut should_increase = false;
if let Some(last_custom_fps) = ctl.last_custom_fps {
// ever set
if last_custom_fps + 5 < limited_fps as i32 && ctl.idle_counter > 3 {
// limited_fps is 5 larger than last set, and idle time is more than 3 seconds
should_increase = true;
}
}
if should_decrease || should_increase {
// limited_fps to ensure decoding is faster than encoding
let mut custom_fps = limited_fps as i32;
if custom_fps < 1 {
custom_fps = 1;
}
// send custom fps
let mut misc = Misc::new();
if version > hbb_common::get_version_number("1.2.1") {
// avoid confusion with custom image quality fps
misc.set_auto_adjust_fps(custom_fps as _);
} else {
misc.set_option(OptionMessage {
custom_fps,
..Default::default()
});
}
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_queue_size = len;
ctl.last_custom_fps = Some(custom_fps);
}
// send refresh
if ctl.refresh_times < 10 // enough
&& (len > video_queue.capacity() / 2
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
{
// Refresh causes client set_display, left frames cause flickering.
while let Some(_) = video_queue.pop() {}
self.handler.refresh_video(*display as _);
ctl.refresh_times += 1;
ctl.last_refresh_instant = Instant::now();
}
let mut msg = Message::new();
msg.set_misc(misc);
self.sender.send(Data::Message(msg)).ok();
ctl.last_queue_size = len;
ctl.last_custom_fps = Some(custom_fps);
}
// send refresh
if ctl.refresh_times < 10 // enough
&& (len > self.video_queue.capacity() / 2
&& (ctl.refresh_times == 0 || ctl.last_refresh_instant.elapsed().as_secs() > 30))
{
// Refresh causes client set_display, left frames cause flickering.
while let Some(_) = self.video_queue.pop() {}
self.handler.refresh_video();
ctl.refresh_times += 1;
ctl.last_refresh_instant = Instant::now();
}
}
@@ -1008,14 +1033,27 @@ impl<T: InvokeUiSession> Remote<T> {
..Default::default()
})
};
let display = vf.display as usize;
let mut video_queue_write = self.video_queue_map.write().unwrap();
if !video_queue_write.contains_key(&display) {
video_queue_write.insert(
display,
ArrayQueue::<VideoFrame>::new(crate::client::VIDEO_QUEUE_SIZE),
);
}
if Self::contains_key_frame(&vf) {
while let Some(_) = self.video_queue.pop() {}
if let Some(video_queue) = video_queue_write.get_mut(&display) {
while let Some(_) = video_queue.pop() {}
}
self.video_sender
.send(MediaData::VideoFrame(Box::new(vf)))
.ok();
} else {
self.video_queue.force_push(vf);
self.video_sender.send(MediaData::VideoQueue).ok();
if let Some(video_queue) = video_queue_write.get_mut(&display) {
video_queue.force_push(vf);
}
self.video_sender.send(MediaData::VideoQueue(display)).ok();
}
}
Some(message::Union::Hash(hash)) => {
@@ -1297,7 +1335,9 @@ impl<T: InvokeUiSession> Remote<T> {
}
Some(misc::Union::SwitchDisplay(s)) => {
self.handler.handle_peer_switch_display(&s);
self.video_sender.send(MediaData::Reset).ok();
self.video_sender
.send(MediaData::Reset(s.display as _))
.ok();
if s.width > 0 && s.height > 0 {
self.handler.set_display(
s.x,
@@ -1674,7 +1714,7 @@ impl<T: InvokeUiSession> Remote<T> {
#[cfg(feature = "flutter")]
if let Some(hbb_common::message_proto::cliprdr::Union::FormatList(_)) = &clip.union {
if self.client_conn_id
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_session_id()).unwrap_or(0)
!= clipboard::get_client_conn_id(&crate::flutter::get_cur_peer_id()).unwrap_or(0)
{
return;
}

View File

@@ -54,6 +54,8 @@ pub const PLATFORM_LINUX: &str = "Linux";
pub const PLATFORM_MACOS: &str = "Mac OS";
pub const PLATFORM_ANDROID: &str = "Android";
const MIN_VER_MULTI_UI_SESSION: &str = "1.2.4";
pub mod input {
pub const MOUSE_TYPE_MOVE: i32 = 0;
pub const MOUSE_TYPE_DOWN: i32 = 1;
@@ -120,6 +122,16 @@ pub fn set_server_running(b: bool) {
*SERVER_RUNNING.write().unwrap() = b;
}
#[inline]
pub fn is_support_multi_ui_session(ver: &str) -> bool {
is_support_multi_ui_session_num(hbb_common::get_version_number(ver))
}
#[inline]
pub fn is_support_multi_ui_session_num(ver: i64) -> bool {
ver >= hbb_common::get_version_number(MIN_VER_MULTI_UI_SESSION)
}
// is server process, with "--server" args
#[inline]
pub fn is_server() -> bool {
@@ -780,13 +792,17 @@ pub fn get_sysinfo() -> serde_json::Value {
}
let hostname = hostname(); // sys.hostname() return localhost on android in my test
use serde_json::json;
let mut out = json!({
#[cfg(any(target_os = "android", target_os = "ios"))]
let out;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let mut out;
out = json!({
"cpu": format!("{cpu}{num_cpus}/{num_pcpus} cores"),
"memory": format!("{memory}GB"),
"os": os,
"hostname": hostname,
});
#[cfg(not(any(target_os = "android", target_os = "ios")))]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
out["username"] = json!(crate::platform::get_active_username());
}

View File

@@ -18,8 +18,6 @@ use hbb_common::{
};
use serde_json::json;
#[cfg(not(feature = "flutter_texture_render"))]
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
collections::HashMap,
ffi::CString,
@@ -150,25 +148,40 @@ pub unsafe extern "C" fn free_c_args(ptr: *mut *mut c_char, len: c_int) {
// Afterwards the vector will be dropped and thus freed.
}
#[derive(Default)]
struct SessionHandler {
event_stream: Option<StreamSink<EventToUI>>,
#[cfg(feature = "flutter_texture_render")]
notify_rendered: bool,
#[cfg(feature = "flutter_texture_render")]
renderer: VideoRenderer,
}
#[cfg(feature = "flutter_texture_render")]
#[derive(Default, Clone)]
pub struct FlutterHandler {
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
notify_rendered: Arc<RwLock<bool>>,
renderer: Arc<RwLock<VideoRenderer>>,
// ui session id -> display handler data
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
peer_info: Arc<RwLock<PeerInfo>>,
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
}
#[cfg(not(feature = "flutter_texture_render"))]
#[derive(Default, Clone)]
pub struct FlutterHandler {
pub event_stream: Arc<RwLock<Option<StreamSink<EventToUI>>>>,
struct RgbaData {
// SAFETY: [rgba] is guarded by [rgba_valid], and it's safe to reach [rgba] with `rgba_valid == true`.
// We must check the `rgba_valid` before reading [rgba].
pub rgba: Arc<RwLock<Vec<u8>>>,
pub rgba_valid: Arc<AtomicBool>,
data: Vec<u8>,
valid: bool,
}
#[cfg(not(feature = "flutter_texture_render"))]
#[derive(Default, Clone)]
pub struct FlutterHandler {
session_handlers: Arc<RwLock<HashMap<SessionID, SessionHandler>>>,
display_rgbas: Arc<RwLock<HashMap<usize, RgbaData>>>,
peer_info: Arc<RwLock<PeerInfo>>,
#[cfg(not(any(target_os = "android", target_os = "ios")))]
hooks: Arc<RwLock<HashMap<String, SessionHook>>>,
@@ -184,14 +197,22 @@ pub type FlutterRgbaRendererPluginOnRgba = unsafe extern "C" fn(
dst_rgba_stride: c_int,
);
#[cfg(feature = "flutter_texture_render")]
pub(super) type TextureRgbaPtr = usize;
#[cfg(feature = "flutter_texture_render")]
struct DisplaySessionInfo {
// TextureRgba pointer in flutter native.
texture_rgba_ptr: TextureRgbaPtr,
size: (usize, usize),
}
// Video Texture Renderer in Flutter
#[cfg(feature = "flutter_texture_render")]
#[derive(Clone)]
struct VideoRenderer {
// TextureRgba pointer in flutter native.
ptr: Arc<RwLock<usize>>,
width: usize,
height: usize,
is_support_multi_ui_session: bool,
map_display_sessions: Arc<RwLock<HashMap<usize, DisplaySessionInfo>>>,
on_rgba_func: Option<Symbol<'static, FlutterRgbaRendererPluginOnRgba>>,
}
@@ -217,9 +238,8 @@ impl Default for VideoRenderer {
}
};
Self {
ptr: Default::default(),
width: 0,
height: 0,
map_display_sessions: Default::default(),
is_support_multi_ui_session: false,
on_rgba_func,
}
}
@@ -228,33 +248,74 @@ impl Default for VideoRenderer {
#[cfg(feature = "flutter_texture_render")]
impl VideoRenderer {
#[inline]
pub fn set_size(&mut self, width: usize, height: usize) {
self.width = width;
self.height = height;
fn set_size(&mut self, display: usize, width: usize, height: usize) {
let mut sessions_lock = self.map_display_sessions.write().unwrap();
if let Some(info) = sessions_lock.get_mut(&display) {
info.size = (width, height);
} else {
sessions_lock.insert(
display,
DisplaySessionInfo {
texture_rgba_ptr: usize::default(),
size: (width, height),
},
);
}
}
pub fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
let ptr = self.ptr.read().unwrap();
if *ptr == usize::default() {
fn register_texture(&self, display: usize, ptr: usize) {
let mut sessions_lock = self.map_display_sessions.write().unwrap();
if ptr == 0 {
sessions_lock.remove(&display);
} else {
if let Some(info) = sessions_lock.get_mut(&display) {
if info.texture_rgba_ptr != 0 && info.texture_rgba_ptr != ptr as TextureRgbaPtr {
log::error!("unreachable, texture_rgba_ptr is not null and not equal to ptr");
}
info.texture_rgba_ptr = ptr as _;
} else {
if ptr != 0 {
sessions_lock.insert(
display,
DisplaySessionInfo {
texture_rgba_ptr: ptr as _,
size: (0, 0),
},
);
}
}
}
}
pub fn on_rgba(&self, display: usize, rgba: &scrap::ImageRgb) {
let read_lock = self.map_display_sessions.read().unwrap();
let opt_info = if !self.is_support_multi_ui_session {
read_lock.values().next()
} else {
read_lock.get(&display)
};
let Some(info) = opt_info else {
return;
};
if info.texture_rgba_ptr == usize::default() {
return;
}
// It is also Ok to skip this check.
if self.width != rgba.w || self.height != rgba.h {
if info.size.0 != rgba.w || info.size.1 != rgba.h {
log::error!(
"width/height mismatch: ({},{}) != ({},{})",
self.width,
self.height,
info.size.0,
info.size.1,
rgba.w,
rgba.h
);
return;
}
if let Some(func) = &self.on_rgba_func {
unsafe {
func(
*ptr as _,
info.texture_rgba_ptr as _,
rgba.raw.as_ptr() as _,
rgba.raw.len() as _,
rgba.w as _,
@@ -266,34 +327,42 @@ impl VideoRenderer {
}
}
impl SessionHandler {
pub fn on_waiting_for_image_dialog_show(&mut self) {
#[cfg(any(feature = "flutter_texture_render"))]
{
self.notify_rendered = false;
}
// rgba array render will notify every frame
}
}
impl FlutterHandler {
/// Push an event to the event queue.
/// An event is stored as json in the event queue.
/// Push an event to all the event queues.
/// An event is stored as json in the event queues.
///
/// # Arguments
///
/// * `name` - The name of the event.
/// * `event` - Fields of the event content.
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) -> Option<bool> {
pub fn push_event(&self, name: &str, event: Vec<(&str, &str)>) {
let mut h: HashMap<&str, &str> = event.iter().cloned().collect();
debug_assert!(h.get("name").is_none());
h.insert("name", name);
let out = serde_json::ser::to_string(&h).unwrap_or("".to_owned());
Some(
self.event_stream
.read()
.unwrap()
.as_ref()?
.add(EventToUI::Event(out)),
)
for (_, session) in self.session_handlers.read().unwrap().iter() {
if let Some(stream) = &session.event_stream {
stream.add(EventToUI::Event(out.clone()));
}
}
}
pub(crate) fn close_event_stream(&self) {
let mut stream_lock = self.event_stream.write().unwrap();
if let Some(stream) = &*stream_lock {
stream.add(EventToUI::Event("close".to_owned()));
pub(crate) fn close_event_stream(&self, session_id: SessionID) {
// to-do: Make sure the following logic is correct.
// No need to remove the display handler, because it will be removed when the connection is closed.
if let Some(session) = self.session_handlers.write().unwrap().get_mut(&session_id) {
try_send_close_event(&session.event_stream);
}
*stream_lock = None;
}
fn make_displays_msg(displays: &Vec<DisplayInfo>) -> String {
@@ -314,6 +383,7 @@ impl FlutterHandler {
serde_json::ser::to_string(&msg_vec).unwrap_or("".to_owned())
}
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(crate) fn add_session_hook(&self, key: String, hook: SessionHook) -> bool {
let mut hooks = self.hooks.write().unwrap();
@@ -325,6 +395,7 @@ impl FlutterHandler {
true
}
#[cfg(feature = "plugin_framework")]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub(crate) fn remove_session_hook(&self, key: &String) -> bool {
let mut hooks = self.hooks.write().unwrap();
@@ -335,27 +406,6 @@ impl FlutterHandler {
let _ = hooks.remove(key);
true
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
pub fn register_texture(&self, ptr: usize) {
*self.renderer.read().unwrap().ptr.write().unwrap() = ptr;
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
pub fn set_size(&self, width: usize, height: usize) {
*self.notify_rendered.write().unwrap() = false;
self.renderer.write().unwrap().set_size(width, height);
}
pub fn on_waiting_for_image_dialog_show(&self) {
#[cfg(any(feature = "flutter_texture_render"))]
{
*self.notify_rendered.write().unwrap() = false;
}
// rgba array render will notify every frame
}
}
impl InvokeUiSession for FlutterHandler {
@@ -408,7 +458,10 @@ impl InvokeUiSession for FlutterHandler {
"update_quality_status",
vec![
("speed", &status.speed.map_or(NULL, |it| it)),
("fps", &status.fps.map_or(NULL, |it| it.to_string())),
(
"fps",
&serde_json::ser::to_string(&status.fps).unwrap_or(NULL.to_owned()),
),
("delay", &status.delay.map_or(NULL, |it| it.to_string())),
(
"target_bitrate",
@@ -529,7 +582,7 @@ impl InvokeUiSession for FlutterHandler {
#[inline]
#[cfg(not(feature = "flutter_texture_render"))]
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
// Give a chance for plugins or etc to hook a rgba data.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
for (key, hook) in self.hooks.read().unwrap().iter() {
@@ -541,27 +594,51 @@ impl InvokeUiSession for FlutterHandler {
}
// If the current rgba is not fetched by flutter, i.e., is valid.
// We give up sending a new event to flutter.
if self.rgba_valid.load(Ordering::Relaxed) {
return;
let mut rgba_write_lock = self.display_rgbas.write().unwrap();
if let Some(rgba_data) = rgba_write_lock.get_mut(&display) {
if rgba_data.valid {
return;
} else {
rgba_data.valid = true;
}
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
} else {
let mut rgba_data = RgbaData::default();
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut rgba_data.data);
rgba_write_lock.insert(display, rgba_data);
}
self.rgba_valid.store(true, Ordering::Relaxed);
// Return the rgba buffer to the video handler for reusing allocated rgba buffer.
std::mem::swap::<Vec<u8>>(&mut rgba.raw, &mut *self.rgba.write().unwrap());
if let Some(stream) = &*self.event_stream.read().unwrap() {
stream.add(EventToUI::Rgba);
drop(rgba_write_lock);
// Non-texture-render UI does not support multiple displays in the one UI session.
// It's Ok to notify each session for now.
for h in self.session_handlers.read().unwrap().values() {
if let Some(stream) = &h.event_stream {
stream.add(EventToUI::Rgba(display));
}
}
}
#[inline]
#[cfg(feature = "flutter_texture_render")]
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
self.renderer.read().unwrap().on_rgba(rgba);
if *self.notify_rendered.read().unwrap() {
return;
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb) {
let mut try_notify_sessions = Vec::new();
for (id, session) in self.session_handlers.read().unwrap().iter() {
session.renderer.on_rgba(display, rgba);
if !session.notify_rendered {
try_notify_sessions.push(id.clone());
}
}
if let Some(stream) = &*self.event_stream.read().unwrap() {
stream.add(EventToUI::Rgba);
*self.notify_rendered.write().unwrap() = true;
if try_notify_sessions.len() > 0 {
let mut write_lock = self.session_handlers.write().unwrap();
for id in try_notify_sessions.iter() {
if let Some(session) = write_lock.get_mut(id) {
if let Some(stream) = &session.event_stream {
stream.add(EventToUI::Rgba(display));
session.notify_rendered = true;
}
}
}
}
}
@@ -578,6 +655,17 @@ impl InvokeUiSession for FlutterHandler {
let features = serde_json::ser::to_string(&features).unwrap_or("".to_owned());
let resolutions = serialize_resolutions(&pi.resolutions.resolutions);
*self.peer_info.write().unwrap() = pi.clone();
#[cfg(feature = "flutter_texture_render")]
{
self.session_handlers
.write()
.unwrap()
.values_mut()
.for_each(|h| {
h.renderer.is_support_multi_ui_session =
crate::common::is_support_multi_ui_session(&pi.version);
});
}
self.push_event(
"peer_info",
vec![
@@ -701,21 +789,31 @@ impl InvokeUiSession for FlutterHandler {
}
#[inline]
fn get_rgba(&self) -> *const u8 {
fn get_rgba(&self, _display: usize) -> *const u8 {
#[cfg(not(feature = "flutter_texture_render"))]
if self.rgba_valid.load(Ordering::Relaxed) {
return self.rgba.read().unwrap().as_ptr();
if let Some(rgba_data) = self.display_rgbas.read().unwrap().get(&_display) {
if rgba_data.valid {
return rgba_data.data.as_ptr();
}
}
std::ptr::null_mut()
}
#[inline]
fn next_rgba(&self) {
fn next_rgba(&self, _display: usize) {
#[cfg(not(feature = "flutter_texture_render"))]
self.rgba_valid.store(false, Ordering::Relaxed);
if let Some(rgba_data) = self.display_rgbas.write().unwrap().get_mut(&_display) {
rgba_data.valid = true;
}
}
}
// This function is only used for the default connection session.
pub fn session_add_existed(peer_id: String, session_id: SessionID) -> ResultType<()> {
sessions::insert_peer_session_id(peer_id, ConnType::DEFAULT_CONN, session_id);
Ok(())
}
/// Create a new remote session with the given id.
///
/// # Arguments
@@ -733,18 +831,6 @@ pub fn session_add(
force_relay: bool,
password: String,
) -> ResultType<FlutterSession> {
LocalConfig::set_remote_id(&id);
let session: Session<FlutterHandler> = Session {
session_id: session_id.clone(),
id: id.to_owned(),
password,
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
..Default::default()
};
let conn_type = if is_file_transfer {
ConnType::FILE_TRANSFER
} else if is_port_forward {
@@ -757,6 +843,26 @@ pub fn session_add(
ConnType::DEFAULT_CONN
};
// to-do: check the same id session.
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
if session.lc.read().unwrap().conn_type != conn_type {
bail!("same session id is found with different conn type?");
}
// The same session is added before?
bail!("same session id is found");
}
LocalConfig::set_remote_id(&id);
let session: Session<FlutterHandler> = Session {
id: id.to_owned(),
password,
server_keyboard_enabled: Arc::new(RwLock::new(true)),
server_file_transfer_enabled: Arc::new(RwLock::new(true)),
server_clipboard_enabled: Arc::new(RwLock::new(true)),
..Default::default()
};
let switch_uuid = if switch_uuid.is_empty() {
None
} else {
@@ -768,12 +874,8 @@ pub fn session_add(
.write()
.unwrap()
.initialize(id.to_owned(), conn_type, switch_uuid, force_relay);
let session = Arc::new(session.clone());
if let Some(same_id_session) = sessions::add_session(session_id.to_owned(), session.clone()) {
log::error!("Should not happen");
same_id_session.close();
}
sessions::insert_session(session_id.to_owned(), conn_type, session.clone());
Ok(session)
}
@@ -789,18 +891,39 @@ pub fn session_start_(
id: &str,
event_stream: StreamSink<EventToUI>,
) -> ResultType<()> {
if let Some(session) = sessions::get_session(session_id) {
#[cfg(feature = "flutter_texture_render")]
log::info!(
"Session {} start, render by flutter texture rgba plugin",
id
// is_connected is used to indicate whether to start a peer connection. For two cases:
// 1. "Move tab to new window"
// 2. multi ui session within the same peer connnection.
let mut is_connected = false;
let mut is_found = false;
for s in sessions::get_sessions() {
if let Some(h) = s.session_handlers.write().unwrap().get_mut(session_id) {
is_connected = h.event_stream.is_some();
try_send_close_event(&h.event_stream);
h.event_stream = Some(event_stream);
is_found = true;
break;
}
}
if !is_found {
bail!(
"No session with peer id {}, session id: {}",
id,
session_id.to_string()
);
#[cfg(not(feature = "flutter_texture_render"))]
log::info!("Session {} start, render by flutter paint widget", id);
let is_pre_added = session.event_stream.read().unwrap().is_some();
session.close_event_stream();
*session.event_stream.write().unwrap() = Some(event_stream);
if !is_pre_added {
}
if let Some(session) = sessions::get_session_by_session_id(session_id) {
let is_first_ui_session = session.session_handlers.read().unwrap().len() == 1;
if !is_connected && is_first_ui_session {
#[cfg(feature = "flutter_texture_render")]
log::info!(
"Session {} start, render by flutter texture rgba plugin",
id
);
#[cfg(not(feature = "flutter_texture_render"))]
log::info!("Session {} start, render by flutter paint widget", id);
let session = (*session).clone();
std::thread::spawn(move || {
let round = session.connection_round_state.lock().unwrap().new_round();
@@ -813,19 +936,26 @@ pub fn session_start_(
}
}
#[inline]
fn try_send_close_event(event_stream: &Option<StreamSink<EventToUI>>) {
if let Some(stream) = &event_stream {
stream.add(EventToUI::Event("close".to_owned()));
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn update_text_clipboard_required() {
let is_required = sessions::get_sessions()
.iter()
.any(|session| session.is_text_clipboard_required());
.any(|s| s.is_text_clipboard_required());
Client::set_is_text_clipboard_required(is_required);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn send_text_clipboard_msg(msg: Message) {
for session in sessions::get_sessions() {
if session.is_text_clipboard_required() {
session.send(Data::Message(msg.clone()));
for s in sessions::get_sessions() {
if s.is_text_clipboard_required() {
s.send(Data::Message(msg.clone()));
}
}
}
@@ -998,6 +1128,11 @@ pub fn get_cur_session_id() -> SessionID {
CUR_SESSION_ID.read().unwrap().clone()
}
pub fn get_cur_peer_id() -> String {
sessions::get_peer_id_by_session_id(&get_cur_session_id(), ConnType::DEFAULT_CONN)
.unwrap_or("".to_string())
}
pub fn set_cur_session_id(session_id: SessionID) {
if get_cur_session_id() != session_id {
*CUR_SESSION_ID.write().unwrap() = session_id;
@@ -1034,47 +1169,76 @@ fn char_to_session_id(c: *const char) -> ResultType<SessionID> {
SessionID::from_str(str).map_err(|e| anyhow!("{:?}", e))
}
pub fn session_get_rgba_size(_session_id: SessionID) -> usize {
pub fn session_get_rgba_size(_session_id: SessionID, _display: usize) -> usize {
#[cfg(not(feature = "flutter_texture_render"))]
if let Some(session) = sessions::get_session(&_session_id) {
return session.rgba.read().unwrap().len();
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
return session
.display_rgbas
.read()
.unwrap()
.get(&_display)
.map_or(0, |rgba| rgba.data.len());
}
0
}
#[no_mangle]
pub extern "C" fn session_get_rgba(session_uuid_str: *const char) -> *const u8 {
pub extern "C" fn session_get_rgba(session_uuid_str: *const char, display: usize) -> *const u8 {
if let Ok(session_id) = char_to_session_id(session_uuid_str) {
if let Some(session) = sessions::get_session(&session_id) {
return session.get_rgba();
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
return s.ui_handler.get_rgba(display);
}
}
std::ptr::null()
}
pub fn session_next_rgba(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
return session.next_rgba();
pub fn session_next_rgba(session_id: SessionID, display: usize) {
if let Some(s) = sessions::get_session_by_session_id(&session_id) {
return s.ui_handler.next_rgba(display);
}
}
#[inline]
pub fn session_register_texture(_session_id: SessionID, _ptr: usize) {
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
#[cfg(feature = "flutter_texture_render")]
if let Some(session) = sessions::get_session(&_session_id) {
session.register_texture(_ptr);
return;
for s in sessions::get_sessions() {
if let Some(h) = s
.ui_handler
.session_handlers
.write()
.unwrap()
.get_mut(&_session_id)
{
h.notify_rendered = false;
h.renderer.set_size(_display, _width, _height);
break;
}
}
}
#[inline]
pub fn push_session_event(
session_id: &SessionID,
name: &str,
event: Vec<(&str, &str)>,
) -> Option<bool> {
sessions::get_session(session_id)?.push_event(name, event)
pub fn session_register_texture(_session_id: SessionID, _display: usize, _ptr: usize) {
#[cfg(feature = "flutter_texture_render")]
for s in sessions::get_sessions() {
if let Some(h) = s
.ui_handler
.session_handlers
.read()
.unwrap()
.get(&_session_id)
{
h.renderer.register_texture(_display, _ptr);
break;
}
}
}
#[inline]
pub fn push_session_event(session_id: &SessionID, name: &str, event: Vec<(&str, &str)>) {
if let Some(s) = sessions::get_session_by_session_id(session_id) {
s.push_event(name, event);
}
}
#[inline]
@@ -1123,7 +1287,7 @@ fn session_send_touch_scale(
) {
match v.get("v").and_then(|s| s.as_i64()) {
Some(scale) => {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_touch_scale(scale as _, alt, ctrl, shift, command);
}
}
@@ -1147,7 +1311,7 @@ fn session_send_touch_pan(
v.get("y").and_then(|y| y.as_i64()),
) {
(Some(x), Some(y)) => {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session
.send_touch_pan_event(pan_event, x as _, y as _, alt, ctrl, shift, command);
}
@@ -1191,6 +1355,15 @@ pub fn session_send_pointer(session_id: SessionID, msg: String) {
}
}
#[inline]
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
for s in sessions::get_sessions() {
if let Some(h) = s.session_handlers.write().unwrap().get_mut(&session_id) {
h.on_waiting_for_image_dialog_show();
}
}
}
/// Hooks for session.
#[derive(Clone)]
pub enum SessionHook {
@@ -1199,7 +1372,7 @@ pub enum SessionHook {
#[inline]
pub fn get_cur_session() -> Option<FlutterSession> {
sessions::get_session(&*CUR_SESSION_ID.read().unwrap())
sessions::get_session_by_session_id(&*CUR_SESSION_ID.read().unwrap())
}
// sessions mod is used to avoid the big lock of sessions' map.
@@ -1207,22 +1380,118 @@ pub mod sessions {
use super::*;
lazy_static::lazy_static! {
static ref SESSIONS: RwLock<HashMap<SessionID, FlutterSession>> = Default::default();
// peer -> peer session, peer session -> ui sessions
static ref SESSIONS: RwLock<HashMap<(String, ConnType), FlutterSession>> = Default::default();
}
#[inline]
pub fn add_session(session_id: SessionID, session: FlutterSession) -> Option<FlutterSession> {
SESSIONS.write().unwrap().insert(session_id, session)
pub fn get_session_count(peer_id: String, conn_type: ConnType) -> usize {
SESSIONS
.read()
.unwrap()
.get(&(peer_id, conn_type))
.map(|s| s.ui_handler.session_handlers.read().unwrap().len())
.unwrap_or(0)
}
#[inline]
pub fn remove_session(session_id: &SessionID) -> Option<FlutterSession> {
SESSIONS.write().unwrap().remove(session_id)
pub fn get_peer_id_by_session_id(id: &SessionID, conn_type: ConnType) -> Option<String> {
SESSIONS
.read()
.unwrap()
.iter()
.find_map(|((peer_id, t), s)| {
if *t == conn_type
&& s.ui_handler
.session_handlers
.read()
.unwrap()
.contains_key(id)
{
Some(peer_id.clone())
} else {
None
}
})
}
#[inline]
pub fn get_session(session_id: &SessionID) -> Option<FlutterSession> {
SESSIONS.read().unwrap().get(session_id).cloned()
pub fn get_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
SESSIONS
.read()
.unwrap()
.values()
.find(|s| {
s.ui_handler
.session_handlers
.read()
.unwrap()
.contains_key(id)
})
.cloned()
}
#[inline]
pub fn get_session_by_peer_id(peer_id: String, conn_type: ConnType) -> Option<FlutterSession> {
SESSIONS.read().unwrap().get(&(peer_id, conn_type)).cloned()
}
#[inline]
pub fn remove_session_by_session_id(id: &SessionID) -> Option<FlutterSession> {
let mut remove_peer_key = None;
for (peer_key, s) in SESSIONS.write().unwrap().iter_mut() {
let mut write_lock = s.ui_handler.session_handlers.write().unwrap();
if write_lock.remove(id).is_some() {
if write_lock.is_empty() {
remove_peer_key = Some(peer_key.clone());
}
break;
}
}
SESSIONS.write().unwrap().remove(&remove_peer_key?)
}
#[inline]
pub fn insert_session(session_id: SessionID, conn_type: ConnType, session: FlutterSession) {
SESSIONS
.write()
.unwrap()
.entry((session.id.clone(), conn_type))
.or_insert(session)
.ui_handler
.session_handlers
.write()
.unwrap()
.insert(session_id, Default::default());
}
#[inline]
pub fn insert_peer_session_id(
peer_id: String,
conn_type: ConnType,
session_id: SessionID,
) -> bool {
if let Some(s) = SESSIONS.read().unwrap().get(&(peer_id, conn_type)) {
#[cfg(not(feature = "flutter_texture_render"))]
let h = SessionHandler::default();
#[cfg(feature = "flutter_texture_render")]
let mut h = SessionHandler::default();
#[cfg(feature = "flutter_texture_render")]
{
h.renderer.is_support_multi_ui_session = crate::common::is_support_multi_ui_session(
&s.ui_handler.peer_info.read().unwrap().version,
);
}
let _ = s
.ui_handler
.session_handlers
.write()
.unwrap()
.insert(session_id, h);
true
} else {
false
}
}
#[inline]
@@ -1230,26 +1499,14 @@ pub mod sessions {
SESSIONS.read().unwrap().values().cloned().collect()
}
#[inline]
pub fn get_session_by_peer_id(peer_id: &str) -> Option<FlutterSession> {
SESSIONS
.read()
.unwrap()
.values()
.find(|session| session.id == peer_id)
.map(|s| s.clone())
.clone()
}
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub fn other_sessions_running(session_id: &SessionID) -> bool {
pub fn other_sessions_running(peer_id: String, conn_type: ConnType) -> bool {
SESSIONS
.read()
.unwrap()
.keys()
.filter(|k| *k != session_id)
.count()
!= 0
.get(&(peer_id, conn_type))
.map(|s| s.session_handlers.read().unwrap().len() != 0)
.unwrap_or(false)
}
}

View File

@@ -4,7 +4,7 @@ use crate::{
client::file_trait::FileManager,
common::is_keyboard_mode_supported,
common::make_fd_to_json,
flutter::{self, session_add, session_start_, sessions},
flutter::{self, session_add, session_add_existed, session_start_, sessions},
input::*,
ui_interface::{self, *},
};
@@ -16,6 +16,7 @@ use hbb_common::{
config::{self, LocalConfig, PeerConfig, PeerInfoSerde},
fs, lazy_static, log,
message_proto::KeyboardMode,
rendezvous_proto::ConnType,
ResultType,
};
use std::{
@@ -67,7 +68,7 @@ pub fn stop_global_event_stream(app_type: String) {
}
pub enum EventToUI {
Event(String),
Rgba,
Rgba(usize),
}
pub fn host_stop_system_key_propagate(_stopped: bool) {
@@ -75,8 +76,26 @@ pub fn host_stop_system_key_propagate(_stopped: bool) {
crate::platform::windows::stop_system_key_propagate(_stopped);
}
// FIXME: -> ResultType<()> cannot be parsed by frb_codegen
// thread 'main' panicked at 'Failed to parse function output type `ResultType<()>`', $HOME\.cargo\git\checkouts\flutter_rust_bridge-ddba876d3ebb2a1e\e5adce5\frb_codegen\src\parser\mod.rs:151:25
// This function is only used to count the number of control sessions.
pub fn peer_get_default_sessions_count(id: String) -> SyncReturn<usize> {
SyncReturn(sessions::get_session_count(id, ConnType::DEFAULT_CONN))
}
pub fn session_add_existed_sync(id: String, session_id: SessionID) -> SyncReturn<String> {
if let Err(e) = session_add_existed(id.clone(), session_id) {
SyncReturn(format!("Failed to add session with id {}, {}", &id, e))
} else {
SyncReturn("".to_owned())
}
}
pub fn session_try_add_display(session_id: SessionID, displays: Vec<i32>) -> SyncReturn<()> {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.capture_displays(displays, vec![], vec![]);
}
SyncReturn(())
}
pub fn session_add_sync(
session_id: SessionID,
id: String,
@@ -112,7 +131,7 @@ pub fn session_start(
}
pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_remember())
} else {
None
@@ -120,7 +139,7 @@ pub fn session_get_remember(session_id: SessionID) -> Option<bool> {
}
pub fn session_get_toggle_option(session_id: SessionID, arg: String) -> Option<bool> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_toggle_option(arg))
} else {
None
@@ -133,7 +152,7 @@ pub fn session_get_toggle_option_sync(session_id: SessionID, arg: String) -> Syn
}
pub fn session_get_option(session_id: SessionID, arg: String) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_option(arg))
} else {
None
@@ -147,55 +166,61 @@ pub fn session_login(
password: String,
remember: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.login(os_username, os_password, password, remember);
}
}
pub fn session_close(session_id: SessionID) {
if let Some(session) = sessions::remove_session(&session_id) {
session.close_event_stream();
if let Some(session) = sessions::remove_session_by_session_id(&session_id) {
session.close_event_stream(session_id);
session.close();
}
}
pub fn session_refresh(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
session.refresh_video();
pub fn session_refresh(session_id: SessionID, display: usize) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.refresh_video(display as _);
}
}
pub fn session_record_screen(session_id: SessionID, start: bool, width: usize, height: usize) {
if let Some(session) = sessions::get_session(&session_id) {
session.record_screen(start, width as _, height as _);
pub fn session_record_screen(
session_id: SessionID,
start: bool,
display: usize,
width: usize,
height: usize,
) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.record_screen(start, display as _, width as _, height as _);
}
}
pub fn session_record_status(session_id: SessionID, status: bool) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.record_status(status);
}
}
pub fn session_reconnect(session_id: SessionID, force_relay: bool) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.reconnect(force_relay);
}
}
pub fn session_toggle_option(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
log::warn!("toggle option {}", &value);
session.toggle_option(value.clone());
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if sessions::get_session(&session_id).is_some() && value == "disable-clipboard" {
if sessions::get_session_by_session_id(&session_id).is_some() && value == "disable-clipboard" {
crate::flutter::update_text_clipboard_required();
}
}
pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_flutter_option(k))
} else {
None
@@ -203,13 +228,14 @@ pub fn session_get_flutter_option(session_id: SessionID, k: String) -> Option<St
}
pub fn session_set_flutter_option(session_id: SessionID, k: String, v: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_flutter_option(k, v);
}
}
// This function is only used for the default connection session.
pub fn session_get_flutter_option_by_peer_id(id: String, k: String) -> Option<String> {
if let Some(session) = sessions::get_session_by_peer_id(&id) {
if let Some(session) = sessions::get_session_by_peer_id(id, ConnType::DEFAULT_CONN) {
Some(session.get_flutter_option(k))
} else {
None
@@ -238,7 +264,7 @@ pub fn set_local_kb_layout_type(kb_layout_type: String) {
}
pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_view_style())
} else {
None
@@ -246,13 +272,13 @@ pub fn session_get_view_style(session_id: SessionID) -> Option<String> {
}
pub fn session_set_view_style(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_view_style(value);
}
}
pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_scroll_style())
} else {
None
@@ -260,13 +286,13 @@ pub fn session_get_scroll_style(session_id: SessionID) -> Option<String> {
}
pub fn session_set_scroll_style(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_scroll_style(value);
}
}
pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_image_quality())
} else {
None
@@ -274,13 +300,13 @@ pub fn session_get_image_quality(session_id: SessionID) -> Option<String> {
}
pub fn session_set_image_quality(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_image_quality(value);
}
}
pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_keyboard_mode())
} else {
None
@@ -289,7 +315,7 @@ pub fn session_get_keyboard_mode(session_id: SessionID) -> Option<String> {
pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
let mut _mode_updated = false;
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_keyboard_mode(value.clone());
_mode_updated = true;
}
@@ -300,7 +326,7 @@ pub fn session_set_keyboard_mode(session_id: SessionID, value: String) {
}
pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_reverse_mouse_wheel())
} else {
None
@@ -308,13 +334,27 @@ pub fn session_get_reverse_mouse_wheel(session_id: SessionID) -> Option<String>
}
pub fn session_set_reverse_mouse_wheel(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_reverse_mouse_wheel(value);
}
}
pub fn session_get_displays_as_individual_windows(session_id: SessionID) -> SyncReturn<Option<String>> {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
SyncReturn(Some(session.get_displays_as_individual_windows()))
} else {
SyncReturn(None)
}
}
pub fn session_set_displays_as_individual_windows(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_displays_as_individual_windows(value);
}
}
pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32>> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
Some(session.get_custom_image_quality())
} else {
None
@@ -322,7 +362,7 @@ pub fn session_get_custom_image_quality(session_id: SessionID) -> Option<Vec<i32
}
pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -> SyncReturn<bool> {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
if let Ok(mode) = KeyboardMode::from_str(&mode[..]) {
SyncReturn(is_keyboard_mode_supported(
&mode,
@@ -337,32 +377,36 @@ pub fn session_is_keyboard_mode_supported(session_id: SessionID, mode: String) -
}
pub fn session_set_custom_image_quality(session_id: SessionID, value: i32) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.save_custom_image_quality(value);
}
}
pub fn session_set_custom_fps(session_id: SessionID, fps: i32) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.set_custom_fps(fps);
}
}
pub fn session_lock_screen(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.lock_screen();
}
}
pub fn session_ctrl_alt_del(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.ctrl_alt_del();
}
}
pub fn session_switch_display(session_id: SessionID, value: i32) {
if let Some(session) = sessions::get_session(&session_id) {
session.switch_display(value);
pub fn session_switch_display(session_id: SessionID, value: Vec<i32>) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
if value.len() == 1 {
session.switch_display(value[0]);
} else {
session.capture_displays(vec![], vec![], value);
}
}
}
@@ -374,7 +418,7 @@ pub fn session_handle_flutter_key_event(
lock_modes: i32,
down_or_up: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
let keyboard_mode = session.get_keyboard_mode();
session.handle_flutter_key_event(
&keyboard_mode,
@@ -395,7 +439,7 @@ pub fn session_handle_flutter_key_event(
// This will cause the keyboard input to take no effect.
pub fn session_enter_or_leave(_session_id: SessionID, _enter: bool) -> SyncReturn<()> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Some(session) = sessions::get_session(&_session_id) {
if let Some(session) = sessions::get_session_by_session_id(&_session_id) {
let keyboard_mode = session.get_keyboard_mode();
if _enter {
set_cur_session_id_(_session_id, &keyboard_mode);
@@ -417,14 +461,14 @@ pub fn session_input_key(
shift: bool,
command: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
// #[cfg(any(target_os = "android", target_os = "ios"))]
session.input_key(&name, down, press, alt, ctrl, shift, command);
}
}
pub fn session_input_string(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
// #[cfg(any(target_os = "android", target_os = "ios"))]
session.input_string(&value);
}
@@ -432,33 +476,33 @@ pub fn session_input_string(session_id: SessionID, value: String) {
// chat_client_mode
pub fn session_send_chat(session_id: SessionID, text: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_chat(text);
}
}
pub fn session_peer_option(session_id: SessionID, name: String, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.set_option(name, value);
}
}
pub fn session_get_peer_option(session_id: SessionID, name: String) -> String {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
return session.get_option(name);
}
"".to_string()
}
pub fn session_input_os_password(session_id: SessionID, value: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.input_os_password(value, true);
}
}
// File Action
pub fn session_read_remote_dir(session_id: SessionID, path: String, include_hidden: bool) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.read_remote_dir(path, include_hidden);
}
}
@@ -472,7 +516,7 @@ pub fn session_send_files(
include_hidden: bool,
is_remote: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_files(act_id, path, to, file_num, include_hidden, is_remote);
}
}
@@ -485,7 +529,7 @@ pub fn session_set_confirm_override_file(
remember: bool,
is_upload: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.set_confirm_override_file(act_id, file_num, need_override, remember, is_upload);
}
}
@@ -497,7 +541,7 @@ pub fn session_remove_file(
file_num: i32,
is_remote: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.remove_file(act_id, path, file_num, is_remote);
}
}
@@ -509,7 +553,7 @@ pub fn session_read_dir_recursive(
is_remote: bool,
show_hidden: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.remove_dir_all(act_id, path, is_remote, show_hidden);
}
}
@@ -520,19 +564,19 @@ pub fn session_remove_all_empty_dirs(
path: String,
is_remote: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.remove_dir(act_id, path, is_remote);
}
}
pub fn session_cancel_job(session_id: SessionID, act_id: i32) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.cancel_job(act_id);
}
}
pub fn session_create_dir(session_id: SessionID, act_id: i32, path: String, is_remote: bool) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.create_dir(act_id, path, is_remote);
}
}
@@ -549,14 +593,14 @@ pub fn session_read_local_dir_sync(
}
pub fn session_get_platform(session_id: SessionID, is_remote: bool) -> String {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
return session.get_platform(is_remote);
}
"".to_string()
}
pub fn session_load_last_transfer_jobs(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
return session.load_last_jobs();
} else {
// a tip for flutter dev
@@ -576,46 +620,44 @@ pub fn session_add_job(
include_hidden: bool,
is_remote: bool,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.add_job(act_id, path, to, file_num, include_hidden, is_remote);
}
}
pub fn session_resume_job(session_id: SessionID, act_id: i32, is_remote: bool) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.resume_job(act_id, is_remote);
}
}
pub fn session_elevate_direct(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.elevate_direct();
}
}
pub fn session_elevate_with_logon(session_id: SessionID, username: String, password: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.elevate_with_logon(username, password);
}
}
pub fn session_switch_sides(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.switch_sides();
}
}
pub fn session_change_resolution(session_id: SessionID, display: i32, width: i32, height: i32) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.change_resolution(display, width, height);
}
}
pub fn session_set_size(_session_id: SessionID, _width: usize, _height: usize) {
pub fn session_set_size(_session_id: SessionID, _display: usize, _width: usize, _height: usize) {
#[cfg(feature = "flutter_texture_render")]
if let Some(session) = sessions::get_session(&_session_id) {
session.set_size(_width, _height);
}
super::flutter::session_set_size(_session_id, _display, _width, _height)
}
pub fn main_get_sound_inputs() -> Vec<String> {
@@ -1008,18 +1050,22 @@ pub fn main_handle_relay_id(id: String) -> String {
handle_relay_id(id)
}
pub fn main_get_current_display() -> SyncReturn<String> {
#[cfg(not(target_os = "ios"))]
let display_info = match crate::video_service::get_current_display() {
Ok((_, _, display)) => serde_json::to_string(&HashMap::from([
("w", display.width()),
("h", display.height()),
]))
.unwrap_or_default(),
Err(..) => "".to_string(),
};
pub fn main_get_main_display() -> SyncReturn<String> {
#[cfg(target_os = "ios")]
let display_info = "".to_owned();
#[cfg(not(target_os = "ios"))]
let mut display_info = "".to_owned();
#[cfg(not(target_os = "ios"))]
if let Ok(displays) = crate::display_service::try_get_displays() {
// to-do: Need to detect current display index.
if let Some(display) = displays.iter().next() {
display_info = serde_json::to_string(&HashMap::from([
("w", display.width()),
("h", display.height()),
]))
.unwrap_or_default();
}
}
SyncReturn(display_info)
}
@@ -1029,31 +1075,31 @@ pub fn session_add_port_forward(
remote_host: String,
remote_port: i32,
) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.add_port_forward(local_port, remote_host, remote_port);
}
}
pub fn session_remove_port_forward(session_id: SessionID, local_port: i32) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.remove_port_forward(local_port);
}
}
pub fn session_new_rdp(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.new_rdp();
}
}
pub fn session_request_voice_call(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.request_voice_call();
}
}
pub fn session_close_voice_call(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.close_voice_call();
}
}
@@ -1238,20 +1284,20 @@ pub fn session_send_mouse(session_id: SessionID, msg: String) {
_ => 0,
} << 3;
}
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_mouse(mask, x, y, alt, ctrl, shift, command);
}
}
}
pub fn session_restart_remote_device(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.restart_remote_device();
}
}
pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> SyncReturn<String> {
let res = if let Some(session) = sessions::get_session(&session_id) {
let res = if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.get_audit_server(typ)
} else {
"".to_owned()
@@ -1260,13 +1306,13 @@ pub fn session_get_audit_server_sync(session_id: SessionID, typ: String) -> Sync
}
pub fn session_send_note(session_id: SessionID, note: String) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.send_note(note)
}
}
pub fn session_alternative_codecs(session_id: SessionID) -> String {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
let (vp8, av1, h264, h265) = session.alternative_codecs();
let msg = HashMap::from([("vp8", vp8), ("av1", av1), ("h264", h264), ("h265", h265)]);
serde_json::ser::to_string(&msg).unwrap_or("".to_owned())
@@ -1276,15 +1322,13 @@ pub fn session_alternative_codecs(session_id: SessionID) -> String {
}
pub fn session_change_prefer_codec(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
session.change_prefer_codec();
}
}
pub fn session_on_waiting_for_image_dialog_show(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
session.ui_handler.on_waiting_for_image_dialog_show();
}
super::flutter::session_on_waiting_for_image_dialog_show(session_id);
}
pub fn main_set_home_dir(_home: String) {
@@ -1434,16 +1478,22 @@ pub fn translate(name: String, locale: String) -> SyncReturn<String> {
SyncReturn(crate::client::translate_locale(name, &locale))
}
pub fn session_get_rgba_size(session_id: SessionID) -> SyncReturn<usize> {
SyncReturn(super::flutter::session_get_rgba_size(session_id))
pub fn session_get_rgba_size(session_id: SessionID, display: usize) -> SyncReturn<usize> {
SyncReturn(super::flutter::session_get_rgba_size(session_id, display))
}
pub fn session_next_rgba(session_id: SessionID) -> SyncReturn<()> {
SyncReturn(super::flutter::session_next_rgba(session_id))
pub fn session_next_rgba(session_id: SessionID, display: usize) -> SyncReturn<()> {
SyncReturn(super::flutter::session_next_rgba(session_id, display))
}
pub fn session_register_texture(session_id: SessionID, ptr: usize) -> SyncReturn<()> {
SyncReturn(super::flutter::session_register_texture(session_id, ptr))
pub fn session_register_texture(
session_id: SessionID,
display: usize,
ptr: usize,
) -> SyncReturn<()> {
SyncReturn(super::flutter::session_register_texture(
session_id, display, ptr,
))
}
pub fn query_onlines(ids: Vec<String>) {
@@ -1522,15 +1572,15 @@ pub fn main_update_me() -> SyncReturn<bool> {
}
pub fn set_cur_session_id(session_id: SessionID) {
if let Some(session) = sessions::get_session(&session_id) {
if let Some(session) = sessions::get_session_by_session_id(&session_id) {
set_cur_session_id_(session_id, &session.get_keyboard_mode())
}
}
fn set_cur_session_id_(session_id: SessionID, keyboard_mode: &str) {
fn set_cur_session_id_(session_id: SessionID, _keyboard_mode: &str) {
super::flutter::set_cur_session_id(session_id);
#[cfg(windows)]
crate::keyboard::update_grab_get_key_name(keyboard_mode);
crate::keyboard::update_grab_get_key_name(_keyboard_mode);
}
pub fn install_show_run_without_install() -> SyncReturn<bool> {
@@ -1625,6 +1675,10 @@ pub fn main_test_wallpaper(_second: u64) {
});
}
pub fn main_support_remove_wallpaper() -> bool {
support_remove_wallpaper()
}
/// Send a url scheme throught the ipc.
///
/// * macOS only
@@ -1808,6 +1862,10 @@ pub fn plugin_install(_id: String, _b: bool) {
}
}
pub fn is_support_multi_ui_session(version: String) -> SyncReturn<bool> {
SyncReturn(crate::common::is_support_multi_ui_session(&version))
}
#[cfg(target_os = "android")]
pub mod server_side {
use hbb_common::{config, log};

View File

@@ -214,6 +214,7 @@ static mut IS_0X021D_DOWN: bool = false;
#[cfg(target_os = "macos")]
static mut IS_LEFT_OPTION_DOWN: bool = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn get_keyboard_mode() -> String {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
if let Some(session) = CUR_SESSION.lock().unwrap().as_ref() {

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", "按交集过滤"),
("Remove wallpaper during incoming sessions", "接受会话时移除桌面壁纸"),
("Test", "测试"),
("switch_display_elevated_connections_tip", "提权后,被控有多个连接,不能切换到非主显示器。若要控制多显示器,请安装后再试。"),
("display_is_plugged_out_msg", "显示器被拔出,切换到第一个显示器。"),
("No displays", "没有显示器。"),
("elevated_switch_display_msg", "切换到主显示器,因为提权后,不支持多显示器画面。"),
("Open in new window", "在新的窗口中打开"),
("Show displays as individual windows", "在单个窗口中打开显示器"),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,12 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", "Nach Schnittmenge filtern"),
("Remove wallpaper during incoming sessions", "Hintergrundbild während eingehender Sitzungen entfernen"),
("Test", "Test"),
("Filter by intersection", "Nach Schnittpunkt filtern"),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -222,5 +222,8 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("upgrade_rustdesk_server_pro_to_{}_tip", "Please upgrade RustDesk Server Pro to version {} or newer!"),
("pull_group_failed_tip", "Failed to refresh group"),
("doc_fix_wayland", "https://rustdesk.com/docs/en/manual/linux/#x11-required"),
("switch_display_elevated_connections_tip", "Switching to non-primary display is not supported in the elevated mode when there are multiple connections. Please try again after installation if you want to control multiple displays."),
("display_is_plugged_out_msg", "The diplay is plugged out, switch to the first display."),
("elevated_switch_display_msg", "Switch to the primary display because multiple display is not supported in elevated mode."),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -559,5 +559,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", "Filtra per incrocio"),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", "Filtrēt pēc krustpunkta"),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -556,7 +556,13 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("upgrade_rustdesk_server_pro_to_{}_tip", "Обновите RustDesk Server Pro до версии {} или новее!"),
("pull_group_failed_tip", "Невозможно обновить группу"),
("Filter by intersection", "Фильтровать по пересечению"),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("Remove wallpaper during incoming sessions", "Удалить обои в сеансе"),
("Test", "Тест"),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -558,5 +558,11 @@ pub static ref T: std::collections::HashMap<&'static str, &'static str> =
("Filter by intersection", ""),
("Remove wallpaper during incoming sessions", ""),
("Test", ""),
("switch_display_elevated_connections_tip", ""),
("display_is_plugged_out_msg", ""),
("No displays", ""),
("elevated_switch_display_msg", ""),
("Open in new window", ""),
("Show displays as individual windows", ""),
].iter().cloned().collect();
}

View File

@@ -1341,6 +1341,14 @@ impl WallPaperRemover {
old_path_dark,
})
}
pub fn support() -> bool {
let desktop = std::env::var("XDG_CURRENT_DESKTOP").unwrap_or_default();
if wallpaper::gnome::is_compliant(&desktop) || desktop.as_str() == "XFCE" {
return wallpaper::get().is_ok();
}
false
}
}
impl Drop for WallPaperRemover {

View File

@@ -83,6 +83,7 @@ pub const PA_SAMPLE_RATE: u32 = 48000;
pub(crate) struct InstallingService; // please use new
impl InstallingService {
#[cfg(any(target_os = "windows", target_os = "linux"))]
pub fn new() -> Self {
*INSTALLING_SERVICE.lock().unwrap() = true;
Self

View File

@@ -2392,6 +2392,10 @@ impl WallPaperRemover {
Ok(Self { old_path })
}
pub fn support() -> bool {
wallpaper::get().is_ok() || !Self::get_recent_wallpaper().unwrap_or_default().is_empty()
}
fn get_recent_wallpaper() -> ResultType<String> {
// SystemParametersInfoW may return %appdata%\Microsoft\Windows\Themes\TranscodedWallpaper, not real path and may not real cache
// https://www.makeuseof.com/find-desktop-wallpapers-file-location-windows-11/

View File

@@ -26,7 +26,7 @@ use hbb_common::{
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use service::ServiceTmpl;
use service::{GenericService, Service, Subscriber};
use service::{EmptyExtraFieldService, GenericService, Service, Subscriber};
use crate::ipc::Data;
@@ -53,6 +53,7 @@ pub const NAME_POS: &'static str = "";
}
mod connection;
pub mod display_service;
#[cfg(windows)]
pub mod portable_service;
mod service;
@@ -80,7 +81,7 @@ lazy_static::lazy_static! {
pub struct Server {
connections: ConnMap,
services: HashMap<&'static str, Box<dyn Service>>,
services: HashMap<String, Box<dyn Service>>,
id_count: i32,
}
@@ -94,11 +95,15 @@ pub fn new() -> ServerPtr {
id_count: hbb_common::rand::random::<i32>() % 1000 + 1000, // ensure positive
};
server.add_service(Box::new(audio_service::new()));
server.add_service(Box::new(video_service::new()));
#[cfg(not(any(target_os = "android", target_os = "ios")))]
server.add_service(Box::new(display_service::new()));
server.add_service(Box::new(video_service::new(
*display_service::PRIMARY_DISPLAY_IDX,
)));
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
server.add_service(Box::new(clipboard_service::new()));
if !video_service::capture_cursor_embedded() {
if !display_service::capture_cursor_embedded() {
server.add_service(Box::new(input_service::new_cursor()));
server.add_service(Box::new(input_service::new_pos()));
}
@@ -253,9 +258,19 @@ async fn create_relay_connection_(
}
impl Server {
fn is_video_service_name(name: &str) -> bool {
name.starts_with(video_service::NAME)
}
pub fn add_connection(&mut self, conn: ConnInner, noperms: &Vec<&'static str>) {
let primary_video_service_name =
video_service::get_service_name(*display_service::PRIMARY_DISPLAY_IDX);
for s in self.services.values() {
if !noperms.contains(&s.name()) {
let name = s.name();
if Self::is_video_service_name(&name) && name != primary_video_service_name {
continue;
}
if !noperms.contains(&(&name as _)) {
s.on_subscribe(conn.clone());
}
}
@@ -287,8 +302,12 @@ impl Server {
self.services.insert(name, service);
}
pub fn contains(&self, name: &str) -> bool {
self.services.contains_key(name)
}
pub fn subscribe(&mut self, name: &str, conn: ConnInner, sub: bool) {
if let Some(s) = self.services.get(&name) {
if let Some(s) = self.services.get(name) {
if s.is_subed(conn.id()) == sub {
return;
}
@@ -305,6 +324,47 @@ impl Server {
self.id_count += 1;
self.id_count
}
pub fn set_video_service_opt(&self, display: Option<usize>, opt: &str, value: &str) {
for (k, v) in self.services.iter() {
if let Some(display) = display {
if k != &video_service::get_service_name(display) {
continue;
}
}
if Self::is_video_service_name(k) {
v.set_option(opt, value);
}
}
}
fn capture_displays(
&mut self,
conn: ConnInner,
displays: &[usize],
include: bool,
exclude: bool,
) {
let displays = displays
.iter()
.map(|d| video_service::get_service_name(*d))
.collect::<Vec<_>>();
let keys = self.services.keys().cloned().collect::<Vec<_>>();
for name in keys.iter() {
if Self::is_video_service_name(&name) {
if displays.contains(&name) {
if include {
self.subscribe(&name, conn.clone(), true);
}
} else {
if exclude {
self.subscribe(&name, conn.clone(), false);
}
}
}
}
}
}
impl Drop for Server {

View File

@@ -24,16 +24,16 @@ static RESTARTING: AtomicBool = AtomicBool::new(false);
#[cfg(not(any(target_os = "linux", target_os = "android")))]
pub fn new() -> GenericService {
let sp = GenericService::new(NAME, true);
sp.repeat::<cpal_impl::State, _>(33, cpal_impl::run);
sp
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
GenericService::repeat::<cpal_impl::State, _, _>(&svc.clone(), 33, cpal_impl::run);
svc.sp
}
#[cfg(any(target_os = "linux", target_os = "android"))]
pub fn new() -> GenericService {
let sp = GenericService::new(NAME, true);
sp.run(pa_impl::run);
sp
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
GenericService::run(&svc.clone(), pa_impl::run);
svc.sp
}
pub fn restart() {
@@ -48,7 +48,7 @@ pub fn restart() {
mod pa_impl {
use super::*;
#[tokio::main(flavor = "current_thread")]
pub async fn run(sp: GenericService) -> ResultType<()> {
pub async fn run(sp: EmptyExtraFieldService) -> ResultType<()> {
hbb_common::sleep(0.1).await; // one moment to wait for _pa ipc
RESTARTING.store(false, Ordering::SeqCst);
#[cfg(target_os = "linux")]
@@ -125,7 +125,7 @@ mod cpal_impl {
}
}
pub fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
pub fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
sp.snapshot(|sps| {
match &state.stream {
None => {

View File

@@ -28,12 +28,12 @@ impl super::service::Reset for State {
}
pub fn new() -> GenericService {
let sp = GenericService::new(NAME, true);
sp.repeat::<State, _>(INTERVAL, run);
sp
let svc = EmptyExtraFieldService::new(NAME.to_owned(), true);
GenericService::repeat::<State, _, _>(&svc.clone(), INTERVAL, run);
svc.sp
}
fn run(sp: GenericService, state: &mut State) -> ResultType<()> {
fn run(sp: EmptyExtraFieldService, state: &mut State) -> ResultType<()> {
if let Some(ctx) = state.ctx.as_mut() {
if let Some(msg) = check_clipboard(ctx, None) {
sp.send(msg);

View File

@@ -15,7 +15,7 @@ use crate::{
new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender,
},
common::{get_default_sound_input, set_sound_input},
video_service,
display_service, video_service,
};
#[cfg(any(target_os = "android", target_os = "ios"))]
use crate::{common::DEVICE_NAME, flutter::connection_manager::start_channel};
@@ -160,6 +160,7 @@ pub enum AuthConnType {
pub struct Connection {
inner: ConnInner,
display_idx: usize,
stream: super::Stream,
server: super::ServerPtrWeak,
hash: Hash,
@@ -303,6 +304,7 @@ impl Connection {
tx: Some(tx),
tx_video: Some(tx_video),
},
display_idx: *display_service::PRIMARY_DISPLAY_IDX,
stream,
server,
hash,
@@ -609,6 +611,9 @@ impl Connection {
_ => {},
}
}
Some(message::Union::PeerInfo(_)) => {
conn.refresh_video_display(None);
}
_ => {}
}
if let Err(err) = conn.stream.send(msg).await {
@@ -1098,19 +1103,20 @@ impl Connection {
pi.username = username;
pi.sas_enabled = sas_enabled;
pi.features = Some(Features {
privacy_mode: video_service::is_privacy_mode_supported(),
privacy_mode: display_service::is_privacy_mode_supported(),
..Default::default()
})
.into();
// `try_reset_current_display` is needed because `get_displays` may change the current display,
// which may cause the mismatch of current display and the current display name.
#[cfg(not(any(target_os = "android", target_os = "ios")))]
video_service::try_reset_current_display();
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
pi.resolutions = Some(SupportedResolutions {
resolutions: video_service::get_current_display()
.map(|(_, _, d)| crate::platform::resolutions(&d.name()))
resolutions: display_service::try_get_displays()
.map(|displays| {
displays
.get(self.display_idx)
.map(|d| crate::platform::resolutions(&d.name()))
.unwrap_or(vec![])
})
.unwrap_or(vec![]),
..Default::default()
})
@@ -1126,16 +1132,15 @@ impl Connection {
self.send(msg_out).await;
}
match super::video_service::get_displays().await {
match super::display_service::get_displays().await {
Err(err) => {
res.set_error(format!("{}", err));
}
Ok((current, displays)) => {
Ok(displays) => {
pi.displays = displays.clone();
pi.current_display = current as _;
pi.current_display = self.display_idx as _;
res.set_peer_info(pi);
sub_service = true;
*super::video_service::LAST_SYNC_DISPLAYS.write().unwrap() = displays;
}
}
Self::on_remote_authorized();
@@ -1289,6 +1294,8 @@ impl Connection {
#[inline]
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn input_key(&self, msg: KeyEvent, press: bool) {
// to-do: if is the legacy mode, and the key is function key "LockScreen".
// Switch to the primary display.
self.tx_input.send(MessageInput::Key((msg, press))).ok();
}
@@ -1945,15 +1952,13 @@ impl Connection {
},
Some(message::Union::Misc(misc)) => match misc.union {
Some(misc::Union::SwitchDisplay(s)) => {
video_service::switch_display(s.display).await;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if s.width != 0 && s.height != 0 {
self.change_resolution(&Resolution {
width: s.width,
height: s.height,
..Default::default()
});
}
self.handle_switch_display(s).await;
}
Some(misc::Union::CaptureDisplays(displays)) => {
let add = displays.add.iter().map(|d| *d as usize).collect::<Vec<_>>();
let sub = displays.sub.iter().map(|d| *d as usize).collect::<Vec<_>>();
let set = displays.set.iter().map(|d| *d as usize).collect::<Vec<_>>();
self.capture_displays(&add, &sub, &set).await;
}
Some(misc::Union::ChatMessage(c)) => {
self.send_to_cm(ipc::Data::ChatMessage { text: c.text });
@@ -1965,10 +1970,16 @@ impl Connection {
}
Some(misc::Union::RefreshVideo(r)) => {
if r {
super::video_service::refresh();
// Refresh all videos.
// Compatibility with old versions and sciter(remote).
self.refresh_video_display(None);
}
self.update_auto_disconnect_timer();
}
Some(misc::Union::RefreshVideoDisplay(display)) => {
self.refresh_video_display(Some(display as usize));
self.update_auto_disconnect_timer();
}
Some(misc::Union::VideoReceived(_)) => {
video_service::notify_video_frame_fetched(
self.inner.id,
@@ -2084,6 +2095,75 @@ impl Connection {
true
}
fn refresh_video_display(&self, display: Option<usize>) {
video_service::refresh();
self.server.upgrade().map(|s| {
s.read().unwrap().set_video_service_opt(
display,
video_service::OPTION_REFRESH,
super::service::SERVICE_OPTION_VALUE_TRUE,
)
});
}
async fn handle_switch_display(&mut self, s: SwitchDisplay) {
#[cfg(windows)]
if portable_client::running()
&& *CONN_COUNT.lock().unwrap() > 1
&& s.display != (*display_service::PRIMARY_DISPLAY_IDX as i32)
{
log::info!("Switch to non-primary display is not supported in the elevated mode when there are multiple connections.");
let mut msg_out = Message::new();
let res = MessageBox {
msgtype: "nook-nocancel-hasclose".to_owned(),
title: "Prompt".to_owned(),
text: "switch_display_elevated_connections_tip".to_owned(),
link: "".to_owned(),
..Default::default()
};
msg_out.set_message_box(res);
self.send(msg_out).await;
return;
}
let display_idx = s.display as usize;
if self.display_idx != display_idx {
if let Some(server) = self.server.upgrade() {
self.switch_display_to(display_idx, server.clone());
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if s.width != 0 && s.height != 0 {
self.change_resolution(&Resolution {
width: s.width,
height: s.height,
..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;
}
}
}
}
fn switch_display_to(&mut self, display_idx: usize, server: Arc<RwLock<Server>>) {
let new_service_name = video_service::get_service_name(display_idx);
let old_service_name = video_service::get_service_name(self.display_idx);
let mut lock = server.write().unwrap();
if display_idx != *display_service::PRIMARY_DISPLAY_IDX {
if !lock.contains(&new_service_name) {
lock.add_service(Box::new(video_service::new(display_idx)));
}
}
lock.subscribe(&old_service_name, self.inner.clone(), false);
lock.subscribe(&new_service_name, self.inner.clone(), true);
self.display_idx = display_idx;
}
#[cfg(windows)]
async fn handle_elevation_request(&mut self, para: portable_client::StartPara) {
let mut err;
@@ -2106,36 +2186,64 @@ impl Connection {
self.update_auto_disconnect_timer();
}
async fn capture_displays(&mut self, add: &[usize], sub: &[usize], set: &[usize]) {
if let Some(sever) = self.server.upgrade() {
let mut lock = sever.write().unwrap();
for display in add.iter() {
let service_name = video_service::get_service_name(*display);
if !lock.contains(&service_name) {
lock.add_service(Box::new(video_service::new(*display)));
}
}
for display in set.iter() {
let service_name = video_service::get_service_name(*display);
if !lock.contains(&service_name) {
lock.add_service(Box::new(video_service::new(*display)));
}
}
if !add.is_empty() {
lock.capture_displays(self.inner.clone(), add, true, false);
} else if !sub.is_empty() {
lock.capture_displays(self.inner.clone(), sub, false, true);
} else {
lock.capture_displays(self.inner.clone(), set, true, true);
}
drop(lock);
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
fn change_resolution(&mut self, r: &Resolution) {
if self.keyboard {
if let Ok((_, _, display)) = video_service::get_current_display() {
let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) =
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
if let Ok(displays) = display_service::try_get_displays() {
if let Some(display) = displays.get(self.display_idx) {
let name = display.name();
#[cfg(all(windows, feature = "virtual_display_driver"))]
if let Some(_ok) =
crate::virtual_display_manager::change_resolution_if_is_virtual_display(
&name,
r.width as _,
r.height as _,
)
{
return;
}
display_service::set_last_changed_resolution(
&name,
r.width as _,
r.height as _,
)
{
return;
}
video_service::set_last_changed_resolution(
&name,
(display.width() as _, display.height() as _),
(r.width, r.height),
);
if let Err(e) =
crate::platform::change_resolution(&name, r.width as _, r.height as _)
{
log::error!(
"Failed to change resolution '{}' to ({},{}):{:?}",
&name,
r.width,
r.height,
e
(display.width() as _, display.height() as _),
(r.width, r.height),
);
if let Err(e) =
crate::platform::change_resolution(&name, r.width as _, r.height as _)
{
log::error!(
"Failed to change resolution '{}' to ({},{}):{:?}",
&name,
r.width,
r.height,
e
);
}
}
}
}
@@ -2281,7 +2389,7 @@ impl Connection {
if self.keyboard {
match q {
BoolOption::Yes => {
let msg_out = if !video_service::is_privacy_mode_supported() {
let msg_out = if !display_service::is_privacy_mode_supported() {
crate::common::make_privacy_mode_msg_with_details(
back_notification::PrivacyModeState::PrvNotSupported,
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
@@ -2289,8 +2397,11 @@ impl Connection {
} else {
match privacy_mode::turn_on_privacy(self.inner.id) {
Ok(true) => {
let err_msg =
video_service::test_create_capturer(self.inner.id, 5_000);
let err_msg = video_service::test_create_capturer(
self.inner.id,
self.display_idx,
5_000,
);
if err_msg.is_empty() {
video_service::set_privacy_mode_conn_id(self.inner.id);
crate::common::make_privacy_mode_msg(
@@ -2326,7 +2437,7 @@ impl Connection {
self.send(msg_out).await;
}
BoolOption::No => {
let msg_out = if !video_service::is_privacy_mode_supported() {
let msg_out = if !display_service::is_privacy_mode_supported() {
crate::common::make_privacy_mode_msg_with_details(
back_notification::PrivacyModeState::PrvNotSupported,
"Unsupported. 1 Multi-screen is not supported. 2 Please confirm the license is activated.".to_string(),
@@ -2801,11 +2912,11 @@ mod raii {
active_conns_lock.retain(|&c| c != self.0);
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if active_conns_lock.is_empty() {
video_service::reset_resolutions();
display_service::reset_resolutions();
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
if active_conns_lock.is_empty() {
video_service::try_plug_out_virtual_display();
display_service::try_plug_out_virtual_display();
}
#[cfg(all(windows))]
if active_conns_lock.is_empty() {

View File

@@ -0,0 +1,262 @@
use super::*;
#[cfg(all(windows, feature = "virtual_display_driver"))]
use crate::virtual_display_manager;
#[cfg(windows)]
use hbb_common::get_version_number;
use hbb_common::protobuf::MessageField;
use scrap::Display;
pub const NAME: &'static str = "display";
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.
// 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();
}
#[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
);
}
}
}
#[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]
pub fn is_privacy_mode_supported() -> bool {
#[cfg(windows)]
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
#[cfg(not(windows))]
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 {
let svc = EmptyExtraFieldService::new(NAME.to_owned(), false);
GenericService::repeat::<StateDisplay, _, _>(&svc.clone(), 300, run);
svc.sp
}
fn check_get_displays_changed_msg(last_synced_displays: &mut Vec<DisplayInfo>) -> Option<Message> {
let displays = try_get_displays().ok()?;
if displays.len() == last_synced_displays.len() {
return None;
}
// Display to DisplayInfo
let displays = to_display_info(&displays);
if last_synced_displays.len() == 0 {
*last_synced_displays = 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"))]
pub fn try_plug_out_virtual_display() {
let _res = virtual_display_manager::plug_out_headless();
}
fn run(sp: EmptyExtraFieldService, state: &mut StateDisplay) -> ResultType<()> {
if let Some(msg_out) = check_get_displays_changed_msg(&mut state.synced_displays) {
sp.send(msg_out);
log::info!("Displays changed");
}
Ok(())
}
#[inline]
pub(super) fn get_original_resolution(
display_name: &str,
w: usize,
h: usize,
) -> MessageField<Resolution> {
#[cfg(all(windows, feature = "virtual_display_driver"))]
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
let is_virtual_display = false;
Some(if is_virtual_display {
Resolution {
width: 0,
height: 0,
..Default::default()
}
} else {
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
let (width, height) = match changed_resolutions.get(display_name) {
Some(res) => {
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 fn to_display_info(all: &Vec<Display>) -> Vec<DisplayInfo> {
all.iter()
.map(|d| {
let display_name = d.name();
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
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,
..Default::default()
}
})
.collect::<Vec<DisplayInfo>>()
}
pub fn is_inited_msg() -> Option<Message> {
#[cfg(target_os = "linux")]
if !scrap::is_x11() {
return super::wayland::is_inited();
}
None
}
pub async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return super::wayland::get_displays().await;
}
}
Ok(to_display_info(&try_get_displays()?))
}
#[inline]
pub fn get_primary() -> usize {
#[cfg(target_os = "linux")]
{
if !scrap::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<Display>) -> usize {
all.iter().position(|d| d.is_primary()).unwrap_or(0)
}
#[inline]
#[cfg(all(windows, feature = "virtual_display_driver"))]
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];
let dummy_display_side_max_size = 800;
display.width() <= dummy_display_side_max_size
&& display.height() <= dummy_display_side_max_size
} else {
false
}
}
#[inline]
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
pub fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(Display::all()?)
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
pub fn try_get_displays() -> ResultType<Vec<Display>> {
let mut displays = Display::all()?;
if no_displays(&displays) {
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)
}

View File

@@ -17,7 +17,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey};
use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput};
use std::{
convert::TryFrom,
ops::Sub,
ops::{Deref, DerefMut, Sub},
sync::atomic::{AtomicBool, Ordering},
thread,
time::{self, Duration, Instant},
@@ -236,18 +236,43 @@ fn should_disable_numlock(evt: &KeyEvent) -> bool {
pub const NAME_CURSOR: &'static str = "mouse_cursor";
pub const NAME_POS: &'static str = "mouse_pos";
pub type MouseCursorService = ServiceTmpl<MouseCursorSub>;
#[derive(Clone)]
pub struct MouseCursorService {
pub sp: ServiceTmpl<MouseCursorSub>,
}
pub fn new_cursor() -> MouseCursorService {
let sp = MouseCursorService::new(NAME_CURSOR, true);
sp.repeat::<StateCursor, _>(33, run_cursor);
sp
impl Deref for MouseCursorService {
type Target = ServiceTmpl<MouseCursorSub>;
fn deref(&self) -> &Self::Target {
&self.sp
}
}
impl DerefMut for MouseCursorService {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.sp
}
}
impl MouseCursorService {
pub fn new(name: String, need_snapshot: bool) -> Self {
Self {
sp: ServiceTmpl::<MouseCursorSub>::new(name, need_snapshot),
}
}
}
pub fn new_cursor() -> ServiceTmpl<MouseCursorSub> {
let svc = MouseCursorService::new(NAME_CURSOR.to_owned(), true);
ServiceTmpl::<MouseCursorSub>::repeat::<StateCursor, _, _>(&svc.clone(), 33, run_cursor);
svc.sp
}
pub fn new_pos() -> GenericService {
let sp = GenericService::new(NAME_POS, false);
sp.repeat::<StatePos, _>(33, run_pos);
sp
let svc = EmptyExtraFieldService::new(NAME_POS.to_owned(), false);
GenericService::repeat::<StatePos, _, _>(&svc.clone(), 33, run_pos);
svc.sp
}
#[inline]
@@ -258,7 +283,7 @@ fn update_last_cursor_pos(x: i32, y: i32) {
}
}
fn run_pos(sp: GenericService, state: &mut StatePos) -> ResultType<()> {
fn run_pos(sp: EmptyExtraFieldService, state: &mut StatePos) -> ResultType<()> {
let (_, (x, y)) = *LATEST_SYS_CURSOR_POS.lock().unwrap();
if x == INVALID_CURSOR_POS || y == INVALID_CURSOR_POS {
return Ok(());
@@ -988,7 +1013,6 @@ pub async fn lock_screen() {
crate::platform::lock_screen();
}
}
super::video_service::switch_to_primary().await;
}
pub fn handle_key(evt: &KeyEvent) {

View File

@@ -25,7 +25,6 @@ use winapi::{
use crate::{
ipc::{self, new_listener, Connection, Data, DataPortableService},
platform::set_path_permission,
video_service::get_current_display,
};
use super::video_qos;
@@ -224,6 +223,8 @@ mod utils {
pub mod server {
use hbb_common::message_proto::PointerDeviceEvent;
use crate::display_service;
use super::*;
lazy_static::lazy_static! {
@@ -324,12 +325,17 @@ pub mod server {
continue;
}
if c.is_none() {
*crate::video_service::CURRENT_DISPLAY.lock().unwrap() = current_display;
let Ok((_, _current, display)) = get_current_display() else {
log::error!("Failed to get current display");
let Ok(mut displays) = display_service::try_get_displays() else {
log::error!("Failed to get displays");
*EXIT.lock().unwrap() = true;
return;
};
if displays.len() <= current_display {
log::error!("Invalid display index:{}", current_display);
*EXIT.lock().unwrap() = true;
return;
}
let display = displays.remove(current_display);
display_width = display.width();
display_height = display.height();
match Capturer::new(display, use_yuv) {
@@ -522,6 +528,8 @@ pub mod server {
pub mod client {
use hbb_common::{anyhow::Context, message_proto::PointerDeviceEvent};
use crate::display_service;
use super::*;
lazy_static::lazy_static! {
@@ -665,8 +673,8 @@ pub mod client {
shmem.write(ADDR_CAPTURE_WOULDBLOCK, &utils::i32_to_vec(TRUE));
}
let (mut width, mut height) = (0, 0);
if let Ok((_, current, display)) = get_current_display() {
if current_display == current {
if let Ok(displays) = display_service::try_get_displays() {
if let Some(display) = displays.get(current_display) {
width = display.width();
height = display.height();
}
@@ -910,7 +918,15 @@ pub mod client {
}
if portable_service_running {
log::info!("Create shared memory capturer");
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
if current_display == *display_service::PRIMARY_DISPLAY_IDX {
return Ok(Box::new(CapturerPortable::new(current_display, use_yuv)));
} else {
bail!(
"Ignore capture display index: {}, the primary display index is: {}",
current_display,
*display_service::PRIMARY_DISPLAY_IDX
);
}
} else {
log::debug!("Create capturer dxgi|gdi");
return Ok(Box::new(

View File

@@ -1,16 +1,19 @@
use super::*;
use std::{
collections::HashSet,
ops::{Deref, DerefMut},
thread::{self, JoinHandle},
time,
};
pub trait Service: Send + Sync {
fn name(&self) -> &'static str;
fn name(&self) -> String;
fn on_subscribe(&self, sub: ConnInner);
fn on_unsubscribe(&self, id: i32);
fn is_subed(&self, id: i32) -> bool;
fn join(&self);
fn get_option(&self, opt: &str) -> Option<String>;
fn set_option(&self, opt: &str, val: &str) -> Option<String>;
}
pub trait Subscriber: Default + Send + Sync + 'static {
@@ -20,12 +23,13 @@ pub trait Subscriber: Default + Send + Sync + 'static {
#[derive(Default)]
pub struct ServiceInner<T: Subscriber + From<ConnInner>> {
name: &'static str,
name: String,
handle: Option<JoinHandle<()>>,
subscribes: HashMap<i32, T>,
new_subscribes: HashMap<i32, T>,
active: bool,
need_snapshot: bool,
options: HashMap<String, String>,
}
pub trait Reset {
@@ -37,6 +41,35 @@ pub struct ServiceSwap<T: Subscriber + From<ConnInner>>(ServiceTmpl<T>);
pub type GenericService = ServiceTmpl<ConnInner>;
pub const HIBERNATE_TIMEOUT: u64 = 30;
pub const MAX_ERROR_TIMEOUT: u64 = 1_000;
pub const SERVICE_OPTION_VALUE_TRUE: &str = "1";
pub const SERVICE_OPTION_VALUE_FALSE: &str = "0";
#[derive(Clone)]
pub struct EmptyExtraFieldService {
pub sp: GenericService,
}
impl Deref for EmptyExtraFieldService {
type Target = ServiceTmpl<ConnInner>;
fn deref(&self) -> &Self::Target {
&self.sp
}
}
impl DerefMut for EmptyExtraFieldService {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.sp
}
}
impl EmptyExtraFieldService {
pub fn new(name: String, need_snapshot: bool) -> Self {
Self {
sp: GenericService::new(name, need_snapshot),
}
}
}
impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
fn send_new_subscribes(&mut self, msg: Arc<Message>) {
@@ -60,8 +93,8 @@ impl<T: Subscriber + From<ConnInner>> ServiceInner<T> {
impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
#[inline]
fn name(&self) -> &'static str {
self.0.read().unwrap().name
fn name(&self) -> String {
self.0.read().unwrap().name.clone()
}
fn is_subed(&self, id: i32) -> bool {
@@ -96,6 +129,18 @@ impl<T: Subscriber + From<ConnInner>> Service for ServiceTmpl<T> {
}
}
}
fn get_option(&self, opt: &str) -> Option<String> {
self.0.read().unwrap().options.get(opt).cloned()
}
fn set_option(&self, opt: &str, val: &str) -> Option<String> {
self.0
.write()
.unwrap()
.options
.insert(opt.to_string(), val.to_string())
}
}
impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
@@ -105,7 +150,7 @@ impl<T: Subscriber + From<ConnInner>> Clone for ServiceTmpl<T> {
}
impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
pub fn new(name: &'static str, need_snapshot: bool) -> Self {
pub fn new(name: String, need_snapshot: bool) -> Self {
Self(Arc::new(RwLock::new(ServiceInner::<T> {
name,
active: true,
@@ -114,6 +159,21 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
})))
}
#[inline]
pub fn is_option_true(&self, opt: &str) -> bool {
self.get_option(opt)
.map_or(false, |v| v == SERVICE_OPTION_VALUE_TRUE)
}
#[inline]
pub fn set_option_bool(&self, opt: &str, val: bool) {
if val {
self.set_option(opt, SERVICE_OPTION_VALUE_TRUE);
} else {
self.set_option(opt, SERVICE_OPTION_VALUE_FALSE);
}
}
#[inline]
pub fn has_subscribes(&self) -> bool {
self.0.read().unwrap().has_subscribes()
@@ -189,14 +249,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
}
pub fn repeat<S, F>(&self, interval_ms: u64, callback: F)
pub fn repeat<S, F, Svc>(svc: &Svc, interval_ms: u64, callback: F)
where
F: 'static + FnMut(Self, &mut S) -> ResultType<()> + Send,
F: 'static + FnMut(Svc, &mut S) -> ResultType<()> + Send,
S: 'static + Default + Reset,
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
{
let interval = time::Duration::from_millis(interval_ms);
let mut callback = callback;
let sp = self.clone();
let sp = svc.clone();
let thread = thread::spawn(move || {
let mut state = S::default();
let mut may_reset = false;
@@ -223,14 +284,15 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
svc.0.write().unwrap().handle = Some(thread);
}
pub fn run<F>(&self, callback: F)
pub fn run<F, Svc>(svc: &Svc, callback: F)
where
F: 'static + FnMut(Self) -> ResultType<()> + Send,
F: 'static + FnMut(Svc) -> ResultType<()> + Send,
Svc: 'static + Clone + Send + DerefMut<Target = ServiceTmpl<T>>,
{
let sp = self.clone();
let sp = svc.clone();
let mut callback = callback;
let thread = thread::spawn(move || {
let mut error_timeout = HIBERNATE_TIMEOUT;
@@ -259,7 +321,7 @@ impl<T: Subscriber + From<ConnInner>> ServiceTmpl<T> {
}
log::info!("Service {} exit", sp.name());
});
self.0.write().unwrap().handle = Some(thread);
svc.0.write().unwrap().handle = Some(thread);
}
#[inline]

View File

@@ -18,15 +18,11 @@
// to-do:
// https://slhck.info/video/2017/03/01/rate-control.html
use super::{video_qos::VideoQoS, *};
#[cfg(all(windows, feature = "virtual_display_driver"))]
use crate::virtual_display_manager;
use super::{service::ServiceTmpl, video_qos::VideoQoS, *};
#[cfg(windows)]
use crate::{platform::windows::is_process_consent_running, privacy_win_mag};
#[cfg(windows)]
use hbb_common::get_version_number;
use hbb_common::{
protobuf::MessageField,
anyhow::anyhow,
tokio::sync::{
mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender},
Mutex as TokioMutex,
@@ -51,71 +47,18 @@ use std::{
};
pub const NAME: &'static str = "video";
struct ChangedResolution {
original: (i32, i32),
changed: (i32, i32),
}
pub const OPTION_DISPLAY_CHANGED: &'static str = "changed";
pub const OPTION_REFRESH: &'static str = "refresh";
lazy_static::lazy_static! {
pub static ref CURRENT_DISPLAY: Arc<Mutex<usize>> = Arc::new(Mutex::new(usize::MAX));
static ref LAST_ACTIVE: Arc<Mutex<Instant>> = Arc::new(Mutex::new(Instant::now()));
static ref SWITCH: Arc<Mutex<bool>> = Default::default();
static ref FRAME_FETCHED_NOTIFIER: (UnboundedSender<(i32, Option<Instant>)>, Arc<TokioMutex<UnboundedReceiver<(i32, Option<Instant>)>>>) = {
let (tx, rx) = unbounded_channel();
(tx, Arc::new(TokioMutex::new(rx)))
};
static ref PRIVACY_MODE_CONN_ID: Mutex<i32> = Mutex::new(0);
static ref IS_CAPTURER_MAGNIFIER_SUPPORTED: bool = is_capturer_mag_supported();
pub static ref VIDEO_QOS: Arc<Mutex<VideoQoS>> = Default::default();
pub static ref IS_UAC_RUNNING: Arc<Mutex<bool>> = Default::default();
pub static ref IS_FOREGROUND_WINDOW_ELEVATED: Arc<Mutex<bool>> = Default::default();
pub static ref LAST_SYNC_DISPLAYS: Arc<RwLock<Vec<DisplayInfo>>> = Default::default();
static ref CHANGED_RESOLUTIONS: Arc<RwLock<HashMap<String, ChangedResolution>>> = Default::default();
}
#[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
);
}
}
}
#[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]
@@ -133,15 +76,6 @@ pub fn get_privacy_mode_conn_id() -> i32 {
*PRIVACY_MODE_CONN_ID.lock().unwrap()
}
#[inline]
pub fn is_privacy_mode_supported() -> bool {
#[cfg(windows)]
return *IS_CAPTURER_MAGNIFIER_SUPPORTED
&& get_version_number(&crate::VERSION) > get_version_number("1.1.9");
#[cfg(not(windows))]
return false;
}
struct VideoFrameController {
cur: Instant,
send_conn_ids: HashSet<i32>,
@@ -192,10 +126,37 @@ impl VideoFrameController {
}
}
pub fn new() -> GenericService {
let sp = GenericService::new(NAME, true);
sp.run(run);
sp
#[derive(Clone)]
pub struct VideoService {
sp: GenericService,
idx: usize,
}
impl Deref for VideoService {
type Target = ServiceTmpl<ConnInner>;
fn deref(&self) -> &Self::Target {
&self.sp
}
}
impl DerefMut for VideoService {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.sp
}
}
pub fn get_service_name(idx: usize) -> String {
format!("{}{}", NAME, idx)
}
pub fn new(idx: usize) -> GenericService {
let vs = VideoService {
sp: GenericService::new(get_service_name(idx), true),
idx,
};
GenericService::run(&vs, run);
vs.sp
}
fn check_display_changed(
@@ -221,19 +182,10 @@ fn check_display_changed(
if n != last_n {
return true;
};
for (i, d) in displays.iter().enumerate() {
if d.is_primary() {
if i != last_current {
return true;
};
if d.width() != last_width || d.height() != last_height {
return true;
};
}
match displays.get(last_current) {
Some(d) => d.width() != last_width || d.height() != last_height,
None => true,
}
return false;
}
// Capturer object is expensive, avoiding to create it frequently.
@@ -317,14 +269,27 @@ fn create_capturer(
}
// This function works on privacy mode. Windows only for now.
pub fn test_create_capturer(privacy_mode_id: i32, timeout_millis: u64) -> String {
pub fn test_create_capturer(
privacy_mode_id: i32,
display_idx: usize,
timeout_millis: u64,
) -> String {
let test_begin = Instant::now();
loop {
let err = match get_current_display() {
Ok((_, current, display)) => {
match create_capturer(privacy_mode_id, display, true, current, false) {
Ok(_) => return "".to_owned(),
Err(e) => e,
let err = match try_get_displays() {
Ok(mut displays) => {
if displays.len() <= display_idx {
anyhow!(
"Failed to get display {}, the displays' count is {}",
display_idx,
displays.len()
)
} else {
let display = displays.remove(display_idx);
match create_capturer(privacy_mode_id, display, true, display_idx, false) {
Ok(_) => return "".to_owned(),
Err(e) => e,
}
}
}
Err(e) => e,
@@ -352,6 +317,7 @@ fn check_uac_switch(privacy_mode_id: i32, capturer_privacy_mode_id: i32) -> Resu
}
pub(super) struct CapturerInfo {
pub name: String,
pub origin: (i32, i32),
pub width: usize,
pub height: usize,
@@ -376,7 +342,11 @@ impl DerefMut for CapturerInfo {
}
}
fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<CapturerInfo> {
fn get_capturer(
current: usize,
use_yuv: bool,
portable_service_running: bool,
) -> ResultType<CapturerInfo> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
@@ -384,8 +354,18 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
}
}
let (ndisplay, current, display) = get_current_display()?;
let mut displays = try_get_displays()?;
let ndisplay = displays.len();
if ndisplay <= current {
bail!(
"Failed to get display {}, displays len: {}",
current,
ndisplay
);
}
let display = displays.remove(current);
let (origin, width, height) = (display.origin(), display.width(), display.height());
let name = display.name();
log::debug!(
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}, name:{}",
ndisplay,
@@ -395,7 +375,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
height,
num_cpus::get_physical(),
num_cpus::get(),
display.name(),
&name,
);
let privacy_mode_id = *PRIVACY_MODE_CONN_ID.lock().unwrap();
@@ -429,6 +409,7 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
portable_service_running,
)?;
Ok(CapturerInfo {
name,
origin,
width,
height,
@@ -440,42 +421,13 @@ fn get_capturer(use_yuv: bool, portable_service_running: bool) -> ResultType<Cap
})
}
fn check_displays_new() -> Option<Vec<Display>> {
let displays = try_get_displays().ok()?;
let last_sync_displays = &*LAST_SYNC_DISPLAYS.read().unwrap();
if displays.len() != last_sync_displays.len() {
// No need to check if the resolutions are changed by third process.
Some(displays)
} else {
None
}
}
fn check_get_displays_changed_msg() -> Option<Message> {
let displays = check_displays_new()?;
// Display to DisplayInfo
let (current, displays) = get_displays_2(&displays);
let mut pi = PeerInfo {
..Default::default()
};
pi.displays = displays.clone();
pi.current_display = current as _;
let mut msg_out = Message::new();
msg_out.set_peer_info(pi);
*LAST_SYNC_DISPLAYS.write().unwrap() = displays;
Some(msg_out)
}
#[cfg(all(windows, feature = "virtual_display_driver"))]
pub fn try_plug_out_virtual_display() {
let _res = virtual_display_manager::plug_out_headless();
}
fn run(sp: GenericService) -> ResultType<()> {
fn run(vs: VideoService) -> ResultType<()> {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
let _wake_lock = get_wake_lock();
// ensure_inited() is needed because clear() may be called.
// to-do: wayland ensure_inited should pass current display index.
// But for now, we do not support multi-screen capture on wayland.
#[cfg(target_os = "linux")]
super::wayland::ensure_inited()?;
#[cfg(windows)]
@@ -483,7 +435,9 @@ fn run(sp: GenericService) -> ResultType<()> {
#[cfg(not(windows))]
let last_portable_service_running = false;
let mut c = get_capturer(true, last_portable_service_running)?;
let display_idx = vs.idx;
let sp = vs.sp;
let mut c = get_capturer(display_idx, true, last_portable_service_running)?;
let mut video_qos = VIDEO_QOS.lock().unwrap();
video_qos.refresh(None);
@@ -506,37 +460,18 @@ fn run(sp: GenericService) -> ResultType<()> {
c.set_use_yuv(encoder.use_yuv());
VIDEO_QOS.lock().unwrap().store_bitrate(encoder.bitrate());
if *SWITCH.lock().unwrap() {
log::debug!("Broadcasting display switch");
let mut misc = Misc::new();
let display_name = get_current_display()
.map(|(_, _, d)| d.name())
.unwrap_or_default();
let original_resolution = get_original_resolution(&display_name, c.width, c.height);
misc.set_switch_display(SwitchDisplay {
display: c.current as _,
x: c.origin.0 as _,
y: c.origin.1 as _,
width: c.width as _,
height: c.height as _,
cursor_embedded: capture_cursor_embedded(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
resolutions: Some(SupportedResolutions {
resolutions: if display_name.is_empty() {
vec![]
} else {
crate::platform::resolutions(&display_name)
},
..SupportedResolutions::default()
})
.into(),
original_resolution,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
*SWITCH.lock().unwrap() = false;
sp.send(msg_out);
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) {
sp.set_option_bool(OPTION_REFRESH, false);
}
let mut frame_controller = VideoFrameController::new();
@@ -572,13 +507,7 @@ fn run(sp: GenericService) -> ResultType<()> {
}
drop(video_qos);
if *SWITCH.lock().unwrap() {
bail!("SWITCH");
}
if c.current != *CURRENT_DISPLAY.lock().unwrap() {
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
if sp.is_option_true(OPTION_DISPLAY_CHANGED) || sp.is_option_true(OPTION_REFRESH) {
bail!("SWITCH");
}
if codec_name != Encoder::negotiated_codec() {
@@ -604,23 +533,12 @@ fn run(sp: GenericService) -> ResultType<()> {
// Capturer on macos does not return Err event the solution is changed.
#[cfg(target_os = "macos")]
if check_display_changed(c.ndisplay, c.current, c.width, c.height) {
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true);
log::info!("Displays changed");
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
if let Some(msg_out) = check_get_displays_changed_msg() {
sp.send(msg_out);
log::info!("Displays changed");
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
bail!("SWITCH");
}
}
*LAST_ACTIVE.lock().unwrap() = now;
frame_controller.reset();
#[cfg(any(target_os = "android", target_os = "ios"))]
@@ -631,8 +549,14 @@ fn run(sp: GenericService) -> ResultType<()> {
match frame {
scrap::Frame::RAW(data) => {
if data.len() != 0 {
let send_conn_ids =
handle_one_frame(&sp, data, ms, &mut encoder, recorder.clone())?;
let send_conn_ids = handle_one_frame(
display_idx,
&sp,
data,
ms,
&mut encoder,
recorder.clone(),
)?;
frame_controller.set_send(now, send_conn_ids);
}
}
@@ -649,7 +573,7 @@ fn run(sp: GenericService) -> ResultType<()> {
let time = now - start;
let ms = (time.as_secs() * 1000 + time.subsec_millis() as u64) as i64;
let send_conn_ids =
handle_one_frame(&sp, &frame, ms, &mut encoder, recorder.clone())?;
handle_one_frame(display_idx, &sp, &frame, ms, &mut encoder, recorder.clone())?;
frame_controller.set_send(now, send_conn_ids);
#[cfg(windows)]
{
@@ -693,7 +617,7 @@ fn run(sp: GenericService) -> ResultType<()> {
log::info!("Displays changed");
#[cfg(target_os = "linux")]
super::wayland::clear();
*SWITCH.lock().unwrap() = true;
sp.set_option_bool(OPTION_DISPLAY_CHANGED, true);
bail!("SWITCH");
}
@@ -829,6 +753,7 @@ fn check_privacy_mode_changed(sp: &GenericService, privacy_mode_id: i32) -> Resu
#[inline]
fn handle_one_frame(
display: usize,
sp: &GenericService,
frame: &[u8],
ms: i64,
@@ -844,7 +769,10 @@ fn handle_one_frame(
})?;
let mut send_conn_ids: HashSet<i32> = Default::default();
if let Ok(msg) = encoder.encode_to_message(frame, ms) {
if let Ok(mut vf) = encoder.encode_to_message(frame, ms) {
vf.display = display as _;
let mut msg = Message::new();
msg.set_video_frame(vf);
#[cfg(not(target_os = "ios"))]
recorder
.lock()
@@ -856,69 +784,6 @@ fn handle_one_frame(
Ok(send_conn_ids)
}
#[inline]
fn get_original_resolution(display_name: &str, w: usize, h: usize) -> MessageField<Resolution> {
#[cfg(all(windows, feature = "virtual_display_driver"))]
let is_virtual_display = crate::virtual_display_manager::is_virtual_display(&display_name);
#[cfg(not(all(windows, feature = "virtual_display_driver")))]
let is_virtual_display = false;
Some(if is_virtual_display {
Resolution {
width: 0,
height: 0,
..Default::default()
}
} else {
let mut changed_resolutions = CHANGED_RESOLUTIONS.write().unwrap();
let (width, height) = match changed_resolutions.get(display_name) {
Some(res) => {
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_displays_2(all: &Vec<Display>) -> (usize, Vec<DisplayInfo>) {
let mut displays = Vec::new();
let mut primary = 0;
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
primary = i;
}
let display_name = d.name();
let original_resolution = get_original_resolution(&display_name, d.width(), d.height());
displays.push(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,
..Default::default()
});
}
let mut lock = CURRENT_DISPLAY.lock().unwrap();
if *lock >= displays.len() {
*lock = primary
}
(*lock, displays)
}
pub fn is_inited_msg() -> Option<Message> {
#[cfg(target_os = "linux")]
if !scrap::is_x11() {
@@ -927,65 +792,10 @@ pub fn is_inited_msg() -> Option<Message> {
None
}
// switch to primary display if long time (30 seconds) no users
#[inline]
pub fn try_reset_current_display() {
if LAST_ACTIVE.lock().unwrap().elapsed().as_secs() >= 30 {
*CURRENT_DISPLAY.lock().unwrap() = usize::MAX;
}
*LAST_ACTIVE.lock().unwrap() = time::Instant::now();
}
pub async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return super::wayland::get_displays().await;
}
}
Ok(get_displays_2(&try_get_displays()?))
}
pub async fn switch_display(i: i32) {
let i = i as usize;
if let Ok((_, displays)) = get_displays().await {
if i < displays.len() {
*CURRENT_DISPLAY.lock().unwrap() = i;
}
}
}
#[inline]
pub fn refresh() {
#[cfg(target_os = "android")]
Display::refresh_size();
*SWITCH.lock().unwrap() = true;
}
fn get_primary() -> usize {
#[cfg(target_os = "linux")]
{
if !scrap::is_x11() {
return match super::wayland::get_primary() {
Ok(n) => n,
Err(_) => 0,
};
}
}
if let Ok(all) = try_get_displays() {
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
return i;
}
}
}
0
}
#[inline]
pub async fn switch_to_primary() {
switch_display(get_primary() as _).await;
}
#[inline]
@@ -1034,30 +844,6 @@ fn try_get_displays() -> ResultType<Vec<Display>> {
Ok(Display::all()?)
}
pub(super) fn get_current_display_2(mut all: Vec<Display>) -> ResultType<(usize, usize, Display)> {
let mut current = *CURRENT_DISPLAY.lock().unwrap() as usize;
if all.len() == 0 {
bail!("No displays");
}
let n = all.len();
if current >= n {
current = 0;
for (i, d) in all.iter().enumerate() {
if d.is_primary() {
current = i;
break;
}
}
*CURRENT_DISPLAY.lock().unwrap() = current;
}
return Ok((n, current, all.remove(current)));
}
#[inline]
pub fn get_current_display() -> ResultType<(usize, usize, Display)> {
get_current_display_2(try_get_displays()?)
}
#[cfg(windows)]
fn start_uac_elevation_check() {
static START: Once = Once::new();
@@ -1090,3 +876,63 @@ fn get_wake_lock() -> crate::platform::WakeLock {
};
crate::platform::WakeLock::new(display, idle, sleep)
}
#[inline]
fn broadcast_display_changed(
display_idx: usize,
sp: &GenericService,
display_meta: Option<(String, (i32, i32), usize, usize)>,
) {
if let Some(msg_out) = make_display_changed_msg(display_idx, display_meta) {
sp.send(msg_out);
}
}
fn get_display_info_simple_meta(display_idx: usize) -> Option<(String, (i32, i32), usize, usize)> {
let displays = display_service::try_get_displays().ok()?;
if let Some(display) = displays.get(display_idx) {
Some((
display.name(),
display.origin(),
display.width(),
display.height(),
))
} else {
None
}
}
pub fn make_display_changed_msg(
display_idx: usize,
display_meta: Option<(String, (i32, i32), usize, usize)>,
) -> Option<Message> {
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 {
display: display_idx as _,
x: origin.0,
y: origin.1,
width: width as _,
height: height as _,
cursor_embedded: display_service::capture_cursor_embedded(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
resolutions: Some(SupportedResolutions {
resolutions: if name.is_empty() {
vec![]
} else {
crate::platform::resolutions(&name)
},
..SupportedResolutions::default()
})
.into(),
original_resolution,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
Some(msg_out)
}

View File

@@ -142,9 +142,11 @@ pub(super) async fn check_init() -> ResultType<()> {
if *CAP_DISPLAY_INFO.read().unwrap() == 0 {
let mut lock = CAP_DISPLAY_INFO.write().unwrap();
if *lock == 0 {
let all = Display::all()?;
let mut all = Display::all()?;
let num = all.len();
let (primary, mut displays) = super::video_service::get_displays_2(&all);
let primary = super::display_service::get_primary_2(&all);
let current = primary;
let mut displays = super::display_service::to_display_info(&all);
for display in displays.iter_mut() {
display.cursor_embedded = is_cursor_embedded();
}
@@ -154,12 +156,11 @@ pub(super) async fn check_init() -> ResultType<()> {
rects.push((d.origin(), d.width(), d.height()));
}
let (ndisplay, current, display) =
super::video_service::get_current_display_2(all)?;
let display = all.remove(current);
let (origin, width, height) = (display.origin(), display.width(), display.height());
log::debug!(
"#displays={}, current={}, origin: {:?}, width={}, height={}, cpus={}/{}",
ndisplay,
num,
current,
&origin,
width,
@@ -213,16 +214,14 @@ pub(super) async fn check_init() -> ResultType<()> {
Ok(())
}
pub(super) async fn get_displays() -> ResultType<(usize, Vec<DisplayInfo>)> {
pub(super) async fn get_displays() -> ResultType<Vec<DisplayInfo>> {
check_init().await?;
let addr = *CAP_DISPLAY_INFO.read().unwrap();
if addr != 0 {
let cap_display_info: *const CapDisplayInfo = addr as _;
unsafe {
let cap_display_info = &*cap_display_info;
let primary = cap_display_info.primary;
let displays = cap_display_info.displays.clone();
Ok((primary, displays))
Ok(cap_display_info.displays.clone())
}
} else {
bail!("Failed to get capturer display info");
@@ -268,6 +267,9 @@ pub(super) fn get_capturer() -> ResultType<super::video_service::CapturerInfo> {
let cap_display_info = &*cap_display_info;
let rect = cap_display_info.rects[cap_display_info.current];
Ok(super::video_service::CapturerInfo {
name: cap_display_info.displays[cap_display_info.current]
.name
.clone(),
origin: rect.0,
width: rect.1,
height: rect.2,

View File

@@ -1,7 +1,6 @@
use std::{
collections::HashMap,
iter::FromIterator,
process::Child,
sync::{Arc, Mutex},
};
@@ -22,7 +21,6 @@ mod cm;
pub mod inline;
pub mod remote;
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
#[allow(dead_code)]
type Status = (i32, bool, i64, String);
@@ -34,7 +32,6 @@ lazy_static::lazy_static! {
#[cfg(not(any(feature = "flutter", feature = "cli")))]
lazy_static::lazy_static! {
pub static ref CUR_SESSION: Arc<Mutex<Option<Session<remote::SciterHandler>>>> = Default::default();
static ref CHILDREN : Children = Default::default();
}
struct UIHostHandler;
@@ -598,6 +595,10 @@ impl UI {
fn get_login_device_info(&self) -> String {
get_login_device_info_json()
}
fn support_remove_wallpaper(&self) -> bool {
support_remove_wallpaper()
}
}
impl sciter::EventHandler for UI {
@@ -683,6 +684,7 @@ impl sciter::EventHandler for UI {
fn default_video_save_directory();
fn handle_relay_id(String);
fn get_login_device_info();
fn support_remove_wallpaper();
}
}

View File

@@ -298,10 +298,11 @@ class Header: Reactor.Component {
recording = !recording;
header.update();
handler.record_status(recording);
// 0 is just a dummy value. It will be ignored by the handler.
if (recording)
handler.refresh_video();
handler.refresh_video(0);
else
handler.record_screen(false, display_width, display_height);
handler.record_screen(false, 0, display_width, display_height);
}
event click $(#screen) (_, me) {
@@ -370,7 +371,8 @@ class Header: Reactor.Component {
}
event click $(#refresh) {
handler.refresh_video();
// 0 is just a dummy value. It will be ignored by the handler.
handler.refresh_video(0);
}
event click $(#block-input) {

View File

@@ -210,6 +210,7 @@ class Enhancements: Reactor.Component {
function render() {
var has_hwcodec = handler.has_hwcodec();
var support_remove_wallpaper = handler.support_remove_wallpaper();
var me = this;
self.timer(1ms, function() { me.toggleMenuState() });
return <li>{translate('Enhancements')}
@@ -217,7 +218,7 @@ class Enhancements: Reactor.Component {
{has_hwcodec ? <li #enable-hwcodec><span>{svg_checkmark}</span>{translate("Hardware Codec")} (beta)</li> : ""}
<li #enable-abr><span>{svg_checkmark}</span>{translate("Adaptive bitrate")} (beta)</li>
<li #screen-recording>{translate("Recording")}</li>
{is_osx ? "" : <li #allow-remove-wallpaper><span>{svg_checkmark}</span>{translate("Remove wallpaper during incoming sessions")}</li>}
{support_remove_wallpaper ? <li #allow-remove-wallpaper><span>{svg_checkmark}</span>{translate("Remove wallpaper during incoming sessions")}</li> : ""}
</menu>
</li>;
}

View File

@@ -122,7 +122,11 @@ impl InvokeUiSession for SciterHandler {
"updateQualityStatus",
&make_args!(
status.speed.map_or(Value::null(), |it| it.into()),
status.fps.map_or(Value::null(), |it| it.into()),
status
.fps
.iter()
.next()
.map_or(Value::null(), |(_, v)| (*v).into()),
status.delay.map_or(Value::null(), |it| it.into()),
status.target_bitrate.map_or(Value::null(), |it| it.into()),
status
@@ -223,7 +227,7 @@ impl InvokeUiSession for SciterHandler {
self.call("adaptSize", &make_args!());
}
fn on_rgba(&self, rgba: &mut scrap::ImageRgb) {
fn on_rgba(&self, _display: usize, rgba: &mut scrap::ImageRgb) {
VIDEO
.lock()
.unwrap()
@@ -304,11 +308,11 @@ impl InvokeUiSession for SciterHandler {
}
/// RGBA is directly rendered by [on_rgba]. No need to store the rgba for the sciter ui.
fn get_rgba(&self) -> *const u8 {
fn get_rgba(&self, _display: usize) -> *const u8 {
std::ptr::null()
}
fn next_rgba(&self) {}
fn next_rgba(&self, _display: usize) {}
}
pub struct SciterSession(Session<SciterHandler>);
@@ -449,8 +453,8 @@ impl sciter::EventHandler for SciterSession {
fn save_view_style(String);
fn save_image_quality(String);
fn save_custom_image_quality(i32);
fn refresh_video();
fn record_screen(bool, i32, i32);
fn refresh_video(i32);
fn record_screen(bool, i32, i32, i32);
fn record_status(bool);
fn get_toggle_option(String);
fn is_privacy_mode_supported();

View File

@@ -23,7 +23,7 @@ handler.setDisplay = function(x, y, w, h, cursor_embedded) {
display_origin_y = y;
display_cursor_embedded = cursor_embedded;
adaptDisplay();
if (recording) handler.record_screen(true, w, h);
if (recording) handler.record_screen(true, 0, w, h);
}
// in case toolbar not shown correctly
@@ -478,7 +478,7 @@ function self.closing() {
var (x, y, w, h) = view.box(#rectw, #border, #screen);
if (is_file_transfer) save_file_transfer_close_state();
if (is_file_transfer || is_port_forward || size_adapted) handler.save_size(x, y, w, h);
if (recording) handler.record_screen(false, display_width, display_height);
if (recording) handler.record_screen(false, 0, display_width, display_height);
}
var qualityMonitor;

View File

@@ -164,6 +164,7 @@ impl<T: InvokeUiCM> ConnectionManager<T> {
}
#[inline]
#[cfg(windows)]
fn is_authorized(&self, id: i32) -> bool {
CLIENTS
.read()

View File

@@ -2,14 +2,14 @@
use hbb_common::password_security;
use hbb_common::{
allow_err,
config::{self, Config, LocalConfig, PeerConfig},
directories_next, log, tokio,
};
use hbb_common::{
bytes::Bytes,
config::{self, Config, LocalConfig, PeerConfig},
config::{CONNECT_TIMEOUT, RENDEZVOUS_PORT},
directories_next,
futures::future::join_all,
log,
rendezvous_proto::*,
tokio,
};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use hbb_common::{
@@ -17,9 +17,10 @@ use hbb_common::{
tokio::{sync::mpsc, time},
};
use serde_derive::Serialize;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::process::Child;
use std::{
collections::HashMap,
process::Child,
sync::{Arc, Mutex},
};
@@ -31,6 +32,7 @@ use crate::ipc;
type Message = RendezvousMessage;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
pub type Children = Arc<Mutex<(bool, HashMap<(String, String), Child>)>>;
#[derive(Clone, Debug, Serialize)]
@@ -594,7 +596,13 @@ pub fn current_is_wayland() -> bool {
#[inline]
pub fn get_new_version() -> String {
(*SOFTWARE_UPDATE_URL.lock().unwrap().rsplit('/').next().unwrap_or("")).to_string()
(*SOFTWARE_UPDATE_URL
.lock()
.unwrap()
.rsplit('/')
.next()
.unwrap_or(""))
.to_string()
}
#[inline]
@@ -1248,3 +1256,10 @@ pub fn handle_relay_id(id: String) -> String {
id
}
}
pub fn support_remove_wallpaper() -> bool {
#[cfg(any(target_os = "windows", target_os = "linux"))]
return crate::platform::WallPaperRemover::support();
#[cfg(not(any(target_os = "windows", target_os = "linux")))]
return false;
}

View File

@@ -3,14 +3,12 @@ use async_trait::async_trait;
use bytes::Bytes;
use rdev::{Event, EventType::*, KeyCode};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
use std::{collections::HashMap, sync::atomic::AtomicBool};
use std::sync::atomic::{AtomicBool, Ordering};
use std::{
collections::HashMap,
ops::{Deref, DerefMut},
str::FromStr,
sync::{
atomic::{AtomicUsize, Ordering},
Arc, Mutex, RwLock,
},
sync::{Arc, Mutex, RwLock},
time::SystemTime,
};
use uuid::Uuid;
@@ -28,7 +26,7 @@ use hbb_common::{
sync::mpsc,
time::{Duration as TokioDuration, Instant},
},
SessionID, Stream,
Stream,
};
use crate::client::io_loop::Remote;
@@ -49,8 +47,7 @@ const CHANGE_RESOLUTION_VALID_TIMEOUT_SECS: u64 = 15;
#[derive(Clone, Default)]
pub struct Session<T: InvokeUiSession> {
pub session_id: SessionID, // different from the one in LoginConfigHandler, used for flutter UI message pass
pub id: String, // peer id
pub id: String, // peer id
pub password: String,
pub args: Vec<String>,
pub lc: Arc<RwLock<LoginConfigHandler>>,
@@ -239,10 +236,18 @@ impl<T: InvokeUiSession> Session<T> {
self.lc.read().unwrap().reverse_mouse_wheel.clone()
}
pub fn get_displays_as_individual_windows(&self) -> String {
self.lc.read().unwrap().displays_as_individual_windows.clone()
}
pub fn save_reverse_mouse_wheel(&self, value: String) {
self.lc.write().unwrap().save_reverse_mouse_wheel(value);
}
pub fn save_displays_as_individual_windows(&self, value: String) {
self.lc.write().unwrap().save_displays_as_individual_windows(value);
}
pub fn save_view_style(&self, value: String) {
self.lc.write().unwrap().save_view_style(value);
}
@@ -286,12 +291,30 @@ impl<T: InvokeUiSession> Session<T> {
&& !self.lc.read().unwrap().disable_clipboard.v
}
pub fn refresh_video(&self) {
#[cfg(feature = "flutter")]
pub fn refresh_video(&self, display: i32) {
if crate::common::is_support_multi_ui_session_num(self.lc.read().unwrap().version) {
self.send(Data::Message(LoginConfigHandler::refresh_display(
display as _,
)));
} else {
self.send(Data::Message(LoginConfigHandler::refresh()));
}
}
#[cfg(not(feature = "flutter"))]
pub fn refresh_video(&self, _display: i32) {
self.send(Data::Message(LoginConfigHandler::refresh()));
}
pub fn record_screen(&self, start: bool, w: i32, h: i32) {
self.send(Data::RecordScreen(start, w, h, self.id.clone()));
pub fn record_screen(&self, start: bool, display: i32, w: i32, h: i32) {
self.send(Data::RecordScreen(
start,
display as usize,
w,
h,
self.id.clone(),
));
}
pub fn record_status(&self, status: bool) {
@@ -603,6 +626,19 @@ impl<T: InvokeUiSession> Session<T> {
self.send(Data::Message(msg_out));
}
pub fn capture_displays(&self, add: Vec<i32>, sub: Vec<i32>, set: Vec<i32>) {
let mut misc = Misc::new();
misc.set_capture_displays(CaptureDisplays {
add,
sub,
set,
..Default::default()
});
let mut msg_out = Message::new();
msg_out.set_misc(misc);
self.send(Data::Message(msg_out));
}
pub fn switch_display(&self, display: i32) {
let (w, h) = match self.lc.read().unwrap().get_custom_resolution(display) {
Some((w, h)) => (w, h),
@@ -1164,7 +1200,7 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn update_block_input_state(&self, on: bool);
fn job_progress(&self, id: i32, file_num: i32, speed: f64, finished_size: f64);
fn adapt_size(&self);
fn on_rgba(&self, rgba: &mut scrap::ImageRgb);
fn on_rgba(&self, display: usize, rgba: &mut scrap::ImageRgb);
fn msgbox(&self, msgtype: &str, title: &str, text: &str, link: &str, retry: bool);
#[cfg(any(target_os = "android", target_os = "ios"))]
fn clipboard(&self, content: String);
@@ -1175,8 +1211,8 @@ pub trait InvokeUiSession: Send + Sync + Clone + 'static + Sized + Default {
fn on_voice_call_closed(&self, reason: &str);
fn on_voice_call_waiting(&self);
fn on_voice_call_incoming(&self);
fn get_rgba(&self) -> *const u8;
fn next_rgba(&self);
fn get_rgba(&self, display: usize) -> *const u8;
fn next_rgba(&self, display: usize);
}
impl<T: InvokeUiSession> Deref for Session<T> {
@@ -1237,7 +1273,7 @@ impl<T: InvokeUiSession> Interface for Session<T> {
if pi.displays.is_empty() {
self.lc.write().unwrap().handle_peer_info(&pi);
self.update_privacy_mode();
self.msgbox("error", "Remote Error", "No Display", "");
self.msgbox("error", "Remote Error", "No Displays", "");
return;
}
self.try_change_init_resolution(pi.current_display);
@@ -1447,24 +1483,29 @@ pub async fn io_loop<T: InvokeUiSession>(handler: Session<T>, round: u32) {
}
return;
}
let frame_count = Arc::new(AtomicUsize::new(0));
let frame_count_cl = frame_count.clone();
let frame_count_map: Arc<RwLock<HashMap<usize, usize>>> = Default::default();
let frame_count_map_cl = frame_count_map.clone();
let ui_handler = handler.ui_handler.clone();
let (video_sender, audio_sender, video_queue, decode_fps) =
start_video_audio_threads(move |data: &mut scrap::ImageRgb| {
frame_count_cl.fetch_add(1, Ordering::Relaxed);
ui_handler.on_rgba(data);
});
let (video_sender, audio_sender, video_queue_map, decode_fps_map) = start_video_audio_threads(
handler.clone(),
move |display: usize, data: &mut scrap::ImageRgb| {
let mut write_lock = frame_count_map_cl.write().unwrap();
let count = write_lock.get(&display).unwrap_or(&0) + 1;
write_lock.insert(display, count);
drop(write_lock);
ui_handler.on_rgba(display, data);
},
);
let mut remote = Remote::new(
handler,
video_queue,
video_queue_map,
video_sender,
audio_sender,
receiver,
sender,
frame_count,
decode_fps,
frame_count_map,
decode_fps_map,
);
remote.io_loop(&key, &token, round).await;
remote.sync_jobs_status_to_local().await;