diff --git a/libs/hbb_common/src/platform/linux.rs b/libs/hbb_common/src/platform/linux.rs index 5e03b6816..60c8714d8 100644 --- a/libs/hbb_common/src/platform/linux.rs +++ b/libs/hbb_common/src/platform/linux.rs @@ -7,6 +7,9 @@ lazy_static::lazy_static! { pub const DISPLAY_SERVER_WAYLAND: &str = "wayland"; pub const DISPLAY_SERVER_X11: &str = "x11"; +pub const DISPLAY_DESKTOP_KDE: &str = "KDE"; + +pub const XDG_CURRENT_DESKTOP: &str = "XDG_CURRENT_DESKTOP"; pub struct Distro { pub name: String, @@ -29,6 +32,15 @@ impl Distro { } } +#[inline] +pub fn is_kde() -> bool { + if let Ok(env) = std::env::var(XDG_CURRENT_DESKTOP) { + env == DISPLAY_DESKTOP_KDE + } else { + false + } +} + #[inline] pub fn is_gdm_user(username: &str) -> bool { username == "gdm" diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index 640f37d0b..2f1e2a852 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -27,39 +27,40 @@ use super::screencast_portal::OrgFreedesktopPortalScreenCast as screencast_porta use lazy_static::lazy_static; lazy_static! { - pub static ref RDP_RESPONSE: Mutex> = Mutex::new(None); + pub static ref RDP_SESSION_INFO: Mutex> = Mutex::new(None); } #[inline] pub fn close_session() { - let _ = RDP_RESPONSE.lock().unwrap().take(); + let _ = RDP_SESSION_INFO.lock().unwrap().take(); } #[inline] pub fn is_rdp_session_hold() -> bool { - RDP_RESPONSE.lock().unwrap().is_some() + RDP_SESSION_INFO.lock().unwrap().is_some() } pub fn try_close_session() { - let mut rdp_res = RDP_RESPONSE.lock().unwrap(); + let mut rdp_info = RDP_SESSION_INFO.lock().unwrap(); let mut close = false; - if let Some(rdp_res) = &*rdp_res { + if let Some(rdp_info) = &*rdp_info { // If is server running and restore token is supported, there's no need to keep the session. - if is_server_running() && rdp_res.is_support_restore_token { + if is_server_running() && rdp_info.is_support_restore_token { close = true; } } if close { - *rdp_res = None; + *rdp_info = None; } } -pub struct RdpResponse { +pub struct RdpSessionInfo { pub conn: Arc, pub streams: Vec, pub fd: OwnedFd, pub session: dbus::Path<'static>, pub is_support_restore_token: bool, + pub resolution: Arc>>, } #[derive(Debug, Clone, Copy)] pub struct PwStreamInfo { @@ -69,6 +70,12 @@ pub struct PwStreamInfo { size: (usize, usize), } +impl PwStreamInfo { + pub fn get_size(&self) -> (usize, usize) { + self.size + } +} + #[derive(Debug)] pub struct DBusError(String); @@ -105,24 +112,31 @@ pub struct PipeWireCapturable { } impl PipeWireCapturable { - fn new(conn: Arc, fd: OwnedFd, stream: PwStreamInfo) -> Self { + fn new( + conn: Arc, + fd: OwnedFd, + resolution: Arc>>, + stream: PwStreamInfo, + ) -> Self { // alternative to get screen resolution as stream.size is not always correct ex: on fractional scaling // https://github.com/rustdesk/rustdesk/issues/6116#issuecomment-1817724244 - let res = get_res(Self { + let size = get_res(Self { dbus_conn: conn.clone(), fd: fd.clone(), path: stream.path, source_type: stream.source_type, position: stream.position, size: stream.size, - }); + }) + .unwrap_or(stream.size); + *resolution.lock().unwrap() = Some(size); Self { dbus_conn: conn, fd, path: stream.path, source_type: stream.source_type, position: stream.position, - size: res.unwrap_or(stream.size), + size, } } } @@ -813,7 +827,7 @@ fn on_start_response( } pub fn get_capturables() -> Result, Box> { - let mut rdp_connection = match RDP_RESPONSE.lock() { + let mut rdp_connection = match RDP_SESSION_INFO.lock() { Ok(conn) => conn, Err(err) => return Err(Box::new(err)), }; @@ -822,28 +836,36 @@ pub fn get_capturables() -> Result, Box> { let (conn, fd, streams, session, is_support_restore_token) = request_remote_desktop()?; let conn = Arc::new(conn); - let rdp_res = RdpResponse { + let rdp_info = RdpSessionInfo { conn, streams, fd, session, is_support_restore_token, + resolution: Arc::new(Mutex::new(None)), }; - *rdp_connection = Some(rdp_res); + *rdp_connection = Some(rdp_info); } - let rdp_res = match rdp_connection.as_ref() { + let rdp_info = match rdp_connection.as_ref() { Some(res) => res, None => { return Err(Box::new(DBusError("RDP response is None.".into()))); } }; - Ok(rdp_res + Ok(rdp_info .streams .clone() .into_iter() - .map(|s| PipeWireCapturable::new(rdp_res.conn.clone(), rdp_res.fd.clone(), s)) + .map(|s| { + PipeWireCapturable::new( + rdp_info.conn.clone(), + rdp_info.fd.clone(), + rdp_info.resolution.clone(), + s, + ) + }) .collect()) } diff --git a/src/server/input_service.rs b/src/server/input_service.rs index 1527009ca..fd4291cc8 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -16,7 +16,7 @@ use rdev::{self, EventType, Key as RdevKey, KeyCode, RawKey}; #[cfg(target_os = "macos")] use rdev::{CGEventSourceStateID, CGEventTapLocation, VirtualInput}; #[cfg(target_os = "linux")] -use scrap::wayland::pipewire::RDP_RESPONSE; +use scrap::wayland::pipewire::RDP_SESSION_INFO; use std::{ convert::TryFrom, ops::{Deref, DerefMut, Sub}, @@ -521,15 +521,25 @@ pub async fn setup_uinput(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultT #[cfg(target_os = "linux")] pub async fn setup_rdp_input() -> ResultType<(), Box> { let mut en = ENIGO.lock()?; - let rdp_res_lock = RDP_RESPONSE.lock()?; - let rdp_res = rdp_res_lock.as_ref().ok_or("RDP response is None")?; + let rdp_info_lock = RDP_SESSION_INFO.lock()?; + let rdp_info = rdp_info_lock.as_ref().ok_or("RDP session is None")?; - let keyboard = RdpInputKeyboard::new(rdp_res.conn.clone(), rdp_res.session.clone())?; + let keyboard = RdpInputKeyboard::new(rdp_info.conn.clone(), rdp_info.session.clone())?; en.set_custom_keyboard(Box::new(keyboard)); log::info!("RdpInput keyboard created"); - if let Some(stream) = rdp_res.streams.clone().into_iter().next() { - let mouse = RdpInputMouse::new(rdp_res.conn.clone(), rdp_res.session.clone(), stream)?; + if let Some(stream) = rdp_info.streams.clone().into_iter().next() { + let resolution = rdp_info + .resolution + .lock() + .unwrap() + .unwrap_or(stream.get_size()); + let mouse = RdpInputMouse::new( + rdp_info.conn.clone(), + rdp_info.session.clone(), + stream, + resolution, + )?; en.set_custom_mouse(Box::new(mouse)); log::info!("RdpInput mouse created"); } diff --git a/src/server/rdp_input.rs b/src/server/rdp_input.rs index 1a0a64054..910a19276 100644 --- a/src/server/rdp_input.rs +++ b/src/server/rdp_input.rs @@ -8,6 +8,8 @@ use std::collections::HashMap; use std::sync::Arc; pub mod client { + use hbb_common::platform::linux::is_kde; + use super::*; const EVDEV_MOUSE_LEFT: i32 = 272; @@ -67,6 +69,8 @@ pub mod client { conn: Arc, session: Path<'static>, stream: PwStreamInfo, + resolution: (usize, usize), + scale: Option, } impl RdpInputMouse { @@ -74,11 +78,32 @@ pub mod client { conn: Arc, session: Path<'static>, stream: PwStreamInfo, + resolution: (usize, usize), ) -> ResultType { + // https://github.com/rustdesk/rustdesk/pull/9019#issuecomment-2295252388 + // There may be a bug in Rdp input on Gnome util Ubuntu 24.04 (Gnome 46) + // + // eg. Resultion 800x600, Fractional scale: 200% (logic size: 400x300) + // https://flatpak.github.io/xdg-desktop-portal/docs/doc-org.freedesktop.impl.portal.RemoteDesktop.html#:~:text=new%20pointer%20position-,in%20the%20streams%20logical%20coordinate%20space,-. + // Then (x,y) in `mouse_move_to()` and `mouse_move_relative()` should be scaled to the logic size(stream.get_size()), which is from (0,0) to (400,300). + // For Ubuntu 24.04(Gnome 46), (x,y) is restricted from (0,0) to (400,300), but the actual range in screen is: + // Logic coordinate from (0,0) to (200x150). + // Or physical coordinate from (0,0) to (400,300). + let scale = if is_kde() { + if resolution.0 == 0 || stream.get_size().0 == 0 { + Some(1.0f64) + } else { + Some(resolution.0 as f64 / stream.get_size().0 as f64) + } + } else { + None + }; Ok(Self { conn, session, stream, + resolution, + scale, }) } } @@ -93,24 +118,44 @@ pub mod client { } fn mouse_move_to(&mut self, x: i32, y: i32) { + let x = if let Some(s) = self.scale { + x as f64 / s + } else { + x as f64 + }; + let y = if let Some(s) = self.scale { + y as f64 / s + } else { + y as f64 + }; let portal = get_portal(&self.conn); let _ = remote_desktop_portal::notify_pointer_motion_absolute( &portal, &self.session, HashMap::new(), self.stream.path as u32, - x as f64, - y as f64, + x, + y, ); } fn mouse_move_relative(&mut self, x: i32, y: i32) { + let x = if let Some(s) = self.scale { + x as f64 / s + } else { + x as f64 + }; + let y = if let Some(s) = self.scale { + y as f64 / s + } else { + y as f64 + }; let portal = get_portal(&self.conn); let _ = remote_desktop_portal::notify_pointer_motion( &portal, &self.session, HashMap::new(), - x as f64, - y as f64, + x, + y, ); } fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType {