Feat: Wayland flatpak input support | Remote desktop portal (#6675)

* autogen portal code

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use remote desktop portal

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove clipboard portal in favour of #6586

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove clipboard portal

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use select_devices for input capture

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove embedded cursor code as not being used | return session path for input capture

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* setup rdp input

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove simulate example

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* setup rdp input raw key events + mouse movements

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix rdp raw key input

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* refact rdp raw key inpuy & fix right meta key

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* refact and support rdp layout mode key input

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* support rdp mouse clicks

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* support rdp mouse scroll

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* support rdp key sequence input

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use rdp input only when uinput is not available

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* combine rdp input and get_capturables into a single rdp request

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* rdp fix build

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* rdp fix build

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix rdp caps lock

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* format pipewire.rs

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* format rdp_input.rs

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* revert #6628 as rdp request state is now managed (better solution)

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* fix rdp crash on arch kde

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* rdp_input.rs improvements

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* refact request_remote_desktop

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* improve unwraps

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* remove unwraps

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

* use session references instead of clones

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>

---------

Signed-off-by: Sahil Yeole <sahilyeole93@gmail.com>
This commit is contained in:
Sahil Yeole 2023-12-19 08:14:58 +05:30 committed by GitHub
parent dc8a70bb26
commit 445fe6e714
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 916 additions and 246 deletions

View File

@ -151,7 +151,7 @@ pub fn is_cursor_embedded() -> bool {
if is_x11() {
x11::IS_CURSOR_EMBEDDED
} else {
wayland::is_cursor_embedded()
false
}
}

View File

@ -4,34 +4,11 @@ use std::{io, sync::RwLock, time::Duration};
pub struct Capturer(Display, Box<dyn Recorder>, Vec<u8>);
static mut IS_CURSOR_EMBEDDED: Option<bool> = None;
lazy_static::lazy_static! {
static ref MAP_ERR: RwLock<Option<fn(err: String)-> 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<Vec<Display>> {
Ok(pipewire::get_capturables(is_cursor_embedded())
Ok(pipewire::get_capturables()
.map_err(map_err)?
.drain(..)
.map(|x| Display(x))

View File

@ -1,3 +1,5 @@
pub mod capturable;
pub mod pipewire;
mod pipewire_dbus;
mod screencast_portal;
mod request_portal;
pub mod remote_desktop_portal;

View File

@ -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<Option<RdpResponse>> = Mutex::new(None);
}
pub struct RdpResponse {
pub conn: Arc<SyncConnection>,
pub streams: Vec<PwStreamInfo>,
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<dyn Error>> {
fn get_res(capturable: PipeWireCapturable) -> Result<(usize, usize), Box<dyn Error>> {
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<u32, dbus::Error> {
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<PwStreamInfo>), Box<dyn Error>> {
// mostly inspired by https://gitlab.gnome.org/-/snippets/39
pub fn request_remote_desktop() -> Result<
(
SyncConnection,
OwnedFd,
Vec<PwStreamInfo>,
dbus::Path<'static>,
),
Box<dyn Error>,
> {
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<Mutex<Option<dbus::Path>>> = 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<Vec<PipeWireCapturable>, Box<dyn Error>> {
let (conn, fd, streams) = request_screen_cast(capture_cursor)?;
let conn = Arc::new(conn);
Ok(streams
fn on_create_session_response(
fd: Arc<Mutex<Option<OwnedFd>>>,
streams: Arc<Mutex<Vec<PwStreamInfo>>>,
session: Arc<Mutex<Option<dbus::Path<'static>>>>,
failure: Arc<AtomicBool>,
) -> impl Fn(
OrgFreedesktopPortalRequestResponse,
&SyncConnection,
&dbus::Message,
) -> Result<(), Box<dyn Error>> {
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<Mutex<Option<OwnedFd>>>,
streams: Arc<Mutex<Vec<PwStreamInfo>>>,
failure: Arc<AtomicBool>,
session: dbus::Path<'static>,
) -> impl Fn(
OrgFreedesktopPortalRequestResponse,
&SyncConnection,
&dbus::Message,
) -> Result<(), Box<dyn Error>> {
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<Mutex<Option<OwnedFd>>>,
streams: Arc<Mutex<Vec<PwStreamInfo>>>,
failure: Arc<AtomicBool>,
session: dbus::Path<'static>,
) -> impl Fn(
OrgFreedesktopPortalRequestResponse,
&SyncConnection,
&dbus::Message,
) -> Result<(), Box<dyn Error>> {
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<Mutex<Option<OwnedFd>>>,
streams: Arc<Mutex<Vec<PwStreamInfo>>>,
session: dbus::Path<'static>,
) -> impl Fn(
OrgFreedesktopPortalRequestResponse,
&SyncConnection,
&dbus::Message,
) -> Result<(), Box<dyn Error>> {
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<Vec<PipeWireCapturable>, Box<dyn Error>> {
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())
}

View File

@ -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::Path<'static>, dbus::Error>;
fn select_devices(
&self,
session_handle: dbus::Path,
options: arg::PropMap,
) -> Result<dbus::Path<'static>, dbus::Error>;
fn start(
&self,
session_handle: dbus::Path,
parent_window: &str,
options: arg::PropMap,
) -> Result<dbus::Path<'static>, 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<arg::OwnedFd, dbus::Error>;
fn available_device_types(&self) -> Result<u32, dbus::Error>;
fn version(&self) -> Result<u32, dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>>
OrgFreedesktopPortalRemoteDesktop for blocking::Proxy<'a, C>
{
fn create_session(&self, options: arg::PropMap) -> Result<dbus::Path<'static>, 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::Path<'static>, 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::Path<'static>, 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<arg::OwnedFd, dbus::Error> {
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<u32, dbus::Error> {
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
&self,
"org.freedesktop.portal.RemoteDesktop",
"AvailableDeviceTypes",
)
}
fn version(&self) -> Result<u32, dbus::Error> {
<Self as blocking::stdintf::org_freedesktop_dbus::Properties>::get(
&self,
"org.freedesktop.portal.RemoteDesktop",
"version",
)
}
}

View File

@ -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<Target = T>> 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<Self, arg::TypeMismatchError> {
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";
}

View File

@ -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<Target = T>>
)
}
}
pub trait OrgFreedesktopPortalRequest {
fn close(&self) -> Result<(), dbus::Error>;
}
impl<'a, T: blocking::BlockingSender, C: ::std::ops::Deref<Target = T>> 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<Self, arg::TypeMismatchError> {
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";
}

View File

@ -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 {

View File

@ -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<SupportedResolutions> {
#[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"))]

View File

@ -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<dyn std::error::Error>> {
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?;

234
src/server/rdp_input.rs Normal file
View File

@ -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<SyncConnection>,
session: Path<'static>,
}
impl RdpInputKeyboard {
pub fn new(conn: Arc<SyncConnection>, session: Path<'static>) -> ResultType<Self> {
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<SyncConnection>,
session: Path<'static>,
stream: PwStreamInfo,
}
impl RdpInputMouse {
pub fn new(
conn: Arc<SyncConnection>,
session: Path<'static>,
stream: PwStreamInfo,
) -> ResultType<Self> {
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<SyncConnection>,
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<SyncConnection>,
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,
);
}
}

View File

@ -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));