view camera (#11040)

* view camera

Signed-off-by: 21pages <sunboeasy@gmail.com>

* `No cameras` prompt if no cameras available,  `peerGetSessionsCount` use
connType as parameter

Signed-off-by: 21pages <sunboeasy@gmail.com>

* fix, use video_service_name rather than display_idx as key in qos,etc

Signed-off-by: 21pages <sunboeasy@gmail.com>

---------

Signed-off-by: 21pages <sunboeasy@gmail.com>
Co-authored-by: Adwin White <adwinw01@gmail.com>
Co-authored-by: RustDesk <71636191+rustdesk@users.noreply.github.com>
This commit is contained in:
21pages
2025-03-10 21:06:53 +08:00
committed by GitHub
parent df4a101316
commit f0f999dc27
96 changed files with 3999 additions and 458 deletions

View File

@@ -44,6 +44,7 @@ use hbb_common::{
};
#[cfg(any(target_os = "android", target_os = "ios"))]
use scrap::android::{call_main_service_key_event, call_main_service_pointer_input};
use scrap::camera;
use serde_derive::Serialize;
use serde_json::{json, value::Value};
#[cfg(not(any(target_os = "android", target_os = "ios")))]
@@ -167,6 +168,7 @@ pub enum AuthConnType {
Remote,
FileTransfer,
PortForward,
ViewCamera,
}
pub struct Connection {
@@ -179,6 +181,7 @@ pub struct Connection {
timer: crate::RustDeskInterval,
file_timer: crate::RustDeskInterval,
file_transfer: Option<(String, bool)>,
view_camera: bool,
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
port_forward_address: String,
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
@@ -222,6 +225,7 @@ pub struct Connection {
portable: PortableState,
from_switch: bool,
voice_call_request_timestamp: Option<NonZeroI64>,
voice_calling: bool,
options_in_login: Option<OptionMessage>,
#[cfg(not(any(target_os = "ios")))]
pressed_modifiers: HashSet<rdev::Key>,
@@ -331,6 +335,7 @@ impl Connection {
timer: crate::rustdesk_interval(time::interval(SEC30)),
file_timer: crate::rustdesk_interval(time::interval(SEC30)),
file_transfer: None,
view_camera: false,
port_forward_socket: None,
port_forward_address: "".to_owned(),
tx_to_cm,
@@ -369,6 +374,7 @@ impl Connection {
from_switch: false,
audio_sender: None,
voice_call_request_timestamp: None,
voice_calling: false,
options_in_login: None,
#[cfg(not(any(target_os = "ios")))]
pressed_modifiers: Default::default(),
@@ -533,9 +539,17 @@ impl Connection {
conn.send_permission(Permission::Audio, enabled).await;
if conn.authorized {
if let Some(s) = conn.server.upgrade() {
s.write().unwrap().subscribe(
super::audio_service::NAME,
conn.inner.clone(), conn.audio_enabled());
if conn.is_authed_view_camera_conn() {
if conn.voice_calling || !conn.audio_enabled() {
s.write().unwrap().subscribe(
super::audio_service::NAME,
conn.inner.clone(), conn.audio_enabled());
}
} else {
s.write().unwrap().subscribe(
super::audio_service::NAME,
conn.inner.clone(), conn.audio_enabled());
}
}
}
} else if &name == "file" {
@@ -774,7 +788,7 @@ impl Connection {
});
conn.send(msg_out.into()).await;
}
if conn.is_authed_remote_conn() {
if conn.is_authed_remote_conn() || conn.view_camera {
if let Some(last_test_delay) = conn.last_test_delay {
video_service::VIDEO_QOS.lock().unwrap().user_delay_response_elapsed(id, last_test_delay.elapsed().as_millis());
}
@@ -1189,6 +1203,8 @@ impl Connection {
(1, AuthConnType::FileTransfer)
} else if self.port_forward_socket.is_some() {
(2, AuthConnType::PortForward)
} else if self.view_camera {
(3, AuthConnType::ViewCamera)
} else {
(0, AuthConnType::Remote)
};
@@ -1277,6 +1293,11 @@ impl Connection {
platform_additions.insert("has_file_clipboard".into(), json!(has_file_clipboard));
}
#[cfg(any(target_os = "windows", target_os = "linux"))]
{
platform_additions.insert("support_view_camera".into(), json!(true));
}
#[cfg(any(target_os = "linux", target_os = "windows", target_os = "macos"))]
if !platform_additions.is_empty() {
pi.platform_additions = serde_json::to_string(&platform_additions).unwrap_or("".into());
@@ -1290,7 +1311,8 @@ impl Connection {
return;
}
#[cfg(target_os = "linux")]
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() {
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() && !self.view_camera
{
let mut msg = "".to_string();
if crate::platform::linux::is_login_screen_wayland() {
msg = crate::client::LOGIN_SCREEN_WAYLAND.to_owned()
@@ -1347,6 +1369,29 @@ impl Connection {
self.handle_windows_specific_session(&mut pi, &mut wait_session_id_confirm);
if self.file_transfer.is_some() {
res.set_peer_info(pi);
} else if self.view_camera {
let supported_encoding = scrap::codec::Encoder::supported_encoding();
self.last_supported_encoding = Some(supported_encoding.clone());
log::info!("peer info supported_encoding: {:?}", supported_encoding);
pi.encoding = Some(supported_encoding).into();
pi.displays = camera::Cameras::all_info().unwrap_or(Vec::new());
pi.current_display = camera::PRIMARY_CAMERA_IDX as _;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
{
pi.resolutions = Some(SupportedResolutions {
resolutions: camera::Cameras::get_camera_resolution(
pi.current_display as usize,
)
.ok()
.into_iter()
.collect(),
..Default::default()
})
.into();
}
res.set_peer_info(pi);
self.update_codec_on_login();
} else {
let supported_encoding = scrap::codec::Encoder::supported_encoding();
self.last_supported_encoding = Some(supported_encoding.clone());
@@ -1414,15 +1459,31 @@ impl Connection {
} else {
self.delayed_read_dir = Some((dir.to_owned(), show_hidden));
}
} else if self.view_camera {
if !wait_session_id_confirm {
self.try_sub_camera_displays();
}
self.keyboard = false;
self.send_permission(Permission::Keyboard, false).await;
} else if sub_service {
if !wait_session_id_confirm {
self.try_sub_services();
self.try_sub_monitor_services();
}
}
}
fn try_sub_services(&mut self) {
let is_remote = self.file_transfer.is_none() && self.port_forward_socket.is_none();
fn try_sub_camera_displays(&mut self) {
if let Some(s) = self.server.upgrade() {
let mut s = s.write().unwrap();
s.try_add_primary_camera_service();
s.add_camera_connection(self.inner.clone());
}
}
fn try_sub_monitor_services(&mut self) {
let is_remote =
self.file_transfer.is_none() && self.port_forward_socket.is_none() && !self.view_camera;
if is_remote && !self.services_subed {
self.services_subed = true;
if let Some(s) = self.server.upgrade() {
@@ -1466,7 +1527,7 @@ impl Connection {
if let Some(current_sid) = crate::platform::get_current_process_session_id() {
if crate::platform::is_installed()
&& crate::platform::is_share_rdp()
&& raii::AuthedConnID::remote_and_file_conn_count() == 1
&& raii::AuthedConnID::non_port_forward_conn_count() == 1
&& sessions.len() > 1
&& sessions.iter().any(|e| e.sid == current_sid)
&& get_version_number(&self.lr.version) >= get_version_number("1.2.4")
@@ -1539,6 +1600,7 @@ impl Connection {
self.send_to_cm(ipc::Data::Login {
id: self.inner.id(),
is_file_transfer: self.file_transfer.is_some(),
is_view_camera: self.view_camera,
port_forward: self.port_forward_address.clone(),
peer_id,
name,
@@ -1781,6 +1843,15 @@ impl Connection {
}
self.file_transfer = Some((ft.dir, ft.show_hidden));
}
Some(login_request::Union::ViewCamera(_vc)) => {
if !Connection::permission(keys::OPTION_ENABLE_CAMERA) {
self.send_login_error("No permission of viewing camera")
.await;
sleep(1.).await;
return false;
}
self.view_camera = true;
}
Some(login_request::Union::PortForward(mut pf)) => {
if !Connection::permission("enable-tunnel") {
self.send_login_error("No permission of IP tunneling").await;
@@ -1987,6 +2058,9 @@ impl Connection {
match msg.union {
#[allow(unused_mut)]
Some(message::Union::MouseEvent(mut me)) => {
if self.is_authed_view_camera_conn() {
return true;
}
#[cfg(any(target_os = "android", target_os = "ios"))]
if let Err(e) = call_main_service_pointer_input("mouse", me.mask, me.x, me.y) {
log::debug!("call_main_service_pointer_input fail:{}", e);
@@ -2005,6 +2079,9 @@ impl Connection {
self.update_auto_disconnect_timer();
}
Some(message::Union::PointerDeviceEvent(pde)) => {
if self.is_authed_view_camera_conn() {
return true;
}
#[cfg(any(target_os = "android", target_os = "ios"))]
if let Err(e) = match pde.union {
Some(pointer_device_event::Union::TouchEvent(touch)) => match touch.union {
@@ -2044,6 +2121,9 @@ impl Connection {
Some(message::Union::KeyEvent(..)) => {}
#[cfg(any(target_os = "android"))]
Some(message::Union::KeyEvent(mut me)) => {
if self.is_authed_view_camera_conn() {
return true;
}
let key = match me.mode.enum_value() {
Ok(KeyboardMode::Map) => {
Some(crate::keyboard::keycode_to_rdev_key(me.chr()))
@@ -2096,6 +2176,9 @@ impl Connection {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
Some(message::Union::KeyEvent(me)) => {
if self.is_authed_view_camera_conn() {
return true;
}
if self.peer_keyboard_enabled() {
if is_enter(&me) {
CLICK_TIME.store(get_time(), Ordering::SeqCst);
@@ -2592,7 +2675,7 @@ impl Connection {
let sessions = crate::platform::get_available_sessions(false);
if crate::platform::is_installed()
&& crate::platform::is_share_rdp()
&& raii::AuthedConnID::remote_and_file_conn_count() == 1
&& raii::AuthedConnID::non_port_forward_conn_count() == 1
&& sessions.len() > 1
&& current_process_sid != sid
&& sessions.iter().any(|e| e.sid == sid)
@@ -2606,15 +2689,19 @@ impl Connection {
if let Some((dir, show_hidden)) = self.delayed_read_dir.take() {
self.read_dir(&dir, show_hidden);
}
} else if self.view_camera {
self.try_sub_camera_displays();
} else {
self.try_sub_services();
self.try_sub_monitor_services();
}
}
}
Some(misc::Union::MessageQuery(mq)) => {
if let Some(msg_out) =
video_service::make_display_changed_msg(mq.switch_display as _, None)
{
if let Some(msg_out) = video_service::make_display_changed_msg(
mq.switch_display as _,
None,
self.video_source(),
) {
self.send(msg_out).await;
}
}
@@ -2713,7 +2800,7 @@ impl Connection {
video_service::refresh();
self.server.upgrade().map(|s| {
s.read().unwrap().set_video_service_opt(
display,
display.map(|d| (self.video_source(), d)),
video_service::OPTION_REFRESH,
super::service::SERVICE_OPTION_VALUE_TRUE,
);
@@ -2743,19 +2830,33 @@ impl Connection {
// 1. For compatibility with old versions ( < 1.2.4 ).
// 2. Sciter version.
// 3. Update `SupportedResolutions`.
if let Some(msg_out) = video_service::make_display_changed_msg(self.display_idx, None) {
if let Some(msg_out) =
video_service::make_display_changed_msg(self.display_idx, None, self.video_source())
{
self.send(msg_out).await;
}
}
}
fn video_source(&self) -> VideoSource {
if self.view_camera {
VideoSource::Camera
} else {
VideoSource::Monitor
}
}
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 new_service_name = video_service::get_service_name(self.video_source(), display_idx);
let old_service_name =
video_service::get_service_name(self.video_source(), 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.add_service(Box::new(video_service::new(
self.video_source(),
display_idx,
)));
}
}
// For versions greater than 1.2.4, a `CaptureDisplays` message will be sent immediately.
@@ -2790,26 +2891,27 @@ impl Connection {
}
async fn capture_displays(&mut self, add: &[usize], sub: &[usize], set: &[usize]) {
let video_source = self.video_source();
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);
let service_name = video_service::get_service_name(video_source, *display);
if !lock.contains(&service_name) {
lock.add_service(Box::new(video_service::new(*display)));
lock.add_service(Box::new(video_service::new(video_source, *display)));
}
}
for display in set.iter() {
let service_name = video_service::get_service_name(*display);
let service_name = video_service::get_service_name(video_source, *display);
if !lock.contains(&service_name) {
lock.add_service(Box::new(video_service::new(*display)));
lock.add_service(Box::new(video_service::new(video_source, *display)));
}
}
if !add.is_empty() {
lock.capture_displays(self.inner.clone(), add, true, false);
lock.capture_displays(self.inner.clone(), video_source, add, true, false);
} else if !sub.is_empty() {
lock.capture_displays(self.inner.clone(), sub, false, true);
lock.capture_displays(self.inner.clone(), video_source, sub, false, true);
} else {
lock.capture_displays(self.inner.clone(), set, true, true);
lock.capture_displays(self.inner.clone(), video_source, set, true, true);
}
self.multi_ui_session = lock.get_subbed_displays_count(self.inner.id()) > 1;
if self.follow_remote_window {
@@ -2931,6 +3033,16 @@ impl Connection {
self.send_to_cm(Data::CloseVoiceCall("".to_owned()));
}
self.send(msg).await;
self.voice_calling = accepted;
if self.is_authed_view_camera_conn() {
if let Some(s) = self.server.upgrade() {
s.write().unwrap().subscribe(
super::audio_service::NAME,
self.inner.clone(),
self.audio_enabled() && accepted,
);
}
}
} else {
log::warn!("Possible a voice call attack.");
}
@@ -2940,6 +3052,14 @@ impl Connection {
crate::audio_service::set_voice_call_input_device(None, true);
// Notify the connection manager that the voice call has been closed.
self.send_to_cm(Data::CloseVoiceCall("".to_owned()));
self.voice_calling = false;
if self.is_authed_view_camera_conn() {
if let Some(s) = self.server.upgrade() {
s.write()
.unwrap()
.subscribe(super::audio_service::NAME, self.inner.clone(), false);
}
}
}
async fn update_options(&mut self, o: &OptionMessage) {
@@ -3016,11 +3136,21 @@ impl Connection {
if q != BoolOption::NotSet {
self.disable_audio = q == BoolOption::Yes;
if let Some(s) = self.server.upgrade() {
s.write().unwrap().subscribe(
super::audio_service::NAME,
self.inner.clone(),
self.audio_enabled(),
);
if self.is_authed_view_camera_conn() {
if self.voice_calling || !self.audio_enabled() {
s.write().unwrap().subscribe(
super::audio_service::NAME,
self.inner.clone(),
self.audio_enabled(),
);
}
} else {
s.write().unwrap().subscribe(
super::audio_service::NAME,
self.inner.clone(),
self.audio_enabled(),
);
}
}
}
}
@@ -3316,6 +3446,7 @@ impl Connection {
fn portable_check(&mut self) {
if self.portable.is_installed
|| self.file_transfer.is_some()
|| self.view_camera
|| self.port_forward_socket.is_some()
|| !self.keyboard
{
@@ -3463,6 +3594,13 @@ impl Connection {
false
}
fn is_authed_view_camera_conn(&self) -> bool {
if let Some(id) = self.authed_conn_id.as_ref() {
return id.conn_type() == AuthConnType::ViewCamera;
}
false
}
#[cfg(feature = "unix-file-copy-paste")]
async fn handle_file_clip(&mut self, clip: clipboard::ClipboardFile) {
let is_stopping_allowed = clip.is_stopping_allowed();
@@ -3966,7 +4104,6 @@ impl Retina {
}
mod raii {
// CONN_COUNT: remote connection count in fact
// ALIVE_CONNS: all connections, including unauthorized connections
// AUTHED_CONNS: all authorized connections
@@ -4001,7 +4138,7 @@ mod raii {
_ONCE.call_once(|| {
shutdown_hooks::add_shutdown_hook(connection_shutdown_hook);
});
if conn_type == AuthConnType::Remote {
if conn_type == AuthConnType::Remote || conn_type == AuthConnType::ViewCamera {
video_service::VIDEO_QOS
.lock()
.unwrap()
@@ -4024,12 +4161,12 @@ mod raii {
.send((conn_count, remote_count)));
}
pub fn remote_and_file_conn_count() -> usize {
pub fn non_port_forward_conn_count() -> usize {
AUTHED_CONNS
.lock()
.unwrap()
.iter()
.filter(|c| c.1 == AuthConnType::Remote || c.1 == AuthConnType::FileTransfer)
.filter(|c| c.1 != AuthConnType::PortForward)
.count()
}
@@ -4112,7 +4249,7 @@ mod raii {
impl Drop for AuthedConnID {
fn drop(&mut self) {
if self.1 == AuthConnType::Remote {
if self.1 == AuthConnType::Remote || self.1 == AuthConnType::ViewCamera {
scrap::codec::Encoder::update(scrap::codec::EncodingUpdate::Remove(self.0));
video_service::VIDEO_QOS
.lock()