terminal works basically. (#12189)

* terminal works basically.
todo:
- persistent
- sessions restore
- web
- mobile

* missed terminal persistent option change

* android sdk 34 -> 35

* +#![cfg_attr(lt_1_77, feature(c_str_literals))]

* fixing ci

* fix ci

* fix ci for android

* try "Fix Android SDK Platform 35"

* fix android 34

* revert flutter_plugin_android_lifecycle to 2.0.17 which used in rustdesk 1.4.0

* refactor, but break something of desktop terminal (new tab showing loading)

* fix connecting...
This commit is contained in:
RustDesk
2025-07-01 13:12:55 +08:00
committed by GitHub
parent ee5cdc3155
commit 5faf0ad3cf
130 changed files with 4064 additions and 4247 deletions

View File

@@ -169,6 +169,7 @@ pub enum AuthConnType {
FileTransfer,
PortForward,
ViewCamera,
Terminal,
}
pub struct Connection {
@@ -182,6 +183,7 @@ pub struct Connection {
file_timer: crate::RustDeskInterval,
file_transfer: Option<(String, bool)>,
view_camera: bool,
terminal: bool,
port_forward_socket: Option<Framed<TcpStream, BytesCodec>>,
port_forward_address: String,
tx_to_cm: mpsc::UnboundedSender<ipc::Data>,
@@ -250,6 +252,9 @@ pub struct Connection {
// For post requests that need to be sent sequentially.
// eg. post_conn_audit
tx_post_seq: mpsc::UnboundedSender<(String, Value)>,
terminal_service_id: String,
terminal_persistent: bool,
terminal_generic_service: Option<Box<GenericService>>,
}
impl ConnInner {
@@ -347,6 +352,7 @@ impl Connection {
file_timer: crate::rustdesk_interval(time::interval(SEC30)),
file_transfer: None,
view_camera: false,
terminal: false,
port_forward_socket: None,
port_forward_address: "".to_owned(),
tx_to_cm,
@@ -410,6 +416,9 @@ impl Connection {
tx_from_authed,
printer_data: Vec::new(),
tx_post_seq,
terminal_service_id: "".to_owned(),
terminal_persistent: false,
terminal_generic_service: None,
};
let addr = hbb_common::try_into_v4(addr);
if !conn.on_open(addr).await {
@@ -450,7 +459,7 @@ impl Connection {
let mut last_recv_time = Instant::now();
conn.stream.set_send_timeout(
if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() {
if conn.file_transfer.is_some() || conn.port_forward_socket.is_some() || conn.terminal {
SEND_TIMEOUT_OTHER
} else {
SEND_TIMEOUT_VIDEO
@@ -1253,6 +1262,8 @@ impl Connection {
(2, AuthConnType::PortForward)
} else if self.view_camera {
(3, AuthConnType::ViewCamera)
} else if self.terminal {
(4, AuthConnType::Terminal)
} else {
(0, AuthConnType::Remote)
};
@@ -1361,8 +1372,7 @@ impl Connection {
return;
}
#[cfg(target_os = "linux")]
if !self.file_transfer.is_some() && !self.port_forward_socket.is_some() && !self.view_camera
{
if self.is_remote() {
let mut msg = "".to_string();
if crate::platform::linux::is_login_screen_wayland() {
msg = crate::client::LOGIN_SCREEN_WAYLAND.to_owned()
@@ -1393,7 +1403,8 @@ impl Connection {
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if self.file_transfer.is_some() {
if crate::platform::is_prelogin() { // }|| self.tx_to_cm.send(ipc::Data::Test).is_err() {
if crate::platform::is_prelogin() {
// }|| self.tx_to_cm.send(ipc::Data::Test).is_err() {
username = "".to_owned();
}
}
@@ -1408,6 +1419,8 @@ impl Connection {
pi.sas_enabled = sas_enabled;
pi.features = Some(Features {
privacy_mode: privacy_mode::is_privacy_mode_supported(),
#[cfg(not(any(target_os = "android", target_os = "ios")))]
terminal: true, // Terminal feature is supported on desktop only
..Default::default()
})
.into();
@@ -1417,7 +1430,7 @@ impl Connection {
let mut wait_session_id_confirm = false;
#[cfg(windows)]
self.handle_windows_specific_session(&mut pi, &mut wait_session_id_confirm);
if self.file_transfer.is_some() {
if self.file_transfer.is_some() || self.terminal {
res.set_peer_info(pi);
} else if self.view_camera {
let supported_encoding = scrap::codec::Encoder::supported_encoding();
@@ -1509,6 +1522,10 @@ impl Connection {
} else {
self.delayed_read_dir = Some((dir.to_owned(), show_hidden));
}
} else if self.terminal {
self.keyboard = false;
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.init_terminal_service().await;
} else if self.view_camera {
if !wait_session_id_confirm {
self.try_sub_camera_displays();
@@ -1531,9 +1548,16 @@ impl Connection {
}
}
#[inline]
fn is_remote(&self) -> bool {
self.file_transfer.is_none()
&& self.port_forward_socket.is_none()
&& !self.view_camera
&& !self.terminal
}
fn try_sub_monitor_services(&mut self) {
let is_remote =
self.file_transfer.is_none() && self.port_forward_socket.is_none() && !self.view_camera;
let is_remote = self.is_remote();
if is_remote && !self.services_subed {
self.services_subed = true;
if let Some(s) = self.server.upgrade() {
@@ -1651,6 +1675,7 @@ impl Connection {
id: self.inner.id(),
is_file_transfer: self.file_transfer.is_some(),
is_view_camera: self.view_camera,
is_terminal: self.terminal,
port_forward: self.port_forward_address.clone(),
peer_id,
name,
@@ -1902,6 +1927,19 @@ impl Connection {
}
self.view_camera = true;
}
Some(login_request::Union::Terminal(terminal)) => {
if !Connection::permission(keys::OPTION_ENABLE_TERMINAL) {
self.send_login_error("No permission of terminal").await;
sleep(1.).await;
return false;
}
self.terminal = true;
if let Some(o) = self.options_in_login.as_ref() {
self.terminal_persistent =
o.terminal_persistent.enum_value() == Ok(BoolOption::Yes);
}
self.terminal_service_id = terminal.service_id;
}
Some(login_request::Union::PortForward(mut pf)) => {
if !Connection::permission("enable-tunnel") {
self.send_login_error("No permission of IP tunneling").await;
@@ -2791,7 +2829,7 @@ impl Connection {
}
} else if self.view_camera {
self.try_sub_camera_displays();
} else {
} else if !self.terminal {
self.try_sub_monitor_services();
}
}
@@ -2843,6 +2881,12 @@ impl Connection {
self.refresh_video_display(Some(request.display as usize));
}
}
Some(message::Union::TerminalAction(action)) => {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
allow_err!(self.handle_terminal_action(action).await);
#[cfg(any(target_os = "android", target_os = "ios"))]
log::warn!("Terminal action received but not supported on this platform");
}
_ => {}
}
}
@@ -3371,6 +3415,12 @@ impl Connection {
}
}
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
if let Ok(q) = o.terminal_persistent.enum_value() {
if q != BoolOption::NotSet {
self.update_terminal_persistence(q == BoolOption::Yes).await;
}
}
}
async fn turn_on_privacy(&mut self, impl_key: String) {
@@ -3562,12 +3612,7 @@ impl Connection {
#[cfg(windows)]
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
{
if self.portable.is_installed || !self.is_remote() || !self.keyboard {
return;
}
let running = portable_client::running();
@@ -3779,6 +3824,55 @@ impl Connection {
msg_out.set_message_box(res);
self.send(msg_out).await;
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn update_terminal_persistence(&mut self, persistent: bool) {
self.terminal_persistent = persistent;
terminal_service::set_persistent(&self.terminal_service_id, persistent).ok();
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn init_terminal_service(&mut self) {
if self.terminal_service_id.is_empty() {
self.terminal_service_id = terminal_service::generate_service_id();
}
let s = Box::new(terminal_service::new(
self.terminal_service_id.clone(),
self.terminal_persistent,
));
s.on_subscribe(self.inner.clone());
self.terminal_generic_service = Some(s);
}
#[cfg(not(any(target_os = "android", target_os = "ios")))]
async fn handle_terminal_action(&mut self, action: TerminalAction) -> ResultType<()> {
let mut proxy = terminal_service::TerminalServiceProxy::new(
self.terminal_service_id.clone(),
Some(self.terminal_persistent),
);
match proxy.handle_action(&action) {
Ok(Some(response)) => {
let mut msg_out = Message::new();
msg_out.set_terminal_response(response);
self.send(msg_out).await;
}
Ok(None) => {
// No response needed
}
Err(err) => {
let mut response = TerminalResponse::new();
let mut error = TerminalError::new();
error.message = format!("Failed to handle action: {}", err);
response.set_error(error);
let mut msg_out = Message::new();
msg_out.set_terminal_response(response);
self.send(msg_out).await;
}
}
Ok(())
}
}
pub fn insert_switch_sides_uuid(id: String, uuid: uuid::Uuid) {
@@ -4151,6 +4245,10 @@ impl Drop for Connection {
fn drop(&mut self) {
#[cfg(not(any(target_os = "android", target_os = "ios")))]
self.release_pressed_modifiers();
if let Some(s) = self.terminal_generic_service.as_ref() {
s.join();
}
}
}