Compare commits

...

3 Commits

Author SHA1 Message Date
Ferdinand Schober
09f08f1798 fix drop impl for desktop-portal 2024-07-05 00:31:16 +02:00
Ferdinand Schober
f97621e987 adjust error handling 2024-07-04 23:44:32 +02:00
Ferdinand Schober
b3aa3f4281 remove dispatch workaround 2024-07-04 22:40:05 +02:00
13 changed files with 357 additions and 394 deletions

1
Cargo.lock generated
View File

@@ -1226,6 +1226,7 @@ dependencies = [
"input-event", "input-event",
"keycode", "keycode",
"log", "log",
"once_cell",
"reis", "reis",
"thiserror", "thiserror",
"tokio", "tokio",

View File

@@ -14,6 +14,7 @@ log = "0.4.22"
input-event = { path = "../input-event", version = "0.1.0" } input-event = { path = "../input-event", version = "0.1.0" }
thiserror = "1.0.61" thiserror = "1.0.61"
tokio = { version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "process", "rt", "sync", "signal"] } tokio = { version = "1.32.0", features = ["io-util", "io-std", "macros", "net", "process", "rt", "sync", "signal"] }
once_cell = "1.19.0"
[target.'cfg(all(unix, not(target_os="macos")))'.dependencies] [target.'cfg(all(unix, not(target_os="macos")))'.dependencies]
wayland-client = { version="0.31.1", optional = true } wayland-client = { version="0.31.1", optional = true }

View File

