use ashpd::{ desktop::{ PersistMode, Session, remote_desktop::{ Axis, DeviceType, KeyState, NotifyPointerAxisOptions, RemoteDesktop, SelectDevicesOptions, }, }, zbus::AsyncDrop, }; use async_trait::async_trait; use futures::FutureExt; use input_event::{ Event::{Keyboard, Pointer}, KeyboardEvent, PointerEvent, }; use crate::error::EmulationError; use super::{Emulation, EmulationHandle, error::XdpEmulationCreationError}; pub(crate) struct DesktopPortalEmulation { proxy: RemoteDesktop, session: Session, } impl DesktopPortalEmulation { pub(crate) async fn new() -> Result { log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ..."); let proxy = RemoteDesktop::new().await?; // retry when user presses the cancel button log::debug!("creating session ..."); let session = proxy.create_session(Default::default()).await?; log::debug!("selecting devices ..."); let options = SelectDevicesOptions::default() .set_devices(DeviceType::Keyboard | DeviceType::Pointer) .set_persist_mode(PersistMode::ExplicitlyRevoked); proxy.select_devices(&session, options).await?; log::info!("requesting permission for input emulation"); let _devices = proxy .start(&session, None, Default::default()) .await? .response()?; log::debug!("started session"); let session = session; Ok(Self { proxy, session }) } } #[async_trait] impl Emulation for DesktopPortalEmulation { async fn consume( &mut self, event: input_event::Event, _client: EmulationHandle, ) -> Result<(), EmulationError> { match event { Pointer(p) => match p { PointerEvent::Motion { time: _, dx, dy } => { self.proxy .notify_pointer_motion(&self.session, dx, dy, Default::default()) .await?; } PointerEvent::Button { time: _, button, state, } => { let state = match state { 0 => KeyState::Released, _ => KeyState::Pressed, }; self.proxy .notify_pointer_button( &self.session, button as i32, state, Default::default(), ) .await?; } PointerEvent::AxisDiscrete120 { axis, value } => { let axis = match axis { 0 => Axis::Vertical, _ => Axis::Horizontal, }; self.proxy .notify_pointer_axis_discrete( &self.session, axis, value / 120, Default::default(), ) .await?; } PointerEvent::Axis { time: _, axis, value, } => { let axis = match axis { 0 => Axis::Vertical, _ => Axis::Horizontal, }; let (dx, dy) = match axis { Axis::Vertical => (0., value), Axis::Horizontal => (value, 0.), }; self.proxy .notify_pointer_axis( &self.session, dx, dy, NotifyPointerAxisOptions::default().set_finish(true), ) .await?; } }, Keyboard(k) => { match k { KeyboardEvent::Key { time: _, key, state, } => { let state = match state { 0 => KeyState::Released, _ => KeyState::Pressed, }; self.proxy .notify_keyboard_keycode( &self.session, key as i32, state, Default::default(), ) .await?; } KeyboardEvent::Modifiers { .. } => { // ignore } } } } Ok(()) } async fn create(&mut self, _client: EmulationHandle) {} async fn destroy(&mut self, _client: EmulationHandle) {} async fn terminate(&mut self) { if let Err(e) = self.session.close().await { log::warn!("session.close(): {e}"); }; if let Err(e) = self.session.receive_closed().await { log::warn!("session.receive_closed(): {e}"); }; } } impl AsyncDrop for DesktopPortalEmulation { #[doc = r" Perform the async cleanup."] #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)] fn async_drop<'async_trait>( self, ) -> ::core::pin::Pin< Box + ::core::marker::Send + 'async_trait>, > where Self: 'async_trait, { async move { let _ = self.session.close().await; } .boxed() } }