diff --git a/src/capture.rs b/src/capture.rs index 1d9bc08..9b438ef 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -30,64 +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> { + 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())), + }; +} + 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 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)); + let b = create_backend(backend).await; + if b.is_ok() { + log::info!("using capture backend: {backend}"); } - Err(e) => log::info!("libei input capture not available: {e}"), + return b; } - #[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)); + 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); + } + Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"), } - 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..0b60443 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 => format!(""), + CaptureCreationError::NoAvailableBackend => format!("no available backend"), }; write!(f, "could not create input capture: {reason}") } diff --git a/src/config.rs b/src/config.rs index 1b4a922..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; @@ -96,12 +97,32 @@ pub enum CaptureBackend { Dummy, } +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(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize, ValueEnum)] pub enum EmulationBackend { - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - Libei, #[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)] @@ -111,6 +132,26 @@ pub enum EmulationBackend { 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, diff --git a/src/emulate.rs b/src/emulate.rs index e37cccb..69a3e76 100644 --- a/src/emulate.rs +++ b/src/emulate.rs @@ -45,76 +45,60 @@ pub trait InputEmulation: Send { async fn destroy(&mut self); } +pub async fn create_backend( + backend: EmulationBackend, +) -> Result, EmulationCreationError> { + return 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(windows::MacOSEmulation::new()?)), + EmulationBackend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())), + }; +} + pub async fn create( backend: Option, ) -> Result, EmulationCreationError> { if let Some(backend) = backend { - return match backend { - #[cfg(windows)] - EmulationBackend::Windows => Ok(Box::new(windows::WindowsEmulation::new()?)), - #[cfg(target_os = "macos")] - EmulationBackend::MacOs => Ok(Box::new(windows::MacOSEmulation::new()?)), - #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] - EmulationBackend::Libei => Ok(Box::new(libei::LibeiEmulation::new().await?)), - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - EmulationBackend::Wlroots => Ok(Box::new(wlroots::WlrootsEmulation::new()?)), - #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] - EmulationBackend::X11 => Ok(Box::new(x11::X11Emulation::new()?)), - EmulationBackend::Dummy => Ok(Box::new(dummy::DummyEmulation::new())), - }; - } - - #[cfg(windows)] - match windows::WindowsEmulation::new() { - Ok(c) => return Ok(Box::new(c)), - Err(e) => log::warn!("windows input emulation unavailable: {e}"), - } - - #[cfg(target_os = "macos")] - match macos::MacOSEmulation::new() { - Ok(c) => { - log::info!("using macos input emulation"); - return Ok(Box::new(c)); + let b = create_backend(backend).await; + if b.is_ok() { + log::info!("using emulation backend: {backend}"); } - Err(e) => log::error!("macos input emulatino not available: {e}"), + return b; } - #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] - match wlroots::WlrootsEmulation::new() { - Ok(c) => { - log::info!("using wlroots input emulation"); - return Ok(Box::new(c)); + 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(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 Ok(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 Ok(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 Ok(Box::new(c)); - } - Err(e) => log::info!("x11 input emulation not available: {e}"), - } - - log::error!("falling back to dummy input emulation"); - Ok(Box::new(dummy::DummyEmulation::new())) + Err(EmulationCreationError::NoAvailableBackend) } diff --git a/src/emulate/error.rs b/src/emulate/error.rs index 36ce7a6..2b141c2 100644 --- a/src/emulate/error.rs +++ b/src/emulate/error.rs @@ -1,6 +1,7 @@ -use std::{fmt::Display, io}; +use std::fmt::Display; use thiserror::Error; +#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] use wayland_client::{ backend::WaylandError, globals::{BindError, GlobalError}, @@ -17,15 +18,21 @@ pub enum EmulationCreationError { Xdp(#[from] XdpEmulationCreationError), #[cfg(all(unix, feature = "x11", not(target_os = "macos")))] X11(#[from] X11EmulationCreationError), + 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}"), + EmulationCreationError::NoAvailableBackend => format!("no backend available"), }; write!(f, "could not create input emulation backend: {reason}") } @@ -39,7 +46,7 @@ pub enum WlrootsEmulationCreationError { Wayland(#[from] WaylandError), Bind(#[from] WaylandBindError), Dispatch(#[from] DispatchError), - Io(#[from] io::Error), + Io(#[from] std::io::Error), } #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] @@ -88,7 +95,7 @@ impl Display for WlrootsEmulationCreationError { #[derive(Debug, Error)] pub enum LibeiEmulationCreationError { Ashpd(#[from] ashpd::Error), - Io(#[from] io::Error), + Io(#[from] std::io::Error), } #[cfg(all(unix, feature = "libei", not(target_os = "macos")))] @@ -103,7 +110,18 @@ impl Display for LibeiEmulationCreationError { #[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] #[derive(Debug, Error)] -pub enum XdpEmulationCreationError {} +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)] 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?;