mirror of
https://github.com/feschber/lan-mouse.git
synced 2026-03-08 04:20:01 +03:00
373 lines
12 KiB
Rust
373 lines
12 KiB
Rust
use futures::{future, StreamExt};
|
|
use once_cell::sync::Lazy;
|
|
use std::{
|
|
collections::HashMap,
|
|
io,
|
|
os::{fd::OwnedFd, unix::net::UnixStream},
|
|
sync::{
|
|
atomic::{AtomicBool, AtomicU32, Ordering},
|
|
Arc, Mutex, RwLock,
|
|
},
|
|
time::{SystemTime, UNIX_EPOCH},
|
|
};
|
|
use tokio::task::JoinHandle;
|
|
|
|
use ashpd::{
|
|
desktop::{
|
|
remote_desktop::{DeviceType, RemoteDesktop},
|
|
PersistMode, Session,
|
|
},
|
|
WindowIdentifier,
|
|
};
|
|
use async_trait::async_trait;
|
|
|
|
use reis::{
|
|
ei::{
|
|
self, button::ButtonState, handshake::ContextType, keyboard::KeyState, Button, Keyboard,
|
|
Pointer, Scroll,
|
|
},
|
|
event::{DeviceCapability, DeviceEvent, EiEvent, SeatEvent},
|
|
tokio::{ei_handshake, EiConvertEventStream, EiEventStream},
|
|
};
|
|
|
|
use input_event::{Event, KeyboardEvent, PointerEvent};
|
|
|
|
use crate::error::{EmulationError, ReisConvertStreamError};
|
|
|
|
use super::{error::LibeiEmulationCreationError, EmulationHandle, InputEmulation};
|
|
|
|
static INTERFACES: Lazy<HashMap<&'static str, u32>> = Lazy::new(|| {
|
|
let mut m = HashMap::new();
|
|
m.insert("ei_connection", 1);
|
|
m.insert("ei_callback", 1);
|
|
m.insert("ei_pingpong", 1);
|
|
m.insert("ei_seat", 1);
|
|
m.insert("ei_device", 2);
|
|
m.insert("ei_pointer", 1);
|
|
m.insert("ei_pointer_absolute", 1);
|
|
m.insert("ei_scroll", 1);
|
|
m.insert("ei_button", 1);
|
|
m.insert("ei_keyboard", 1);
|
|
m.insert("ei_touchscreen", 1);
|
|
m
|
|
});
|
|
|
|
#[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 struct LibeiEmulation<'a> {
|
|
context: ei::Context,
|
|
devices: Devices,
|
|
ei_task: JoinHandle<()>,
|
|
error: Arc<Mutex<Option<EmulationError>>>,
|
|
libei_error: Arc<AtomicBool>,
|
|
serial: AtomicU32,
|
|
_remote_desktop: RemoteDesktop<'a>,
|
|
session: Session<'a, RemoteDesktop<'a>>,
|
|
}
|
|
|
|
async fn get_ei_fd<'a>(
|
|
) -> Result<(RemoteDesktop<'a>, Session<'a, RemoteDesktop<'a>>, OwnedFd), ashpd::Error> {
|
|
let remote_desktop = RemoteDesktop::new().await?;
|
|
|
|
log::debug!("creating session ...");
|
|
let session = remote_desktop.create_session().await?;
|
|
|
|
log::debug!("selecting devices ...");
|
|
remote_desktop
|
|
.select_devices(
|
|
&session,
|
|
DeviceType::Keyboard | DeviceType::Pointer,
|
|
None,
|
|
PersistMode::ExplicitlyRevoked,
|
|
)
|
|
.await?;
|
|
|
|
log::info!("requesting permission for input emulation");
|
|
let _devices = remote_desktop
|
|
.start(&session, &WindowIdentifier::default())
|
|
.await?
|
|
.response()?;
|
|
|
|
let fd = remote_desktop.connect_to_eis(&session).await?;
|
|
Ok((remote_desktop, session, fd))
|
|
}
|
|
|
|
impl<'a> LibeiEmulation<'a> {
|
|
pub 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 mut events = EiEventStream::new(context.clone())?;
|
|
let handshake = ei_handshake(
|
|
&mut events,
|
|
"de.feschber.LanMouse",
|
|
ContextType::Sender,
|
|
&INTERFACES,
|
|
)
|
|
.await?;
|
|
let events = EiConvertEventStream::new(events, handshake.serial);
|
|
let devices = Devices::default();
|
|
let libei_error = Arc::new(AtomicBool::default());
|
|
let error = Arc::new(Mutex::new(None));
|
|
let ei_handler = ei_task(
|
|
events,
|
|
context.clone(),
|
|
devices.clone(),
|
|
libei_error.clone(),
|
|
error.clone(),
|
|
);
|
|
let ei_task = tokio::task::spawn_local(ei_handler);
|
|
|
|
let serial = AtomicU32::new(handshake.serial);
|
|
|
|
Ok(Self {
|
|
context,
|
|
devices,
|
|
ei_task,
|
|
error,
|
|
libei_error,
|
|
serial,
|
|
_remote_desktop,
|
|
session,
|
|
})
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for LibeiEmulation<'a> {
|
|
fn drop(&mut self) {
|
|
self.ei_task.abort();
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl<'a> InputEmulation for LibeiEmulation<'a> {
|
|
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.serial.load(Ordering::SeqCst), 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.serial.load(Ordering::SeqCst), 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.serial.load(Ordering::SeqCst), 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.serial.load(Ordering::SeqCst), now);
|
|
}
|
|
}
|
|
PointerEvent::Frame {} => {}
|
|
},
|
|
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.serial.load(Ordering::SeqCst), 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,
|
|
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)?
|
|
.map_err(ReisConvertStreamError::from)?;
|
|
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))?;
|
|
}
|
|
}
|