diff --git a/Cargo.lock b/Cargo.lock index 929591c..81c6524 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1312,6 +1312,7 @@ dependencies = [ "serde_json", "slab", "tempfile", + "thiserror", "tokio", "toml", "wayland-client", @@ -2011,18 +2012,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index dda5c5a..059bd1d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ num_enum = "0.7.2" hostname = "0.4.0" slab = "0.4.9" endi = "1.1.0" +thiserror = "1.0.61" [target.'cfg(unix)'.dependencies] libc = "0.2.148" diff --git a/config.toml b/config.toml index ef66ae3..b05d249 100644 --- a/config.toml +++ b/config.toml @@ -1,5 +1,7 @@ # example configuration +# capture_backend = "LayerShell" + # release bind release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ] diff --git a/src/capture.rs b/src/capture.rs index d3b99db..ca93091 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -4,9 +4,14 @@ use futures_core::Stream; use crate::{ client::{ClientEvent, ClientHandle}, + config::CaptureBackend, event::Event, }; +use self::error::CaptureCreationError; + +pub mod error; + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] pub mod libei; @@ -25,24 +30,42 @@ pub mod x11; /// fallback input capture (does not produce events) pub mod dummy; -pub async fn create() -> Box>> { +#[allow(unreachable_code)] +pub async fn create( + backend: Option, +) -> Result>>, CaptureCreationError> { + if let Some(backend) = backend { + return match backend { + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + CaptureBackend::InputCapturePortal => { + Ok(Box::new(libei::LibeiInputCapture::new().await?)) + } + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + CaptureBackend::LayerShell => Ok(Box::new(wayland::WaylandInputCapture::new()?)), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + CaptureBackend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)), + #[cfg(windows)] + CaptureBackend::Windows => Ok(Box::new(windows::WindowsInputCapture::new())), + #[cfg(target_os = "macos")] + CaptureBackend::MacOs => Ok(Box::new(macos::MacOSInputCapture::new()?)), + CaptureBackend::Dummy => Ok(Box::new(dummy::DummyInputCapture::new())), + }; + } + #[cfg(target_os = "macos")] match macos::MacOSInputCapture::new() { - Ok(p) => return Box::new(p), + Ok(p) => return Ok(Box::new(p)), Err(e) => log::info!("macos input capture not available: {e}"), } #[cfg(windows)] - match windows::WindowsInputCapture::new() { - Ok(p) => return Box::new(p), - Err(e) => log::info!("windows input capture not available: {e}"), - } + return Ok(Box::new(windows::WindowsInputCapture::new())); #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] match libei::LibeiInputCapture::new().await { Ok(p) => { log::info!("using libei input capture"); - return Box::new(p); + return Ok(Box::new(p)); } Err(e) => log::info!("libei input capture not available: {e}"), } @@ -51,7 +74,7 @@ pub async fn create() -> Box { log::info!("using layer-shell input capture"); - return Box::new(p); + return Ok(Box::new(p)); } Err(e) => log::info!("layer_shell input capture not available: {e}"), } @@ -60,13 +83,13 @@ pub async fn create() -> Box { log::info!("using x11 input capture"); - return Box::new(p); + return Ok(Box::new(p)); } Err(e) => log::info!("x11 input capture not available: {e}"), } log::error!("falling back to dummy input capture"); - Box::new(dummy::DummyInputCapture::new()) + Ok(Box::new(dummy::DummyInputCapture::new())) } pub trait InputCapture: Stream> + Unpin { diff --git a/src/capture/error.rs b/src/capture/error.rs new file mode 100644 index 0000000..15231c8 --- /dev/null +++ b/src/capture/error.rs @@ -0,0 +1,140 @@ +use std::fmt::Display; +use thiserror::Error; + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +use std::io; +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +use wayland_client::{ + backend::WaylandError, + globals::{BindError, GlobalError}, + ConnectError, DispatchError, +}; + +#[derive(Debug, Error)] +pub enum CaptureCreationError { + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + Libei(#[from] LibeiCaptureCreationError), + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + LayerShell(#[from] LayerShellCaptureCreationError), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + X11(#[from] X11InputCaptureCreationError), + #[cfg(target_os = "macos")] + Macos(#[from] MacOSInputCaptureCreationError), + #[cfg(windows)] + Windows, +} + +impl Display for CaptureCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let reason = match self { + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + CaptureCreationError::Libei(reason) => { + format!("error creating portal backend: {reason}") + } + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + CaptureCreationError::LayerShell(reason) => { + format!("error creating layer-shell backend: {reason}") + } + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + CaptureCreationError::X11(e) => format!("{e}"), + #[cfg(target_os = "macos")] + CaptureCreationError::Macos(e) => format!("{e}"), + #[cfg(windows)] + CaptureCreationError::Windows => String::from(""), + }; + write!(f, "could not create input capture: {reason}") + } +} + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum LibeiCaptureCreationError { + Ashpd(#[from] ashpd::Error), +} + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +impl Display for LibeiCaptureCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LibeiCaptureCreationError::Ashpd(portal_error) => write!(f, "{portal_error}"), + } + } +} + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub struct WaylandBindError { + inner: BindError, + protocol: &'static str, +} +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +impl WaylandBindError { + pub(crate) fn new(inner: BindError, protocol: &'static str) -> Self { + Self { inner, protocol } + } +} + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +impl Display for WaylandBindError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "{} protocol not supported: {}", + self.protocol, self.inner + ) + } +} + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum LayerShellCaptureCreationError { + Connect(#[from] ConnectError), + Global(#[from] GlobalError), + Wayland(#[from] WaylandError), + Bind(#[from] WaylandBindError), + Dispatch(#[from] DispatchError), + Io(#[from] io::Error), +} + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +impl Display for LayerShellCaptureCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LayerShellCaptureCreationError::Bind(e) => write!(f, "{e}"), + LayerShellCaptureCreationError::Connect(e) => { + write!(f, "could not connect to wayland compositor: {e}") + } + LayerShellCaptureCreationError::Global(e) => write!(f, "wayland error: {e}"), + LayerShellCaptureCreationError::Wayland(e) => write!(f, "wayland error: {e}"), + LayerShellCaptureCreationError::Dispatch(e) => { + write!(f, "error dispatching wayland events: {e}") + } + LayerShellCaptureCreationError::Io(e) => write!(f, "io error: {e}"), + } + } +} + +#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum X11InputCaptureCreationError { + NotImplemented, +} + +#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] +impl Display for X11InputCaptureCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "X11 input capture is not yet implemented :(") + } +} +#[cfg(target_os = "macos")] +#[derive(Debug, Error)] +pub enum MacOSInputCaptureCreationError { + NotImplemented, +} + +#[cfg(target_os = "macos")] +impl Display for MacOSInputCaptureCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "macos input capture is not yet implemented :(") + } +} diff --git a/src/capture/libei.rs b/src/capture/libei.rs index 4b0000d..816f18a 100644 --- a/src/capture/libei.rs +++ b/src/capture/libei.rs @@ -36,6 +36,8 @@ use crate::{ event::{Event, KeyboardEvent, PointerEvent}, }; +use super::error::LibeiCaptureCreationError; + #[derive(Debug)] enum ProducerEvent { Release, @@ -131,7 +133,7 @@ impl<'a> Drop for LibeiInputCapture<'a> { async fn create_session<'a>( input_capture: &'a InputCapture<'a>, -) -> Result<(Session<'a>, BitFlags)> { +) -> std::result::Result<(Session<'a>, BitFlags), ashpd::Error> { log::debug!("creating input capture session"); let (session, capabilities) = loop { match input_capture @@ -213,7 +215,7 @@ async fn wait_for_active_client( } impl<'a> LibeiInputCapture<'a> { - pub async fn new() -> Result { + pub async fn new() -> std::result::Result { let input_capture = Box::pin(InputCapture::new().await?); let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>; let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?); diff --git a/src/capture/macos.rs b/src/capture/macos.rs index f95fbde..5d122e1 100644 --- a/src/capture/macos.rs +++ b/src/capture/macos.rs @@ -1,7 +1,7 @@ +use crate::capture::error::MacOSInputCaptureCreationError; use crate::capture::InputCapture; use crate::client::{ClientEvent, ClientHandle}; use crate::event::Event; -use anyhow::{anyhow, Result}; use futures_core::Stream; use std::task::{Context, Poll}; use std::{io, pin::Pin}; @@ -9,8 +9,8 @@ use std::{io, pin::Pin}; pub struct MacOSInputCapture; impl MacOSInputCapture { - pub fn new() -> Result { - Err(anyhow!("not yet implemented")) + pub fn new() -> std::result::Result { + Err(MacOSInputCaptureCreationError::NotImplemented) } } diff --git a/src/capture/wayland.rs b/src/capture/wayland.rs index 9f2b82c..0d64105 100644 --- a/src/capture/wayland.rs +++ b/src/capture/wayland.rs @@ -1,9 +1,8 @@ use crate::{ - capture::InputCapture, + capture::{error::WaylandBindError, InputCapture}, client::{ClientEvent, ClientHandle, Position}, }; -use anyhow::{anyhow, Result}; use futures_core::Stream; use memmap::MmapOptions; use std::{ @@ -68,6 +67,8 @@ use tempfile; use crate::event::{Event, KeyboardEvent, PointerEvent}; +use super::error::LayerShellCaptureCreationError; + struct Globals { compositor: wl_compositor::WlCompositor, pointer_constraints: ZwpPointerConstraintsV1, @@ -258,64 +259,37 @@ fn draw(f: &mut File, (width, height): (u32, u32)) { } impl WaylandInputCapture { - pub fn new() -> Result { - let conn = match Connection::connect_to_env() { - Ok(c) => c, - Err(e) => return Err(anyhow!("could not connect to wayland compositor: {e:?}")), - }; - - let (g, mut queue) = match registry_queue_init::(&conn) { - Ok(q) => q, - Err(e) => return Err(anyhow!("failed to initialize wl_registry: {e:?}")), - }; + pub fn new() -> std::result::Result { + let conn = Connection::connect_to_env()?; + let (g, mut queue) = registry_queue_init::(&conn)?; let qh = queue.handle(); - let compositor: wl_compositor::WlCompositor = match g.bind(&qh, 4..=5, ()) { - Ok(compositor) => compositor, - Err(_) => return Err(anyhow!("wl_compositor >= v4 not supported")), - }; - - let xdg_output_manager: ZxdgOutputManagerV1 = match g.bind(&qh, 1..=3, ()) { - Ok(xdg_output_manager) => xdg_output_manager, - Err(_) => return Err(anyhow!("xdg_output not supported!")), - }; - - let shm: wl_shm::WlShm = match g.bind(&qh, 1..=1, ()) { - Ok(wl_shm) => wl_shm, - Err(_) => return Err(anyhow!("wl_shm v1 not supported")), - }; - - let layer_shell: ZwlrLayerShellV1 = match g.bind(&qh, 3..=4, ()) { - Ok(layer_shell) => layer_shell, - Err(_) => return Err(anyhow!("zwlr_layer_shell_v1 >= v3 not supported - required to display a surface at the edge of the screen")), - }; - - let seat: wl_seat::WlSeat = match g.bind(&qh, 7..=8, ()) { - Ok(wl_seat) => wl_seat, - Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")), - }; - - let pointer_constraints: ZwpPointerConstraintsV1 = match g.bind(&qh, 1..=1, ()) { - Ok(pointer_constraints) => pointer_constraints, - Err(_) => return Err(anyhow!("zwp_pointer_constraints_v1 not supported")), - }; - - let relative_pointer_manager: ZwpRelativePointerManagerV1 = match g.bind(&qh, 1..=1, ()) { - Ok(relative_pointer_manager) => relative_pointer_manager, - Err(_) => return Err(anyhow!("zwp_relative_pointer_manager_v1 not supported")), - }; - - let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 = - match g.bind(&qh, 1..=1, ()) { - Ok(shortcut_inhibit_manager) => shortcut_inhibit_manager, - Err(_) => { - return Err(anyhow!( - "zwp_keyboard_shortcuts_inhibit_manager_v1 not supported" - )) - } - }; + let compositor: wl_compositor::WlCompositor = g + .bind(&qh, 4..=5, ()) + .map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?; + let xdg_output_manager: ZxdgOutputManagerV1 = g + .bind(&qh, 1..=3, ()) + .map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?; + let shm: wl_shm::WlShm = g + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "wl_shm"))?; + let layer_shell: ZwlrLayerShellV1 = g + .bind(&qh, 3..=4, ()) + .map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?; + let seat: wl_seat::WlSeat = g + .bind(&qh, 7..=8, ()) + .map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?; + let pointer_constraints: ZwpPointerConstraintsV1 = g + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "zwp_pointer_constraints_v1"))?; + let relative_pointer_manager: ZwpRelativePointerManagerV1 = g + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "zwp_relative_pointer_manager_v1"))?; + let shortcut_inhibit_manager: ZwpKeyboardShortcutsInhibitManagerV1 = g + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "zwp_keyboard_shortcuts_inhibit_manager_v1"))?; let outputs = vec![]; let g = Globals { diff --git a/src/capture/windows.rs b/src/capture/windows.rs index cd93bf5..eff4f9c 100644 --- a/src/capture/windows.rs +++ b/src/capture/windows.rs @@ -575,7 +575,7 @@ fn update_clients(client_event: ClientEvent) { } impl WindowsInputCapture { - pub(crate) fn new() -> Result { + pub(crate) fn new() -> Self { unsafe { let (tx, rx) = channel(10); EVENT_TX.replace(tx); @@ -583,10 +583,10 @@ impl WindowsInputCapture { let msg_thread = Some(thread::spawn(|| message_thread(ready_tx))); /* wait for thread to set its id */ ready_rx.recv().expect("channel closed"); - Ok(Self { + Self { msg_thread, event_rx: rx, - }) + } } } } diff --git a/src/capture/x11.rs b/src/capture/x11.rs index 7f9654b..edc2f53 100644 --- a/src/capture/x11.rs +++ b/src/capture/x11.rs @@ -1,4 +1,3 @@ -use anyhow::{anyhow, Result}; use std::io; use std::task::Poll; @@ -9,11 +8,13 @@ use crate::event::Event; use crate::client::{ClientEvent, ClientHandle}; +use super::error::X11InputCaptureCreationError; + pub struct X11InputCapture {} impl X11InputCapture { - pub fn new() -> Result { - Err(anyhow!("not implemented")) + pub fn new() -> std::result::Result { + Err(X11InputCaptureCreationError::NotImplemented) } } diff --git a/src/capture_test.rs b/src/capture_test.rs index 584ab97..b8f652a 100644 --- a/src/capture_test.rs +++ b/src/capture_test.rs @@ -1,5 +1,6 @@ use crate::capture; use crate::client::{ClientEvent, Position}; +use crate::config::Config; use crate::event::{Event, KeyboardEvent}; use anyhow::{anyhow, Result}; use futures::StreamExt; @@ -12,12 +13,14 @@ pub fn run() -> Result<()> { .enable_time() .build()?; - runtime.block_on(LocalSet::new().run_until(input_capture_test())) + let config = Config::new()?; + + runtime.block_on(LocalSet::new().run_until(input_capture_test(config))) } -async fn input_capture_test() -> Result<()> { +async fn input_capture_test(config: Config) -> Result<()> { log::info!("creating input capture"); - let mut input_capture = capture::create().await; + 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))?; diff --git a/src/config.rs b/src/config.rs index 0878800..c88af93 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use clap::Parser; +use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::env; @@ -15,6 +15,7 @@ pub const DEFAULT_PORT: u16 = 4242; #[derive(Serialize, Deserialize, Debug)] pub struct ConfigToml { + pub capture_backend: Option, pub port: Option, pub frontend: Option, pub release_bind: Option>, @@ -26,6 +27,7 @@ pub struct ConfigToml { #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] pub struct TomlClient { + pub capture_backend: Option, pub hostname: Option, pub host_name: Option, pub ips: Option>, @@ -68,8 +70,34 @@ struct CliArgs { /// test input emulation #[arg(long)] test_emulation: bool, + + /// capture backend override + #[arg(long)] + capture_backend: Option, + + /// emulation backend override + #[arg(long)] + emulation_backend: Option, } +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] +pub enum CaptureBackend { + #[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, +} + +#[derive(Debug, Clone, Copy, ValueEnum)] +pub enum EmulationBackend {} + #[derive(Debug, PartialEq, Eq)] pub enum Frontend { Gtk, @@ -78,6 +106,7 @@ pub enum Frontend { #[derive(Debug)] pub struct Config { + pub capture_backend: Option, pub frontend: Frontend, pub port: u16, pub clients: Vec<(TomlClient, Position)>, @@ -163,6 +192,11 @@ impl Config { .and_then(|c| c.release_bind.clone()) .unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned())); + let capture_backend = match args.capture_backend { + Some(b) => Some(b), + None => config_toml.as_ref().and_then(|c| c.capture_backend), + }; + let mut clients: Vec<(TomlClient, Position)> = vec![]; if let Some(config_toml) = config_toml { @@ -185,6 +219,7 @@ impl Config { let test_emulation = args.test_emulation; Ok(Config { + capture_backend, daemon, frontend, clients, diff --git a/src/main.rs b/src/main.rs index 48bb136..7bd7a04 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,7 @@ fn run_service(config: &Config) -> Result<()> { log::info!("Press Ctrl+Alt+Shift+Super to release the mouse"); let server = Server::new(config); - server.run().await?; + server.run(config.capture_backend).await?; log::debug!("service exiting"); anyhow::Ok(()) diff --git a/src/server.rs b/src/server.rs index 7fc0dbe..d342b2b 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,7 +8,7 @@ use tokio::signal; use crate::{ client::{ClientConfig, ClientHandle, ClientManager, ClientState}, - config::Config, + config::{CaptureBackend, Config}, dns, frontend::{FrontendListener, FrontendRequest}, server::capture_task::CaptureEvent, @@ -77,7 +77,7 @@ impl Server { } } - pub async fn run(&self) -> anyhow::Result<()> { + pub async fn run(&self, backend: Option) -> anyhow::Result<()> { // create frontend communication adapter let frontend = match FrontendListener::new().await { Some(f) => f?, @@ -97,11 +97,12 @@ impl Server { // input capture let (mut capture_task, capture_channel) = capture_task::new( + backend, self.clone(), sender_tx.clone(), timer_tx.clone(), self.release_bind.clone(), - ); + )?; // input emulation let (mut emulation_task, emulate_channel) = emulation_task::new( diff --git a/src/server/capture_task.rs b/src/server/capture_task.rs index 1f0f409..0e19172 100644 --- a/src/server/capture_task.rs +++ b/src/server/capture_task.rs @@ -5,8 +5,9 @@ use std::{collections::HashSet, net::SocketAddr}; use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle}; use crate::{ - capture::{self, InputCapture}, + capture::{self, error::CaptureCreationError, InputCapture}, client::{ClientEvent, ClientHandle}, + config::CaptureBackend, event::{Event, KeyboardEvent}, scancode, server::State, @@ -25,14 +26,15 @@ pub enum CaptureEvent { } pub fn new( + backend: Option, server: Server, sender_tx: Sender<(Event, SocketAddr)>, timer_tx: Sender<()>, release_bind: Vec, -) -> (JoinHandle>, Sender) { +) -> Result<(JoinHandle>, Sender), CaptureCreationError> { let (tx, mut rx) = tokio::sync::mpsc::channel(32); let task = tokio::task::spawn_local(async move { - let mut capture = capture::create().await; + let mut capture = capture::create(backend).await?; let mut pressed_keys = HashSet::new(); loop { tokio::select! { @@ -62,7 +64,7 @@ pub fn new( } anyhow::Ok(()) }); - (task, tx) + Ok((task, tx)) } fn update_pressed_keys(pressed_keys: &mut HashSet, key: u32, state: u8) {