use futures::{StreamExt, future}; use std::{ env, fs, io, os::{fd::OwnedFd, unix::net::UnixStream}, path::PathBuf, sync::{ Arc, Mutex, RwLock, atomic::{AtomicBool, Ordering}, }, time::{SystemTime, UNIX_EPOCH}, }; use tokio::task::JoinHandle; use ashpd::desktop::{ PersistMode, Session, remote_desktop::{DeviceType, RemoteDesktop}, }; use async_trait::async_trait; use reis::{ ei::{ self, Button, Keyboard, Pointer, Scroll, button::ButtonState, handshake::ContextType, keyboard::KeyState, }, event::{self, Connection, DeviceCapability, DeviceEvent, EiEvent, SeatEvent}, tokio::EiConvertEventStream, }; use input_event::{Event, KeyboardEvent, PointerEvent}; use crate::error::EmulationError; use super::{Emulation, EmulationHandle, error::LibeiEmulationCreationError}; #[derive(Clone, Default)] struct Devices { pointer: Arc>>, scroll: Arc>>, button: Arc>>, keyboard: Arc>>, } pub(crate) struct LibeiEmulation<'a> { context: ei::Context, conn: event::Connection, devices: Devices, ei_task: JoinHandle<()>, error: Arc>>, libei_error: Arc, _remote_desktop: RemoteDesktop<'a>, session: Session<'a, RemoteDesktop<'a>>, } /// Get the path to the RemoteDesktop token file fn get_token_file_path() -> PathBuf { let cache_dir = env::var("XDG_CACHE_HOME") .ok() .map(PathBuf::from) .unwrap_or_else(|| { let home = env::var("HOME").expect("HOME not set"); PathBuf::from(home).join(".cache") }); cache_dir.join("lan-mouse").join("remote-desktop.token") } /// Read the RemoteDesktop token from file fn read_token() -> Option { let token_path = get_token_file_path(); match fs::read_to_string(&token_path) { Ok(token) => Some(token.trim().to_string()), Err(_) => None, } } /// Write the RemoteDesktop token to file fn write_token(token: &str) -> io::Result<()> { let token_path = get_token_file_path(); if let Some(parent) = token_path.parent() { fs::create_dir_all(parent)?; } fs::write(&token_path, token)?; Ok(()) } async fn get_ei_fd<'a>() -> Result<(RemoteDesktop<'a>, Session<'a, RemoteDesktop<'a>>, OwnedFd), ashpd::Error> { let remote_desktop = RemoteDesktop::new().await?; let restore_token = read_token(); log::debug!("creating session ..."); let session = remote_desktop.create_session().await?; log::debug!("selecting devices ..."); remote_desktop .select_devices( &session, DeviceType::Keyboard | DeviceType::Pointer, restore_token.as_deref(), PersistMode::ExplicitlyRevoked, ) .await?; log::info!("requesting permission for input emulation"); let start_response = remote_desktop.start(&session, None).await?.response()?; // The restore token is only valid once, we need to re-save it each time if let Some(token_str) = start_response.restore_token() { if let Err(e) = write_token(token_str) { log::warn!("failed to save RemoteDesktop token: {}", e); } } let fd = remote_desktop.connect_to_eis(&session).await?; Ok((remote_desktop, session, fd)) } impl LibeiEmulation<'_> { pub(crate) async fn new() -> Result { let (_remote_desktop, session, eifd) = get_ei_fd().await?; let stream = UnixStream::from(eifd); stream.set_nonblocking(true)?; let context = ei::Context::new(stream)?; let (conn, events) = context .handshake_tokio("de.feschber.LanMouse", ContextType::Sender) .await?; let devices = Devices::default(); let libei_error = Arc::new(AtomicBool::default()); let error = Arc::new(Mutex::new(None)); let ei_handler = ei_task( events, conn.clone(), context.clone(), devices.clone(), libei_error.clone(), error.clone(), ); let ei_task = tokio::task::spawn_local(ei_handler); Ok(Self { context, conn, devices, ei_task, error, libei_error, _remote_desktop, session, }) } } impl Drop for LibeiEmulation<'_> { fn drop(&mut self) { self.ei_task.abort(); } } #[async_trait] impl Emulation for LibeiEmulation<'_> { async fn consume( &mut self, event: Event, _handle: EmulationHandle, ) -> Result<(), EmulationError> { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_micros() as u64; if self.libei_error.load(Ordering::SeqCst) { // don't break sending additional events but signal error if let Some(e) = self.error.lock().unwrap().take() { return Err(e); } } match event { Event::Pointer(p) => match p { PointerEvent::Motion { time: _, dx, dy } => { let pointer_device = self.devices.pointer.read().unwrap(); if let Some((d, p)) = pointer_device.as_ref() { p.motion_relative(dx as f32, dy as f32); d.frame(self.conn.serial(), now); } } PointerEvent::Button { time: _, button, state, } => { let button_device = self.devices.button.read().unwrap(); if let Some((d, b)) = button_device.as_ref() { b.button( button, match state { 0 => ButtonState::Released, _ => ButtonState::Press, }, ); d.frame(self.conn.serial(), now); } } PointerEvent::Axis { time: _, axis, value, } => { let scroll_device = self.devices.scroll.read().unwrap(); if let Some((d, s)) = scroll_device.as_ref() { match axis { 0 => s.scroll(0., value as f32), _ => s.scroll(value as f32, 0.), } d.frame(self.conn.serial(), now); } } PointerEvent::AxisDiscrete120 { axis, value } => { let scroll_device = self.devices.scroll.read().unwrap(); if let Some((d, s)) = scroll_device.as_ref() { match axis { 0 => s.scroll_discrete(0, value), _ => s.scroll_discrete(value, 0), } d.frame(self.conn.serial(), now); } } }, Event::Keyboard(k) => match k { KeyboardEvent::Key { time: _, key, state, } => { let keyboard_device = self.devices.keyboard.read().unwrap(); if let Some((d, k)) = keyboard_device.as_ref() { k.key( key, match state { 0 => KeyState::Released, _ => KeyState::Press, }, ); d.frame(self.conn.serial(), now); } } KeyboardEvent::Modifiers { .. } => {} }, } self.context .flush() .map_err(|e| io::Error::new(e.kind(), e))?; Ok(()) } async fn create(&mut self, _: EmulationHandle) {} async fn destroy(&mut self, _: EmulationHandle) {} async fn terminate(&mut self) { let _ = self.session.close().await; self.ei_task.abort(); } } async fn ei_task( mut events: EiConvertEventStream, _conn: Connection, context: ei::Context, devices: Devices, libei_error: Arc, error: Arc>>, ) { loop { match ei_event_handler(&mut events, &context, &devices).await { Ok(()) => {} Err(e) => { libei_error.store(true, Ordering::SeqCst); error.lock().unwrap().replace(e); // wait for termination -> otherwise we will loop forever future::pending::<()>().await; } } } } async fn ei_event_handler( events: &mut EiConvertEventStream, context: &ei::Context, devices: &Devices, ) -> Result<(), EmulationError> { loop { let event = events.next().await.ok_or(EmulationError::EndOfStream)??; const CAPABILITIES: &[DeviceCapability] = &[ DeviceCapability::Pointer, DeviceCapability::PointerAbsolute, DeviceCapability::Keyboard, DeviceCapability::Touch, DeviceCapability::Scroll, DeviceCapability::Button, ]; log::debug!("{event:?}"); match event { EiEvent::Disconnected(e) => { log::debug!("ei disconnected: {e:?}"); return Err(EmulationError::EndOfStream); } EiEvent::SeatAdded(e) => { e.seat().bind_capabilities(CAPABILITIES); } EiEvent::SeatRemoved(e) => { log::debug!("seat removed: {:?}", e.seat()); } EiEvent::DeviceAdded(e) => { let device_type = e.device().device_type(); log::debug!("device added: {device_type:?}"); e.device().device(); let device = e.device(); if let Some(pointer) = e.device().interface::() { devices .pointer .write() .unwrap() .replace((device.device().clone(), pointer)); } if let Some(keyboard) = e.device().interface::() { devices .keyboard .write() .unwrap() .replace((device.device().clone(), keyboard)); } if let Some(scroll) = e.device().interface::() { devices .scroll .write() .unwrap() .replace((device.device().clone(), scroll)); } if let Some(button) = e.device().interface::