Configurable capture backend (#150)

capture backend can now be configured via the `capture_backend` cli argument / config entry
This commit is contained in:
Ferdinand Schober
2024-06-29 00:10:36 +02:00
committed by GitHub
parent 232c048c19
commit 3528ef4fae
15 changed files with 278 additions and 93 deletions

9
Cargo.lock generated
View File

@@ -1312,6 +1312,7 @@ dependencies = [
"serde_json", "serde_json",
"slab", "slab",
"tempfile", "tempfile",
"thiserror",
"tokio", "tokio",
"toml", "toml",
"wayland-client", "wayland-client",
@@ -2011,18 +2012,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.58" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.58" version = "1.0.61"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@@ -36,6 +36,7 @@ num_enum = "0.7.2"
hostname = "0.4.0" hostname = "0.4.0"
slab = "0.4.9" slab = "0.4.9"
endi = "1.1.0" endi = "1.1.0"
thiserror = "1.0.61"
[target.'cfg(unix)'.dependencies] [target.'cfg(unix)'.dependencies]
libc = "0.2.148" libc = "0.2.148"

View File

@@ -1,5 +1,7 @@
# example configuration # example configuration
# capture_backend = "LayerShell"
# release bind # release bind
release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ] release_bind = [ "KeyA", "KeyS", "KeyD", "KeyF" ]

View File

@@ -4,9 +4,14 @@ use futures_core::Stream;
use crate::{ use crate::{
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
config::CaptureBackend,
event::Event, event::Event,
}; };
use self::error::CaptureCreationError;
pub mod error;
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
pub mod libei; pub mod libei;
@@ -25,24 +30,42 @@ pub mod x11;
/// fallback input capture (does not produce events) /// fallback input capture (does not produce events)
pub mod dummy; pub mod dummy;
pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>> { #[allow(unreachable_code)]
pub async fn create(
backend: Option<CaptureBackend>,
) -> Result<Box<dyn InputCapture<Item = io::Result<(ClientHandle, Event)>>>, 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")] #[cfg(target_os = "macos")]
match macos::MacOSInputCapture::new() { 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}"), Err(e) => log::info!("macos input capture not available: {e}"),
} }
#[cfg(windows)] #[cfg(windows)]
match windows::WindowsInputCapture::new() { return Ok(Box::new(windows::WindowsInputCapture::new()));
Ok(p) => return Box::new(p),
Err(e) => log::info!("windows input capture not available: {e}"),
}
#[cfg(all(unix, feature = "libei", not(target_os = "macos")))] #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
match libei::LibeiInputCapture::new().await { match libei::LibeiInputCapture::new().await {
Ok(p) => { Ok(p) => {
log::info!("using libei input capture"); 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}"), Err(e) => log::info!("libei input capture not available: {e}"),
} }
@@ -51,7 +74,7 @@ pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, E
match wayland::WaylandInputCapture::new() { match wayland::WaylandInputCapture::new() {
Ok(p) => { Ok(p) => {
log::info!("using layer-shell input capture"); 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}"), Err(e) => log::info!("layer_shell input capture not available: {e}"),
} }
@@ -60,13 +83,13 @@ pub async fn create() -> Box<dyn InputCapture<Item = io::Result<(ClientHandle, E
match x11::X11InputCapture::new() { match x11::X11InputCapture::new() {
Ok(p) => { Ok(p) => {
log::info!("using x11 input capture"); 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}"), Err(e) => log::info!("x11 input capture not available: {e}"),
} }
log::error!("falling back to dummy input capture"); log::error!("falling back to dummy input capture");
Box::new(dummy::DummyInputCapture::new()) 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 {

140
src/capture/error.rs Normal file
View File

@@ -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 :(")
}
}

View File

@@ -36,6 +36,8 @@ use crate::{
event::{Event, KeyboardEvent, PointerEvent}, event::{Event, KeyboardEvent, PointerEvent},
}; };
use super::error::LibeiCaptureCreationError;
#[derive(Debug)] #[derive(Debug)]
enum ProducerEvent { enum ProducerEvent {
Release, Release,
@@ -131,7 +133,7 @@ impl<'a> Drop for LibeiInputCapture<'a> {
async fn create_session<'a>( async fn create_session<'a>(
input_capture: &'a InputCapture<'a>, input_capture: &'a InputCapture<'a>,
) -> Result<(Session<'a>, BitFlags<Capabilities>)> { ) -> std::result::Result<(Session<'a>, BitFlags<Capabilities>), ashpd::Error> {
log::debug!("creating input capture session"); log::debug!("creating input capture session");
let (session, capabilities) = loop { let (session, capabilities) = loop {
match input_capture match input_capture
@@ -213,7 +215,7 @@ async fn wait_for_active_client(
} }
impl<'a> LibeiInputCapture<'a> { impl<'a> LibeiInputCapture<'a> {
pub async fn new() -> Result<Self> { pub async fn new() -> std::result::Result<Self, LibeiCaptureCreationError> {
let input_capture = Box::pin(InputCapture::new().await?); let input_capture = Box::pin(InputCapture::new().await?);
let input_capture_ptr = input_capture.as_ref().get_ref() as *const InputCapture<'static>; 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?); let mut first_session = Some(create_session(unsafe { &*input_capture_ptr }).await?);

View File

@@ -1,7 +1,7 @@
use crate::capture::error::MacOSInputCaptureCreationError;
use crate::capture::InputCapture; use crate::capture::InputCapture;
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use crate::event::Event; use crate::event::Event;
use anyhow::{anyhow, Result};
use futures_core::Stream; use futures_core::Stream;
use std::task::{Context, Poll}; use std::task::{Context, Poll};
use std::{io, pin::Pin}; use std::{io, pin::Pin};
@@ -9,8 +9,8 @@ use std::{io, pin::Pin};
pub struct MacOSInputCapture; pub struct MacOSInputCapture;
impl MacOSInputCapture { impl MacOSInputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, MacOSInputCaptureCreationError> {
Err(anyhow!("not yet implemented")) Err(MacOSInputCaptureCreationError::NotImplemented)
} }
} }

View File

@@ -1,9 +1,8 @@
use crate::{ use crate::{
capture::InputCapture, capture::{error::WaylandBindError, InputCapture},
client::{ClientEvent, ClientHandle, Position}, client::{ClientEvent, ClientHandle, Position},
}; };
use anyhow::{anyhow, Result};
use futures_core::Stream; use futures_core::Stream;
use memmap::MmapOptions; use memmap::MmapOptions;
use std::{ use std::{
@@ -68,6 +67,8 @@ use tempfile;
use crate::event::{Event, KeyboardEvent, PointerEvent}; use crate::event::{Event, KeyboardEvent, PointerEvent};
use super::error::LayerShellCaptureCreationError;
struct Globals { struct Globals {
compositor: wl_compositor::WlCompositor, compositor: wl_compositor::WlCompositor,
pointer_constraints: ZwpPointerConstraintsV1, pointer_constraints: ZwpPointerConstraintsV1,
@@ -258,64 +259,37 @@ fn draw(f: &mut File, (width, height): (u32, u32)) {
} }
impl WaylandInputCapture { impl WaylandInputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, LayerShellCaptureCreationError> {
let conn = match Connection::connect_to_env() { let conn = Connection::connect_to_env()?;
Ok(c) => c, let (g, mut queue) = registry_queue_init::<State>(&conn)?;
Err(e) => return Err(anyhow!("could not connect to wayland compositor: {e:?}")),
};
let (g, mut queue) = match registry_queue_init::<State>(&conn) {
Ok(q) => q,
Err(e) => return Err(anyhow!("failed to initialize wl_registry: {e:?}")),
};
let qh = queue.handle(); let qh = queue.handle();
let compositor: wl_compositor::WlCompositor = match g.bind(&qh, 4..=5, ()) { let compositor: wl_compositor::WlCompositor = g
Ok(compositor) => compositor, .bind(&qh, 4..=5, ())
Err(_) => return Err(anyhow!("wl_compositor >= v4 not supported")), .map_err(|e| WaylandBindError::new(e, "wl_compositor 4..=5"))?;
}; let xdg_output_manager: ZxdgOutputManagerV1 = g
.bind(&qh, 1..=3, ())
let xdg_output_manager: ZxdgOutputManagerV1 = match g.bind(&qh, 1..=3, ()) { .map_err(|e| WaylandBindError::new(e, "xdg_output_manager 1..=3"))?;
Ok(xdg_output_manager) => xdg_output_manager, let shm: wl_shm::WlShm = g
Err(_) => return Err(anyhow!("xdg_output not supported!")), .bind(&qh, 1..=1, ())
}; .map_err(|e| WaylandBindError::new(e, "wl_shm"))?;
let layer_shell: ZwlrLayerShellV1 = g
let shm: wl_shm::WlShm = match g.bind(&qh, 1..=1, ()) { .bind(&qh, 3..=4, ())
Ok(wl_shm) => wl_shm, .map_err(|e| WaylandBindError::new(e, "wlr_layer_shell 3..=4"))?;
Err(_) => return Err(anyhow!("wl_shm v1 not supported")), let seat: wl_seat::WlSeat = g
}; .bind(&qh, 7..=8, ())
.map_err(|e| WaylandBindError::new(e, "wl_seat 7..=8"))?;
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 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 outputs = vec![];
let g = Globals { let g = Globals {

View File

@@ -575,7 +575,7 @@ fn update_clients(client_event: ClientEvent) {
} }
impl WindowsInputCapture { impl WindowsInputCapture {
pub(crate) fn new() -> Result<Self> { pub(crate) fn new() -> Self {
unsafe { unsafe {
let (tx, rx) = channel(10); let (tx, rx) = channel(10);
EVENT_TX.replace(tx); EVENT_TX.replace(tx);
@@ -583,10 +583,10 @@ impl WindowsInputCapture {
let msg_thread = Some(thread::spawn(|| message_thread(ready_tx))); let msg_thread = Some(thread::spawn(|| message_thread(ready_tx)));
/* wait for thread to set its id */ /* wait for thread to set its id */
ready_rx.recv().expect("channel closed"); ready_rx.recv().expect("channel closed");
Ok(Self { Self {
msg_thread, msg_thread,
event_rx: rx, event_rx: rx,
}) }
} }
} }
} }

View File

@@ -1,4 +1,3 @@
use anyhow::{anyhow, Result};
use std::io; use std::io;
use std::task::Poll; use std::task::Poll;
@@ -9,11 +8,13 @@ use crate::event::Event;
use crate::client::{ClientEvent, ClientHandle}; use crate::client::{ClientEvent, ClientHandle};
use super::error::X11InputCaptureCreationError;
pub struct X11InputCapture {} pub struct X11InputCapture {}
impl X11InputCapture { impl X11InputCapture {
pub fn new() -> Result<Self> { pub fn new() -> std::result::Result<Self, X11InputCaptureCreationError> {
Err(anyhow!("not implemented")) Err(X11InputCaptureCreationError::NotImplemented)
} }
} }

View File

@@ -1,5 +1,6 @@
use crate::capture; use crate::capture;
use crate::client::{ClientEvent, Position}; use crate::client::{ClientEvent, Position};
use crate::config::Config;
use crate::event::{Event, KeyboardEvent}; use crate::event::{Event, KeyboardEvent};
use anyhow::{anyhow, Result}; use anyhow::{anyhow, Result};
use futures::StreamExt; use futures::StreamExt;
@@ -12,12 +13,14 @@ pub fn run() -> Result<()> {
.enable_time() .enable_time()
.build()?; .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"); 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"); log::info!("creating clients");
input_capture.notify(ClientEvent::Create(0, Position::Left))?; input_capture.notify(ClientEvent::Create(0, Position::Left))?;
input_capture.notify(ClientEvent::Create(1, Position::Right))?; input_capture.notify(ClientEvent::Create(1, Position::Right))?;

View File

@@ -1,5 +1,5 @@
use anyhow::Result; use anyhow::Result;
use clap::Parser; 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;
@@ -15,6 +15,7 @@ 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 port: Option<u16>, pub port: Option<u16>,
pub frontend: Option<String>, pub frontend: Option<String>,
pub release_bind: Option<Vec<scancode::Linux>>, pub release_bind: Option<Vec<scancode::Linux>>,
@@ -26,6 +27,7 @@ pub struct ConfigToml {
#[derive(Serialize, Deserialize, Debug, Eq, PartialEq)] #[derive(Serialize, Deserialize, Debug, Eq, PartialEq)]
pub struct TomlClient { pub struct TomlClient {
pub capture_backend: Option<CaptureBackend>,
pub hostname: Option<String>, pub hostname: Option<String>,
pub host_name: Option<String>, pub host_name: Option<String>,
pub ips: Option<Vec<IpAddr>>, pub ips: Option<Vec<IpAddr>>,
@@ -68,8 +70,34 @@ struct CliArgs {
/// test input emulation /// test input emulation
#[arg(long)] #[arg(long)]
test_emulation: bool, test_emulation: bool,
/// capture backend override
#[arg(long)]
capture_backend: Option<CaptureBackend>,
/// emulation backend override
#[arg(long)]
emulation_backend: Option<EmulationBackend>,
} }
#[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)] #[derive(Debug, PartialEq, Eq)]
pub enum Frontend { pub enum Frontend {
Gtk, Gtk,
@@ -78,6 +106,7 @@ pub enum Frontend {
#[derive(Debug)] #[derive(Debug)]
pub struct Config { pub struct Config {
pub capture_backend: Option<CaptureBackend>,
pub frontend: Frontend, pub frontend: Frontend,
pub port: u16, pub port: u16,
pub clients: Vec<(TomlClient, Position)>, pub clients: Vec<(TomlClient, Position)>,
@@ -163,6 +192,11 @@ 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 {
Some(b) => Some(b),
None => config_toml.as_ref().and_then(|c| c.capture_backend),
};
let mut clients: Vec<(TomlClient, Position)> = vec![]; let mut clients: Vec<(TomlClient, Position)> = vec![];
if let Some(config_toml) = config_toml { if let Some(config_toml) = config_toml {
@@ -185,6 +219,7 @@ impl Config {
let test_emulation = args.test_emulation; let test_emulation = args.test_emulation;
Ok(Config { Ok(Config {
capture_backend,
daemon, daemon,
frontend, frontend,
clients, clients,

View File

@@ -71,7 +71,7 @@ fn run_service(config: &Config) -> Result<()> {
log::info!("Press Ctrl+Alt+Shift+Super to release the mouse"); log::info!("Press Ctrl+Alt+Shift+Super to release the mouse");
let server = Server::new(config); let server = Server::new(config);
server.run().await?; server.run(config.capture_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::Config, config::{CaptureBackend, Config},
dns, dns,
frontend::{FrontendListener, FrontendRequest}, frontend::{FrontendListener, FrontendRequest},
server::capture_task::CaptureEvent, server::capture_task::CaptureEvent,
@@ -77,7 +77,7 @@ impl Server {
} }
} }
pub async fn run(&self) -> anyhow::Result<()> { pub async fn run(&self, backend: Option<CaptureBackend>) -> 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,11 +97,12 @@ 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,
self.clone(), self.clone(),
sender_tx.clone(), sender_tx.clone(),
timer_tx.clone(), timer_tx.clone(),
self.release_bind.clone(), self.release_bind.clone(),
); )?;
// input emulation // input emulation
let (mut emulation_task, emulate_channel) = emulation_task::new( let (mut emulation_task, emulate_channel) = emulation_task::new(

View File

@@ -5,8 +5,9 @@ use std::{collections::HashSet, net::SocketAddr};
use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle}; use tokio::{process::Command, sync::mpsc::Sender, task::JoinHandle};
use crate::{ use crate::{
capture::{self, InputCapture}, capture::{self, error::CaptureCreationError, InputCapture},
client::{ClientEvent, ClientHandle}, client::{ClientEvent, ClientHandle},
config::CaptureBackend,
event::{Event, KeyboardEvent}, event::{Event, KeyboardEvent},
scancode, scancode,
server::State, server::State,
@@ -25,14 +26,15 @@ pub enum CaptureEvent {
} }
pub fn new( pub fn new(
backend: Option<CaptureBackend>,
server: Server, server: Server,
sender_tx: Sender<(Event, SocketAddr)>, sender_tx: Sender<(Event, SocketAddr)>,
timer_tx: Sender<()>, timer_tx: Sender<()>,
release_bind: Vec<scancode::Linux>, release_bind: Vec<scancode::Linux>,
) -> (JoinHandle<Result<()>>, Sender<CaptureEvent>) { ) -> Result<(JoinHandle<Result<()>>, Sender<CaptureEvent>), CaptureCreationError> {
let (tx, mut rx) = tokio::sync::mpsc::channel(32); let (tx, mut rx) = tokio::sync::mpsc::channel(32);
let task = tokio::task::spawn_local(async move { 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(); let mut pressed_keys = HashSet::new();
loop { loop {
tokio::select! { tokio::select! {
@@ -62,7 +64,7 @@ pub fn new(
} }
anyhow::Ok(()) anyhow::Ok(())
}); });
(task, tx) Ok((task, tx))
} }
fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) { fn update_pressed_keys(pressed_keys: &mut HashSet<scancode::Linux>, key: u32, state: u8) {