Compare commits

...

13 Commits

Author SHA1 Message Date
Ferdinand Schober
a038a24317 fix the 2nd 2024-07-01 18:47:25 +02:00
Ferdinand Schober
a4cdc138f0 fix windows 2024-07-01 17:34:28 +02:00
Ferdinand Schober
db2004fd02 fix windows 2024-07-01 17:16:36 +02:00
Ferdinand Schober
3c2f9da5b7 clippy 2024-07-01 17:03:53 +02:00
Ferdinand Schober
1599e6e76a formatting 2024-07-01 17:02:37 +02:00
Ferdinand Schober
aba9ddbdcb fix macos 2024-07-01 16:21:17 +02:00
Ferdinand Schober
984efe7dd7 fix typo 2024-07-01 16:04:09 +02:00
Ferdinand Schober
160b2532ff fix module 2024-07-01 16:02:47 +02:00
Ferdinand Schober
51d98cc2eb improve backend creation code 2024-07-01 14:01:42 +02:00
Ferdinand Schober
b1d9180cca correct conditional compilation 2024-07-01 12:13:27 +02:00
Ferdinand Schober
08f24293f8 fix compilation errors 2024-07-01 10:02:55 +02:00
Ferdinand Schober
e2162b905a update 2024-06-30 19:46:51 +02:00
Ferdinand Schober
2ac193a5b2 refactor error types 2024-06-30 19:35:34 +02:00
15 changed files with 417 additions and 179 deletions

View File

