From 110b37e26ef079dec9aec8a8b861c0e60ce6bbb3 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Wed, 10 Jul 2024 09:00:43 +0200 Subject: [PATCH] improve capture error handling --- input-capture/src/dummy.rs | 4 +- input-capture/src/lib.rs | 16 +- input-capture/src/libei.rs | 71 ++++--- input-capture/src/macos.rs | 6 +- input-capture/src/wayland.rs | 10 +- input-capture/src/windows.rs | 4 +- input-capture/src/x11.rs | 4 +- resources/window.ui | 347 +++++++++++++++++---------------- src/frontend.rs | 4 + src/frontend/gtk/window.rs | 7 + src/frontend/gtk/window/imp.rs | 15 ++ src/server/capture_task.rs | 9 + src/server/emulation_task.rs | 9 + src/server/frontend_task.rs | 6 + 14 files changed, 296 insertions(+), 216 deletions(-) diff --git a/input-capture/src/dummy.rs b/input-capture/src/dummy.rs index 21808db..42a119c 100644 --- a/input-capture/src/dummy.rs +++ b/input-capture/src/dummy.rs @@ -6,6 +6,8 @@ use futures_core::Stream; use input_event::Event; +use crate::CaptureError; + use super::{CaptureHandle, InputCapture, Position}; pub struct DummyInputCapture {} @@ -37,7 +39,7 @@ impl InputCapture for DummyInputCapture { } impl Stream for DummyInputCapture { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Pending diff --git a/input-capture/src/lib.rs b/input-capture/src/lib.rs index 90ed483..c4a64a2 100644 --- a/input-capture/src/lib.rs +++ b/input-capture/src/lib.rs @@ -92,7 +92,9 @@ impl Display for Backend { } } -pub trait InputCapture: Stream> + Unpin { +pub trait InputCapture: + Stream> + Unpin +{ /// create a new client with the given id fn create(&mut self, id: CaptureHandle, pos: Position) -> io::Result<()>; @@ -105,8 +107,10 @@ pub trait InputCapture: Stream> + Unpi pub async fn create_backend( backend: Backend, -) -> Result>>, CaptureCreationError> -{ +) -> Result< + Box>>, + CaptureCreationError, +> { match backend { #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)), @@ -124,8 +128,10 @@ pub async fn create_backend( pub async fn create( backend: Option, -) -> Result>>, CaptureCreationError> -{ +) -> Result< + Box>>, + CaptureCreationError, +> { if let Some(backend) = backend { let b = create_backend(backend).await; if b.is_ok() { diff --git a/input-capture/src/libei.rs b/input-capture/src/libei.rs index 0b19203..04dff3b 100644 --- a/input-capture/src/libei.rs +++ b/input-capture/src/libei.rs @@ -5,7 +5,7 @@ use ashpd::{ }, enumflags2::BitFlags, }; -use futures::StreamExt; +use futures::{FutureExt, StreamExt}; use reis::{ ei::{self, keyboard::KeyState}, eis::button::ButtonState, @@ -17,9 +17,9 @@ use std::{ collections::HashMap, io, os::unix::net::UnixStream, - pin::Pin, + pin::{pin, Pin}, rc::Rc, - task::{ready, Context, Poll}, + task::{Context, Poll}, }; use tokio::{ sync::mpsc::{Receiver, Sender}, @@ -192,7 +192,7 @@ async fn libei_event_handler( .map_err(ReisConvertEventStreamError::from)?; log::trace!("from ei: {ei_event:?}"); let client = current_client.get(); - handle_ei_event(ei_event, client, &context, &event_tx).await; + handle_ei_event(ei_event, client, &context, &event_tx).await?; } } @@ -252,10 +252,9 @@ async fn do_capture<'a>( if active_clients.is_empty() { wait_for_active_client(&mut notify_rx, &mut active_clients).await; if notify_rx.is_closed() { - break Ok(()); - } else { - continue; + break; } + continue; } let current_client = Rc::new(Cell::new(None)); @@ -270,13 +269,12 @@ async fn do_capture<'a>( let (context, ei_event_stream) = connect_to_eis(input_capture, &session).await?; // async event task - let mut ei_task: JoinHandle> = - tokio::task::spawn_local(libei_event_handler( - ei_event_stream, - context, - event_tx.clone(), - current_client.clone(), - )); + let mut ei_task = pin!(libei_event_handler( + ei_event_stream, + context, + event_tx.clone(), + current_client.clone(), + )); let mut activated = input_capture.receive_activated().await?; let mut zones_changed = input_capture.receive_zones_changed().await?; @@ -320,10 +318,8 @@ async fn do_capture<'a>( break; } res = &mut ei_task => { - if let Err(e) = res.expect("ei task paniced") { - log::warn!("libei task exited: {e}"); - } - break; + /* propagate errors to toplevel task */ + res?; } } release_capture( @@ -342,19 +338,16 @@ async fn do_capture<'a>( } }, res = &mut ei_task => { - if let Err(e) = res.expect("ei task paniced") { - log::warn!("libei task exited: {e}"); - } - break; + res?; } } } - ei_task.abort(); input_capture.disable(&session).await?; if event_tx.is_closed() { - break Ok(()); + break; } } + Ok(()) } async fn release_capture( @@ -410,7 +403,7 @@ async fn handle_ei_event( current_client: Option, context: &ei::Context, event_tx: &Sender<(CaptureHandle, Event)>, -) { +) -> Result<(), CaptureError> { match ei_event { EiEvent::SeatAdded(s) => { s.seat.bind_capabilities(&[ @@ -421,7 +414,7 @@ async fn handle_ei_event( DeviceCapability::Scroll, DeviceCapability::Button, ]); - context.flush().unwrap(); + context.flush().map_err(|e| io::Error::new(e.kind(), e))?; } EiEvent::SeatRemoved(_) => {} EiEvent::DeviceAdded(_) => {} @@ -439,7 +432,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Keyboard(modifier_event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } EiEvent::Frame(_) => {} @@ -459,7 +452,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Pointer(motion_event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } EiEvent::PointerMotionAbsolute(_) => {} @@ -476,7 +469,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Pointer(button_event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } EiEvent::ScrollDelta(delta) => { @@ -500,7 +493,7 @@ async fn handle_ei_event( event_tx .send((handle, Event::Pointer(event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } } @@ -516,7 +509,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Pointer(event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } if scroll.discrete_dx != 0 { @@ -528,7 +521,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Pointer(event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } }; } @@ -545,7 +538,7 @@ async fn handle_ei_event( event_tx .send((current_client, Event::Keyboard(key_event))) .await - .unwrap(); + .map_err(|_| CaptureError::EndOfStream)?; } } EiEvent::TouchDown(_) => {} @@ -555,6 +548,7 @@ async fn handle_ei_event( log::error!("disconnect: {d:?}"); } } + Ok(()) } impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { @@ -584,12 +578,15 @@ impl<'a> LanMouseInputCapture for LibeiInputCapture<'a> { } impl<'a> Stream for LibeiInputCapture<'a> { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match ready!(self.event_rx.poll_recv(cx)) { - None => Poll::Ready(None), - Some(e) => Poll::Ready(Some(Ok(e))), + match self.libei_task.poll_unpin(cx) { + Poll::Ready(r) => match r.expect("failed to join") { + Ok(()) => Poll::Ready(None), + Err(e) => Poll::Ready(Some(Err(e))), + }, + Poll::Pending => self.event_rx.poll_recv(cx).map(|e| e.map(Result::Ok)), } } } diff --git a/input-capture/src/macos.rs b/input-capture/src/macos.rs index a2383e1..b549f5d 100644 --- a/input-capture/src/macos.rs +++ b/input-capture/src/macos.rs @@ -1,4 +1,6 @@ -use crate::{error::MacOSInputCaptureCreationError, CaptureHandle, InputCapture, Position}; +use crate::{ + error::MacOSInputCaptureCreationError, CaptureError, CaptureHandle, InputCapture, Position, +}; use futures_core::Stream; use input_event::Event; use std::task::{Context, Poll}; @@ -13,7 +15,7 @@ impl MacOSInputCapture { } impl Stream for MacOSInputCapture { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Pending diff --git a/input-capture/src/wayland.rs b/input-capture/src/wayland.rs index 22368cf..efb65fb 100644 --- a/input-capture/src/wayland.rs +++ b/input-capture/src/wayland.rs @@ -62,6 +62,8 @@ use tempfile; use input_event::{Event, KeyboardEvent, PointerEvent}; +use crate::CaptureError; + use super::{ error::{LayerShellCaptureCreationError, WaylandBindError}, CaptureHandle, InputCapture, Position, @@ -582,7 +584,7 @@ impl InputCapture for WaylandInputCapture { } impl Stream for WaylandInputCapture { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; 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() { @@ -600,7 +602,7 @@ impl Stream for WaylandInputCapture { // prepare next read match inner.prepare_read() { Ok(_) => {} - Err(e) => return Poll::Ready(Some(Err(e))), + Err(e) => return Poll::Ready(Some(Err(e.into()))), } } @@ -610,14 +612,14 @@ impl Stream for WaylandInputCapture { // flush outgoing events if let Err(e) = inner.flush_events() { if e.kind() != ErrorKind::WouldBlock { - return Poll::Ready(Some(Err(e))); + return Poll::Ready(Some(Err(e.into()))); } } // prepare for the next read match inner.prepare_read() { Ok(_) => {} - Err(e) => return Poll::Ready(Some(Err(e))), + Err(e) => return Poll::Ready(Some(Err(e.into()))), } } diff --git a/input-capture/src/windows.rs b/input-capture/src/windows.rs index bfbf6c3..bf9ad4e 100644 --- a/input-capture/src/windows.rs +++ b/input-capture/src/windows.rs @@ -36,7 +36,7 @@ use input_event::{ Event, KeyboardEvent, PointerEvent, BTN_BACK, BTN_FORWARD, BTN_LEFT, BTN_MIDDLE, BTN_RIGHT, }; -use super::{CaptureHandle, InputCapture, Position}; +use super::{CaptureError, CaptureHandle, InputCapture, Position}; enum Request { Create(CaptureHandle, Position), @@ -609,7 +609,7 @@ impl WindowsInputCapture { } impl Stream for WindowsInputCapture { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; 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/input-capture/src/x11.rs b/input-capture/src/x11.rs index 2378c85..fcd1624 100644 --- a/input-capture/src/x11.rs +++ b/input-capture/src/x11.rs @@ -3,6 +3,8 @@ use std::task::Poll; use futures_core::Stream; +use crate::CaptureError; + use super::InputCapture; use input_event::Event; @@ -32,7 +34,7 @@ impl InputCapture for X11InputCapture { } impl Stream for X11InputCapture { - type Item = io::Result<(CaptureHandle, Event)>; + type Item = Result<(CaptureHandle, Event), CaptureError>; fn poll_next( self: std::pin::Pin<&mut Self>, diff --git a/resources/window.ui b/resources/window.ui index ea094b7..f021581 100644 --- a/resources/window.ui +++ b/resources/window.ui @@ -1,167 +1,186 @@ - - - - - _Close window - window.close - - - + + + + + _Close window + window.close + + + diff --git a/src/frontend.rs b/src/frontend.rs index 2adb78b..5e1083c 100644 --- a/src/frontend.rs +++ b/src/frontend.rs @@ -109,6 +109,10 @@ pub enum FrontendRequest { UpdateFixIps(ClientHandle, Vec), /// request the state of the given client GetState(ClientHandle), + /// request reenabling input capture + EnableCapture, + /// request reenabling input emulation + EnableEmulation, } #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/src/frontend/gtk/window.rs b/src/frontend/gtk/window.rs index e3726a6..5f371ee 100644 --- a/src/frontend/gtk/window.rs +++ b/src/frontend/gtk/window.rs @@ -215,6 +215,13 @@ impl Window { } } + pub fn request_capture(&self) { + self.request(FrontendRequest::EnableCapture); + } + + pub fn request_emulation(&self) { + self.request(FrontendRequest::EnableEmulation); + } pub fn request_client_state(&self, client: &ClientObject) { let handle = client.handle(); let event = FrontendRequest::GetState(handle); diff --git a/src/frontend/gtk/window/imp.rs b/src/frontend/gtk/window/imp.rs index f53b7f4..f79f031 100644 --- a/src/frontend/gtk/window/imp.rs +++ b/src/frontend/gtk/window/imp.rs @@ -30,6 +30,10 @@ pub struct Window { pub hostname_label: TemplateChild