mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-07 20:09:59 +03:00
For a session to actually persist, we need to request a persistence mode which we already do. The portal then returns a restore-token (in the form of an uuid) to us as part of the response to Start. This token must then be passed into the *next* session during SelectDevices to restore the previous session. The token is officially a single-use token, so we need to overwrite it every time. In practise the current XDP implementation may re-use the token but we cannot rely on that. Reading and writing the token is not async since we expect them to be uuid-length. Closes #74.
380 lines
13 KiB
Rust
380 lines
13 KiB
Rust
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<RwLock<Option<(ei::Device, ei::Pointer)>>>,
|
|
scroll: Arc<RwLock<Option<(ei::Device, ei::Scroll)>>>,
|
|
button: Arc<RwLock<Option<(ei::Device, ei::Button)>>>,
|
|
keyboard: Arc<RwLock<Option<(ei::Device, ei::Keyboard)>>>,
|
|
}
|
|
|
|
pub(crate) struct LibeiEmulation<'a> {
|
|
context: ei::Context,
|
|
conn: event::Connection,
|
|
devices: Devices,
|
|
ei_task: JoinHandle<()>,
|
|
error: Arc<Mutex<Option<EmulationError>>>,
|
|
libei_error: Arc<AtomicBool>,
|
|
_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<String> {
|
|
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<Self, LibeiEmulationCreationError> {
|
|
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<AtomicBool>,
|
|
error: Arc<Mutex<Option<EmulationError>>>,
|
|
) {
|
|
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::<Pointer>() {
|
|
devices
|
|
.pointer
|
|
.write()
|
|
.unwrap()
|
|
.replace((device.device().clone(), pointer));
|
|
}
|
|
if let Some(keyboard) = e.device().interface::<Keyboard>() {
|
|
devices
|
|
.keyboard
|
|
.write()
|
|
.unwrap()
|
|
.replace((device.device().clone(), keyboard));
|
|
}
|
|
if let Some(scroll) = e.device().interface::<Scroll>() {
|
|
devices
|
|
.scroll
|
|
.write()
|
|
.unwrap()
|
|
.replace((device.device().clone(), scroll));
|
|
}
|
|
if let Some(button) = e.device().interface::<Button>() {
|
|
devices
|
|
.button
|
|
.write()
|
|
.unwrap()
|
|
.replace((device.device().clone(), button));
|
|
}
|
|
}
|
|
EiEvent::DeviceRemoved(e) => {
|
|
log::debug!("device removed: {:?}", e.device().device_type());
|
|
}
|
|
EiEvent::DevicePaused(e) => {
|
|
log::debug!("device paused: {:?}", e.device().device_type());
|
|
}
|
|
EiEvent::DeviceResumed(e) => {
|
|
log::debug!("device resumed: {:?}", e.device().device_type());
|
|
e.device().device().start_emulating(0, 0);
|
|
}
|
|
EiEvent::KeyboardModifiers(e) => {
|
|
log::debug!("modifiers: {e:?}");
|
|
}
|
|
// only for receiver context
|
|
// EiEvent::Frame(_) => { },
|
|
// EiEvent::DeviceStartEmulating(_) => { },
|
|
// EiEvent::DeviceStopEmulating(_) => { },
|
|
// EiEvent::PointerMotion(_) => { },
|
|
// EiEvent::PointerMotionAbsolute(_) => { },
|
|
// EiEvent::Button(_) => { },
|
|
// EiEvent::ScrollDelta(_) => { },
|
|
// EiEvent::ScrollStop(_) => { },
|
|
// EiEvent::ScrollCancel(_) => { },
|
|
// EiEvent::ScrollDiscrete(_) => { },
|
|
// EiEvent::KeyboardKey(_) => { },
|
|
// EiEvent::TouchDown(_) => { },
|
|
// EiEvent::TouchUp(_) => { },
|
|
// EiEvent::TouchMotion(_) => { },
|
|
_ => unreachable!("unexpected ei event"),
|
|
}
|
|
context.flush().map_err(|e| io::Error::new(e.kind(), e))?;
|
|
}
|
|
}
|