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) {
if (bind.mainCurrentIsWayland()) {
return buildInstallCard(
"Warning", translate("wayland_experiment_tip"), "", () async {},
"Warning", "wayland_experiment_tip", "", () async {},
help: 'Help',
link: 'https://rustdesk.com/docs/en/manual/linux/#x11-required');
} else if (bind.mainIsLoginWayland()) {

View File

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

View File

@ -32,6 +32,10 @@ pub const COMPRESS_LEVEL: i32 = 3;
const SERIAL: i32 = 3;
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")]
lazy_static::lazy_static! {
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_NO_PASSWORD_ACCESS: &str = "No Password Access";
pub const LOGIN_MSG_OFFLINE: &str = "Offline";
pub const LOGIN_SCREEN_WAYLAND: &str = "Wayland login screen is not supported";
#[cfg(target_os = "linux")]
pub const SCRAP_UBUNTU_HIGHER_REQUIRED: &str = "Wayland requires Ubuntu 21.04 or higher version.";
#[cfg(target_os = "linux")]
@ -2100,7 +2101,13 @@ struct LoginErrorMsgBox {
lazy_static::lazy_static! {
static ref LOGIN_ERROR_MAP: Arc<HashMap<&'static str, LoginErrorMsgBox>> = {
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",
title: "",
text: "",

View File

@ -614,6 +614,15 @@ pub fn main_get_error() -> String {
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) {
if key.eq("custom-rendezvous-server") {
set_option(key, value);

View File

@ -1,5 +1,8 @@
use super::{CursorData, ResultType};
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::*;
use hbb_common::{
allow_err, bail,
@ -69,6 +72,19 @@ pub struct xcb_xfixes_get_cursor_image {
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]
fn sleep_millis(millis: u64) {
std::thread::sleep(Duration::from_millis(millis));
@ -429,13 +445,21 @@ fn get_cm() -> bool {
}
pub fn is_login_wayland() -> bool {
if let Ok(contents) = std::fs::read_to_string("/etc/gdm3/custom.conf") {
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true")
} else if let Ok(contents) = std::fs::read_to_string("/etc/gdm/custom.conf") {
contents.contains("#WaylandEnable=false") || contents.contains("WaylandEnable=true")
} else {
false
let files = ["/etc/gdm3/custom.conf", "/etc/gdm/custom.conf"];
match (
Regex::new(r"# *WaylandEnable *= *false"),
Regex::new(r"WaylandEnable *= *true"),
) {
(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]

View File

@ -109,6 +109,10 @@ pub fn try_start_desktop(_username: &str, _passsword: &str) -> String {
// No need to verify password here.
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() {
return msg.to_owned();

View File

@ -73,6 +73,7 @@ impl RendezvousMediator {
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(not(any(feature = "flatpak", feature = "appimage")))]
crate::platform::linux_desktop_manager::start_xdesktop();

View File

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