Merge pull request #5191 from fufesou/refact/linux_headless_allow_option

Refact/linux headless allow option
This commit is contained in:
RustDesk 2023-07-30 11:04:50 +08:00 committed by GitHub
commit 69f1969e60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 167 additions and 109 deletions

View File

@ -372,7 +372,7 @@ class _DesktopHomePageState extends State<DesktopHomePage>
} else if (Platform.isLinux) { } else if (Platform.isLinux) {
if (bind.mainCurrentIsWayland()) { if (bind.mainCurrentIsWayland()) {
return buildInstallCard( return buildInstallCard(
"Warning", translate("wayland_experiment_tip"), "", () async {}, "Warning", "wayland_experiment_tip", "", () async {},
help: 'Help', help: 'Help',
link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required'); link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required');
} else if (bind.mainIsLoginWayland()) { } else if (bind.mainIsLoginWayland()) {

View File

@ -312,20 +312,26 @@ class _GeneralState extends State<_General> {
} }
Widget other() { Widget other() {
return _Card(title: 'Other', children: [ final children = [
_OptionCheckBox(context, 'Confirm before closing multiple tabs', _OptionCheckBox(context, 'Confirm before closing multiple tabs',
'enable-confirm-closing-tabs'), 'enable-confirm-closing-tabs'),
_OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr'), _OptionCheckBox(context, 'Adaptive Bitrate', 'enable-abr')
if (Platform.isLinux) ];
Tooltip( if (Platform.isLinux) {
message: translate('software_render_tip'), children.add(Tooltip(
child: _OptionCheckBox( message: translate('software_render_tip'),
context, child: _OptionCheckBox(
"Always use software rendering", context,
'allow-always-software-render', "Always use software rendering",
), 'allow-always-software-render',
) ),
]); ));
}
if (bind.mainShowOption(key: 'allow-linux-headless')) {
children.add(_OptionCheckBox(
context, 'Allow linux headless', 'allow-linux-headless'));
}
return _Card(title: 'Other', children: children);
} }
Widget hwcodec() { Widget hwcodec() {

View File

@ -32,6 +32,10 @@ pub const COMPRESS_LEVEL: i32 = 3;
const SERIAL: i32 = 3; const SERIAL: i32 = 3;
const PASSWORD_ENC_VERSION: &str = "00"; const PASSWORD_ENC_VERSION: &str = "00";
// config2 options
#[cfg(target_os = "linux")]
pub const CONFIG_OPTION_ALLOW_LINUX_HEADLESS: &str = "allow-linux-headless";
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
lazy_static::lazy_static! { lazy_static::lazy_static! {
pub static ref ORG: Arc<RwLock<String>> = Arc::new(RwLock::new("com.carriez".to_owned())); pub static ref ORG: Arc<RwLock<String>> = Arc::new(RwLock::new("com.carriez".to_owned()));

View File

@ -90,6 +90,7 @@ pub const LOGIN_MSG_PASSWORD_EMPTY: &str = "Empty Password";
pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password"; pub const LOGIN_MSG_PASSWORD_WRONG: &str = "Wrong Password";
pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access"; pub const LOGIN_MSG_NO_PASSWORD_ACCESS: &str = "No Password Access";
pub const LOGIN_MSG_OFFLINE: &str = "Offline"; pub const LOGIN_MSG_OFFLINE: &str = "Offline";
pub const LOGIN_SCREEN_WAYLAND: &str = "Wayland login screen is not supported";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version."; pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
@ -2100,7 +2101,13 @@ struct LoginErrorMsgBox {
lazy_static::lazy_static! { lazy_static::lazy_static! {
static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = { static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = {
use hbb_common::config::LINK_HEADLESS_LINUX_SUPPORT; use hbb_common::config::LINK_HEADLESS_LINUX_SUPPORT;
let map = HashMap::from([(LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{ let map = HashMap::from([(LOGIN_SCREEN_WAYLAND, LoginErrorMsgBox{
msgtype: "error",
title: "Login Error",
text: "Login screen using Wayland is not supported",
link: "https://rustdesk.com/docs/en/manual/linux/#login-screen",
try_again: true,
}), (LOGIN_MSG_DESKTOP_SESSION_NOT_READY, LoginErrorMsgBox{
msgtype: "session-login", msgtype: "session-login",
title: "", title: "",
text: "", text: "",

View File

@ -614,6 +614,15 @@ pub fn main_get_error() -> String {
get_error() get_error()
} }
pub fn main_show_option(_key: String) -> SyncReturn<bool> {
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if _key.eq(config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS) {
return SyncReturn(true)
}
SyncReturn(false)
}
pub fn main_set_option(key: String, value: String) { pub fn main_set_option(key: String, value: String) {
if key.eq("custom-rendezvous-server") { if key.eq("custom-rendezvous-server") {
set_option(key, value); set_option(key, value);

View File

@ -1,5 +1,8 @@
use super::{CursorData, ResultType}; use super::{CursorData, ResultType};
use desktop::Desktop; use desktop::Desktop;
#[cfg(all(feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
use hbb_common::config::CONFIG_OPTION_ALLOW_LINUX_HEADLESS;
pub use hbb_common::platform::linux::*; pub use hbb_common::platform::linux::*;
use hbb_common::{ use hbb_common::{
allow_err, bail, allow_err, bail,
@ -69,6 +72,19 @@ pub struct xcb_xfixes_get_cursor_image {
pub pixels: *const c_long, pub pixels: *const c_long,
} }
#[inline]
#[cfg(feature = "linux_headless")]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
pub fn is_headless_allowed() -> bool {
Config::get_option(CONFIG_OPTION_ALLOW_LINUX_HEADLESS) == "Y"
}
#[inline]
pub fn is_login_screen_wayland() -> bool {
let values = get_values_of_seat0_with_gdm_wayland(&[0, 2]);
is_gdm_user(&values[1]) && get_display_server_of_session(&values[0]) == DISPLAY_SERVER_WAYLAND
}
#[inline] #[inline]
fn sleep_millis(millis: u64) { fn sleep_millis(millis: u64) {
std::thread::sleep(Duration::from_millis(millis)); std::thread::sleep(Duration::from_millis(millis));
@ -429,13 +445,21 @@ fn get_cm() -> bool {
} }
pub fn is_login_wayland() -> bool { pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") { let files = ["/etc/gdm3/custom.conf", "/etc/gdm/custom.conf"];
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true") match (
} else if let Ok(contents) = std::fs::read_to_string("/etc/gdm/custom.conf") { Regex::new(r"# *WaylandEnable *= *false"),
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true") Regex::new(r"WaylandEnable *= *true"),
} else { ) {
false (Ok(pat1), Ok(pat2)) => {
for file in files {
if let Ok(contents) = std::fs::read_to_string(file) {
return pat1.is_match(&contents) || pat2.is_match(&contents);
}
}
}
_ => {}
} }
false
} }
#[inline] #[inline]

View File

@ -109,6 +109,10 @@ pub fn try_start_desktop(_username: &str, _passsword: &str) -> String {
// No need to verify password here. // No need to verify password here.
return "".to_owned(); return "".to_owned();
} }
if !username.is_empty() {
// Another user is logged in. No need to start a new xsession.
return "".to_owned();
}
if let Some(msg) = detect_headless() { if let Some(msg) = detect_headless() {
return msg.to_owned(); return msg.to_owned();

View File

@ -73,6 +73,7 @@ impl RendezvousMediator {
allow_err!(super::lan::start_listening()); allow_err!(super::lan::start_listening());
}); });
} }
// It is ok to run xdesktop manager when the headless function is not allowed.
#[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
crate::platform::linux_desktop_manager::start_xdesktop(); crate::platform::linux_desktop_manager::start_xdesktop();

View File

@ -191,10 +191,7 @@ pub struct Connection {
pressed_modifiers: HashSet<rdev::Key>, pressed_modifiers: HashSet<rdev::Key>,
#[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
rx_cm_stream_ready: mpsc::Receiver<()>, linux_headless_handle: LinuxHeadlessHandle,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
tx_desktop_ready: mpsc::Sender<()>,
closed: bool, closed: bool,
delay_response_instant: Instant, delay_response_instant: Instant,
} }
@ -266,6 +263,10 @@ impl Connection {
let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1); let (tx_cm_stream_ready, _rx_cm_stream_ready) = mpsc::channel(1);
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let (_tx_desktop_ready, rx_desktop_ready) = mpsc::channel(1); let (_tx_desktop_ready, rx_desktop_ready) = mpsc::channel(1);
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let linux_headless_handle =
LinuxHeadlessHandle::new(_rx_cm_stream_ready, _tx_desktop_ready);
#[cfg(not(any(target_os = "android", target_os = "ios")))] #[cfg(not(any(target_os = "android", target_os = "ios")))]
let tx_cloned = tx.clone(); let tx_cloned = tx.clone();
@ -322,10 +323,7 @@ impl Connection {
pressed_modifiers: Default::default(), pressed_modifiers: Default::default(),
#[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
rx_cm_stream_ready: _rx_cm_stream_ready, linux_headless_handle,
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
tx_desktop_ready: _tx_desktop_ready,
closed: false, closed: false,
delay_response_instant: Instant::now(), delay_response_instant: Instant::now(),
}; };
@ -985,8 +983,10 @@ impl Connection {
} }
#[cfg(feature = "linux_headless")] #[cfg(feature = "linux_headless")]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if linux_desktop_manager::is_headless() { if crate::platform::is_headless_allowed() {
platform_additions.insert("headless".into(), json!(true)); if linux_desktop_manager::is_headless() {
platform_additions.insert("headless".into(), json!(true));
}
} }
if !platform_additions.is_empty() { if !platform_additions.is_empty() {
pi.platform_additions = pi.platform_additions =
@ -1009,10 +1009,15 @@ impl Connection {
if dtype != crate::platform::linux::DISPLAY_SERVER_X11 if dtype != crate::platform::linux::DISPLAY_SERVER_X11
&& dtype != crate::platform::linux::DISPLAY_SERVER_WAYLAND && dtype != crate::platform::linux::DISPLAY_SERVER_WAYLAND
{ {
res.set_error(format!( let msg = if crate::platform::linux::is_login_screen_wayland() {
"Unsupported display server type \"{}\", x11 or wayland expected", crate::client::LOGIN_SCREEN_WAYLAND.to_owned()
dtype } else {
)); format!(
"Unsupported display server type \"{}\", x11 or wayland expected",
dtype
)
};
res.set_error(msg);
let mut msg_out = Message::new(); let mut msg_out = Message::new();
msg_out.set_login_response(res); msg_out.set_login_response(res);
self.send(msg_out).await; self.send(msg_out).await;
@ -1373,28 +1378,22 @@ impl Connection {
} }
} }
#[cfg(any(
feature = "flatpak",
feature = "appimage",
not(all(target_os = "linux", feature = "linux_headless"))
))]
let err_msg = "".to_owned();
#[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let desktop_err = match lr.os_login.as_ref() { let err_msg = self
Some(os_login) => { .linux_headless_handle
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password) .try_start_desktop(lr.os_login.as_ref());
}
None => linux_desktop_manager::try_start_desktop("", ""),
};
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let is_headless = linux_desktop_manager::is_headless();
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let wait_ipc_timeout = 10_000;
// If err is LOGIN_MSG_DESKTOP_SESSION_NOT_READY, just keep this msg and go on checking password. // If err is LOGIN_MSG_DESKTOP_SESSION_NOT_READY, just keep this msg and go on checking password.
#[cfg(all(target_os = "linux", feature = "linux_headless"))] if !err_msg.is_empty() && err_msg != crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if !desktop_err.is_empty()
&& desktop_err != crate::client::LOGIN_MSG_DESKTOP_SESSION_NOT_READY
{ {
self.send_login_error(desktop_err).await; self.send_login_error(err_msg).await;
return true; return true;
} }
@ -1422,34 +1421,20 @@ impl Connection {
self.send_login_error("Connection not allowed").await; self.send_login_error("Connection not allowed").await;
return false; return false;
} else if self.is_recent_session() { } else if self.is_recent_session() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))] if err_msg.is_empty() {
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() { #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
#[cfg(target_os = "linux")] self.linux_headless_handle.wait_desktop_cm_ready().await;
if is_headless { self.try_start_cm(lr.my_id.clone(), lr.my_name.clone(), true);
self.tx_desktop_ready.send(()).await.ok();
let _res = timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await; self.send_logon_response().await;
if self.port_forward_socket.is_some() { if self.port_forward_socket.is_some() {
return false; return false;
} }
} else { } else {
self.send_login_error(desktop_err).await; self.send_login_error(err_msg).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.try_start_cm(lr.my_id, lr.my_name, true);
self.send_logon_response().await;
if self.port_forward_socket.is_some() {
return false;
}
} }
} else if lr.password.is_empty() { } else if lr.password.is_empty() {
#[cfg(all(target_os = "linux", feature = "linux_headless"))] if err_msg.is_empty() {
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
self.try_start_cm(lr.my_id, lr.my_name, false); self.try_start_cm(lr.my_id, lr.my_name, false);
} else { } else {
self.send_login_error( self.send_login_error(
@ -1457,8 +1442,6 @@ impl Connection {
) )
.await; .await;
} }
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
self.try_start_cm(lr.my_id, lr.my_name, false);
} else { } else {
let mut failure = LOGIN_FAILURES let mut failure = LOGIN_FAILURES
.lock() .lock()
@ -1497,9 +1480,7 @@ impl Connection {
.lock() .lock()
.unwrap() .unwrap()
.insert(self.ip.clone(), failure); .insert(self.ip.clone(), failure);
#[cfg(all(target_os = "linux", feature = "linux_headless"))] if err_msg.is_empty() {
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if desktop_err.is_empty() {
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG) self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
.await; .await;
self.try_start_cm(lr.my_id, lr.my_name, false); self.try_start_cm(lr.my_id, lr.my_name, false);
@ -1509,40 +1490,21 @@ impl Connection {
) )
.await; .await;
} }
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_login_error(crate::client::LOGIN_MSG_PASSWORD_WRONG)
.await;
self.try_start_cm(lr.my_id, lr.my_name, false);
}
} else { } else {
if failure.0 != 0 { if failure.0 != 0 {
LOGIN_FAILURES.lock().unwrap().remove(&self.ip); LOGIN_FAILURES.lock().unwrap().remove(&self.ip);
} }
#[cfg(all(target_os = "linux", feature = "linux_headless"))] if err_msg.is_empty() {
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
if desktop_err.is_empty() { #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
#[cfg(target_os = "linux")] self.linux_headless_handle.wait_desktop_cm_ready().await;
if is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res =
timeout(wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
self.send_logon_response().await; self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true); self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() { if self.port_forward_socket.is_some() {
return false; return false;
} }
} else { } else {
self.send_login_error(desktop_err).await; self.send_login_error(err_msg).await;
}
#[cfg(not(all(target_os = "linux", feature = "linux_headless")))]
{
self.send_logon_response().await;
self.try_start_cm(lr.my_id, lr.my_name, true);
if self.port_forward_socket.is_some() {
return false;
}
} }
} }
} }
@ -2361,19 +2323,14 @@ async fn start_ipc(
args.push("--hide"); args.push("--hide");
}; };
#[allow(unused_mut)]
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[cfg(not(feature = "linux_headless"))]
let user = None;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(any(feature = "flatpak", feature = "appimage"))]
let user = None;
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
let mut user = None; let mut user = None;
// Cm run as user, wait until desktop session is ready. // Cm run as user, wait until desktop session is ready.
#[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))]
if linux_desktop_manager::is_headless() { if crate::platform::is_headless_allowed() && linux_desktop_manager::is_headless() {
let mut username = linux_desktop_manager::get_username(); let mut username = linux_desktop_manager::get_username();
loop { loop {
if !username.is_empty() { if !username.is_empty() {
@ -2572,6 +2529,52 @@ impl Drop for Connection {
} }
} }
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
struct LinuxHeadlessHandle {
pub is_headless_allowed: bool,
pub is_headless: bool,
pub wait_ipc_timeout: u64,
pub rx_cm_stream_ready: mpsc::Receiver<()>,
pub tx_desktop_ready: mpsc::Sender<()>,
}
#[cfg(all(target_os = "linux", feature = "linux_headless"))]
#[cfg(not(any(feature = "flatpak", feature = "appimage")))]
impl LinuxHeadlessHandle {
pub fn new(rx_cm_stream_ready: mpsc::Receiver<()>, tx_desktop_ready: mpsc::Sender<()>) -> Self {
let is_headless_allowed = crate::platform::is_headless_allowed();
let is_headless = is_headless_allowed && linux_desktop_manager::is_headless();
Self {
is_headless_allowed,
is_headless,
wait_ipc_timeout: 10_000,
rx_cm_stream_ready,
tx_desktop_ready,
}
}
pub fn try_start_desktop(&mut self, os_login: Option<&OSLogin>) -> String {
if self.is_headless_allowed {
match os_login {
Some(os_login) => {
linux_desktop_manager::try_start_desktop(&os_login.username, &os_login.password)
}
None => linux_desktop_manager::try_start_desktop("", ""),
}
} else {
"".to_string()
}
}
pub async fn wait_desktop_cm_ready(&mut self) {
if self.is_headless {
self.tx_desktop_ready.send(()).await.ok();
let _res = timeout(self.wait_ipc_timeout, self.rx_cm_stream_ready.recv()).await;
}
}
}
mod raii { mod raii {
use super::*; use super::*;
pub struct ConnectionID(i32); pub struct ConnectionID(i32);