From 70a23b9fa7473e29bd9db44ac992556628b348a1 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Tue, 2 Jul 2024 11:17:08 +0200 Subject: [PATCH] reduce coupling of emulation and capture backends --- src/capture.rs | 80 ++++++++++++++++++++++++------- src/capture/dummy.rs | 11 +++-- src/capture/libei.rs | 66 +++++++++++++------------ src/capture/macos.rs | 11 +++-- src/capture/wayland.rs | 40 +++++++--------- src/capture/windows.rs | 76 ++++++++++++++++++----------- src/capture/x11.rs | 13 +++-- src/capture_test.rs | 11 ++--- src/client.rs | 30 +++++------- src/emulate.rs | 15 +++--- src/emulate/dummy.rs | 16 +++---- src/emulate/libei.rs | 15 ++---- src/emulate/macos.rs | 9 ++-- src/emulate/windows.rs | 20 ++++---- src/emulate/wlroots.rs | 41 +++++++++------- src/emulate/x11.rs | 18 ++++--- src/emulate/xdg_desktop_portal.rs | 66 +++++++++++++++++-------- src/emulation_test.rs | 5 +- src/server/capture_task.rs | 16 ++++--- src/server/emulation_task.rs | 15 +++--- src/server/frontend_task.rs | 27 +++++------ 21 files changed, 341 insertions(+), 260 deletions(-) diff --git a/src/capture.rs b/src/capture.rs index 8b100ca..65e671b 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1,12 +1,8 @@ -use std::io; +use std::{fmt::Display, io}; use futures_core::Stream; -use crate::{ - client::{ClientEvent, ClientHandle}, - config::CaptureBackend, - event::Event, -}; +use crate::{config::CaptureBackend, event::Event}; use self::error::CaptureCreationError; @@ -30,9 +26,68 @@ pub mod x11; /// fallback input capture (does not produce events) pub mod dummy; +pub type CaptureHandle = u64; + +#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)] +pub enum Position { + Left, + Right, + Top, + Bottom, +} + +impl Position { + pub fn opposite(&self) -> Self { + match self { + Position::Left => Self::Right, + Position::Right => Self::Left, + Position::Top => Self::Bottom, + Position::Bottom => Self::Top, + } + } +} + +impl Display for Position { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let pos = match self { + Position::Left => "left", + Position::Right => "right", + Position::Top => "top", + Position::Bottom => "bottom", + }; + write!(f, "{}", pos) + } +} + +pub enum Backend { + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + InputCapturePortal, + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + LayerShell, + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + X11, + #[cfg(windows)] + Windows, + #[cfg(target_os = "macos")] + MacOs, + Dummy, +} + +pub trait InputCapture: Stream> + Unpin { + /// create a new client with the given id + fn create(&mut self, id: CaptureHandle, pos: Position) -> io::Result<()>; + + /// destroy the client with the given id, if it exists + fn destroy(&mut self, id: CaptureHandle) -> io::Result<()>; + + /// release mouse + fn release(&mut self) -> io::Result<()>; +} + pub async fn create_backend( backend: CaptureBackend, -) -> Result>>, CaptureCreationError> { +) -> Result>>, CaptureCreationError> +{ match backend { #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] CaptureBackend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)), @@ -50,7 +105,8 @@ pub async fn create_backend( pub async fn create( backend: Option, -) -> Result>>, CaptureCreationError> { +) -> Result>>, CaptureCreationError> +{ if let Some(backend) = backend { let b = create_backend(backend).await; if b.is_ok() { @@ -82,11 +138,3 @@ pub async fn create( } Err(CaptureCreationError::NoAvailableBackend) } - -pub trait InputCapture: Stream> + Unpin { - /// notify input capture of configuration changes - fn notify(&mut self, event: ClientEvent) -> io::Result<()>; - - /// release mouse - fn release(&mut self) -> io::Result<()>; -} diff --git a/src/capture/dummy.rs b/src/capture/dummy.rs index 045f22f..9ef549c 100644 --- a/src/capture/dummy.rs +++ b/src/capture/dummy.rs @@ -4,10 +4,9 @@ use std::task::{Context, Poll}; use futures_core::Stream; -use crate::capture::InputCapture; use crate::event::Event; -use crate::client::{ClientEvent, ClientHandle}; +use super::{CaptureHandle, InputCapture, Position}; pub struct DummyInputCapture {} @@ -24,7 +23,11 @@ impl Default for DummyInputCapture { } impl InputCapture for DummyInputCapture { - fn notify(&mut self, _event: ClientEvent) -> io::Result<()> { + fn create(&mut self, _handle: CaptureHandle, _pos: Position) -> io::Result<()> { + Ok(()) + } + + fn destroy(&mut self, _handle: CaptureHandle) -> io::Result<()> { Ok(()) } @@ -34,7 +37,7 @@ impl InputCapture for DummyInputCapture { } impl Stream for DummyInputCapture { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Pending diff --git a/src/capture/libei.rs b/src/capture/libei.rs index 816f18a..f845926 100644 --- a/src/capture/libei.rs +++ b/src/capture/libei.rs @@ -30,25 +30,24 @@ use tokio::{ use futures_core::Stream; use once_cell::sync::Lazy; -use crate::{ - capture::InputCapture as LanMouseInputCapture, - client::{ClientEvent, ClientHandle, Position}, - event::{Event, KeyboardEvent, PointerEvent}, -}; +use crate::event::{Event, KeyboardEvent, PointerEvent}; -use super::error::LibeiCaptureCreationError; +use super::{ + error::LibeiCaptureCreationError, CaptureHandle, InputCapture as LanMouseInputCapture, Position, +}; #[derive(Debug)] enum ProducerEvent { Release, - ClientEvent(ClientEvent), + Create(CaptureHandle, Position), + Destroy(CaptureHandle), } #[allow(dead_code)] pub struct LibeiInputCapture<'a> { input_capture: Pin>>, libei_task: JoinHandle>, - event_rx: tokio::sync::mpsc::Receiver<(ClientHandle, Event)>, + event_rx: tokio::sync::mpsc::Receiver<(CaptureHandle, Event)>, notify_tx: tokio::sync::mpsc::Sender, } @@ -81,9 +80,9 @@ fn pos_to_barrier(r: &Region, pos: Position) -> (i32, i32, i32, i32) { fn select_barriers( zones: &Zones, - clients: &Vec<(ClientHandle, Position)>, + clients: &Vec<(CaptureHandle, Position)>, next_barrier_id: &mut u32, -) -> (Vec, HashMap) { +) -> (Vec, HashMap) { let mut client_for_barrier = HashMap::new(); let mut barriers: Vec = vec![]; @@ -107,9 +106,9 @@ fn select_barriers( async fn update_barriers( input_capture: &InputCapture<'_>, session: &Session<'_>, - active_clients: &Vec<(ClientHandle, Position)>, + active_clients: &Vec<(CaptureHandle, Position)>, next_barrier_id: &mut u32, -) -> Result> { +) -> Result> { let zones = input_capture.zones(session).await?.response()?; log::debug!("zones: {zones:?}"); @@ -185,8 +184,8 @@ async fn connect_to_eis( async fn libei_event_handler( mut ei_event_stream: EiConvertEventStream, context: ei::Context, - event_tx: Sender<(ClientHandle, Event)>, - current_client: Rc>>, + event_tx: Sender<(CaptureHandle, Event)>, + current_client: Rc>>, ) -> Result<()> { loop { let ei_event = match ei_event_stream.next().await { @@ -202,12 +201,12 @@ async fn libei_event_handler( async fn wait_for_active_client( notify_rx: &mut Receiver, - active_clients: &mut Vec<(ClientHandle, Position)>, + active_clients: &mut Vec<(CaptureHandle, Position)>, ) -> Result<()> { // wait for a client update while let Some(producer_event) = notify_rx.recv().await { - if let ProducerEvent::ClientEvent(c) = producer_event { - handle_producer_event(ProducerEvent::ClientEvent(c), active_clients)?; + if let ProducerEvent::Create(c, p) = producer_event { + handle_producer_event(ProducerEvent::Create(c, p), active_clients)?; break; } } @@ -226,7 +225,7 @@ impl<'a> LibeiInputCapture<'a> { /* safety: libei_task does not outlive Self */ let input_capture = unsafe { &*input_capture_ptr }; - let mut active_clients: Vec<(ClientHandle, Position)> = vec![]; + let mut active_clients: Vec<(CaptureHandle, Position)> = vec![]; let mut next_barrier_id = 1u32; /* there is a bug in xdg-remote-desktop-portal-gnome / mutter that @@ -349,8 +348,8 @@ async fn release_capture( input_capture: &InputCapture<'_>, session: &Session<'_>, activated: Activated, - current_client: ClientHandle, - active_clients: &[(ClientHandle, Position)], + current_client: CaptureHandle, + active_clients: &[(CaptureHandle, Position)], ) -> Result<()> { log::debug!("releasing input capture {}", activated.activation_id()); let (x, y) = activated.cursor_position(); @@ -377,16 +376,16 @@ async fn release_capture( fn handle_producer_event( producer_event: ProducerEvent, - active_clients: &mut Vec<(ClientHandle, Position)>, + active_clients: &mut Vec<(CaptureHandle, Position)>, ) -> Result { log::debug!("handling event: {producer_event:?}"); let updated = match producer_event { ProducerEvent::Release => false, - ProducerEvent::ClientEvent(ClientEvent::Create(c, p)) => { + ProducerEvent::Create(c, p) => { active_clients.push((c, p)); true } - ProducerEvent::ClientEvent(ClientEvent::Destroy(c)) => { + ProducerEvent::Destroy(c) => { active_clients.retain(|(h, _)| *h != c); true } @@ -396,9 +395,9 @@ fn handle_producer_event( async fn handle_ei_event( ei_event: EiEvent, - current_client: Option, + current_client: Option, context: &ei::Context, - event_tx: &Sender<(ClientHandle, Event)>, + event_tx: &Sender<(CaptureHandle, Event)>, ) { match ei_event { EiEvent::SeatAdded(s) => { @@ -547,12 +546,18 @@ async fn handle_ei_event( } impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { - fn notify(&mut self, event: ClientEvent) -> io::Result<()> { + fn create(&mut self, handle: super::CaptureHandle, pos: super::Position) -> io::Result<()> { let notify_tx = self.notify_tx.clone(); tokio::task::spawn_local(async move { - log::debug!("notifying {event:?}"); - let _ = notify_tx.send(ProducerEvent::ClientEvent(event)).await; - log::debug!("done !"); + let _ = notify_tx.send(ProducerEvent::Create(handle, pos)).await; + }); + Ok(()) + } + + fn destroy(&mut self, handle: super::CaptureHandle) -> io::Result<()> { + let notify_tx = self.notify_tx.clone(); + tokio::task::spawn_local(async move { + let _ = notify_tx.send(ProducerEvent::Destroy(handle)).await; }); Ok(()) } @@ -560,7 +565,6 @@ impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { fn release(&mut self) -> io::Result<()> { let notify_tx = self.notify_tx.clone(); tokio::task::spawn_local(async move { - log::debug!("notifying Release"); let _ = notify_tx.send(ProducerEvent::Release).await; }); Ok(()) @@ -568,7 +572,7 @@ impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { } impl<'a> Stream for LibeiInputCapture<'a> { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match ready!(self.event_rx.poll_recv(cx)) { diff --git a/src/capture/macos.rs b/src/capture/macos.rs index 5d122e1..fb0efb7 100644 --- a/src/capture/macos.rs +++ b/src/capture/macos.rs @@ -1,6 +1,5 @@ use crate::capture::error::MacOSInputCaptureCreationError; -use crate::capture::InputCapture; -use crate::client::{ClientEvent, ClientHandle}; +use crate::capture::{CaptureHandle, InputCapture, Position}; use crate::event::Event; use futures_core::Stream; use std::task::{Context, Poll}; @@ -15,7 +14,7 @@ impl MacOSInputCapture { } impl Stream for MacOSInputCapture { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Pending @@ -23,7 +22,11 @@ impl Stream for MacOSInputCapture { } impl InputCapture for MacOSInputCapture { - fn notify(&mut self, _event: ClientEvent) -> io::Result<()> { + fn create(&mut self, _id: CaptureHandle, _pos: Position) -> io::Result<()> { + Ok(()) + } + + fn destroy(&mut self, _id: CaptureHandle) -> io::Result<()> { Ok(()) } diff --git a/src/capture/wayland.rs b/src/capture/wayland.rs index 0d64105..ecfaa2b 100644 --- a/src/capture/wayland.rs +++ b/src/capture/wayland.rs @@ -1,8 +1,3 @@ -use crate::{ - capture::{error::WaylandBindError, InputCapture}, - client::{ClientEvent, ClientHandle, Position}, -}; - use futures_core::Stream; use memmap::MmapOptions; use std::{ @@ -67,7 +62,10 @@ use tempfile; use crate::event::{Event, KeyboardEvent, PointerEvent}; -use super::error::LayerShellCaptureCreationError; +use super::{ + error::{LayerShellCaptureCreationError, WaylandBindError}, + CaptureHandle, InputCapture, Position, +}; struct Globals { compositor: wl_compositor::WlCompositor, @@ -104,13 +102,13 @@ struct State { pointer_lock: Option, rel_pointer: Option, shortcut_inhibitor: Option, - client_for_window: Vec<(Rc, ClientHandle)>, - focused: Option<(Rc, ClientHandle)>, + client_for_window: Vec<(Rc, CaptureHandle)>, + focused: Option<(Rc, CaptureHandle)>, g: Globals, wayland_fd: OwnedFd, read_guard: Option, qh: QueueHandle, - pending_events: VecDeque<(ClientHandle, Event)>, + pending_events: VecDeque<(CaptureHandle, Event)>, output_info: Vec<(WlOutput, OutputInfo)>, scroll_discrete_pending: bool, } @@ -369,11 +367,11 @@ impl WaylandInputCapture { Ok(WaylandInputCapture(inner)) } - fn add_client(&mut self, handle: ClientHandle, pos: Position) { + fn add_client(&mut self, handle: CaptureHandle, pos: Position) { self.0.get_mut().state.add_client(handle, pos); } - fn delete_client(&mut self, handle: ClientHandle) { + fn delete_client(&mut self, handle: CaptureHandle) { let inner = self.0.get_mut(); // remove all windows corresponding to this client while let Some(i) = inner @@ -471,7 +469,7 @@ impl State { } } - fn add_client(&mut self, client: ClientHandle, pos: Position) { + fn add_client(&mut self, client: CaptureHandle, pos: Position) { let outputs = get_output_configuration(self, pos); log::debug!("outputs: {outputs:?}"); @@ -564,15 +562,13 @@ impl Inner { } impl InputCapture for WaylandInputCapture { - fn notify(&mut self, client_event: ClientEvent) -> io::Result<()> { - match client_event { - ClientEvent::Create(handle, pos) => { - self.add_client(handle, pos); - } - ClientEvent::Destroy(handle) => { - self.delete_client(handle); - } - } + fn create(&mut self, handle: CaptureHandle, pos: Position) -> io::Result<()> { + self.add_client(handle, pos); + let inner = self.0.get_mut(); + inner.flush_events() + } + fn destroy(&mut self, handle: CaptureHandle) -> io::Result<()> { + self.delete_client(handle); let inner = self.0.get_mut(); inner.flush_events() } @@ -586,7 +582,7 @@ impl InputCapture for WaylandInputCapture { } impl Stream for WaylandInputCapture { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if let Some(event) = self.0.get_mut().state.pending_events.pop_front() { diff --git a/src/capture/windows.rs b/src/capture/windows.rs index eff4f9c..6aa88c7 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -9,7 +9,7 @@ use std::ptr::{addr_of, addr_of_mut}; use futures::executor::block_on; use std::default::Default; use std::sync::atomic::{AtomicU32, Ordering}; -use std::sync::mpsc; +use std::sync::{mpsc, Mutex}; use std::task::ready; use std::{io, pin::Pin, thread}; use tokio::sync::mpsc::{channel, Receiver, Sender}; @@ -32,25 +32,25 @@ use windows::Win32::UI::WindowsAndMessaging::{ WNDPROC, }; -use crate::client::Position; use crate::event::{ KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, }; -use crate::scancode::Linux; -use crate::{ - capture::InputCapture, - client::{ClientEvent, ClientHandle}, - event::Event, - scancode, -}; +use crate::{event::Event, scancode, scancode::Linux}; + +use super::{CaptureHandle, InputCapture, Position}; + +enum Request { + Create(CaptureHandle, Position), + Destroy(CaptureHandle), +} pub struct WindowsInputCapture { - event_rx: Receiver<(ClientHandle, Event)>, + event_rx: Receiver<(CaptureHandle, Event)>, msg_thread: Option>, } enum EventType { - ClientEvent = 0, + Request = 0, Release = 1, Exit = 2, } @@ -59,15 +59,28 @@ unsafe fn signal_message_thread(event_type: EventType) { if let Some(event_tid) = get_event_tid() { PostThreadMessageW(event_tid, WM_USER, WPARAM(event_type as usize), LPARAM(0)).unwrap(); } else { - log::warn!("lost event"); + panic!(); } } impl InputCapture for WindowsInputCapture { - fn notify(&mut self, event: ClientEvent) -> io::Result<()> { + fn create(&mut self, handle: CaptureHandle, pos: Position) -> io::Result<()> { unsafe { - EVENT_BUFFER.push(event); - signal_message_thread(EventType::ClientEvent); + { + let mut requests = REQUEST_BUFFER.lock().unwrap(); + requests.push(Request::Create(handle, pos)); + } + signal_message_thread(EventType::Request); + } + Ok(()) + } + fn destroy(&mut self, handle: CaptureHandle) -> io::Result<()> { + unsafe { + { + let mut requests = REQUEST_BUFFER.lock().unwrap(); + requests.push(Request::Destroy(handle)); + } + signal_message_thread(EventType::Request); } Ok(()) } @@ -78,10 +91,10 @@ impl InputCapture for WindowsInputCapture { } } -static mut EVENT_BUFFER: Vec = Vec::new(); -static mut ACTIVE_CLIENT: Option = None; -static mut CLIENT_FOR_POS: Lazy> = Lazy::new(HashMap::new); -static mut EVENT_TX: Option> = None; +static mut REQUEST_BUFFER: Mutex> = Mutex::new(Vec::new()); +static mut ACTIVE_CLIENT: Option = None; +static mut CLIENT_FOR_POS: Lazy> = Lazy::new(HashMap::new); +static mut EVENT_TX: Option> = None; static mut EVENT_THREAD_ID: AtomicU32 = AtomicU32::new(0); unsafe fn set_event_tid(tid: u32) { EVENT_THREAD_ID.store(tid, Ordering::SeqCst); @@ -535,9 +548,18 @@ fn message_thread(ready_tx: mpsc::Sender<()>) { x if x == EventType::Release as usize => { ACTIVE_CLIENT.take(); } - x if x == EventType::ClientEvent as usize => { - while let Some(event) = EVENT_BUFFER.pop() { - update_clients(event) + x if x == EventType::Request as usize => { + let requests = { + let mut res = vec![]; + let mut requests = REQUEST_BUFFER.lock().unwrap(); + while let Some(request) = requests.pop() { + res.push(request); + } + res + }; + + for request in requests { + update_clients(request) } } _ => {} @@ -551,12 +573,12 @@ fn message_thread(ready_tx: mpsc::Sender<()>) { } } -fn update_clients(client_event: ClientEvent) { - match client_event { - ClientEvent::Create(handle, pos) => { +fn update_clients(request: Request) { + match request { + Request::Create(handle, pos) => { unsafe { CLIENT_FOR_POS.insert(pos, handle) }; } - ClientEvent::Destroy(handle) => unsafe { + Request::Destroy(handle) => unsafe { for pos in [ Position::Left, Position::Right, @@ -592,7 +614,7 @@ impl WindowsInputCapture { } impl Stream for WindowsInputCapture { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match ready!(self.event_rx.poll_recv(cx)) { None => Poll::Ready(None), diff --git a/src/capture/x11.rs b/src/capture/x11.rs index edc2f53..b4cd2a8 100644 --- a/src/capture/x11.rs +++ b/src/capture/x11.rs @@ -3,12 +3,11 @@ use std::task::Poll; use futures_core::Stream; -use crate::capture::InputCapture; +use super::InputCapture; use crate::event::Event; -use crate::client::{ClientEvent, ClientHandle}; - use super::error::X11InputCaptureCreationError; +use super::{CaptureHandle, Position}; pub struct X11InputCapture {} @@ -19,7 +18,11 @@ impl X11InputCapture { } impl InputCapture for X11InputCapture { - fn notify(&mut self, _event: ClientEvent) -> io::Result<()> { + fn create(&mut self, _id: CaptureHandle, _pos: Position) -> io::Result<()> { + Ok(()) + } + + fn destroy(&mut self, _id: CaptureHandle) -> io::Result<()> { Ok(()) } @@ -29,7 +32,7 @@ impl InputCapture for X11InputCapture { } impl Stream for X11InputCapture { - type Item = io::Result<(ClientHandle, Event)>; + type Item = io::Result<(CaptureHandle, Event)>; fn poll_next( self: std::pin::Pin<&mut Self>, diff --git a/src/capture_test.rs b/src/capture_test.rs index b8f652a..e6be9b3 100644 --- a/src/capture_test.rs +++ b/src/capture_test.rs @@ -1,5 +1,4 @@ -use crate::capture; -use crate::client::{ClientEvent, Position}; +use crate::capture::{self, Position}; use crate::config::Config; use crate::event::{Event, KeyboardEvent}; use anyhow::{anyhow, Result}; @@ -22,10 +21,10 @@ async fn input_capture_test(config: Config) -> Result<()> { log::info!("creating input capture"); let mut input_capture = capture::create(config.capture_backend).await?; log::info!("creating clients"); - input_capture.notify(ClientEvent::Create(0, Position::Left))?; - input_capture.notify(ClientEvent::Create(1, Position::Right))?; - input_capture.notify(ClientEvent::Create(2, Position::Top))?; - input_capture.notify(ClientEvent::Create(3, Position::Bottom))?; + input_capture.create(0, Position::Left)?; + input_capture.create(1, Position::Right)?; + input_capture.create(2, Position::Top)?; + input_capture.create(3, Position::Bottom)?; loop { let (client, event) = input_capture .next() diff --git a/src/client.rs b/src/client.rs index 5d70ef5..c6c533b 100644 --- a/src/client.rs +++ b/src/client.rs @@ -9,7 +9,7 @@ use std::{ use serde::{Deserialize, Serialize}; use slab::Slab; -use crate::config::DEFAULT_PORT; +use crate::{capture, config::DEFAULT_PORT}; #[derive(Debug, Eq, Hash, PartialEq, Clone, Copy, Serialize, Deserialize)] pub enum Position { @@ -25,6 +25,17 @@ impl Default for Position { } } +impl From for capture::Position { + fn from(position: Position) -> capture::Position { + match position { + Position::Left => capture::Position::Left, + Position::Right => capture::Position::Right, + Position::Top => capture::Position::Top, + Position::Bottom => capture::Position::Bottom, + } + } +} + #[derive(Debug)] pub struct PositionParseError { string: String, @@ -52,17 +63,6 @@ impl FromStr for Position { } } -impl Position { - pub fn opposite(&self) -> Self { - match self { - Position::Left => Self::Right, - Position::Right => Self::Left, - Position::Top => Self::Bottom, - Position::Bottom => Self::Top, - } - } -} - impl Display for Position { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( @@ -118,12 +118,6 @@ impl Default for ClientConfig { } } -#[derive(Clone, Copy, Debug)] -pub enum ClientEvent { - Create(ClientHandle, Position), - Destroy(ClientHandle), -} - pub type ClientHandle = u64; #[derive(Debug, Default, Clone, Serialize, Deserialize)] diff --git a/src/emulate.rs b/src/emulate.rs index 07b7cb4..759365a 100644 --- a/src/emulate.rs +++ b/src/emulate.rs @@ -1,11 +1,7 @@ use async_trait::async_trait; use std::future; -use crate::{ - client::{ClientEvent, ClientHandle}, - config::EmulationBackend, - event::Event, -}; +use crate::{config::EmulationBackend, event::Event}; use anyhow::Result; use self::error::EmulationCreationError; @@ -32,17 +28,18 @@ pub mod macos; pub mod dummy; pub mod error; +pub type EmulationHandle = u64; + #[async_trait] pub trait InputEmulation: Send { - async fn consume(&mut self, event: Event, client_handle: ClientHandle); - async fn notify(&mut self, client_event: ClientEvent); + async fn consume(&mut self, event: Event, handle: EmulationHandle); + async fn create(&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(()) } - - async fn destroy(&mut self); } pub async fn create_backend( diff --git a/src/emulate/dummy.rs b/src/emulate/dummy.rs index fbb7b31..6b1de3d 100644 --- a/src/emulate/dummy.rs +++ b/src/emulate/dummy.rs @@ -1,10 +1,8 @@ -use crate::{ - client::{ClientEvent, ClientHandle}, - emulate::InputEmulation, - event::Event, -}; +use crate::event::Event; use async_trait::async_trait; +use super::{EmulationHandle, InputEmulation}; + #[derive(Default)] pub struct DummyEmulation; @@ -16,11 +14,9 @@ impl DummyEmulation { #[async_trait] impl InputEmulation for DummyEmulation { - async fn consume(&mut self, event: Event, client_handle: ClientHandle) { + async fn consume(&mut self, event: Event, client_handle: EmulationHandle) { log::info!("received event: ({client_handle}) {event}"); } - async fn notify(&mut self, client_event: ClientEvent) { - log::info!("{client_event:?}"); - } - async fn destroy(&mut self) {} + async fn create(&mut self, _: EmulationHandle) {} + async fn destroy(&mut self, _: EmulationHandle) {} } diff --git a/src/emulate/libei.rs b/src/emulate/libei.rs index eff7f7a..c18b166 100644 --- a/src/emulate/libei.rs +++ b/src/emulate/libei.rs @@ -22,13 +22,9 @@ use reis::{ PendingRequestResult, }; -use crate::{ - client::{ClientEvent, ClientHandle}, - emulate::InputEmulation, - event::Event, -}; +use crate::event::Event; -use super::error::LibeiEmulationCreationError; +use super::{error::LibeiEmulationCreationError, EmulationHandle, InputEmulation}; pub struct LibeiEmulation { handshake: bool, @@ -111,7 +107,7 @@ impl LibeiEmulation { #[async_trait] impl InputEmulation for LibeiEmulation { - async fn consume(&mut self, event: Event, _client_handle: ClientHandle) { + async fn consume(&mut self, event: Event, _client_handle: EmulationHandle) { let now = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() @@ -396,7 +392,6 @@ impl InputEmulation for LibeiEmulation { Ok(()) } - async fn notify(&mut self, _client_event: ClientEvent) {} - - async fn destroy(&mut self) {} + async fn create(&mut self, _: EmulationHandle) {} + async fn destroy(&mut self, _: EmulationHandle) {} } diff --git a/src/emulate/macos.rs b/src/emulate/macos.rs index 16481ed..bc80c2c 100644 --- a/src/emulate/macos.rs +++ b/src/emulate/macos.rs @@ -1,5 +1,4 @@ -use crate::client::{ClientEvent, ClientHandle}; -use crate::emulate::InputEmulation; +use super::{EmulationHandle, InputEmulation}; use crate::event::{Event, KeyboardEvent, PointerEvent}; use async_trait::async_trait; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; @@ -108,7 +107,7 @@ fn key_event(event_source: CGEventSource, key: u16, state: u8) { #[async_trait] impl InputEmulation for MacOSEmulation { - async fn consume(&mut self, event: Event, _client_handle: ClientHandle) { + async fn consume(&mut self, event: Event, _handle: EmulationHandle) { match event { Event::Pointer(pointer_event) => match pointer_event { PointerEvent::Motion { @@ -297,7 +296,7 @@ impl InputEmulation for MacOSEmulation { } } - async fn notify(&mut self, _client_event: ClientEvent) {} + async fn create(&mut self, _handle: EmulationHandle) {} - async fn destroy(&mut self) {} + async fn destroy(&mut self, _handle: EmulationHandle) {} } diff --git a/src/emulate/windows.rs b/src/emulate/windows.rs index a7f0f22..96860f9 100644 --- a/src/emulate/windows.rs +++ b/src/emulate/windows.rs @@ -1,9 +1,11 @@ use super::error::WindowsEmulationCreationError; use crate::{ - emulate::InputEmulation, - event::{KeyboardEvent, PointerEvent}, + event::{ + Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, + }, scancode, }; + use async_trait::async_trait; use std::ops::BitOrAssign; use std::time::Duration; @@ -19,11 +21,7 @@ use windows::Win32::UI::Input::KeyboardAndMouse::{ }; use windows::Win32::UI::WindowsAndMessaging::{XBUTTON1, XBUTTON2}; -use crate::event::{BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT}; -use crate::{ - client::{ClientEvent, ClientHandle}, - event::Event, -}; +use super::{EmulationHandle, InputEmulation}; const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500); const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32); @@ -40,7 +38,7 @@ impl WindowsEmulation { #[async_trait] impl InputEmulation for WindowsEmulation { - async fn consume(&mut self, event: Event, _: ClientHandle) { + async fn consume(&mut self, event: Event, _: EmulationHandle) { match event { Event::Pointer(pointer_event) => match pointer_event { PointerEvent::Motion { @@ -83,11 +81,9 @@ impl InputEmulation for WindowsEmulation { } } - async fn notify(&mut self, _: ClientEvent) { - // nothing to do - } + async fn create(&mut self, _handle: EmulationHandle) {} - async fn destroy(&mut self) {} + async fn destroy(&mut self, _handle: EmulationHandle) {} } impl WindowsEmulation { diff --git a/src/emulate/wlroots.rs b/src/emulate/wlroots.rs index 5d1f7c4..0da3347 100644 --- a/src/emulate/wlroots.rs +++ b/src/emulate/wlroots.rs @@ -1,4 +1,3 @@ -use crate::client::{ClientEvent, ClientHandle}; use crate::emulate::{error::WlrootsEmulationCreationError, InputEmulation}; use async_trait::async_trait; use std::collections::HashMap; @@ -30,10 +29,11 @@ use wayland_client::{ use crate::event::{Event, KeyboardEvent, PointerEvent}; use super::error::WaylandBindError; +use super::EmulationHandle; struct State { keymap: Option<(u32, OwnedFd, u32)>, - input_for_client: HashMap, + input_for_client: HashMap, seat: wl_seat::WlSeat, qh: QueueHandle, vpm: VpManager, @@ -64,7 +64,7 @@ impl WlrootsEmulation { .bind(&qh, 1..=1, ()) .map_err(|e| WaylandBindError::new(e, "virtual-keyboard-unstable-v1"))?; - let input_for_client: HashMap = HashMap::new(); + let input_for_client: HashMap = HashMap::new(); let mut emulate = WlrootsEmulation { last_flush_failed: false, @@ -89,7 +89,7 @@ impl WlrootsEmulation { } impl State { - fn add_client(&mut self, client: ClientHandle) { + fn add_client(&mut self, client: EmulationHandle) { let pointer: Vp = self.vpm.create_virtual_pointer(None, &self.qh, ()); let keyboard: Vk = self.vkm.create_virtual_keyboard(&self.seat, &self.qh, ()); @@ -104,12 +104,19 @@ impl State { self.input_for_client.insert(client, vinput); } + + fn destroy_client(&mut self, handle: EmulationHandle) { + if let Some(input) = self.input_for_client.remove(&handle) { + input.pointer.destroy(); + input.keyboard.destroy(); + } + } } #[async_trait] impl InputEmulation for WlrootsEmulation { - async fn consume(&mut self, event: Event, client_handle: ClientHandle) { - if let Some(virtual_input) = self.state.input_for_client.get(&client_handle) { + async fn consume(&mut self, event: Event, handle: EmulationHandle) { + if let Some(virtual_input) = self.state.input_for_client.get(&handle) { if self.last_flush_failed { if let Err(WaylandError::Io(e)) = self.queue.flush() { if e.kind() == io::ErrorKind::WouldBlock { @@ -118,9 +125,7 @@ impl InputEmulation for WlrootsEmulation { * will overwhelm the output buffer and leave the * wayland connection in a broken state */ - log::warn!( - "can't keep up, discarding event: ({client_handle}) - {event:?}" - ); + log::warn!("can't keep up, discarding event: ({handle}) - {event:?}"); return; } } @@ -144,16 +149,18 @@ impl InputEmulation for WlrootsEmulation { } } - async fn notify(&mut self, client_event: ClientEvent) { - if let ClientEvent::Create(client, _) = client_event { - self.state.add_client(client); - if let Err(e) = self.queue.flush() { - log::error!("{}", e); - } + async fn create(&mut self, handle: EmulationHandle) { + self.state.add_client(handle); + if let Err(e) = self.queue.flush() { + log::error!("{}", e); + } + } + async fn destroy(&mut self, handle: EmulationHandle) { + self.state.destroy_client(handle); + if let Err(e) = self.queue.flush() { + log::error!("{}", e); } } - - async fn destroy(&mut self) {} } struct VirtualInput { diff --git a/src/emulate/x11.rs b/src/emulate/x11.rs index 8a0deb6..36f4043 100644 --- a/src/emulate/x11.rs +++ b/src/emulate/x11.rs @@ -5,15 +5,11 @@ use x11::{ xtest, }; -use crate::{ - client::ClientHandle, - emulate::InputEmulation, - event::{ - Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, - }, +use crate::event::{ + Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, }; -use super::error::X11EmulationCreationError; +use super::{error::X11EmulationCreationError, EmulationHandle, InputEmulation}; pub struct X11Emulation { display: *mut xlib::Display, @@ -102,7 +98,7 @@ impl Drop for X11Emulation { #[async_trait] impl InputEmulation for X11Emulation { - async fn consume(&mut self, event: Event, _: ClientHandle) { + async fn consume(&mut self, event: Event, _: EmulationHandle) { match event { Event::Pointer(pointer_event) => match pointer_event { PointerEvent::Motion { @@ -145,9 +141,11 @@ impl InputEmulation for X11Emulation { } } - async fn notify(&mut self, _: crate::client::ClientEvent) { + async fn create(&mut self, _: EmulationHandle) { // for our purposes it does not matter what client sent the event } - async fn destroy(&mut self) {} + async fn destroy(&mut self, _: EmulationHandle) { + // for our purposes it does not matter what client sent the event + } } diff --git a/src/emulate/xdg_desktop_portal.rs b/src/emulate/xdg_desktop_portal.rs index e4d81c7..75f974e 100644 --- a/src/emulate/xdg_desktop_portal.rs +++ b/src/emulate/xdg_desktop_portal.rs @@ -8,20 +8,16 @@ use ashpd::{ }; use async_trait::async_trait; -use crate::{ - client::ClientEvent, - emulate::InputEmulation, - event::{ - Event::{Keyboard, Pointer}, - KeyboardEvent, PointerEvent, - }, +use crate::event::{ + Event::{Keyboard, Pointer}, + KeyboardEvent, PointerEvent, }; -use super::error::XdpEmulationCreationError; +use super::{error::XdpEmulationCreationError, EmulationHandle, InputEmulation}; pub struct DesktopPortalEmulation<'a> { proxy: RemoteDesktop<'a>, - session: Session<'a>, + session: Option>, } impl<'a> DesktopPortalEmulation<'a> { @@ -55,6 +51,7 @@ impl<'a> DesktopPortalEmulation<'a> { }; log::debug!("started session"); + let session = Some(session); Ok(Self { proxy, session }) } @@ -72,7 +69,11 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { } => { if let Err(e) = self .proxy - .notify_pointer_motion(&self.session, relative_x, relative_y) + .notify_pointer_motion( + self.session.as_ref().expect("no session"), + relative_x, + relative_y, + ) .await { log::warn!("{e}"); @@ -89,7 +90,11 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { }; if let Err(e) = self .proxy - .notify_pointer_button(&self.session, button as i32, state) + .notify_pointer_button( + self.session.as_ref().expect("no session"), + button as i32, + state, + ) .await { log::warn!("{e}"); @@ -102,7 +107,11 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { }; if let Err(e) = self .proxy - .notify_pointer_axis_discrete(&self.session, axis, value) + .notify_pointer_axis_discrete( + self.session.as_ref().expect("no session"), + axis, + value, + ) .await { log::warn!("{e}"); @@ -123,7 +132,12 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { }; if let Err(e) = self .proxy - .notify_pointer_axis(&self.session, dx, dy, true) + .notify_pointer_axis( + self.session.as_ref().expect("no session"), + dx, + dy, + true, + ) .await { log::warn!("{e}"); @@ -144,7 +158,11 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { }; if let Err(e) = self .proxy - .notify_keyboard_keycode(&self.session, key as i32, state) + .notify_keyboard_keycode( + self.session.as_ref().expect("no session"), + key as i32, + state, + ) .await { log::warn!("{e}"); @@ -159,12 +177,20 @@ impl<'a> InputEmulation for DesktopPortalEmulation<'a> { } } - async fn notify(&mut self, _client: ClientEvent) {} + async fn create(&mut self, _client: EmulationHandle) {} + async fn destroy(&mut self, _client: EmulationHandle) {} +} - async fn destroy(&mut self) { - log::debug!("closing remote desktop session"); - if let Err(e) = self.session.close().await { - log::error!("failed to close remote desktop session: {e}"); - } +impl<'a> Drop for DesktopPortalEmulation<'a> { + fn drop(&mut self) { + let session = self.session.take().expect("no session"); + tokio::runtime::Handle::try_current() + .expect("no runtime") + .block_on(async move { + log::debug!("closing remote desktop session"); + if let Err(e) = session.close().await { + log::error!("failed to close remote desktop session: {e}"); + } + }); } } diff --git a/src/emulation_test.rs b/src/emulation_test.rs index f5fde9a..b6fd3d8 100644 --- a/src/emulation_test.rs +++ b/src/emulation_test.rs @@ -1,4 +1,3 @@ -use crate::client::{ClientEvent, Position}; use crate::config::Config; use crate::emulate; use crate::event::{Event, PointerEvent}; @@ -24,9 +23,7 @@ const RADIUS: f64 = 100.0; async fn input_emulation_test(config: Config) -> Result<()> { let mut emulation = emulate::create(config.emulation_backend).await?; - emulation - .notify(ClientEvent::Create(0, Position::Left)) - .await; + emulation.create(0).await; let start = Instant::now(); let mut offset = (0, 0); loop { diff --git a/src/server/capture_task.rs b/src/server/capture_task.rs index 0e19172..cc85b30 100644 --- a/src/server/capture_task.rs +++ b/src/server/capture_task.rs @@ -5,8 +5,8 @@ use std::{collections::HashSet, net::SocketAddr}; use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle}; use crate::{ - capture::{self, error::CaptureCreationError, InputCapture}, - client::{ClientEvent, ClientHandle}, + capture::{self, error::CaptureCreationError, CaptureHandle, InputCapture, Position}, + client::ClientHandle, config::CaptureBackend, event::{Event, KeyboardEvent}, scancode, @@ -19,8 +19,10 @@ use super::Server; pub enum CaptureEvent { /// capture must release the mouse Release, - /// capture is notified of a change in client states - ClientEvent(ClientEvent), + /// add a capture client + Create(CaptureHandle, Position), + /// destory a capture client + Destroy(CaptureHandle), /// termination signal Terminate, } @@ -52,9 +54,9 @@ pub fn new( CaptureEvent::Release => { capture.release()?; server.state.replace(State::Receiving); - } - CaptureEvent::ClientEvent(e) => capture.notify(e)?, + CaptureEvent::Create(h, p) => capture.create(h, p)?, + CaptureEvent::Destroy(h) => capture.destroy(h)?, CaptureEvent::Terminate => break, }, None => break, @@ -82,7 +84,7 @@ async fn handle_capture_event( capture: &mut Box, sender_tx: &Sender<(Event, SocketAddr)>, timer_tx: &Sender<()>, - event: (ClientHandle, Event), + event: (CaptureHandle, Event), pressed_keys: &mut HashSet, release_bind: &[scancode::Linux], ) -> Result<()> { diff --git a/src/server/emulation_task.rs b/src/server/emulation_task.rs index 4064df6..cd18d0f 100644 --- a/src/server/emulation_task.rs +++ b/src/server/emulation_task.rs @@ -7,9 +7,9 @@ use tokio::{ }; use crate::{ - client::{ClientEvent, ClientHandle}, + client::ClientHandle, config::EmulationBackend, - emulate::{self, error::EmulationCreationError, InputEmulation}, + emulate::{self, error::EmulationCreationError, EmulationHandle, InputEmulation}, event::{Event, KeyboardEvent}, scancode, server::State, @@ -19,8 +19,10 @@ use super::{CaptureEvent, Server}; #[derive(Clone, Debug)] pub enum EmulationEvent { - /// input emulation is notified of a change in client states - ClientEvent(ClientEvent), + /// create a new client + Create(EmulationHandle), + /// destroy a client + Destroy(EmulationHandle), /// input emulation must release keys for client ReleaseKeys(ClientHandle), /// termination signal @@ -49,7 +51,8 @@ pub fn new( emulate_event = rx.recv() => { match emulate_event { Some(e) => match e { - EmulationEvent::ClientEvent(e) => emulate.notify(e).await, + EmulationEvent::Create(h) => emulate.create(h).await, + EmulationEvent::Destroy(h) => emulate.destroy(h).await, EmulationEvent::ReleaseKeys(c) => release_keys(&server, &mut emulate, c).await, EmulationEvent::Terminate => break, }, @@ -73,8 +76,6 @@ pub fn new( release_keys(&server, &mut emulate, client).await; } - // destroy emulator - emulate.destroy().await; anyhow::Ok(()) }); Ok((emulate_task, tx)) diff --git a/src/server/frontend_task.rs b/src/server/frontend_task.rs index a3c3fc9..c860828 100644 --- a/src/server/frontend_task.rs +++ b/src/server/frontend_task.rs @@ -17,7 +17,7 @@ use tokio::{ }; use crate::{ - client::{ClientEvent, ClientHandle, Position}, + client::{ClientHandle, Position}, frontend::{self, FrontendEvent, FrontendListener, FrontendRequest}, }; @@ -205,9 +205,8 @@ pub async fn deactivate_client( None => return, }; - let event = ClientEvent::Destroy(handle); - let _ = capture.send(CaptureEvent::ClientEvent(event)).await; - let _ = emulate.send(EmulationEvent::ClientEvent(event)).await; + let _ = capture.send(CaptureEvent::Destroy(handle)).await; + let _ = emulate.send(EmulationEvent::Destroy(handle)).await; } pub async fn activate_client( @@ -237,9 +236,8 @@ pub async fn activate_client( }; /* notify emulation, capture and frontends */ - let event = ClientEvent::Create(handle, pos); - let _ = capture.send(CaptureEvent::ClientEvent(event)).await; - let _ = emulate.send(EmulationEvent::ClientEvent(event)).await; + let _ = capture.send(CaptureEvent::Create(handle, pos.into())).await; + let _ = emulate.send(EmulationEvent::Create(handle)).await; } pub async fn remove_client( @@ -258,9 +256,8 @@ pub async fn remove_client( }; if active { - let destroy = ClientEvent::Destroy(handle); - let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await; - let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await; + let _ = capture.send(CaptureEvent::Destroy(handle)).await; + let _ = emulate.send(EmulationEvent::Destroy(handle)).await; } } @@ -335,13 +332,11 @@ async fn update_pos( // update state in event input emulator & input capture if changed { if active { - let destroy = ClientEvent::Destroy(handle); - let _ = capture.send(CaptureEvent::ClientEvent(destroy)).await; - let _ = emulate.send(EmulationEvent::ClientEvent(destroy)).await; + let _ = capture.send(CaptureEvent::Destroy(handle)).await; + let _ = emulate.send(EmulationEvent::Destroy(handle)).await; } - let create = ClientEvent::Create(handle, pos); - let _ = capture.send(CaptureEvent::ClientEvent(create)).await; - let _ = emulate.send(EmulationEvent::ClientEvent(create)).await; + let _ = capture.send(CaptureEvent::Create(handle, pos.into())).await; + let _ = emulate.send(EmulationEvent::Create(handle)).await; } }