@@ -1,6 +1,8 @@
use async_trait::async_trait; use async_trait::async_trait;
use input_event::Event; use input_event::Event;
use crate::error::EmulationError;
use super::{EmulationHandle, InputEmulation}; use super::{EmulationHandle, InputEmulation};
#[derive(Default)] #[derive(Default)]
@@ -14,8 +16,13 @@ impl DummyEmulation {
#[async_trait] #[async_trait]
impl InputEmulation for DummyEmulation { impl InputEmulation for DummyEmulation {
async fn consume(&mut self, event: Event, client_handle: EmulationHandle) { async fn consume(
&mut self,
event: Event,
client_handle: EmulationHandle,
) -> Result<(), EmulationError> {
log::info!("received event: ({client_handle}) {event}"); log::info!("received event: ({client_handle}) {event}");
Ok(())
} }
async fn create(&mut self, _: EmulationHandle) {} async fn create(&mut self, _: EmulationHandle) {}
async fn destroy(&mut self, _: EmulationHandle) {} async fn destroy(&mut self, _: EmulationHandle) {}

View File

@@ -1,4 +1,4 @@
use std::fmt::Display; use std::{fmt::Display, io};
use thiserror::Error; use thiserror::Error;
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
@@ -8,6 +8,28 @@ use wayland_client::{
ConnectError, DispatchError, ConnectError, DispatchError,
}; };
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
use reis::tokio::HandshakeError;
#[derive(Debug, Error)]
pub enum EmulationError {
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
#[error("libei error flushing events: `{0}`")]
Libei(#[from] reis::event::Error),
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
#[error("wayland error: `{0}`")]
Wayland(#[from] wayland_client::backend::WaylandError),
#[cfg(all(
unix,
any(feature = "xdg_desktop_portal", feature = "libei"),
not(target_os = "macos")
))]
#[error("xdg-desktop-portal: `{0}`")]
Ashpd(#[from] ashpd::Error),
#[error("io error: `{0}`")]
Io(#[from] io::Error),
}
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum EmulationCreationError { pub enum EmulationCreationError {
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
@@ -104,6 +126,7 @@ impl Display for WlrootsEmulationCreationError {
pub enum LibeiEmulationCreationError { pub enum LibeiEmulationCreationError {
Ashpd(#[from] ashpd::Error), Ashpd(#[from] ashpd::Error),
Io(#[from] std::io::Error), Io(#[from] std::io::Error),
Handshake(#[from] HandshakeError),
} }
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
@@ -112,6 +135,7 @@ impl Display for LibeiEmulationCreationError {
match self { match self {
LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"), LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"),
LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"), LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"),
LibeiEmulationCreationError::Handshake(e) => write!(f, "error in libei handshake: {e}"),
} }
} }
} }

View File

@@ -1,5 +1,6 @@
use async_trait::async_trait; use async_trait::async_trait;
use std::{fmt::Display, future}; use error::EmulationError;
use std::fmt::Display;
use input_event::Event; use input_event::Event;
@@ -70,14 +71,13 @@ impl Display for Backend {
#[async_trait] #[async_trait]
pub trait InputEmulation: Send { pub trait InputEmulation: Send {
async fn consume(&mut self, event: Event, handle: EmulationHandle); async fn consume(
&mut self,
event: Event,
handle: EmulationHandle,
) -> Result<(), EmulationError>;
async fn create(&mut self, handle: EmulationHandle); async fn create(&mut self, handle: EmulationHandle);
async fn destroy(&mut self, handle: EmulationHandle); async fn destroy(&mut self, handle: EmulationHandle);
/// this function is waited on continuously and can be used to handle events
async fn dispatch(&mut self) -> Result<()> {
let _: () = future::pending().await;
Ok(())
}
} }
pub async fn create_backend( pub async fn create_backend(

View File

@@ -1,10 +1,17 @@
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use futures::StreamExt;
use once_cell::sync::Lazy;
use std::{ use std::{
collections::HashMap, collections::HashMap,
io, io,
os::{fd::OwnedFd, unix::net::UnixStream}, os::{fd::OwnedFd, unix::net::UnixStream},
sync::{
atomic::{AtomicU32, Ordering},
Arc, RwLock,
},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use tokio::task::JoinHandle;
use ashpd::{ use ashpd::{
desktop::{ desktop::{
@@ -14,34 +21,51 @@ use ashpd::{
WindowIdentifier, WindowIdentifier,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use futures::StreamExt;
use reis::{ use reis::{
ei::{self, button::ButtonState, handshake::ContextType, keyboard::KeyState}, ei::{
tokio::EiEventStream, self, button::ButtonState, handshake::ContextType, keyboard::KeyState, Button, Keyboard,
PendingRequestResult, Pointer, Scroll,
},
event::{DeviceCapability, DeviceEvent, EiEvent, SeatEvent},
tokio::{ei_handshake, EiConvertEventStream, EiEventStream},
}; };
use input_event::{Event, KeyboardEvent, PointerEvent}; use input_event::{Event, KeyboardEvent, PointerEvent};
use crate::error::EmulationError;
use super::{error::LibeiEmulationCreationError, EmulationHandle, InputEmulation}; 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 { pub struct LibeiEmulation {
handshake: bool,
context: ei::Context, context: ei::Context,
events: EiEventStream, devices: Devices,
pointer: Option<(ei::Device, ei::Pointer)>, serial: AtomicU32,
has_pointer: bool, ei_task: JoinHandle<Result<()>>,
scroll: Option<(ei::Device, ei::Scroll)>,
has_scroll: bool,
button: Option<(ei::Device, ei::Button)>,
has_button: bool,
keyboard: Option<(ei::Device, ei::Keyboard)>,
has_keyboard: bool,
capabilities: HashMap<String, u64>,
capability_mask: u64,
sequence: u32,
serial: u32,
} }
async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> { async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
@@ -77,37 +101,48 @@ async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
impl LibeiEmulation { impl LibeiEmulation {
pub async fn new() -> Result<Self, LibeiEmulationCreationError> { pub async fn new() -> Result<Self, LibeiEmulationCreationError> {
// fd is owned by the message, so we need to dup it
let eifd = get_ei_fd().await?; let eifd = get_ei_fd().await?;
let stream = UnixStream::from(eifd); let stream = UnixStream::from(eifd);
// let stream = UnixStream::connect("/run/user/1000/eis-0")?;
stream.set_nonblocking(true)?; stream.set_nonblocking(true)?;
let context = ei::Context::new(stream)?; let context = ei::Context::new(stream)?;
context.flush().map_err(|e| io::Error::new(e.kind(), e))?; context.flush().map_err(|e| io::Error::new(e.kind(), e))?;
let events = EiEventStream::new(context.clone())?; 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 ei_task =
tokio::task::spawn_local(ei_event_handler(events, context.clone(), devices.clone()));
let serial = AtomicU32::new(handshake.serial);
Ok(Self { Ok(Self {
handshake: false, serial,
context, context,
events, ei_task,
pointer: None, devices,
button: None,
scroll: None,
keyboard: None,
has_pointer: false,
has_button: false,
has_scroll: false,
has_keyboard: false,
capabilities: HashMap::new(),
capability_mask: 0,
sequence: 0,
serial: 0,
}) })
} }
} }
impl Drop for LibeiEmulation {
fn drop(&mut self) {
self.ei_task.abort();
}
}
#[async_trait] #[async_trait]
impl InputEmulation for LibeiEmulation { impl InputEmulation for LibeiEmulation {
async fn consume(&mut self, event: Event, _client_handle: EmulationHandle) { async fn consume(
&mut self,
event: Event,
_handle: EmulationHandle,
) -> Result<(), EmulationError> {
let now = SystemTime::now() let now = SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)
.unwrap() .unwrap()
@@ -119,12 +154,10 @@ impl InputEmulation for LibeiEmulation {
relative_x, relative_x,
relative_y, relative_y,
} => { } => {
if !self.has_pointer { let pointer_device = self.devices.pointer.read().unwrap();
return; if let Some((d, p)) = pointer_device.as_ref() {
}
if let Some((d, p)) = self.pointer.as_mut() {
p.motion_relative(relative_x as f32, relative_y as f32); p.motion_relative(relative_x as f32, relative_y as f32);
d.frame(self.serial, now); d.frame(self.serial.load(Ordering::SeqCst), now);
} }
} }
PointerEvent::Button { PointerEvent::Button {
@@ -132,10 +165,8 @@ impl InputEmulation for LibeiEmulation {
button, button,
state, state,
} => { } => {
if !self.has_button { let button_device = self.devices.button.read().unwrap();
return; if let Some((d, b)) = button_device.as_ref() {
}
if let Some((d, b)) = self.button.as_mut() {
b.button( b.button(
button, button,
match state { match state {
@@ -143,7 +174,7 @@ impl InputEmulation for LibeiEmulation {
_ => ButtonState::Press, _ => ButtonState::Press,
}, },
); );
d.frame(self.serial, now); d.frame(self.serial.load(Ordering::SeqCst), now);
} }
} }
PointerEvent::Axis { PointerEvent::Axis {
@@ -151,27 +182,23 @@ impl InputEmulation for LibeiEmulation {
axis, axis,
value, value,
} => { } => {
if !self.has_scroll { let scroll_device = self.devices.scroll.read().unwrap();
return; if let Some((d, s)) = scroll_device.as_ref() {
}
if let Some((d, s)) = self.scroll.as_mut() {
match axis { match axis {
0 => s.scroll(0., value as f32), 0 => s.scroll(0., value as f32),
_ => s.scroll(value as f32, 0.), _ => s.scroll(value as f32, 0.),
} }
d.frame(self.serial, now); d.frame(self.serial.load(Ordering::SeqCst), now);
} }
} }
PointerEvent::AxisDiscrete120 { axis, value } => { PointerEvent::AxisDiscrete120 { axis, value } => {
if !self.has_scroll { let scroll_device = self.devices.scroll.read().unwrap();
return; if let Some((d, s)) = scroll_device.as_ref() {
}
if let Some((d, s)) = self.scroll.as_mut() {
match axis { match axis {
0 => s.scroll_discrete(0, value), 0 => s.scroll_discrete(0, value),
_ => s.scroll_discrete(value, 0), _ => s.scroll_discrete(value, 0),
} }
d.frame(self.serial, now); d.frame(self.serial.load(Ordering::SeqCst), now);
} }
} }
PointerEvent::Frame {} => {} PointerEvent::Frame {} => {}
@@ -182,10 +209,8 @@ impl InputEmulation for LibeiEmulation {
key, key,
state, state,
} => { } => {
if !self.has_keyboard { let keyboard_device = self.devices.keyboard.read().unwrap();
return; if let Some((d, k)) = keyboard_device.as_ref() {
}
if let Some((d, k)) = &mut self.keyboard {
k.key( k.key(
key, key,
match state { match state {
@@ -193,205 +218,119 @@ impl InputEmulation for LibeiEmulation {
_ => KeyState::Press, _ => KeyState::Press,
}, },
); );
d.frame(self.serial, now); d.frame(self.serial.load(Ordering::SeqCst), now);
} }
} }
KeyboardEvent::Modifiers { .. } => {} KeyboardEvent::Modifiers { .. } => {}
}, },
_ => {} _ => {}
} }
self.context.flush().unwrap(); self.context
} .flush()
.map_err(|e| io::Error::new(e.kind(), e))?;
async fn dispatch(&mut self) -> Result<()> {
let event = match self.events.next().await {
Some(e) => e?,
None => return Err(anyhow!("libei connection lost")),
};
let event = match event {
PendingRequestResult::Request(result) => result,
PendingRequestResult::ParseError(e) => {
return Err(anyhow!("libei protocol violation: {e}"))
}
PendingRequestResult::InvalidObject(e) => return Err(anyhow!("invalid object {e}")),
};
match event {
ei::Event::Handshake(handshake, request) => match request {
ei::handshake::Event::HandshakeVersion { version } => {
if self.handshake {
return Ok(());
}
log::info!("libei version {}", version);
// sender means we are sending events _to_ the eis server
handshake.handshake_version(version); // FIXME
handshake.context_type(ContextType::Sender);
handshake.name("ei-demo-client");
handshake.interface_version("ei_connection", 1);
handshake.interface_version("ei_callback", 1);
handshake.interface_version("ei_pingpong", 1);
handshake.interface_version("ei_seat", 1);
handshake.interface_version("ei_device", 2);
handshake.interface_version("ei_pointer", 1);
handshake.interface_version("ei_pointer_absolute", 1);
handshake.interface_version("ei_scroll", 1);
handshake.interface_version("ei_button", 1);
handshake.interface_version("ei_keyboard", 1);
handshake.interface_version("ei_touchscreen", 1);
handshake.finish();
self.handshake = true;
}
ei::handshake::Event::InterfaceVersion { name, version } => {
log::debug!("handshake: Interface {name} @ {version}");
}
ei::handshake::Event::Connection { serial, connection } => {
connection.sync(1);
self.serial = serial;
}
_ => unreachable!(),
},
ei::Event::Connection(_connection, request) => match request {
ei::connection::Event::Seat { seat } => {
log::debug!("connected to seat: {seat:?}");
}
ei::connection::Event::Ping { ping } => {
ping.done(0);
}
ei::connection::Event::Disconnected {
last_serial: _,
reason,
explanation,
} => {
log::debug!("ei - disconnected: reason: {reason:?}: {explanation}")
}
ei::connection::Event::InvalidObject {
last_serial,
invalid_id,
} => {
return Err(anyhow!(
"invalid object: id: {invalid_id}, serial: {last_serial}"
));
}
_ => unreachable!(),
},
ei::Event::Device(device, request) => match request {
ei::device::Event::Destroyed { serial } => {
log::debug!("device destroyed: {device:?} - serial: {serial}")
}
ei::device::Event::Name { name } => {
log::debug!("device name: {name}")
}
ei::device::Event::DeviceType { device_type } => {
log::debug!("device type: {device_type:?}")
}
ei::device::Event::Dimensions { width, height } => {
log::debug!("device dimensions: {width}x{height}")
}
ei::device::Event::Region {
offset_x,
offset_y,
width,
hight,
scale,
} => log::debug!(
"device region: {width}x{hight} @ ({offset_x},{offset_y}), scale: {scale}"
),
ei::device::Event::Interface { object } => {
log::debug!("device interface: {object:?}");
if object.interface().eq("ei_pointer") {
log::debug!("GOT POINTER DEVICE");
self.pointer.replace((device, object.downcast().unwrap()));
} else if object.interface().eq("ei_button") {
log::debug!("GOT BUTTON DEVICE");
self.button.replace((device, object.downcast().unwrap()));
} else if object.interface().eq("ei_scroll") {
log::debug!("GOT SCROLL DEVICE");
self.scroll.replace((device, object.downcast().unwrap()));
} else if object.interface().eq("ei_keyboard") {
log::debug!("GOT KEYBOARD DEVICE");
self.keyboard.replace((device, object.downcast().unwrap()));
}
}
ei::device::Event::Done => {
log::debug!("device: done {device:?}");
}
ei::device::Event::Resumed { serial } => {
self.serial = serial;
device.start_emulating(serial, self.sequence);
self.sequence += 1;
log::debug!("resumed: {device:?}");
if let Some((d, _)) = &mut self.pointer {
if d == &device {
log::debug!("pointer resumed {serial}");
self.has_pointer = true;
}
}
if let Some((d, _)) = &mut self.button {
if d == &device {
log::debug!("button resumed {serial}");
self.has_button = true;
}
}
if let Some((d, _)) = &mut self.scroll {
if d == &device {
log::debug!("scroll resumed {serial}");
self.has_scroll = true;
}
}
if let Some((d, _)) = &mut self.keyboard {
if d == &device {
log::debug!("keyboard resumed {serial}");
self.has_keyboard = true;
}
}
}
ei::device::Event::Paused { serial } => {
self.has_pointer = false;
self.has_button = false;
self.serial = serial;
}
ei::device::Event::StartEmulating { serial, sequence } => {
log::debug!("start emulating {serial}, {sequence}")
}
ei::device::Event::StopEmulating { serial } => {
log::debug!("stop emulating {serial}")
}
ei::device::Event::Frame { serial, timestamp } => {
log::debug!("frame: {serial}, {timestamp}");
}
ei::device::Event::RegionMappingId { mapping_id } => {
log::debug!("RegionMappingId {mapping_id}")
}
e => log::debug!("invalid event: {e:?}"),
},
ei::Event::Seat(seat, request) => match request {
ei::seat::Event::Destroyed { serial } => {
self.serial = serial;
log::debug!("seat destroyed: {seat:?}");
}
ei::seat::Event::Name { name } => {
log::debug!("seat name: {name}");
}
ei::seat::Event::Capability { mask, interface } => {
log::debug!("seat capabilities: {mask}, interface: {interface:?}");
self.capabilities.insert(interface, mask);
self.capability_mask |= mask;
}
ei::seat::Event::Done => {
log::debug!("seat done");
log::debug!("binding capabilities: {}", self.capability_mask);
seat.bind(self.capability_mask);
}
ei::seat::Event::Device { device } => {
log::debug!("seat: new device - {device:?}");
}
_ => todo!(),
},
e => log::debug!("unhandled event: {e:?}"),
}
self.context.flush()?;
Ok(()) Ok(())
} }
async fn create(&mut self, _: EmulationHandle) {} async fn create(&mut self, _: EmulationHandle) {}
async fn destroy(&mut self, _: EmulationHandle) {} async fn destroy(&mut self, _: EmulationHandle) {}
} }
async fn ei_event_handler(
mut events: EiConvertEventStream,
context: ei::Context,
devices: Devices,
) -> Result<()> {
loop {
let event = events
.next()
.await
.ok_or(anyhow!("ei stream closed"))?
.map_err(|e| anyhow!("libei error: {e:?}"))?;
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:?}");
break;
}
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()?;
}
Ok(())
}

View File

@@ -1,4 +1,4 @@
use super::{EmulationHandle, InputEmulation}; use super::{error::EmulationError, EmulationHandle, InputEmulation};
use async_trait::async_trait; use async_trait::async_trait;
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
use core_graphics::event::{ use core_graphics::event::{
@@ -107,7 +107,11 @@ fn key_event(event_source: CGEventSource, key: u16, state: u8) {
#[async_trait] #[async_trait]
impl InputEmulation for MacOSEmulation { impl InputEmulation for MacOSEmulation {
async fn consume(&mut self, event: Event, _handle: EmulationHandle) { async fn consume(
&mut self,
event: Event,
_handle: EmulationHandle,
) -> Result<(), EmulationError> {
match event { match event {
Event::Pointer(pointer_event) => match pointer_event { Event::Pointer(pointer_event) => match pointer_event {
PointerEvent::Motion { PointerEvent::Motion {
@@ -129,7 +133,7 @@ impl InputEmulation for MacOSEmulation {
Some(l) => l, Some(l) => l,
None => { None => {
log::warn!("could not get mouse location!"); log::warn!("could not get mouse location!");
return; return Ok(());
} }
}; };
@@ -153,7 +157,7 @@ impl InputEmulation for MacOSEmulation {
Ok(e) => e, Ok(e) => e,
Err(_) => { Err(_) => {
log::warn!("mouse event creation failed!"); log::warn!("mouse event creation failed!");
return; return Ok(());
} }
}; };
event.set_integer_value_field( event.set_integer_value_field(
@@ -192,7 +196,7 @@ impl InputEmulation for MacOSEmulation {
} }
_ => { _ => {
log::warn!("invalid button event: {button},{state}"); log::warn!("invalid button event: {button},{state}");
return; return Ok(());
} }
}; };
// store button state // store button state
@@ -208,7 +212,7 @@ impl InputEmulation for MacOSEmulation {
Ok(e) => e, Ok(e) => e,
Err(()) => { Err(()) => {
log::warn!("mouse event creation failed!"); log::warn!("mouse event creation failed!");
return; return Ok(());
} }
}; };
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
@@ -224,7 +228,7 @@ impl InputEmulation for MacOSEmulation {
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x) 1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
_ => { _ => {
log::warn!("invalid scroll event: {axis}, {value}"); log::warn!("invalid scroll event: {axis}, {value}");
return; return Ok(());
} }
}; };
let event = match CGEvent::new_scroll_event( let event = match CGEvent::new_scroll_event(
@@ -238,7 +242,7 @@ impl InputEmulation for MacOSEmulation {
Ok(e) => e, Ok(e) => e,
Err(()) => { Err(()) => {
log::warn!("scroll event creation failed!"); log::warn!("scroll event creation failed!");
return; return Ok(());
} }
}; };
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
@@ -249,7 +253,7 @@ impl InputEmulation for MacOSEmulation {
1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x) 1 => (2, 0, value, 0), // 1 = horizontal => 2 scroll wheel devices (y, x) -> (0, x)
_ => { _ => {
log::warn!("invalid scroll event: {axis}, {value}"); log::warn!("invalid scroll event: {axis}, {value}");
return; return Ok(());
} }
}; };
let event = match CGEvent::new_scroll_event( let event = match CGEvent::new_scroll_event(
@@ -263,7 +267,7 @@ impl InputEmulation for MacOSEmulation {
Ok(e) => e, Ok(e) => e,
Err(()) => { Err(()) => {
log::warn!("scroll event creation failed!"); log::warn!("scroll event creation failed!");
return; return Ok(());
} }
}; };
event.post(CGEventTapLocation::HID); event.post(CGEventTapLocation::HID);
@@ -280,7 +284,7 @@ impl InputEmulation for MacOSEmulation {
Ok(k) => k.mac as CGKeyCode, Ok(k) => k.mac as CGKeyCode,
Err(_) => { Err(_) => {
log::warn!("unable to map key event"); log::warn!("unable to map key event");
return; return Ok(());
} }
}; };
match state { match state {
@@ -294,6 +298,8 @@ impl InputEmulation for MacOSEmulation {
}, },
_ => (), _ => (),
} }
// FIXME
Ok(())
} }
async fn create(&mut self, _handle: EmulationHandle) {} async fn create(&mut self, _handle: EmulationHandle) {}

View File

@@ -1,4 +1,4 @@
use super::error::WindowsEmulationCreationError; use super::error::{EmulationError, WindowsEmulationCreationError};
use input_event::{ use input_event::{
scancode, Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, scancode, Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE,
BTN_RIGHT, BTN_RIGHT,
@@ -36,7 +36,7 @@ impl WindowsEmulation {
#[async_trait] #[async_trait]
impl InputEmulation for WindowsEmulation { impl InputEmulation for WindowsEmulation {
async fn consume(&mut self, event: Event, _: EmulationHandle) { async fn consume(&mut self, event: Event, _: EmulationHandle) -> Result<(), EmulationError> {
match event { match event {
Event::Pointer(pointer_event) => match pointer_event { Event::Pointer(pointer_event) => match pointer_event {
PointerEvent::Motion { PointerEvent::Motion {
@@ -77,6 +77,8 @@ impl InputEmulation for WindowsEmulation {
}, },
_ => {} _ => {}
} }
// FIXME
Ok(())
} }
async fn create(&mut self, _handle: EmulationHandle) {} async fn create(&mut self, _handle: EmulationHandle) {}

View File

@@ -1,3 +1,5 @@
use crate::error::EmulationError;
use super::{error::WlrootsEmulationCreationError, InputEmulation}; use super::{error::WlrootsEmulationCreationError, InputEmulation};
use async_trait::async_trait; use async_trait::async_trait;
use std::collections::HashMap; use std::collections::HashMap;
@@ -115,38 +117,40 @@ impl State {
#[async_trait] #[async_trait]
impl InputEmulation for WlrootsEmulation { impl InputEmulation for WlrootsEmulation {
async fn consume(&mut self, event: Event, handle: EmulationHandle) { async fn consume(
&mut self,
event: Event,
handle: EmulationHandle,
) -> Result<(), EmulationError> {
if let Some(virtual_input) = self.state.input_for_client.get(&handle) { if let Some(virtual_input) = self.state.input_for_client.get(&handle) {
if self.last_flush_failed { if self.last_flush_failed {
if let Err(WaylandError::Io(e)) = self.queue.flush() { match self.queue.flush() {
if e.kind() == io::ErrorKind::WouldBlock { Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => {
/* /*
* outgoing buffer is full - sending more events * outgoing buffer is full - sending more events
* will overwhelm the output buffer and leave the * will overwhelm the output buffer and leave the
* wayland connection in a broken state * wayland connection in a broken state
*/ */
log::warn!("can't keep up, discarding event: ({handle}) - {event:?}"); log::warn!("can't keep up, discarding event: ({handle}) - {event:?}");
return; return Ok(());
} }
_ => {}
} }
} }
virtual_input.consume_event(event).unwrap(); virtual_input
.consume_event(event)
.unwrap_or_else(|_| panic!("failed to convert event: {event:?}"));
match self.queue.flush() { match self.queue.flush() {
Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => { Err(WaylandError::Io(e)) if e.kind() == io::ErrorKind::WouldBlock => {
self.last_flush_failed = true; self.last_flush_failed = true;
log::warn!("can't keep up, retrying ..."); log::warn!("can't keep up, discarding event: ({handle}) - {event:?}");
}
Err(WaylandError::Io(e)) => {
log::error!("{e}")
}
Err(WaylandError::Protocol(e)) => {
panic!("wayland protocol violation: {e}")
}
Ok(()) => {
self.last_flush_failed = false;
} }
Err(WaylandError::Protocol(e)) => panic!("wayland protocol violation: {e}"),
Ok(()) => self.last_flush_failed = false,
Err(e) => Err(e)?,
} }
} }
Ok(())
} }
async fn create(&mut self, handle: EmulationHandle) { async fn create(&mut self, handle: EmulationHandle) {

View File

@@ -9,6 +9,8 @@ use input_event::{
Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT,
}; };
use crate::error::EmulationError;
use super::{error::X11EmulationCreationError, EmulationHandle, InputEmulation}; use super::{error::X11EmulationCreationError, EmulationHandle, InputEmulation};
pub struct X11Emulation { pub struct X11Emulation {
@@ -98,7 +100,7 @@ impl Drop for X11Emulation {
#[async_trait] #[async_trait]
impl InputEmulation for X11Emulation { impl InputEmulation for X11Emulation {
async fn consume(&mut self, event: Event, _: EmulationHandle) { async fn consume(&mut self, event: Event, _: EmulationHandle) -> Result<(), EmulationError> {
match event { match event {
Event::Pointer(pointer_event) => match pointer_event { Event::Pointer(pointer_event) => match pointer_event {
PointerEvent::Motion { PointerEvent::Motion {
@@ -139,6 +141,8 @@ impl InputEmulation for X11Emulation {
unsafe { unsafe {
xlib::XFlush(self.display); xlib::XFlush(self.display);
} }
// FIXME
Ok(())
} }
async fn create(&mut self, _: EmulationHandle) { async fn create(&mut self, _: EmulationHandle) {

View File

@@ -4,20 +4,24 @@ use ashpd::{
remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop}, remote_desktop::{Axis, DeviceType, KeyState, RemoteDesktop},
ResponseError, Session, ResponseError, Session,
}, },
zbus::AsyncDrop,
WindowIdentifier, WindowIdentifier,
}; };
use async_trait::async_trait; use async_trait::async_trait;
use futures::FutureExt;
use input_event::{ use input_event::{
Event::{Keyboard, Pointer}, Event::{Keyboard, Pointer},
KeyboardEvent, PointerEvent, KeyboardEvent, PointerEvent,
}; };
use crate::error::EmulationError;
use super::{error::XdpEmulationCreationError, EmulationHandle, InputEmulation}; use super::{error::XdpEmulationCreationError, EmulationHandle, InputEmulation};
pub struct DesktopPortalEmulation<'a> { pub struct DesktopPortalEmulation<'a> {
proxy: RemoteDesktop<'a>, proxy: RemoteDesktop<'a>,
session: Option<Session<'a>>, session: Session<'a>,
} }
impl<'a> DesktopPortalEmulation<'a> { impl<'a> DesktopPortalEmulation<'a> {
@@ -51,7 +55,7 @@ impl<'a> DesktopPortalEmulation<'a> {
}; };
log::debug!("started session"); log::debug!("started session");
let session = Some(session); let session = session;
Ok(Self { proxy, session }) Ok(Self { proxy, session })
} }
@@ -59,7 +63,11 @@ impl<'a> DesktopPortalEmulation<'a> {
#[async_trait] #[async_trait]
impl<'a> InputEmulation for DesktopPortalEmulation<'a> { impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
async fn consume(&mut self, event: input_event::Event, _client: EmulationHandle) { async fn consume(
&mut self,
event: input_event::Event,
_client: EmulationHandle,
) -> Result<(), EmulationError> {
match event { match event {
Pointer(p) => match p { Pointer(p) => match p {
PointerEvent::Motion { PointerEvent::Motion {
@@ -67,17 +75,9 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
relative_x, relative_x,
relative_y, relative_y,
} => { } => {
if let Err(e) = self self.proxy
.proxy .notify_pointer_motion(&self.session, relative_x, relative_y)
.notify_pointer_motion( .await?;
self.session.as_ref().expect("no session"),
relative_x,
relative_y,
)
.await
{
log::warn!("{e}");
}
} }
PointerEvent::Button { PointerEvent::Button {
time: _, time: _,
@@ -88,34 +88,18 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
0 => KeyState::Released, 0 => KeyState::Released,
_ => KeyState::Pressed, _ => KeyState::Pressed,
}; };
if let Err(e) = self self.proxy
.proxy .notify_pointer_button(&self.session, button as i32, state)
.notify_pointer_button( .await?;
self.session.as_ref().expect("no session"),
button as i32,
state,
)
.await
{
log::warn!("{e}");
}
} }
PointerEvent::AxisDiscrete120 { axis, value } => { PointerEvent::AxisDiscrete120 { axis, value } => {
let axis = match axis { let axis = match axis {
0 => Axis::Vertical, 0 => Axis::Vertical,
_ => Axis::Horizontal, _ => Axis::Horizontal,
}; };
if let Err(e) = self self.proxy
.proxy .notify_pointer_axis_discrete(&self.session, axis, value)
.notify_pointer_axis_discrete( .await?;
self.session.as_ref().expect("no session"),
axis,
value,
)
.await
{
log::warn!("{e}");
}
} }
PointerEvent::Axis { PointerEvent::Axis {
time: _, time: _,
@@ -130,18 +114,9 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
Axis::Vertical => (0., value), Axis::Vertical => (0., value),
Axis::Horizontal => (value, 0.), Axis::Horizontal => (value, 0.),
}; };
if let Err(e) = self self.proxy
.proxy .notify_pointer_axis(&self.session, dx, dy, true)
.notify_pointer_axis( .await?;
self.session.as_ref().expect("no session"),
dx,
dy,
true,
)
.await
{
log::warn!("{e}");
}
} }
PointerEvent::Frame {} => {} PointerEvent::Frame {} => {}
}, },
@@ -156,17 +131,9 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
0 => KeyState::Released, 0 => KeyState::Released,
_ => KeyState::Pressed, _ => KeyState::Pressed,
}; };
if let Err(e) = self self.proxy
.proxy .notify_keyboard_keycode(&self.session, key as i32, state)
.notify_keyboard_keycode( .await?;
self.session.as_ref().expect("no session"),
key as i32,
state,
)
.await
{
log::warn!("{e}");
}
} }
KeyboardEvent::Modifiers { .. } => { KeyboardEvent::Modifiers { .. } => {
// ignore // ignore
@@ -175,22 +142,28 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> {
} }
_ => {} _ => {}
} }
Ok(())
} }
async fn create(&mut self, _client: EmulationHandle) {} async fn create(&mut self, _client: EmulationHandle) {}
async fn destroy(&mut self, _client: EmulationHandle) {} async fn destroy(&mut self, _client: EmulationHandle) {}
} }
impl<'a> Drop for DesktopPortalEmulation<'a> { impl<'a> AsyncDrop for DesktopPortalEmulation<'a> {
fn drop(&mut self) { #[doc = r" Perform the async cleanup."]
let session = self.session.take().expect("no session"); #[must_use]
tokio::runtime::Handle::try_current() #[allow(clippy::type_complexity, clippy::type_repetition_in_bounds)]
.expect("no runtime") fn async_drop<'async_trait>(
.block_on(async move { self,
log::debug!("closing remote desktop session"); ) -> ::core::pin::Pin<
if let Err(e) = session.close().await { Box<dyn ::core::future::Future<Output = ()> + ::core::marker::Send + 'async_trait>,
log::error!("failed to close remote desktop session: {e}"); >
} where
}); Self: 'async_trait,
{
async move {
let _ = self.session.close().await;
}
.boxed()
} }
} }

View File

@@ -27,22 +27,23 @@ async fn input_emulation_test(config: Config) -> Result<()> {
let start = Instant::now(); let start = Instant::now();
let mut offset = (0, 0); let mut offset = (0, 0);
loop { loop {
tokio::select! { tokio::time::sleep(Duration::from_millis(1)).await;
_ = emulation.dispatch() => {} let elapsed = start.elapsed();
_ = tokio::time::sleep(Duration::from_millis(1)) => { let elapsed_sec_f64 = elapsed.as_secs_f64();
let elapsed = start.elapsed(); let second_fraction = elapsed_sec_f64 - elapsed_sec_f64 as u64 as f64;
let elapsed_sec_f64 = elapsed.as_secs_f64(); let radians = second_fraction * 2. * PI * FREQUENCY_HZ;
let second_fraction = elapsed_sec_f64 - elapsed_sec_f64 as u64 as f64; let new_offset_f = (radians.cos() * RADIUS * 2., (radians * 2.).sin() * RADIUS);
let radians = second_fraction * 2. * PI * FREQUENCY_HZ; let new_offset = (new_offset_f.0 as i32, new_offset_f.1 as i32);
let new_offset_f = (radians.cos() * RADIUS * 2., (radians * 2.).sin() * RADIUS); if new_offset != offset {
let new_offset = (new_offset_f.0 as i32, new_offset_f.1 as i32); let relative_motion = (new_offset.0 - offset.0, new_offset.1 - offset.1);
if new_offset != offset { offset = new_offset;
let relative_motion = (new_offset.0 - offset.0, new_offset.1 - offset.1); let (relative_x, relative_y) = (relative_motion.0 as f64, relative_motion.1 as f64);
offset = new_offset; let event = Event::Pointer(PointerEvent::Motion {
let (relative_x, relative_y) = (relative_motion.0 as f64, relative_motion.1 as f64); time: 0,
emulation.consume(Event::Pointer(PointerEvent::Motion {time: 0, relative_x, relative_y }), 0).await; relative_x,
} relative_y,
} });
emulation.consume(event, 0).await?;
} }
} }
} }

View File

@@ -7,7 +7,11 @@ use tokio::{
}; };
use crate::{client::ClientHandle, config::EmulationBackend, server::State}; use crate::{client::ClientHandle, config::EmulationBackend, server::State};
use input_emulation::{self, error::EmulationCreationError, EmulationHandle, InputEmulation}; use input_emulation::{
self,
error::{EmulationCreationError, EmulationError},
EmulationHandle, InputEmulation,
};
use input_event::{Event, KeyboardEvent}; use input_event::{Event, KeyboardEvent};
use super::{CaptureEvent, Server}; use super::{CaptureEvent, Server};
@@ -42,22 +46,19 @@ pub fn new(
tokio::select! { tokio::select! {
udp_event = udp_rx.recv() => { udp_event = udp_rx.recv() => {
let udp_event = udp_event.ok_or(anyhow!("receiver closed"))??; let udp_event = udp_event.ok_or(anyhow!("receiver closed"))??;
handle_udp_rx(&server, &capture_tx, &mut emulate, &sender_tx, &mut last_ignored, udp_event, &timer_tx).await; handle_udp_rx(&server, &capture_tx, &mut emulate, &sender_tx, &mut last_ignored, udp_event, &timer_tx).await?;
} }
emulate_event = rx.recv() => { emulate_event = rx.recv() => {
match emulate_event { match emulate_event {
Some(e) => match e { Some(e) => match e {
EmulationEvent::Create(h) => emulate.create(h).await, EmulationEvent::Create(h) => emulate.create(h).await,
EmulationEvent::Destroy(h) => emulate.destroy(h).await, EmulationEvent::Destroy(h) => emulate.destroy(h).await,
EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulate, c).await, EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulate, c).await?,
EmulationEvent::Terminate => break, EmulationEvent::Terminate => break,
}, },
None => break, None => break,
} }
} }
res = emulate.dispatch() => {
res?;
}
} }
} }
@@ -69,7 +70,7 @@ pub fn new(
.map(|(h, _)| h) .map(|(h, _)| h)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
for client in clients { for client in clients {
release_keys(&server, &mut emulate, client).await; release_keys(&server, &mut emulate, client).await?;
} }
anyhow::Ok(()) anyhow::Ok(())
@@ -85,7 +86,7 @@ async fn handle_udp_rx(
last_ignored: &mut Option<SocketAddr>, last_ignored: &mut Option<SocketAddr>,
event: (Event, SocketAddr), event: (Event, SocketAddr),
timer_tx: &Sender<()>, timer_tx: &Sender<()>,
) { ) -> Result<(), EmulationError> {
let (event, addr) = event; let (event, addr) = event;
// get handle for addr // get handle for addr
@@ -96,7 +97,7 @@ async fn handle_udp_rx(
log::warn!("ignoring events from client {addr}"); log::warn!("ignoring events from client {addr}");
last_ignored.replace(addr); last_ignored.replace(addr);
} }
return; return Ok(());
} }
}; };
@@ -110,7 +111,7 @@ async fn handle_udp_rx(
Some((_, s)) => s, Some((_, s)) => s,
None => { None => {
log::error!("unknown handle"); log::error!("unknown handle");
return; return Ok(());
} }
}; };
@@ -126,7 +127,7 @@ async fn handle_udp_rx(
let _ = sender_tx.send((Event::Pong(), addr)).await; let _ = sender_tx.send((Event::Pong(), addr)).await;
} }
(Event::Disconnect(), _) => { (Event::Disconnect(), _) => {
release_keys(server, emulate, handle).await; release_keys(server, emulate, handle).await?;
} }
(event, addr) => { (event, addr) => {
// tell clients that we are ready to receive events // tell clients that we are ready to receive events
@@ -159,7 +160,7 @@ async fn handle_udp_rx(
s s
} else { } else {
log::error!("unknown handle"); log::error!("unknown handle");
return; return Ok(());
}; };
if state == 0 { if state == 0 {
// ignore release event if key not pressed // ignore release event if key not pressed
@@ -174,7 +175,7 @@ async fn handle_udp_rx(
// workaround buggy rdp backend. // workaround buggy rdp backend.
if !ignore_event { if !ignore_event {
// consume event // consume event
emulate.consume(event, handle).await; emulate.consume(event, handle).await?;
log::trace!("{event} => emulate"); log::trace!("{event} => emulate");
} }
} }
@@ -199,13 +200,14 @@ async fn handle_udp_rx(
} }
} }
} }
Ok(())
} }
async fn release_keys( async fn release_keys(
server: &Server, server: &Server,
emulate: &mut Box<dyn InputEmulation>, emulate: &mut Box<dyn InputEmulation>,
client: ClientHandle, client: ClientHandle,
) { ) -> Result<(), EmulationError> {
let keys = server let keys = server
.client_manager .client_manager
.borrow_mut() .borrow_mut()
@@ -220,19 +222,18 @@ async fn release_keys(
key, key,
state: 0, state: 0,
}); });
emulate.consume(event, client).await; emulate.consume(event, client).await?;
if let Ok(key) = input_event::scancode::Linux::try_from(key) { if let Ok(key) = input_event::scancode::Linux::try_from(key) {
log::warn!("releasing stuck key: {key:?}"); log::warn!("releasing stuck key: {key:?}");
} }
} }
let modifiers_event = KeyboardEvent::Modifiers { let event = Event::Keyboard(KeyboardEvent::Modifiers {
mods_depressed: 0, mods_depressed: 0,
mods_latched: 0, mods_latched: 0,
mods_locked: 0, mods_locked: 0,
group: 0, group: 0,
}; });
emulate emulate.consume(event, client).await?;
.consume(Event::Keyboard(modifiers_event), client) Ok(())
.await;
} }