diff --git a/libs/scrap/src/common/mod.rs b/libs/scrap/src/common/mod.rs index 4df0f0192..54be2ee91 100644 --- a/libs/scrap/src/common/mod.rs +++ b/libs/scrap/src/common/mod.rs @@ -151,7 +151,7 @@ pub fn is_cursor_embedded() -> bool { if is_x11() { x11::IS_CURSOR_EMBEDDED } else { - wayland::is_cursor_embedded() + false } } diff --git a/libs/scrap/src/common/wayland.rs b/libs/scrap/src/common/wayland.rs index cf36a83a4..0eee79c12 100644 --- a/libs/scrap/src/common/wayland.rs +++ b/libs/scrap/src/common/wayland.rs @@ -4,34 +4,11 @@ use std::{io, sync::RwLock, time::Duration}; pub struct Capturer(Display, Box, Vec); -static mut IS_CURSOR_EMBEDDED: Option = None; lazy_static::lazy_static! { static ref MAP_ERR: RwLock io::Error>> = Default::default(); } -pub fn is_cursor_embedded() -> bool { - unsafe { - if IS_CURSOR_EMBEDDED.is_none() { - init_cursor_embedded(); - } - IS_CURSOR_EMBEDDED.unwrap_or(false) - } -} - -unsafe fn init_cursor_embedded() { - use crate::common::wayland::pipewire::get_available_cursor_modes; - match get_available_cursor_modes() { - Ok(_modes) => { - // IS_CURSOR_EMBEDDED = Some((_modes & 0x02) > 0); - IS_CURSOR_EMBEDDED = Some(false) - } - Err(..) => { - IS_CURSOR_EMBEDDED = Some(false); - } - } -} - pub fn set_map_err(f: fn(err: String) -> io::Error) { *MAP_ERR.write().unwrap() = Some(f); } @@ -82,7 +59,7 @@ impl Display { } pub fn all() -> io::Result> { - Ok(pipewire::get_capturables(is_cursor_embedded()) + Ok(pipewire::get_capturables() .map_err(map_err)? .drain(..) .map(|x| Display(x)) diff --git a/libs/scrap/src/wayland.rs b/libs/scrap/src/wayland.rs index a5c4a3903..501fec859 100644 --- a/libs/scrap/src/wayland.rs +++ b/libs/scrap/src/wayland.rs @@ -1,3 +1,5 @@ pub mod capturable; pub mod pipewire; -mod pipewire_dbus; +mod screencast_portal; +mod request_portal; +pub mod remote_desktop_portal; diff --git a/libs/scrap/src/wayland/pipewire.rs b/libs/scrap/src/wayland/pipewire.rs index bb7eec241..5db46cb19 100644 --- a/libs/scrap/src/wayland/pipewire.rs +++ b/libs/scrap/src/wayland/pipewire.rs @@ -20,11 +20,24 @@ use hbb_common::config; use super::capturable::PixelProvider; use super::capturable::{Capturable, Recorder}; -use super::pipewire_dbus::{OrgFreedesktopPortalRequestResponse, OrgFreedesktopPortalScreenCast}; +use super::remote_desktop_portal::OrgFreedesktopPortalRemoteDesktop as remote_desktop_portal; +use super::request_portal::OrgFreedesktopPortalRequestResponse; +use super::screencast_portal::OrgFreedesktopPortalScreenCast as screencast_portal; +use lazy_static::lazy_static; +lazy_static! { + pub static ref RDP_RESPONSE: Mutex> = Mutex::new(None); +} + +pub struct RdpResponse { + pub conn: Arc, + pub streams: Vec, + pub fd: OwnedFd, + pub session: dbus::Path<'static>, +} #[derive(Debug, Clone, Copy)] -struct PwStreamInfo { - path: u64, +pub struct PwStreamInfo { + pub path: u64, source_type: u64, position: (i32, i32), size: (usize, usize), @@ -124,24 +137,26 @@ impl Capturable for PipeWireCapturable { } } -fn get_res(capturable:PipeWireCapturable) -> Result<(usize, usize), Box> { +fn get_res(capturable: PipeWireCapturable) -> Result<(usize, usize), Box> { let rec = PipeWireRecorder::new(capturable)?; - if let Some(sample) = rec.appsink - .try_pull_sample(gst::ClockTime::from_mseconds(300)) - { - let cap = sample - .get_caps() - .ok_or("Failed get caps")? - .get_structure(0) - .ok_or("Failed to get structure")?; - let w: i32 = cap.get_value("width")?.get_some()?; - let h: i32 = cap.get_value("height")?.get_some()?; - let w = w as usize; - let h = h as usize; - Ok((w,h)) - } - else { - Err(Box::new(GStreamerError("Error getting screen resolution".into()))) + if let Some(sample) = rec + .appsink + .try_pull_sample(gst::ClockTime::from_mseconds(300)) + { + let cap = sample + .get_caps() + .ok_or("Failed get caps")? + .get_structure(0) + .ok_or("Failed to get structure")?; + let w: i32 = cap.get_value("width")?.get_some()?; + let h: i32 = cap.get_value("height")?.get_some()?; + let w = w as usize; + let h = h as usize; + Ok((w, h)) + } else { + Err(Box::new(GStreamerError( + "Error getting screen resolution".into(), + ))) } } @@ -357,7 +372,7 @@ where }) } -fn get_portal(conn: &SyncConnection) -> Proxy<&SyncConnection> { +pub fn get_portal(conn: &SyncConnection) -> Proxy<&SyncConnection> { conn.with_proxy( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", @@ -447,22 +462,22 @@ static mut INIT: bool = false; const RESTORE_TOKEN: &str = "restore_token"; const RESTORE_TOKEN_CONF_KEY: &str = "wayland-restore-token"; -const PORTAL_CURSOR_MODE_HIDDEN: u32 = 1; -#[allow(dead_code)] -const PORTAL_CURSOR_MODE_EMBEDDED: u32 = 2; -#[allow(dead_code)] -const PORTAL_CURSOR_MODE_METADATA: u32 = 4; - pub fn get_available_cursor_modes() -> Result { let conn = SyncConnection::new_session()?; let portal = get_portal(&conn); portal.available_cursor_modes() } -// mostly inspired by https://gitlab.gnome.org/snippets/19 -fn request_screen_cast( - capture_cursor: bool, -) -> Result<(SyncConnection, OwnedFd, Vec), Box> { +// mostly inspired by https://gitlab.gnome.org/-/snippets/39 +pub fn request_remote_desktop() -> Result< + ( + SyncConnection, + OwnedFd, + Vec, + dbus::Path<'static>, + ), + Box, +> { unsafe { if !INIT { gstreamer::init()?; @@ -478,6 +493,8 @@ fn request_screen_cast( let streams_res = streams.clone(); let failure = Arc::new(AtomicBool::new(false)); let failure_res = failure.clone(); + let session: Arc>> = Arc::new(Mutex::new(None)); + let session_res = session.clone(); args.insert( "session_handle_token".to_string(), Variant(Box::new("u1".to_string())), @@ -492,125 +509,19 @@ fn request_screen_cast( // between the caller subscribing to the signal after receiving the reply for the method call and the signal getting emitted, // a convention for Request object paths has been established that allows // the caller to subscribe to the signal before making the method call. - let path = portal.create_session(args)?; + let path = remote_desktop_portal::create_session(&portal, args)?; handle_response( &conn, path, - move |r: OrgFreedesktopPortalRequestResponse, c, _| { - let portal = get_portal(c); - let mut args: PropMap = HashMap::new(); - if let Ok(version) = portal.version() { - if version >= 4 { - let restore_token = config::LocalConfig::get_option(RESTORE_TOKEN_CONF_KEY); - if !restore_token.is_empty() { - args.insert(RESTORE_TOKEN.to_string(), Variant(Box::new(restore_token))); - } - // persist_mode may be configured by the user. - args.insert("persist_mode".to_string(), Variant(Box::new(2u32))); - } - } - args.insert( - "handle_token".to_string(), - Variant(Box::new("u2".to_string())), - ); - // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-method-org-freedesktop-portal-ScreenCast.SelectSources - args.insert("multiple".into(), Variant(Box::new(true))); - args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32))); - - let mut cursor_mode = 0u32; - let mut available_cursor_modes = 0u32; - if let Ok(modes) = portal.available_cursor_modes() { - available_cursor_modes = modes; - } - // if capture_cursor { - // cursor_mode = PORTAL_CURSOR_MODE_METADATA & available_cursor_modes; - // } - if cursor_mode == 0 { - cursor_mode = PORTAL_CURSOR_MODE_HIDDEN & available_cursor_modes; - } - let plasma = std::env::var("DESKTOP_SESSION").map_or(false, |s| s.contains("plasma")); - if plasma && capture_cursor { - // Warn the user if capturing the cursor is tried on kde as this can crash - // kwin_wayland and tear down the plasma desktop, see: - // https://bugs.kde.org/show_bug.cgi?id=435042 - warn!("You are attempting to capture the cursor under KDE Plasma, this may crash your \ - desktop, see https://bugs.kde.org/show_bug.cgi?id=435042 for details! \ - You have been warned."); - } - if cursor_mode > 0 { - args.insert("cursor_mode".into(), Variant(Box::new(cursor_mode))); - } - let session: dbus::Path = r - .results - .get("session_handle") - .ok_or_else(|| { - DBusError(format!( - "Failed to obtain session_handle from response: {:?}", - r - )) - })? - .as_str() - .ok_or_else(|| DBusError("Failed to convert session_handle to string.".into()))? - .to_string() - .into(); - let path = portal.select_sources(session.clone(), args)?; - let fd = fd.clone(); - let streams = streams.clone(); - let failure = failure.clone(); - let failure_out = failure.clone(); - handle_response( - c, - path, - move |_: OrgFreedesktopPortalRequestResponse, c, _| { - let portal = get_portal(c); - let mut args: PropMap = HashMap::new(); - args.insert( - "handle_token".to_string(), - Variant(Box::new("u3".to_string())), - ); - let path = portal.start(session.clone(), "", args)?; - let session = session.clone(); - let fd = fd.clone(); - let streams = streams.clone(); - let failure = failure.clone(); - let failure_out = failure.clone(); - handle_response( - c, - path, - move |r: OrgFreedesktopPortalRequestResponse, c, _| { - let portal = get_portal(c); - if let Ok(version) = portal.version() { - if version >= 4 { - if let Some(restore_token) = r.results.get(RESTORE_TOKEN) { - if let Some(restore_token) = restore_token.as_str() { - config::LocalConfig::set_option( - RESTORE_TOKEN_CONF_KEY.to_owned(), - restore_token.to_owned(), - ); - } - } - } - } - streams - .clone() - .lock() - .unwrap() - .append(&mut streams_from_response(r)); - fd.clone().lock().unwrap().replace( - portal.open_pipe_wire_remote(session.clone(), HashMap::new())?, - ); - Ok(()) - }, - failure_out, - )?; - Ok(()) - }, - failure_out, - )?; - Ok(()) - }, + on_create_session_response( + fd.clone(), + streams.clone(), + session.clone(), + failure.clone(), + ), failure_res.clone(), )?; + // wait 3 minutes for user interaction for _ in 0..1800 { conn.process(Duration::from_millis(100))?; @@ -625,9 +536,13 @@ fn request_screen_cast( } let fd_res = fd_res.lock().unwrap(); let streams_res = streams_res.lock().unwrap(); + let session_res = session_res.lock().unwrap(); + if let Some(fd_res) = fd_res.clone() { - if !streams_res.is_empty() { - return Ok((conn, fd_res, streams_res.clone())); + if let Some(session) = session_res.clone() { + if !streams_res.is_empty() { + return Ok((conn, fd_res, streams_res.clone(), session)); + } } } Err(Box::new(DBusError( @@ -635,11 +550,209 @@ fn request_screen_cast( ))) } -pub fn get_capturables(capture_cursor: bool) -> Result, Box> { - let (conn, fd, streams) = request_screen_cast(capture_cursor)?; - let conn = Arc::new(conn); - Ok(streams +fn on_create_session_response( + fd: Arc>>, + streams: Arc>>, + session: Arc>>>, + failure: Arc, +) -> impl Fn( + OrgFreedesktopPortalRequestResponse, + &SyncConnection, + &dbus::Message, +) -> Result<(), Box> { + move |r: OrgFreedesktopPortalRequestResponse, c, _| { + let portal = get_portal(c); + let mut args: PropMap = HashMap::new(); + + args.insert( + "handle_token".to_string(), + Variant(Box::new("u2".to_string())), + ); + args.insert("types".to_string(), Variant(Box::new(7u32))); + + let ses: dbus::Path = r + .results + .get("session_handle") + .ok_or_else(|| { + DBusError(format!( + "Failed to obtain session_handle from response: {:?}", + r + )) + })? + .as_str() + .ok_or_else(|| DBusError("Failed to convert session_handle to string.".into()))? + .to_string() + .into(); + + let mut session = match session.lock() { + Ok(session) => session, + Err(_) => { + return Err(Box::new(DBusError( + "Failed to lock session.".into(), + ))) + } + }; + + session.replace(ses.clone()); + + let path = portal.select_devices(ses.clone(), args)?; + handle_response( + c, + path, + on_select_devices_response(fd.clone(), streams.clone(), failure.clone(), ses), + failure.clone(), + )?; + + Ok(()) + } +} + +fn on_select_devices_response( + fd: Arc>>, + streams: Arc>>, + failure: Arc, + session: dbus::Path<'static>, +) -> impl Fn( + OrgFreedesktopPortalRequestResponse, + &SyncConnection, + &dbus::Message, +) -> Result<(), Box> { + move |_: OrgFreedesktopPortalRequestResponse, c, _| { + let portal = get_portal(c); + let mut args: PropMap = HashMap::new(); + if let Ok(version) = remote_desktop_portal::version(&portal) { + if version >= 4 { + let restore_token = config::LocalConfig::get_option(RESTORE_TOKEN_CONF_KEY); + if !restore_token.is_empty() { + args.insert(RESTORE_TOKEN.to_string(), Variant(Box::new(restore_token))); + } + // persist_mode may be configured by the user. + args.insert("persist_mode".to_string(), Variant(Box::new(2u32))); + } + } + args.insert( + "handle_token".to_string(), + Variant(Box::new("u3".to_string())), + ); + // https://flatpak.github.io/xdg-desktop-portal/portal-docs.html#gdbus-method-org-freedesktop-portal-ScreenCast.SelectSources + args.insert("multiple".into(), Variant(Box::new(true))); + args.insert("types".into(), Variant(Box::new(1u32))); //| 2u32))); + + let session = session.clone(); + let path = portal.select_sources(session.clone(), args)?; + handle_response( + c, + path, + on_select_sources_response( + fd.clone(), + streams.clone(), + failure.clone(), + session.clone(), + ), + failure.clone(), + )?; + + Ok(()) + } +} + +fn on_select_sources_response( + fd: Arc>>, + streams: Arc>>, + failure: Arc, + session: dbus::Path<'static>, +) -> impl Fn( + OrgFreedesktopPortalRequestResponse, + &SyncConnection, + &dbus::Message, +) -> Result<(), Box> { + move |_: OrgFreedesktopPortalRequestResponse, c, _| { + let portal = get_portal(c); + let mut args: PropMap = HashMap::new(); + args.insert( + "handle_token".to_string(), + Variant(Box::new("u4".to_string())), + ); + let path = remote_desktop_portal::start(&portal, session.clone(), "", args)?; + handle_response( + c, + path, + on_start_response(fd.clone(), streams.clone(), session.clone()), + failure.clone(), + )?; + + Ok(()) + } +} + +fn on_start_response( + fd: Arc>>, + streams: Arc>>, + session: dbus::Path<'static>, +) -> impl Fn( + OrgFreedesktopPortalRequestResponse, + &SyncConnection, + &dbus::Message, +) -> Result<(), Box> { + move |r: OrgFreedesktopPortalRequestResponse, c, _| { + let portal = get_portal(c); + if let Ok(version) = remote_desktop_portal::version(&portal) { + if version >= 4 { + if let Some(restore_token) = r.results.get(RESTORE_TOKEN) { + if let Some(restore_token) = restore_token.as_str() { + config::LocalConfig::set_option( + RESTORE_TOKEN_CONF_KEY.to_owned(), + restore_token.to_owned(), + ); + } + } + } + } + + streams + .clone() + .lock() + .unwrap() + .append(&mut streams_from_response(r)); + fd.clone() + .lock() + .unwrap() + .replace(portal.open_pipe_wire_remote(session.clone(), HashMap::new())?); + + Ok(()) + } +} + +pub fn get_capturables() -> Result, Box> { + let mut rdp_connection = match RDP_RESPONSE.lock() { + Ok(conn) => conn, + Err(err) => return Err(Box::new(err)), + }; + + if rdp_connection.is_none() { + let (conn, fd, streams, session) = request_remote_desktop()?; + let conn = Arc::new(conn); + + let rdp_res = RdpResponse { + conn, + streams, + fd, + session, + }; + *rdp_connection = Some(rdp_res); + } + + let rdp_res = match rdp_connection.as_ref() { + Some(res) => res, + None => { + return Err(Box::new(DBusError("RDP response is None.".into()))); + } + }; + + Ok(rdp_res + .streams + .clone() .into_iter() - .map(|s| PipeWireCapturable::new(conn.clone(), fd.clone(), s)) + .map(|s| PipeWireCapturable::new(rdp_res.conn.clone(), rdp_res.fd.clone(), s)) .collect()) } diff --git a/libs/scrap/src/wayland/remote_desktop_portal.rs b/libs/scrap/src/wayland/remote_desktop_portal.rs new file mode 100644 index 000000000..22ccddc2e --- /dev/null +++ b/libs/scrap/src/wayland/remote_desktop_portal.rs @@ -0,0 +1,315 @@ +// This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs +// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.RemoteDesktop.xml +use dbus; +#[allow(unused_imports)] +use dbus::arg; +use dbus::blocking; + +pub trait OrgFreedesktopPortalRemoteDesktop { + fn create_session(&self, options: arg::PropMap) -> Result, dbus::Error>; + fn select_devices( + &self, + session_handle: dbus::Path, + options: arg::PropMap, + ) -> Result, dbus::Error>; + fn start( + &self, + session_handle: dbus::Path, + parent_window: &str, + options: arg::PropMap, + ) -> Result, dbus::Error>; + fn notify_pointer_motion( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + dx: f64, + dy: f64, + ) -> Result<(), dbus::Error>; + fn notify_pointer_motion_absolute( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error>; + fn notify_pointer_button( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + button: i32, + state: u32, + ) -> Result<(), dbus::Error>; + fn notify_pointer_axis( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + dx: f64, + dy: f64, + ) -> Result<(), dbus::Error>; + fn notify_pointer_axis_discrete( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + axis: u32, + steps: i32, + ) -> Result<(), dbus::Error>; + fn notify_keyboard_keycode( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + keycode: i32, + state: u32, + ) -> Result<(), dbus::Error>; + fn notify_keyboard_keysym( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + keysym: i32, + state: u32, + ) -> Result<(), dbus::Error>; + fn notify_touch_down( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + slot: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error>; + fn notify_touch_motion( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + slot: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error>; + fn notify_touch_up( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + slot: u32, + ) -> Result<(), dbus::Error>; + fn connect_to_eis( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + ) -> Result; + fn available_device_types(&self) -> Result; + fn version(&self) -> Result; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> + OrgFreedesktopPortalRemoteDesktop for blocking::Proxy<'a, C> +{ + fn create_session(&self, options: arg::PropMap) -> Result, dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "CreateSession", + (options,), + ) + .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) + } + + fn select_devices( + &self, + session_handle: dbus::Path, + options: arg::PropMap, + ) -> Result, dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "SelectDevices", + (session_handle, options), + ) + .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) + } + + fn start( + &self, + session_handle: dbus::Path, + parent_window: &str, + options: arg::PropMap, + ) -> Result, dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "Start", + (session_handle, parent_window, options), + ) + .and_then(|r: (dbus::Path<'static>,)| Ok(r.0)) + } + + fn notify_pointer_motion( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + dx: f64, + dy: f64, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyPointerMotion", + (session_handle, options, dx, dy), + ) + } + + fn notify_pointer_motion_absolute( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyPointerMotionAbsolute", + (session_handle, options, stream, x_, y_), + ) + } + + fn notify_pointer_button( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + button: i32, + state: u32, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyPointerButton", + (session_handle, options, button, state), + ) + } + + fn notify_pointer_axis( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + dx: f64, + dy: f64, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyPointerAxis", + (session_handle, options, dx, dy), + ) + } + + fn notify_pointer_axis_discrete( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + axis: u32, + steps: i32, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyPointerAxisDiscrete", + (session_handle, options, axis, steps), + ) + } + + fn notify_keyboard_keycode( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + keycode: i32, + state: u32, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyKeyboardKeycode", + (session_handle, options, keycode, state), + ) + } + + fn notify_keyboard_keysym( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + keysym: i32, + state: u32, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyKeyboardKeysym", + (session_handle, options, keysym, state), + ) + } + + fn notify_touch_down( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + slot: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyTouchDown", + (session_handle, options, stream, slot, x_, y_), + ) + } + + fn notify_touch_motion( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + stream: u32, + slot: u32, + x_: f64, + y_: f64, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyTouchMotion", + (session_handle, options, stream, slot, x_, y_), + ) + } + + fn notify_touch_up( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + slot: u32, + ) -> Result<(), dbus::Error> { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "NotifyTouchUp", + (session_handle, options, slot), + ) + } + + fn connect_to_eis( + &self, + session_handle: &dbus::Path, + options: arg::PropMap, + ) -> Result { + self.method_call( + "org.freedesktop.portal.RemoteDesktop", + "ConnectToEIS", + (session_handle, options), + ) + .and_then(|r: (arg::OwnedFd,)| Ok(r.0)) + } + + fn available_device_types(&self) -> Result { + ::get( + &self, + "org.freedesktop.portal.RemoteDesktop", + "AvailableDeviceTypes", + ) + } + + fn version(&self) -> Result { + ::get( + &self, + "org.freedesktop.portal.RemoteDesktop", + "version", + ) + } +} diff --git a/libs/scrap/src/wayland/request_portal.rs b/libs/scrap/src/wayland/request_portal.rs new file mode 100644 index 000000000..c2314a312 --- /dev/null +++ b/libs/scrap/src/wayland/request_portal.rs @@ -0,0 +1,45 @@ +// This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs +// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.Request.xml +use dbus; +#[allow(unused_imports)] +use dbus::arg; +use dbus::blocking; + +pub trait OrgFreedesktopPortalRequest { + fn close(&self) -> Result<(), dbus::Error>; +} + +impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopPortalRequest + for blocking::Proxy<'a, C> +{ + fn close(&self) -> Result<(), dbus::Error> { + self.method_call("org.freedesktop.portal.Request", "Close", ()) + } +} + +#[derive(Debug)] +pub struct OrgFreedesktopPortalRequestResponse { + pub response: u32, + pub results: arg::PropMap, +} + +impl arg::AppendAll for OrgFreedesktopPortalRequestResponse { + fn append(&self, i: &mut arg::IterAppend) { + arg::RefArg::append(&self.response, i); + arg::RefArg::append(&self.results, i); + } +} + +impl arg::ReadAll for OrgFreedesktopPortalRequestResponse { + fn read(i: &mut arg::Iter) -> Result { + Ok(OrgFreedesktopPortalRequestResponse { + response: i.read()?, + results: i.read()?, + }) + } +} + +impl dbus::message::SignalArgs for OrgFreedesktopPortalRequestResponse { + const NAME: &'static str = "Response"; + const INTERFACE: &'static str = "org.freedesktop.portal.Request"; +} diff --git a/libs/scrap/src/wayland/pipewire_dbus.rs b/libs/scrap/src/wayland/screencast_portal.rs similarity index 74% rename from libs/scrap/src/wayland/pipewire_dbus.rs rename to libs/scrap/src/wayland/screencast_portal.rs index 3349ec8a9..a8f7a911b 100644 --- a/libs/scrap/src/wayland/pipewire_dbus.rs +++ b/libs/scrap/src/wayland/screencast_portal.rs @@ -1,4 +1,5 @@ // This code was autogenerated with `dbus-codegen-rust -c blocking -m None`, see https://github.com/diwic/dbus-rs +// https://github.com/flatpak/xdg-desktop-portal/blob/main/data/org.freedesktop.portal.ScreenCast.xml use dbus; #[allow(unused_imports)] use dbus::arg; @@ -103,42 +104,3 @@ impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> ) } } - -pub trait OrgFreedesktopPortalRequest { - fn close(&self) -> Result<(), dbus::Error>; -} - -impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref> OrgFreedesktopPortalRequest - for blocking::Proxy<'a, C> -{ - fn close(&self) -> Result<(), dbus::Error> { - self.method_call("org.freedesktop.portal.Request", "Close", ()) - } -} - -#[derive(Debug)] -pub struct OrgFreedesktopPortalRequestResponse { - pub response: u32, - pub results: arg::PropMap, -} - -impl arg::AppendAll for OrgFreedesktopPortalRequestResponse { - fn append(&self, i: &mut arg::IterAppend) { - arg::RefArg::append(&self.response, i); - arg::RefArg::append(&self.results, i); - } -} - -impl arg::ReadAll for OrgFreedesktopPortalRequestResponse { - fn read(i: &mut arg::Iter) -> Result { - Ok(OrgFreedesktopPortalRequestResponse { - response: i.read()?, - results: i.read()?, - }) - } -} - -impl dbus::message::SignalArgs for OrgFreedesktopPortalRequestResponse { - const NAME: &'static str = "Response"; - const INTERFACE: &'static str = "org.freedesktop.portal.Request"; -} diff --git a/src/server.rs b/src/server.rs index 128fb6d1f..a6cafb315 100644 --- a/src/server.rs +++ b/src/server.rs @@ -39,6 +39,8 @@ pub(crate) mod wayland; #[cfg(target_os = "linux")] pub mod uinput; #[cfg(target_os = "linux")] +pub mod rdp_input; +#[cfg(target_os = "linux")] pub mod dbus; pub mod input_service; } else { diff --git a/src/server/connection.rs b/src/server/connection.rs index 9346f6826..febeff377 100644 --- a/src/server/connection.rs +++ b/src/server/connection.rs @@ -5,6 +5,8 @@ use crate::clipboard_file::*; use crate::common::update_clipboard; #[cfg(target_os = "android")] use crate::keyboard::client::map_key_to_control_key; +#[cfg(target_os = "linux")] +use crate::platform::linux::is_x11; #[cfg(all(target_os = "linux", feature = "linux_headless"))] #[cfg(not(any(feature = "flatpak", feature = "appimage")))] use crate::platform::linux_desktop_manager; @@ -12,8 +14,6 @@ use crate::platform::linux_desktop_manager; use crate::platform::WallPaperRemover; #[cfg(windows)] use crate::portable_service::client as portable_client; -#[cfg(target_os = "linux")] -use crate::platform::linux::is_x11; use crate::{ client::{ new_voice_call_request, new_voice_call_response, start_audio_thread, MediaData, MediaSender, @@ -1172,7 +1172,21 @@ impl Connection { ..Default::default() }) .into(); - pi.resolutions = Self::get_supported_resolutions(self.display_idx).into(); + #[cfg(not(any(target_os = "android", target_os = "ios")))] + { + pi.resolutions = Some(SupportedResolutions { + resolutions: display_service::try_get_displays() + .map(|displays| { + displays + .get(self.display_idx) + .map(|d| crate::platform::resolutions(&d.name())) + .unwrap_or(vec![]) + }) + .unwrap_or(vec![]), + ..Default::default() + }) + .into(); + } let mut sub_service = false; if self.file_transfer.is_some() { @@ -1194,6 +1208,14 @@ impl Connection { pi.current_display = self.display_idx as _; res.set_peer_info(pi); sub_service = true; + + #[cfg(target_os = "linux")] + { + // use rdp_input when uinput is not available in wayland. Ex: flatpak + if !is_x11() && !crate::is_server() { + let _ = setup_rdp_input().await; + } + } } } self.on_remote_authorized(); @@ -1236,31 +1258,6 @@ impl Connection { } } - fn get_supported_resolutions(display_idx: usize) -> Option { - #[cfg(any(target_os = "android", target_os = "ios"))] - return None; - #[cfg(not(any(target_os = "android", target_os = "ios")))] - { - #[cfg(target_os = "linux")] - { - if !is_x11() { - return None; - } - } - Some(SupportedResolutions { - resolutions: display_service::try_get_displays() - .map(|displays| { - displays - .get(display_idx) - .map(|d| crate::platform::resolutions(&d.name())) - .unwrap_or(vec![]) - }) - .unwrap_or(vec![]), - ..Default::default() - }) - } - } - fn on_remote_authorized(&self) { self.update_codec_on_login(); #[cfg(any(target_os = "windows", target_os = "linux"))] diff --git a/src/server/input_service.rs b/src/server/input_service.rs index a90a0005d..92e818d36 100644 --- a/src/server/input_service.rs +++ b/src/server/input_service.rs @@ -5,6 +5,8 @@ use crate::input::*; #[cfg(target_os = "macos")] use dispatch::Queue; use enigo::{Enigo, Key, KeyboardControllable, MouseButton, MouseControllable}; +#[cfg(target_os = "linux")] +use super::rdp_input::client::{RdpInputKeyboard, RdpInputMouse}; use hbb_common::{ get_time, message_proto::{pointer_device_event::Union::TouchEvent, touch_event::Union::ScaleUpdate}, @@ -13,6 +15,8 @@ use hbb_common::{ 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 std::{ convert::TryFrom, ops::{Deref, DerefMut, Sub}, @@ -461,6 +465,25 @@ pub async fn setup_uinput(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultT Ok(()) } +#[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 keyboard = RdpInputKeyboard::new(rdp_res.conn.clone(), rdp_res.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)?; + en.set_custom_mouse(Box::new(mouse)); + log::info!("RdpInput mouse created"); + } + + Ok(()) +} + #[cfg(target_os = "linux")] pub async fn update_mouse_resolution(minx: i32, maxx: i32, miny: i32, maxy: i32) -> ResultType<()> { set_uinput_resolution(minx, maxx, miny, maxy).await?; diff --git a/src/server/rdp_input.rs b/src/server/rdp_input.rs new file mode 100644 index 000000000..1a0a64054 --- /dev/null +++ b/src/server/rdp_input.rs @@ -0,0 +1,234 @@ +use crate::uinput::service::map_key; +use dbus::{blocking::SyncConnection, Path}; +use enigo::{Key, KeyboardControllable, MouseButton, MouseControllable}; +use hbb_common::ResultType; +use scrap::wayland::pipewire::{get_portal, PwStreamInfo}; +use scrap::wayland::remote_desktop_portal::OrgFreedesktopPortalRemoteDesktop as remote_desktop_portal; +use std::collections::HashMap; +use std::sync::Arc; + +pub mod client { + use super::*; + + const EVDEV_MOUSE_LEFT: i32 = 272; + const EVDEV_MOUSE_RIGHT: i32 = 273; + const EVDEV_MOUSE_MIDDLE: i32 = 274; + + const PRESSED_DOWN_STATE: u32 = 1; + const PRESSED_UP_STATE: u32 = 0; + + pub struct RdpInputKeyboard { + conn: Arc, + session: Path<'static>, + } + + impl RdpInputKeyboard { + pub fn new(conn: Arc, session: Path<'static>) -> ResultType { + Ok(Self { conn, session }) + } + } + + impl KeyboardControllable for RdpInputKeyboard { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self + } + + fn get_key_state(&mut self, _: Key) -> bool { + // no api for this + false + } + + fn key_sequence(&mut self, s: &str) { + for c in s.chars() { + let key = Key::Layout(c); + let _ = handle_key(true, key, self.conn.clone(), &self.session); + let _ = handle_key(false, key, self.conn.clone(), &self.session); + } + } + + fn key_down(&mut self, key: Key) -> enigo::ResultType { + handle_key(true, key, self.conn.clone(), &self.session)?; + Ok(()) + } + fn key_up(&mut self, key: Key) { + let _ = handle_key(false, key, self.conn.clone(), &self.session); + } + fn key_click(&mut self, key: Key) { + let _ = handle_key(true, key, self.conn.clone(), &self.session); + let _ = handle_key(false, key, self.conn.clone(), &self.session); + } + } + + pub struct RdpInputMouse { + conn: Arc, + session: Path<'static>, + stream: PwStreamInfo, + } + + impl RdpInputMouse { + pub fn new( + conn: Arc, + session: Path<'static>, + stream: PwStreamInfo, + ) -> ResultType { + Ok(Self { + conn, + session, + stream, + }) + } + } + + impl MouseControllable for RdpInputMouse { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn as_mut_any(&mut self) -> &mut dyn std::any::Any { + self + } + + fn mouse_move_to(&mut self, x: i32, y: i32) { + 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, + ); + } + fn mouse_move_relative(&mut self, x: i32, y: i32) { + let portal = get_portal(&self.conn); + let _ = remote_desktop_portal::notify_pointer_motion( + &portal, + &self.session, + HashMap::new(), + x as f64, + y as f64, + ); + } + fn mouse_down(&mut self, button: MouseButton) -> enigo::ResultType { + handle_mouse(true, button, self.conn.clone(), &self.session); + Ok(()) + } + fn mouse_up(&mut self, button: MouseButton) { + handle_mouse(false, button, self.conn.clone(), &self.session); + } + fn mouse_click(&mut self, button: MouseButton) { + handle_mouse(true, button, self.conn.clone(), &self.session); + handle_mouse(false, button, self.conn.clone(), &self.session); + } + fn mouse_scroll_x(&mut self, length: i32) { + let portal = get_portal(&self.conn); + let _ = remote_desktop_portal::notify_pointer_axis( + &portal, + &self.session, + HashMap::new(), + length as f64, + 0 as f64, + ); + } + fn mouse_scroll_y(&mut self, length: i32) { + let portal = get_portal(&self.conn); + let _ = remote_desktop_portal::notify_pointer_axis( + &portal, + &self.session, + HashMap::new(), + 0 as f64, + length as f64, + ); + } + } + + fn get_raw_evdev_keycode(key: u16) -> i32 { + // 8 is the offset between xkb and evdev + let mut key = key as i32 - 8; + // fix for right_meta key + if key == 126 { + key = 125; + } + key + } + + fn handle_key( + down: bool, + key: Key, + conn: Arc, + session: &Path<'static>, + ) -> ResultType<()> { + let state: u32 = if down { + PRESSED_DOWN_STATE + } else { + PRESSED_UP_STATE + }; + let portal = get_portal(&conn); + match key { + Key::Raw(key) => { + let key = get_raw_evdev_keycode(key); + remote_desktop_portal::notify_keyboard_keycode( + &portal, + &session, + HashMap::new(), + key, + state, + )?; + } + _ => { + if let Ok((key, is_shift)) = map_key(&key) { + if is_shift { + remote_desktop_portal::notify_keyboard_keycode( + &portal, + &session, + HashMap::new(), + evdev::Key::KEY_LEFTSHIFT.code() as i32, + state, + )?; + } + remote_desktop_portal::notify_keyboard_keycode( + &portal, + &session, + HashMap::new(), + key.code() as i32, + state, + )?; + } + } + } + Ok(()) + } + + fn handle_mouse( + down: bool, + button: MouseButton, + conn: Arc, + session: &Path<'static>, + ) { + let portal = get_portal(&conn); + let but_key = match button { + MouseButton::Left => EVDEV_MOUSE_LEFT, + MouseButton::Right => EVDEV_MOUSE_RIGHT, + MouseButton::Middle => EVDEV_MOUSE_MIDDLE, + _ => { + return; + } + }; + let state: u32 = if down { + PRESSED_DOWN_STATE + } else { + PRESSED_UP_STATE + }; + let _ = remote_desktop_portal::notify_pointer_button( + &portal, + &session, + HashMap::new(), + but_key, + state, + ); + } +} diff --git a/src/server/uinput.rs b/src/server/uinput.rs index 4e43f12ca..f36ad0336 100644 --- a/src/server/uinput.rs +++ b/src/server/uinput.rs @@ -382,7 +382,7 @@ pub mod service { Ok(keyboard) } - fn map_key(key: &enigo::Key) -> ResultType<(evdev::Key, bool)> { + pub fn map_key(key: &enigo::Key) -> ResultType<(evdev::Key, bool)> { if let Some(k) = KEY_MAP.get(&key) { log::trace!("mapkey {:?}, get {:?}", &key, &k); return Ok((k.clone(), false));