From b6b16063a80e545d08b282b881705e7f4756d163 Mon Sep 17 00:00:00 2001 From: Ferdinand Schober Date: Mon, 1 Jul 2024 20:09:16 +0200 Subject: [PATCH] Configurable emulation backend (#151) --- src/capture.rs | 97 ++++++++--------- src/capture/error.rs | 4 +- src/config.rs | 120 ++++++++++++++------- src/emulate.rs | 112 ++++++++++---------- src/emulate/error.rs | 168 ++++++++++++++++++++++++++++++ src/emulate/libei.rs | 9 +- src/emulate/macos.rs | 11 +- src/emulate/windows.rs | 4 +- src/emulate/wlroots.rs | 24 +++-- src/emulate/x11.rs | 7 +- src/emulate/xdg_desktop_portal.rs | 4 +- src/emulation_test.rs | 9 +- src/main.rs | 4 +- src/server.rs | 13 ++- src/server/emulation_task.rs | 10 +- 15 files changed, 417 insertions(+), 179 deletions(-) create mode 100644 src/emulate/error.rs diff --git a/src/capture.rs b/src/capture.rs index ca93091..8b100ca 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -30,66 +30,57 @@ pub mod x11; /// fallback input capture (does not produce events) pub mod dummy; -#[allow(unreachable_code)] +pub async fn create_backend( + backend: CaptureBackend, +) -> Result>>, CaptureCreationError> { + 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())), + } +} + 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?)) + let b = create_backend(backend).await; + if b.is_ok() { + log::info!("using capture backend: {backend}"); + } + return b; + } + + for backend in [ + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + CaptureBackend::InputCapturePortal, + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + CaptureBackend::LayerShell, + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + CaptureBackend::X11, + #[cfg(windows)] + CaptureBackend::Windows, + #[cfg(target_os = "macos")] + CaptureBackend::MacOs, + CaptureBackend::Dummy, + ] { + match create_backend(backend).await { + Ok(b) => { + log::info!("using capture backend: {backend}"); + return Ok(b); } - #[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 Ok(Box::new(p)), - Err(e) => log::info!("macos input capture not available: {e}"), - } - - #[cfg(windows)] - 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 Ok(Box::new(p)); + Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"), } - Err(e) => log::info!("libei input capture not available: {e}"), } - - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - match wayland::WaylandInputCapture::new() { - Ok(p) => { - log::info!("using layer-shell input capture"); - return Ok(Box::new(p)); - } - Err(e) => log::info!("layer_shell input capture not available: {e}"), - } - - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - match x11::X11InputCapture::new() { - Ok(p) => { - log::info!("using x11 input capture"); - return Ok(Box::new(p)); - } - Err(e) => log::info!("x11 input capture not available: {e}"), - } - - log::error!("falling back to dummy input capture"); - Ok(Box::new(dummy::DummyInputCapture::new())) + Err(CaptureCreationError::NoAvailableBackend) } pub trait InputCapture: Stream> + Unpin { diff --git a/src/capture/error.rs b/src/capture/error.rs index 15231c8..778aacb 100644 --- a/src/capture/error.rs +++ b/src/capture/error.rs @@ -12,6 +12,7 @@ use wayland_client::{ #[derive(Debug, Error)] pub enum CaptureCreationError { + NoAvailableBackend, #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] Libei(#[from] LibeiCaptureCreationError), #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] @@ -40,7 +41,8 @@ impl Display for CaptureCreationError { #[cfg(target_os = "macos")] CaptureCreationError::Macos(e) => format!("{e}"), #[cfg(windows)] - CaptureCreationError::Windows => String::from(""), + CaptureCreationError::Windows => String::new(), + CaptureCreationError::NoAvailableBackend => "no available backend".to_string(), }; write!(f, "could not create input capture: {reason}") } diff --git a/src/config.rs b/src/config.rs index c88af93..ecbffe3 100644 --- a/src/config.rs +++ b/src/config.rs @@ -3,6 +3,7 @@ use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use std::collections::HashSet; use std::env; +use std::fmt::Display; use std::net::IpAddr; use std::{error::Error, fs}; use toml; @@ -16,8 +17,9 @@ pub const DEFAULT_PORT: u16 = 4242; #[derive(Serialize, Deserialize, Debug)] pub struct ConfigToml { pub capture_backend: Option, + pub emulation_backend: Option, pub port: Option, - pub frontend: Option, + pub frontend: Option, pub release_bind: Option>, pub left: Option, pub right: Option, @@ -53,7 +55,7 @@ struct CliArgs { /// the frontend to use [cli | gtk] #[arg(short, long)] - frontend: Option, + frontend: Option, /// non-default config file location #[arg(short, long)] @@ -95,18 +97,81 @@ pub enum CaptureBackend { Dummy, } -#[derive(Debug, Clone, Copy, ValueEnum)] -pub enum EmulationBackend {} +impl Display for CaptureBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + CaptureBackend::InputCapturePortal => write!(f, "input-capture-portal"), + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + CaptureBackend::LayerShell => write!(f, "layer-shell"), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + CaptureBackend::X11 => write!(f, "X11"), + #[cfg(windows)] + CaptureBackend::Windows => write!(f, "windows"), + #[cfg(target_os = "macos")] + CaptureBackend::MacOs => write!(f, "MacOS"), + CaptureBackend::Dummy => write!(f, "dummy"), + } + } +} -#[derive(Debug, PartialEq, Eq)] +#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] +pub enum EmulationBackend { + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + Wlroots, + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + Libei, + #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + Xdp, + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + X11, + #[cfg(windows)] + Windows, + #[cfg(target_os = "macos")] + MacOs, + Dummy, +} + +impl Display for EmulationBackend { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + EmulationBackend::Wlroots => write!(f, "wlroots"), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + EmulationBackend::Libei => write!(f, "libei"), + #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + EmulationBackend::Xdp => write!(f, "xdg-desktop-portal"), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + EmulationBackend::X11 => write!(f, "X11"), + #[cfg(windows)] + EmulationBackend::Windows => write!(f, "windows"), + #[cfg(target_os = "macos")] + EmulationBackend::MacOs => write!(f, "macos"), + EmulationBackend::Dummy => write!(f, "dummy"), + } + } +} + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Serialize, ValueEnum)] pub enum Frontend { Gtk, Cli, } +impl Default for Frontend { + fn default() -> Self { + if cfg!(feature = "gtk") { + Self::Gtk + } else { + Self::Cli + } + } +} + #[derive(Debug)] pub struct Config { pub capture_backend: Option, + pub emulation_backend: Option, pub frontend: Frontend, pub port: u16, pub clients: Vec<(TomlClient, Position)>, @@ -158,33 +223,14 @@ impl Config { Ok(c) => Some(c), }; - let frontend = match args.frontend { - None => match &config_toml { - Some(c) => c.frontend.clone(), - None => None, - }, - frontend => frontend, - }; + let frontend_arg = args.frontend; + let frontend_cfg = config_toml.as_ref().and_then(|c| c.frontend); + let frontend = frontend_arg.or(frontend_cfg).unwrap_or_default(); - let frontend = match frontend { - #[cfg(feature = "gtk")] - None => Frontend::Gtk, - #[cfg(not(feature = "gtk"))] - None => Frontend::Cli, - Some(s) => match s.as_str() { - "cli" => Frontend::Cli, - "gtk" => Frontend::Gtk, - _ => Frontend::Cli, - }, - }; - - let port = match args.port { - Some(port) => port, - None => match &config_toml { - Some(c) => c.port.unwrap_or(DEFAULT_PORT), - None => DEFAULT_PORT, - }, - }; + let port = args + .port + .or(config_toml.as_ref().and_then(|c| c.port)) + .unwrap_or(DEFAULT_PORT); log::debug!("{config_toml:?}"); let release_bind = config_toml @@ -192,10 +238,13 @@ 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 capture_backend = args + .capture_backend + .or(config_toml.as_ref().and_then(|c| c.capture_backend)); + + let emulation_backend = args + .emulation_backend + .or(config_toml.as_ref().and_then(|c| c.emulation_backend)); let mut clients: Vec<(TomlClient, Position)> = vec![]; @@ -220,6 +269,7 @@ impl Config { Ok(Config { capture_backend, + emulation_backend, daemon, frontend, clients, diff --git a/src/emulate.rs b/src/emulate.rs index 4819b5c..07b7cb4 100644 --- a/src/emulate.rs +++ b/src/emulate.rs @@ -3,10 +3,13 @@ use std::future; use crate::{ client::{ClientEvent, ClientHandle}, + config::EmulationBackend, event::Event, }; use anyhow::Result; +use self::error::EmulationCreationError; + #[cfg(windows)] pub mod windows; @@ -27,6 +30,7 @@ pub mod macos; /// fallback input emulation (logs events) pub mod dummy; +pub mod error; #[async_trait] pub trait InputEmulation: Send { @@ -41,58 +45,60 @@ pub trait InputEmulation: Send { async fn destroy(&mut self); } -pub async fn create() -> Box { - #[cfg(windows)] - match windows::WindowsEmulation::new() { - Ok(c) => return Box::new(c), - Err(e) => log::warn!("windows input emulation unavailable: {e}"), +pub async fn create_backend( + backend: EmulationBackend, +) -> Result, EmulationCreationError> { + match backend { + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + EmulationBackend::Wlroots => Ok(Box::new(wlroots::WlrootsEmulation::new()?)), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + EmulationBackend::Libei => Ok(Box::new(libei::LibeiEmulation::new().await?)), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + EmulationBackend::X11 => Ok(Box::new(x11::X11Emulation::new()?)), + #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + EmulationBackend::Xdp => Ok(Box::new( + xdg_desktop_portal::DesktopPortalEmulation::new().await?, + )), + #[cfg(windows)] + EmulationBackend::Windows => Ok(Box::new(windows::WindowsEmulation::new()?)), + #[cfg(target_os = "macos")] + EmulationBackend::MacOs => Ok(Box::new(macos::MacOSEmulation::new()?)), + EmulationBackend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())), } - - #[cfg(target_os = "macos")] - match macos::MacOSEmulation::new() { - Ok(c) => { - log::info!("using macos input emulation"); - return Box::new(c); - } - Err(e) => log::error!("macos input emulatino not available: {e}"), - } - - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - match wlroots::WlrootsEmulation::new() { - Ok(c) => { - log::info!("using wlroots input emulation"); - return Box::new(c); - } - Err(e) => log::info!("wayland backend not available: {e}"), - } - - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - match libei::LibeiEmulation::new().await { - Ok(c) => { - log::info!("using libei input emulation"); - return Box::new(c); - } - Err(e) => log::info!("libei not available: {e}"), - } - - #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] - match xdg_desktop_portal::DesktopPortalEmulation::new().await { - Ok(c) => { - log::info!("using xdg-remote-desktop-portal input emulation"); - return Box::new(c); - } - Err(e) => log::info!("remote desktop portal not available: {e}"), - } - - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - match x11::X11Emulation::new() { - Ok(c) => { - log::info!("using x11 input emulation"); - return Box::new(c); - } - Err(e) => log::info!("x11 input emulation not available: {e}"), - } - - log::error!("falling back to dummy input emulation"); - Box::new(dummy::DummyEmulation::new()) +} + +pub async fn create( + backend: Option, +) -> Result, EmulationCreationError> { + if let Some(backend) = backend { + let b = create_backend(backend).await; + if b.is_ok() { + log::info!("using emulation backend: {backend}"); + } + return b; + } + + for backend in [ + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + EmulationBackend::Wlroots, + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + EmulationBackend::Libei, + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + EmulationBackend::X11, + #[cfg(windows)] + EmulationBackend::Windows, + #[cfg(target_os = "macos")] + EmulationBackend::MacOs, + EmulationBackend::Dummy, + ] { + match create_backend(backend).await { + Ok(b) => { + log::info!("using emulation backend: {backend}"); + return Ok(b); + } + Err(e) => log::warn!("{e}"), + } + } + + Err(EmulationCreationError::NoAvailableBackend) } diff --git a/src/emulate/error.rs b/src/emulate/error.rs new file mode 100644 index 0000000..75d8d6e --- /dev/null +++ b/src/emulate/error.rs @@ -0,0 +1,168 @@ +use std::fmt::Display; + +use thiserror::Error; +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +use wayland_client::{ + backend::WaylandError, + globals::{BindError, GlobalError}, + ConnectError, DispatchError, +}; + +#[derive(Debug, Error)] +pub enum EmulationCreationError { + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + Wlroots(#[from] WlrootsEmulationCreationError), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + Libei(#[from] LibeiEmulationCreationError), + #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + Xdp(#[from] XdpEmulationCreationError), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + X11(#[from] X11EmulationCreationError), + #[cfg(target_os = "macos")] + MacOs(#[from] MacOSEmulationCreationError), + #[cfg(windows)] + Windows(#[from] WindowsEmulationCreationError), + NoAvailableBackend, +} + +impl Display for EmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let reason = match self { + #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] + EmulationCreationError::Wlroots(e) => format!("wlroots backend: {e}"), + #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] + EmulationCreationError::Libei(e) => format!("libei backend: {e}"), + #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] + EmulationCreationError::Xdp(e) => format!("desktop portal backend: {e}"), + #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] + EmulationCreationError::X11(e) => format!("x11 backend: {e}"), + #[cfg(target_os = "macos")] + EmulationCreationError::MacOs(e) => format!("macos backend: {e}"), + #[cfg(windows)] + EmulationCreationError::Windows(e) => format!("windows backend: {e}"), + EmulationCreationError::NoAvailableBackend => "no backend available".to_string(), + }; + write!(f, "could not create input emulation backend: {reason}") + } +} + +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum WlrootsEmulationCreationError { + Connect(#[from] ConnectError), + Global(#[from] GlobalError), + Wayland(#[from] WaylandError), + Bind(#[from] WaylandBindError), + Dispatch(#[from] DispatchError), + Io(#[from] std::io::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")))] +impl Display for WlrootsEmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + WlrootsEmulationCreationError::Bind(e) => write!(f, "{e}"), + WlrootsEmulationCreationError::Connect(e) => { + write!(f, "could not connect to wayland compositor: {e}") + } + WlrootsEmulationCreationError::Global(e) => write!(f, "wayland error: {e}"), + WlrootsEmulationCreationError::Wayland(e) => write!(f, "wayland error: {e}"), + WlrootsEmulationCreationError::Dispatch(e) => { + write!(f, "error dispatching wayland events: {e}") + } + WlrootsEmulationCreationError::Io(e) => write!(f, "io error: {e}"), + } + } +} + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum LibeiEmulationCreationError { + Ashpd(#[from] ashpd::Error), + Io(#[from] std::io::Error), +} + +#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] +impl Display for LibeiEmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + LibeiEmulationCreationError::Ashpd(e) => write!(f, "xdg-desktop-portal: {e}"), + LibeiEmulationCreationError::Io(e) => write!(f, "io error: {e}"), + } + } +} + +#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum XdpEmulationCreationError { + Ashpd(#[from] ashpd::Error), +} + +#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] +impl Display for XdpEmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + XdpEmulationCreationError::Ashpd(e) => write!(f, "portal error: {e}"), + } + } +} + +#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] +#[derive(Debug, Error)] +pub enum X11EmulationCreationError { + OpenDisplay, +} + +#[cfg(all(unix, feature = "x11", not(target_os = "macos")))] +impl Display for X11EmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + X11EmulationCreationError::OpenDisplay => write!(f, "could not open display!"), + } + } +} + +#[cfg(target_os = "macos")] +#[derive(Debug, Error)] +pub enum MacOSEmulationCreationError { + EventSourceCreation, +} + +#[cfg(target_os = "macos")] +impl Display for MacOSEmulationCreationError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + MacOSEmulationCreationError::EventSourceCreation => { + write!(f, "could not create event source") + } + } + } +} + +#[cfg(windows)] +#[derive(Debug, Error)] +pub enum WindowsEmulationCreationError {} diff --git a/src/emulate/libei.rs b/src/emulate/libei.rs index 7b6f159..eff7f7a 100644 --- a/src/emulate/libei.rs +++ b/src/emulate/libei.rs @@ -1,10 +1,11 @@ +use anyhow::{anyhow, Result}; use std::{ collections::HashMap, + io, os::{fd::OwnedFd, unix::net::UnixStream}, time::{SystemTime, UNIX_EPOCH}, }; -use anyhow::{anyhow, Result}; use ashpd::{ desktop::{ remote_desktop::{DeviceType, RemoteDesktop}, @@ -27,6 +28,8 @@ use crate::{ event::Event, }; +use super::error::LibeiEmulationCreationError; + pub struct LibeiEmulation { handshake: bool, context: ei::Context, @@ -77,14 +80,14 @@ async fn get_ei_fd() -> Result { } impl LibeiEmulation { - pub async fn new() -> Result { + pub async fn new() -> Result { // fd is owned by the message, so we need to dup it let eifd = get_ei_fd().await?; let stream = UnixStream::from(eifd); // let stream = UnixStream::connect("/run/user/1000/eis-0")?; stream.set_nonblocking(true)?; let context = ei::Context::new(stream)?; - context.flush()?; + context.flush().map_err(|e| io::Error::new(e.kind(), e))?; let events = EiEventStream::new(context.clone())?; Ok(Self { handshake: false, diff --git a/src/emulate/macos.rs b/src/emulate/macos.rs index f69b4fc..16481ed 100644 --- a/src/emulate/macos.rs +++ b/src/emulate/macos.rs @@ -1,7 +1,6 @@ use crate::client::{ClientEvent, ClientHandle}; use crate::emulate::InputEmulation; use crate::event::{Event, KeyboardEvent, PointerEvent}; -use anyhow::{anyhow, Result}; use async_trait::async_trait; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::event::{ @@ -13,6 +12,8 @@ use std::ops::{Index, IndexMut}; use std::time::Duration; use tokio::task::AbortHandle; +use super::error::MacOSEmulationCreationError; + const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500); const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32); @@ -53,11 +54,9 @@ impl IndexMut for ButtonState { unsafe impl Send for MacOSEmulation {} impl MacOSEmulation { - pub fn new() -> Result { - let event_source = match CGEventSource::new(CGEventSourceStateID::CombinedSessionState) { - Ok(e) => e, - Err(_) => return Err(anyhow!("event source creation failed!")), - }; + pub fn new() -> Result { + let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState) + .map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?; let button_state = ButtonState { left: false, right: false, diff --git a/src/emulate/windows.rs b/src/emulate/windows.rs index 7eeb5c5..a7f0f22 100644 --- a/src/emulate/windows.rs +++ b/src/emulate/windows.rs @@ -1,9 +1,9 @@ +use super::error::WindowsEmulationCreationError; use crate::{ emulate::InputEmulation, event::{KeyboardEvent, PointerEvent}, scancode, }; -use anyhow::Result; use async_trait::async_trait; use std::ops::BitOrAssign; use std::time::Duration; @@ -33,7 +33,7 @@ pub struct WindowsEmulation { } impl WindowsEmulation { - pub fn new() -> Result { + pub fn new() -> Result { Ok(Self { repeat_task: None }) } } diff --git a/src/emulate/wlroots.rs b/src/emulate/wlroots.rs index d83e715..5d1f7c4 100644 --- a/src/emulate/wlroots.rs +++ b/src/emulate/wlroots.rs @@ -1,5 +1,5 @@ use crate::client::{ClientEvent, ClientHandle}; -use crate::emulate::InputEmulation; +use crate::emulate::{error::WlrootsEmulationCreationError, InputEmulation}; use async_trait::async_trait; use std::collections::HashMap; use std::io; @@ -7,7 +7,6 @@ use std::os::fd::{AsFd, OwnedFd}; use wayland_client::backend::WaylandError; use wayland_client::WEnum; -use anyhow::{anyhow, Result}; use wayland_client::protocol::wl_keyboard::{self, WlKeyboard}; use wayland_client::protocol::wl_pointer::{Axis, ButtonState}; use wayland_client::protocol::wl_seat::WlSeat; @@ -30,6 +29,8 @@ use wayland_client::{ use crate::event::{Event, KeyboardEvent, PointerEvent}; +use super::error::WaylandBindError; + struct State { keymap: Option<(u32, OwnedFd, u32)>, input_for_client: HashMap, @@ -47,18 +48,21 @@ pub(crate) struct WlrootsEmulation { } impl WlrootsEmulation { - pub fn new() -> Result { + pub fn new() -> Result { let conn = Connection::connect_to_env()?; let (globals, queue) = registry_queue_init::(&conn)?; let qh = queue.handle(); - let seat: wl_seat::WlSeat = match globals.bind(&qh, 7..=8, ()) { - Ok(wl_seat) => wl_seat, - Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")), - }; + let seat: wl_seat::WlSeat = globals + .bind(&qh, 7..=8, ()) + .map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?; - let vpm: VpManager = globals.bind(&qh, 1..=1, ())?; - let vkm: VkManager = globals.bind(&qh, 1..=1, ())?; + let vpm: VpManager = globals + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "wlr-virtual-pointer-unstable-v1"))?; + let vkm: VkManager = globals + .bind(&qh, 1..=1, ()) + .map_err(|e| WaylandBindError::new(e, "virtual-keyboard-unstable-v1"))?; let input_for_client: HashMap = HashMap::new(); @@ -75,7 +79,7 @@ impl WlrootsEmulation { queue, }; while emulate.state.keymap.is_none() { - emulate.queue.blocking_dispatch(&mut emulate.state).unwrap(); + emulate.queue.blocking_dispatch(&mut emulate.state)?; } // let fd = unsafe { &File::from_raw_fd(emulate.state.keymap.unwrap().1.as_raw_fd()) }; // let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() }; diff --git a/src/emulate/x11.rs b/src/emulate/x11.rs index 24cdb9d..8a0deb6 100644 --- a/src/emulate/x11.rs +++ b/src/emulate/x11.rs @@ -1,4 +1,3 @@ -use anyhow::{anyhow, Result}; use async_trait::async_trait; use std::ptr; use x11::{ @@ -14,6 +13,8 @@ use crate::{ }, }; +use super::error::X11EmulationCreationError; + pub struct X11Emulation { display: *mut xlib::Display, } @@ -21,11 +22,11 @@ pub struct X11Emulation { unsafe impl Send for X11Emulation {} impl X11Emulation { - pub fn new() -> Result { + pub fn new() -> Result { let display = unsafe { match xlib::XOpenDisplay(ptr::null()) { d if d == ptr::null::() as *mut xlib::Display => { - Err(anyhow!("could not open display")) + Err(X11EmulationCreationError::OpenDisplay) } display => Ok(display), } diff --git a/src/emulate/xdg_desktop_portal.rs b/src/emulate/xdg_desktop_portal.rs index e0ff6c4..e4d81c7 100644 --- a/src/emulate/xdg_desktop_portal.rs +++ b/src/emulate/xdg_desktop_portal.rs @@ -17,13 +17,15 @@ use crate::{ }, }; +use super::error::XdpEmulationCreationError; + pub struct DesktopPortalEmulation<'a> { proxy: RemoteDesktop<'a>, session: Session<'a>, } impl<'a> DesktopPortalEmulation<'a> { - pub async fn new() -> Result> { + pub async fn new() -> Result, XdpEmulationCreationError> { log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ..."); let proxy = RemoteDesktop::new().await?; diff --git a/src/emulation_test.rs b/src/emulation_test.rs index 3104328..f5fde9a 100644 --- a/src/emulation_test.rs +++ b/src/emulation_test.rs @@ -1,4 +1,5 @@ use crate::client::{ClientEvent, Position}; +use crate::config::Config; use crate::emulate; use crate::event::{Event, PointerEvent}; use anyhow::Result; @@ -13,14 +14,16 @@ pub fn run() -> Result<()> { .enable_time() .build()?; - runtime.block_on(LocalSet::new().run_until(input_emulation_test())) + let config = Config::new()?; + + runtime.block_on(LocalSet::new().run_until(input_emulation_test(config))) } const FREQUENCY_HZ: f64 = 1.0; const RADIUS: f64 = 100.0; -async fn input_emulation_test() -> Result<()> { - let mut emulation = emulate::create().await; +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; diff --git a/src/main.rs b/src/main.rs index b649c44..28434d6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -71,7 +71,9 @@ fn run_service(config: &Config) -> Result<()> { log::info!("Press {:?} to release the mouse", config.release_bind); let server = Server::new(config); - server.run(config.capture_backend).await?; + server + .run(config.capture_backend, config.emulation_backend) + .await?; log::debug!("service exiting"); anyhow::Ok(()) diff --git a/src/server.rs b/src/server.rs index d342b2b..596b125 100644 --- a/src/server.rs +++ b/src/server.rs @@ -8,7 +8,7 @@ use tokio::signal; use crate::{ client::{ClientConfig, ClientHandle, ClientManager, ClientState}, - config::{CaptureBackend, Config}, + config::{CaptureBackend, Config, EmulationBackend}, dns, frontend::{FrontendListener, FrontendRequest}, server::capture_task::CaptureEvent, @@ -77,7 +77,11 @@ impl Server { } } - pub async fn run(&self, backend: Option) -> anyhow::Result<()> { + pub async fn run( + &self, + capture_backend: Option, + emulation_backend: Option, + ) -> anyhow::Result<()> { // create frontend communication adapter let frontend = match FrontendListener::new().await { Some(f) => f?, @@ -97,7 +101,7 @@ impl Server { // input capture let (mut capture_task, capture_channel) = capture_task::new( - backend, + capture_backend, self.clone(), sender_tx.clone(), timer_tx.clone(), @@ -106,12 +110,13 @@ impl Server { // input emulation let (mut emulation_task, emulate_channel) = emulation_task::new( + emulation_backend, self.clone(), receiver_rx, sender_tx.clone(), capture_channel.clone(), timer_tx, - ); + )?; // create dns resolver let resolver = dns::DnsResolver::new().await?; diff --git a/src/server/emulation_task.rs b/src/server/emulation_task.rs index c688880..4064df6 100644 --- a/src/server/emulation_task.rs +++ b/src/server/emulation_task.rs @@ -8,7 +8,8 @@ use tokio::{ use crate::{ client::{ClientEvent, ClientHandle}, - emulate::{self, InputEmulation}, + config::EmulationBackend, + emulate::{self, error::EmulationCreationError, InputEmulation}, event::{Event, KeyboardEvent}, scancode, server::State, @@ -27,15 +28,16 @@ pub enum EmulationEvent { } pub fn new( + backend: Option, server: Server, mut udp_rx: Receiver>, sender_tx: Sender<(Event, SocketAddr)>, capture_tx: Sender, timer_tx: Sender<()>, -) -> (JoinHandle>, Sender) { +) -> Result<(JoinHandle>, Sender), EmulationCreationError> { let (tx, mut rx) = tokio::sync::mpsc::channel(32); let emulate_task = tokio::task::spawn_local(async move { - let mut emulate = emulate::create().await; + let mut emulate = emulate::create(backend).await?; let mut last_ignored = None; loop { @@ -75,7 +77,7 @@ pub fn new( emulate.destroy().await; anyhow::Ok(()) }); - (emulate_task, tx) + Ok((emulate_task, tx)) } async fn handle_udp_rx(