@@ -30,66 +30,57 @@ pub mod x11;
/// fallback input capture (does not produce events) /// fallback input capture (does not produce events)
pub mod dummy; pub mod dummy;
#[allow(unreachable_code)] pub async fn create_backend(
backend: CaptureBackend,
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, 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( pub async fn create(
backend: Option<CaptureBackend>, backend: Option<CaptureBackend>,
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, CaptureCreationError> { ) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, CaptureCreationError> {
if let Some(backend) = backend { if let Some(backend) = backend {
return match backend { let b = create_backend(backend).await;
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] if b.is_ok() {
CaptureBackend::InputCapturePortal => { log::info!("using capture backend: {backend}");
Ok(Box::new(libei::LibeiInputCapture::new().await?)) }
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")))] Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"),
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::info!("libei input capture not available: {e}"),
} }
Err(CaptureCreationError::NoAvailableBackend)
#[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()))
} }
pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin { pub trait InputCapture: Stream<Item = io::Result<(ClientHandle, Event)>> + Unpin {

View File

@@ -12,6 +12,7 @@ use wayland_client::{
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum CaptureCreationError { pub enum CaptureCreationError {
NoAvailableBackend,
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
Libei(#[from] LibeiCaptureCreationError), Libei(#[from] LibeiCaptureCreationError),
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
@@ -40,7 +41,8 @@ impl Display for CaptureCreationError {
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
CaptureCreationError::Macos(e) => format!("{e}"), CaptureCreationError::Macos(e) => format!("{e}"),
#[cfg(windows)] #[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}") write!(f, "could not create input capture: {reason}")
} }

View File

@@ -3,6 +3,7 @@ use clap::{Parser, ValueEnum};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use std::env; use std::env;
use std::fmt::Display;
use std::net::IpAddr; use std::net::IpAddr;
use std::{error::Error, fs}; use std::{error::Error, fs};
use toml; use toml;
@@ -16,8 +17,9 @@ pub const DEFAULT_PORT: u16 = 4242;
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct ConfigToml { pub struct ConfigToml {
pub capture_backend: Option<CaptureBackend>, pub capture_backend: Option<CaptureBackend>,
pub emulation_backend: Option<EmulationBackend>,
pub port: Option<u16>, pub port: Option<u16>,
pub frontend: Option<String>, pub frontend: Option<Frontend>,
pub release_bind: Option<Vec<scancode::Linux>>, pub release_bind: Option<Vec<scancode::Linux>>,
pub left: Option<TomlClient>, pub left: Option<TomlClient>,
pub right: Option<TomlClient>, pub right: Option<TomlClient>,
@@ -53,7 +55,7 @@ struct CliArgs {
/// the frontend to use [cli | gtk] /// the frontend to use [cli | gtk]
#[arg(short, long)] #[arg(short, long)]
frontend: Option<String>, frontend: Option<Frontend>,
/// non-default config file location /// non-default config file location
#[arg(short, long)] #[arg(short, long)]
@@ -95,18 +97,81 @@ pub enum CaptureBackend {
Dummy, Dummy,
} }
#[derive(Debug, Clone, Copy, ValueEnum)] impl Display for CaptureBackend {
pub enum EmulationBackend {} 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 { pub enum Frontend {
Gtk, Gtk,
Cli, Cli,
} }
impl Default for Frontend {
fn default() -> Self {
if cfg!(feature = "gtk") {
Self::Gtk
} else {
Self::Cli
}
}
}
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub capture_backend: Option<CaptureBackend>, pub capture_backend: Option<CaptureBackend>,
pub emulation_backend: Option<EmulationBackend>,
pub frontend: Frontend, pub frontend: Frontend,
pub port: u16, pub port: u16,
pub clients: Vec<(TomlClient, Position)>, pub clients: Vec<(TomlClient, Position)>,
@@ -158,33 +223,14 @@ impl Config {
Ok(c) => Some(c), Ok(c) => Some(c),
}; };
let frontend = match args.frontend { let frontend_arg = args.frontend;
None => match &config_toml { let frontend_cfg = config_toml.as_ref().and_then(|c| c.frontend);
Some(c) => c.frontend.clone(), let frontend = frontend_arg.or(frontend_cfg).unwrap_or_default();
None => None,
},
frontend => frontend,
};
let frontend = match frontend { let port = args
#[cfg(feature = "gtk")] .port
None => Frontend::Gtk, .or(config_toml.as_ref().and_then(|c| c.port))
#[cfg(not(feature = "gtk"))] .unwrap_or(DEFAULT_PORT);
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,
},
};
log::debug!("{config_toml:?}"); log::debug!("{config_toml:?}");
let release_bind = config_toml let release_bind = config_toml
@@ -192,10 +238,13 @@ impl Config {
.and_then(|c| c.release_bind.clone()) .and_then(|c| c.release_bind.clone())
.unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned())); .unwrap_or(Vec::from_iter(DEFAULT_RELEASE_KEYS.iter().cloned()));
let capture_backend = match args.capture_backend { let capture_backend = args
Some(b) => Some(b), .capture_backend
None => config_toml.as_ref().and_then(|c| c.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![]; let mut clients: Vec<(TomlClient, Position)> = vec![];
@@ -220,6 +269,7 @@ impl Config {
Ok(Config { Ok(Config {
capture_backend, capture_backend,
emulation_backend,
daemon, daemon,
frontend, frontend,
clients, clients,

View File

@@ -3,10 +3,13 @@ use std::future;
use crate::{ use crate::{
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
config::EmulationBackend,
event::Event, event::Event,
}; };
use anyhow::Result; use anyhow::Result;
use self::error::EmulationCreationError;
#[cfg(windows)] #[cfg(windows)]
pub mod windows; pub mod windows;
@@ -27,6 +30,7 @@ pub mod macos;
/// fallback input emulation (logs events) /// fallback input emulation (logs events)
pub mod dummy; pub mod dummy;
pub mod error;
#[async_trait] #[async_trait]
pub trait InputEmulation: Send { pub trait InputEmulation: Send {
@@ -41,58 +45,60 @@ pub trait InputEmulation: Send {
async fn destroy(&mut self); async fn destroy(&mut self);
} }
pub async fn create() -> Box<dyn InputEmulation> { pub async fn create_backend(
#[cfg(windows)] backend: EmulationBackend,
match windows::WindowsEmulation::new() { ) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
Ok(c) => return Box::new(c), match backend {
Err(e) => log::warn!("windows input emulation unavailable: {e}"), #[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() { pub async fn create(
Ok(c) => { backend: Option<EmulationBackend>,
log::info!("using macos input emulation"); ) -> Result<Box<dyn InputEmulation>, EmulationCreationError> {
return Box::new(c); if let Some(backend) = backend {
} let b = create_backend(backend).await;
Err(e) => log::error!("macos input emulatino not available: {e}"), if b.is_ok() {
} log::info!("using emulation backend: {backend}");
}
#[cfg(all(unix, feature = "wayland", not(target_os = "macos")))] return b;
match wlroots::WlrootsEmulation::new() { }
Ok(c) => {
log::info!("using wlroots input emulation"); for backend in [
return Box::new(c); #[cfg(all(unix, feature = "wayland", not(target_os = "macos")))]
} EmulationBackend::Wlroots,
Err(e) => log::info!("wayland backend not available: {e}"), #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
} EmulationBackend::Libei,
#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] EmulationBackend::X11,
match libei::LibeiEmulation::new().await { #[cfg(windows)]
Ok(c) => { EmulationBackend::Windows,
log::info!("using libei input emulation"); #[cfg(target_os = "macos")]
return Box::new(c); EmulationBackend::MacOs,
} EmulationBackend::Dummy,
Err(e) => log::info!("libei not available: {e}"), ] {
} match create_backend(backend).await {
Ok(b) => {
#[cfg(all(unix, feature = "xdg_desktop_portal", not(target_os = "macos")))] log::info!("using emulation backend: {backend}");
match xdg_desktop_portal::DesktopPortalEmulation::new().await { return Ok(b);
Ok(c) => { }
log::info!("using xdg-remote-desktop-portal input emulation"); Err(e) => log::warn!("{e}"),
return Box::new(c); }
} }
Err(e) => log::info!("remote desktop portal not available: {e}"),
} Err(EmulationCreationError::NoAvailableBackend)
#[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())
} }

168
src/emulate/error.rs Normal file
View File

@@ -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 {}

View File

@@ -1,10 +1,11 @@
use anyhow::{anyhow, Result};
use std::{ use std::{
collections::HashMap, collections::HashMap,
io,
os::{fd::OwnedFd, unix::net::UnixStream}, os::{fd::OwnedFd, unix::net::UnixStream},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use anyhow::{anyhow, Result};
use ashpd::{ use ashpd::{
desktop::{ desktop::{
remote_desktop::{DeviceType, RemoteDesktop}, remote_desktop::{DeviceType, RemoteDesktop},
@@ -27,6 +28,8 @@ use crate::{
event::Event, event::Event,
}; };
use super::error::LibeiEmulationCreationError;
pub struct LibeiEmulation { pub struct LibeiEmulation {
handshake: bool, handshake: bool,
context: ei::Context, context: ei::Context,
@@ -77,14 +80,14 @@ async fn get_ei_fd() -> Result<OwnedFd, ashpd::Error> {
} }
impl LibeiEmulation { impl LibeiEmulation {
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self, LibeiEmulationCreationError> {
// fd is owned by the message, so we need to dup it // fd is owned by the message, so we need to dup it
let eifd = get_ei_fd().await?; let eifd = get_ei_fd().await?;
let stream = UnixStream::from(eifd); let stream = UnixStream::from(eifd);
// let stream = UnixStream::connect("/run/user/1000/eis-0")?; // let stream = UnixStream::connect("/run/user/1000/eis-0")?;
stream.set_nonblocking(true)?; stream.set_nonblocking(true)?;
let context = ei::Context::new(stream)?; 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())?; let events = EiEventStream::new(context.clone())?;
Ok(Self { Ok(Self {
handshake: false, handshake: false,

View File

@@ -1,7 +1,6 @@
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use crate::emulate::InputEmulation; use crate::emulate::InputEmulation;
use crate::event::{Event, KeyboardEvent, PointerEvent}; use crate::event::{Event, KeyboardEvent, PointerEvent};
use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint}; use core_graphics::display::{CGDisplayBounds, CGMainDisplayID, CGPoint};
use core_graphics::event::{ use core_graphics::event::{
@@ -13,6 +12,8 @@ use std::ops::{Index, IndexMut};
use std::time::Duration; use std::time::Duration;
use tokio::task::AbortHandle; use tokio::task::AbortHandle;
use super::error::MacOSEmulationCreationError;
const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500); const DEFAULT_REPEAT_DELAY: Duration = Duration::from_millis(500);
const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32); const DEFAULT_REPEAT_INTERVAL: Duration = Duration::from_millis(32);
@@ -53,11 +54,9 @@ impl IndexMut<CGMouseButton> for ButtonState {
unsafe impl Send for MacOSEmulation {} unsafe impl Send for MacOSEmulation {}
impl MacOSEmulation { impl MacOSEmulation {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self, MacOSEmulationCreationError> {
let event_source = match CGEventSource::new(CGEventSourceStateID::CombinedSessionState) { let event_source = CGEventSource::new(CGEventSourceStateID::CombinedSessionState)
Ok(e) => e, .map_err(|_| MacOSEmulationCreationError::EventSourceCreation)?;
Err(_) => return Err(anyhow!("event source creation failed!")),
};
let button_state = ButtonState { let button_state = ButtonState {
left: false, left: false,
right: false, right: false,

View File

@@ -1,9 +1,9 @@
use super::error::WindowsEmulationCreationError;
use crate::{ use crate::{
emulate::InputEmulation, emulate::InputEmulation,
event::{KeyboardEvent, PointerEvent}, event::{KeyboardEvent, PointerEvent},
scancode, scancode,
}; };
use anyhow::Result;
use async_trait::async_trait; use async_trait::async_trait;
use std::ops::BitOrAssign; use std::ops::BitOrAssign;
use std::time::Duration; use std::time::Duration;
@@ -33,7 +33,7 @@ pub struct WindowsEmulation {
} }
impl WindowsEmulation { impl WindowsEmulation {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self, WindowsEmulationCreationError> {
Ok(Self { repeat_task: None }) Ok(Self { repeat_task: None })
} }
} }

View File

@@ -1,5 +1,5 @@
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use crate::emulate::InputEmulation; use crate::emulate::{error::WlrootsEmulationCreationError, InputEmulation};
use async_trait::async_trait; use async_trait::async_trait;
use std::collections::HashMap; use std::collections::HashMap;
use std::io; use std::io;
@@ -7,7 +7,6 @@ use std::os::fd::{AsFd, OwnedFd};
use wayland_client::backend::WaylandError; use wayland_client::backend::WaylandError;
use wayland_client::WEnum; use wayland_client::WEnum;
use anyhow::{anyhow, Result};
use wayland_client::protocol::wl_keyboard::{self, WlKeyboard}; use wayland_client::protocol::wl_keyboard::{self, WlKeyboard};
use wayland_client::protocol::wl_pointer::{Axis, ButtonState}; use wayland_client::protocol::wl_pointer::{Axis, ButtonState};
use wayland_client::protocol::wl_seat::WlSeat; use wayland_client::protocol::wl_seat::WlSeat;
@@ -30,6 +29,8 @@ use wayland_client::{
use crate::event::{Event, KeyboardEvent, PointerEvent}; use crate::event::{Event, KeyboardEvent, PointerEvent};
use super::error::WaylandBindError;
struct State { struct State {
keymap: Option<(u32, OwnedFd, u32)>, keymap: Option<(u32, OwnedFd, u32)>,
input_for_client: HashMap<ClientHandle, VirtualInput>, input_for_client: HashMap<ClientHandle, VirtualInput>,
@@ -47,18 +48,21 @@ pub(crate) struct WlrootsEmulation {
} }
impl WlrootsEmulation { impl WlrootsEmulation {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self, WlrootsEmulationCreationError> {
let conn = Connection::connect_to_env()?; let conn = Connection::connect_to_env()?;
let (globals, queue) = registry_queue_init::<State>(&conn)?; let (globals, queue) = registry_queue_init::<State>(&conn)?;
let qh = queue.handle(); let qh = queue.handle();
let seat: wl_seat::WlSeat = match globals.bind(&qh, 7..=8, ()) { let seat: wl_seat::WlSeat = globals
Ok(wl_seat) => wl_seat, .bind(&qh, 7..=8, ())
Err(_) => return Err(anyhow!("wl_seat >= v7 not supported")), .map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
};
let vpm: VpManager = globals.bind(&qh, 1..=1, ())?; let vpm: VpManager = globals
let vkm: VkManager = globals.bind(&qh, 1..=1, ())?; .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<ClientHandle, VirtualInput> = HashMap::new(); let input_for_client: HashMap<ClientHandle, VirtualInput> = HashMap::new();
@@ -75,7 +79,7 @@ impl WlrootsEmulation {
queue, queue,
}; };
while emulate.state.keymap.is_none() { 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 fd = unsafe { &File::from_raw_fd(emulate.state.keymap.unwrap().1.as_raw_fd()) };
// let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() }; // let mmap = unsafe { MmapOptions::new().map_copy(fd).unwrap() };

View File

@@ -1,4 +1,3 @@
use anyhow::{anyhow, Result};
use async_trait::async_trait; use async_trait::async_trait;
use std::ptr; use std::ptr;
use x11::{ use x11::{
@@ -14,6 +13,8 @@ use crate::{
}, },
}; };
use super::error::X11EmulationCreationError;
pub struct X11Emulation { pub struct X11Emulation {
display: *mut xlib::Display, display: *mut xlib::Display,
} }
@@ -21,11 +22,11 @@ pub struct X11Emulation {
unsafe impl Send for X11Emulation {} unsafe impl Send for X11Emulation {}
impl X11Emulation { impl X11Emulation {
pub fn new() -> Result<Self> { pub fn new() -> Result<Self, X11EmulationCreationError> {
let display = unsafe { let display = unsafe {
match xlib::XOpenDisplay(ptr::null()) { match xlib::XOpenDisplay(ptr::null()) {
d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => { d if d == ptr::null::<xlib::Display>() as *mut xlib::Display => {
Err(anyhow!("could not open display")) Err(X11EmulationCreationError::OpenDisplay)
} }
display => Ok(display), display => Ok(display),
} }

View File

@@ -17,13 +17,15 @@ use crate::{
}, },
}; };
use super::error::XdpEmulationCreationError;
pub struct DesktopPortalEmulation<'a> { pub struct DesktopPortalEmulation<'a> {
proxy: RemoteDesktop<'a>, proxy: RemoteDesktop<'a>,
session: Session<'a>, session: Session<'a>,
} }
impl<'a> DesktopPortalEmulation<'a> { impl<'a> DesktopPortalEmulation<'a> {
pub async fn new() -> Result<DesktopPortalEmulation<'a>> { pub async fn new() -> Result<DesktopPortalEmulation<'a>, XdpEmulationCreationError> {
log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ..."); log::debug!("connecting to org.freedesktop.portal.RemoteDesktop portal ...");
let proxy = RemoteDesktop::new().await?; let proxy = RemoteDesktop::new().await?;

View File

@@ -1,4 +1,5 @@
use crate::client::{ClientEvent, Position}; use crate::client::{ClientEvent, Position};
use crate::config::Config;
use crate::emulate; use crate::emulate;
use crate::event::{Event, PointerEvent}; use crate::event::{Event, PointerEvent};
use anyhow::Result; use anyhow::Result;
@@ -13,14 +14,16 @@ pub fn run() -> Result<()> {
.enable_time() .enable_time()
.build()?; .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 FREQUENCY_HZ: f64 = 1.0;
const RADIUS: f64 = 100.0; const RADIUS: f64 = 100.0;
async fn input_emulation_test() -> Result<()> { async fn input_emulation_test(config: Config) -> Result<()> {
let mut emulation = emulate::create().await; let mut emulation = emulate::create(config.emulation_backend).await?;
emulation emulation
.notify(ClientEvent::Create(0, Position::Left)) .notify(ClientEvent::Create(0, Position::Left))
.await; .await;

View File

@@ -71,7 +71,9 @@ fn run_service(config: &Config) -> Result<()> {
log::info!("Press {:?} to release the mouse", config.release_bind); log::info!("Press {:?} to release the mouse", config.release_bind);
let server = Server::new(config); let server = Server::new(config);
server.run(config.capture_backend).await?; server
.run(config.capture_backend, config.emulation_backend)
.await?;
log::debug!("service exiting"); log::debug!("service exiting");
anyhow::Ok(()) anyhow::Ok(())

View File

@@ -8,7 +8,7 @@ use tokio::signal;
use crate::{ use crate::{
client::{ClientConfig, ClientHandle, ClientManager, ClientState}, client::{ClientConfig, ClientHandle, ClientManager, ClientState},
config::{CaptureBackend, Config}, config::{CaptureBackend, Config, EmulationBackend},
dns, dns,
frontend::{FrontendListener, FrontendRequest}, frontend::{FrontendListener, FrontendRequest},
server::capture_task::CaptureEvent, server::capture_task::CaptureEvent,
@@ -77,7 +77,11 @@ impl Server {
} }
} }
pub async fn run(&self, backend: Option<CaptureBackend>) -> anyhow::Result<()> { pub async fn run(
&self,
capture_backend: Option<CaptureBackend>,
emulation_backend: Option<EmulationBackend>,
) -> anyhow::Result<()> {
// create frontend communication adapter // create frontend communication adapter
let frontend = match FrontendListener::new().await { let frontend = match FrontendListener::new().await {
Some(f) => f?, Some(f) => f?,
@@ -97,7 +101,7 @@ impl Server {
// input capture // input capture
let (mut capture_task, capture_channel) = capture_task::new( let (mut capture_task, capture_channel) = capture_task::new(
backend, capture_backend,
self.clone(), self.clone(),
sender_tx.clone(), sender_tx.clone(),
timer_tx.clone(), timer_tx.clone(),
@@ -106,12 +110,13 @@ impl Server {
// input emulation // input emulation
let (mut emulation_task, emulate_channel) = emulation_task::new( let (mut emulation_task, emulate_channel) = emulation_task::new(
emulation_backend,
self.clone(), self.clone(),
receiver_rx, receiver_rx,
sender_tx.clone(), sender_tx.clone(),
capture_channel.clone(), capture_channel.clone(),
timer_tx, timer_tx,
); )?;
// create dns resolver // create dns resolver
let resolver = dns::DnsResolver::new().await?; let resolver = dns::DnsResolver::new().await?;

View File

@@ -8,7 +8,8 @@ use tokio::{
use crate::{ use crate::{
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
emulate::{self, InputEmulation}, config::EmulationBackend,
emulate::{self, error::EmulationCreationError, InputEmulation},
event::{Event, KeyboardEvent}, event::{Event, KeyboardEvent},
scancode, scancode,
server::State, server::State,
@@ -27,15 +28,16 @@ pub enum EmulationEvent {
} }
pub fn new( pub fn new(
backend: Option<EmulationBackend>,
server: Server, server: Server,
mut udp_rx: Receiver<Result<(Event, SocketAddr)>>, mut udp_rx: Receiver<Result<(Event, SocketAddr)>>,
sender_tx: Sender<(Event, SocketAddr)>, sender_tx: Sender<(Event, SocketAddr)>,
capture_tx: Sender<CaptureEvent>, capture_tx: Sender<CaptureEvent>,
timer_tx: Sender<()>, timer_tx: Sender<()>,
) -> (JoinHandle<Result<()>>, Sender<EmulationEvent>) { ) -> Result<(JoinHandle<Result<()>>, Sender<EmulationEvent>), EmulationCreationError> {
let (tx, mut rx) = tokio::sync::mpsc::channel(32); let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let emulate_task = tokio::task::spawn_local(async move { 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; let mut last_ignored = None;
loop { loop {
@@ -75,7 +77,7 @@ pub fn new(
emulate.destroy().await; emulate.destroy().await;
anyhow::Ok(()) anyhow::Ok(())
}); });
(emulate_task, tx) Ok((emulate_task, tx))
} }
async fn handle_udp_rx( async fn handle_udp_rx(