mod buttons;
mod capture;
mod inject;
mod release;
mod scroll;
mod session;
mod tokens;
mod translate;
mod zones;
use std::sync::mpsc as std_mpsc;
use std::thread;
use std::time::Duration;
use tokio::sync::mpsc;
use super::{
Bounds, CAPTURE_CONTROL_CAPACITY, CAPTURE_EVENT_CAPACITY, Capture, CaptureControl, Captured,
INJECT_COMMAND_CAPACITY, INJECT_POINTER_CAPACITY, Inject, Injector, InputError,
};
use crate::clipboard::wayland::portal::Service;
use capture::Capturer;
use inject::Injecting;
use tokens::TokenStore;
const START_TIMEOUT: Duration = Duration::from_secs(120);
const SHUTDOWN_TIMEOUT: Duration = Duration::from_millis(500);
const THREAD_NAME: &str = "a7n-wayland-input";
struct Channels {
events: mpsc::Sender<Captured>,
control: mpsc::Receiver<CaptureControl>,
commands: mpsc::Receiver<Inject>,
pointer: mpsc::Sender<Captured>,
}
type Ready = Result<(Bounds, Bounds), InputError>;
pub(super) fn start() -> Result<(Capture, Injector), InputError> {
let (events_tx, events_rx) = mpsc::channel(CAPTURE_EVENT_CAPACITY);
let (control_tx, control_rx) = mpsc::channel(CAPTURE_CONTROL_CAPACITY);
let (commands_tx, commands_rx) = mpsc::channel(INJECT_COMMAND_CAPACITY);
let (pointer_tx, pointer_rx) = mpsc::channel(INJECT_POINTER_CAPACITY);
let channels = Channels {
events: events_tx,
control: control_rx,
commands: commands_rx,
pointer: pointer_tx,
};
let (ready_tx, ready_rx) = std_mpsc::channel();
thread::Builder::new()
.name(THREAD_NAME.to_owned())
.spawn(move || run_thread(&ready_tx, channels))
.map_err(InputError::Thread)?;
let (capture_bounds, inject_bounds) = ready_rx
.recv()
.map_err(|_| InputError::Portal("the input thread stopped during startup".to_owned()))??;
let capture = Capture {
bounds: capture_bounds,
events: events_rx,
control: control_tx,
};
let injector = Injector {
bounds: inject_bounds,
commands: commands_tx,
pointer: pointer_rx,
};
Ok((capture, injector))
}
fn run_thread(ready: &std_mpsc::Sender<Ready>, channels: Channels) {
let runtime = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build();
match runtime {
Ok(runtime) => runtime.block_on(run(ready, channels)),
Err(error) => {
let _ = ready.send(Err(InputError::Thread(error)));
}
}
}
async fn run(ready: &std_mpsc::Sender<Ready>, mut channels: Channels) {
let mut tokens = TokenStore::open_default();
session::register_app().await;
let started = tokio::time::timeout(START_TIMEOUT, open(&mut tokens)).await;
let (mut capturer, mut injecting) = match started {
Ok(Ok(backend)) => backend,
Ok(Err(error)) => {
let _ = ready.send(Err(error));
return;
}
Err(_) => {
let _ = ready.send(Err(InputError::Portal(format!(
"timed out after {}s waiting for the portal sessions to start",
START_TIMEOUT.as_secs()
))));
return;
}
};
let mut clipboard = injecting.take_clipboard();
if let Some(service) = &mut clipboard {
service.hand_over();
}
let bounds = (capturer.bounds(), capturer.bounds());
if ready.send(Ok(bounds)).is_err() {
close(&mut capturer, &mut injecting).await;
return;
}
let outcome = tokio::select! {
result = capturer.run(&channels.events, &mut channels.control) => result,
result = injecting.run(&mut channels.commands, &channels.pointer) => result,
() = run_clipboard(clipboard) => unreachable!("the clipboard service never ends the backend"),
};
if let Err(error) = outcome {
eprintln!("kind=input_wayland_stopped reason={error}");
}
close(&mut capturer, &mut injecting).await;
}
async fn run_clipboard(service: Option<Service>) {
if let Some(service) = service {
service.run().await;
}
std::future::pending::<()>().await;
}
async fn open(tokens: &mut Option<TokenStore>) -> Result<(Capturer, Injecting), InputError> {
let mut capturer = Capturer::new(session::open_capture(tokens).await?).await?;
let injecting = async {
let desktop = session::open_desktop(tokens).await?;
Injecting::new(desktop, capturer.bounds()).await
}
.await;
match injecting {
Ok(injecting) => Ok((capturer, injecting)),
Err(error) => {
let _ = tokio::time::timeout(SHUTDOWN_TIMEOUT, capturer.shutdown()).await;
Err(error)
}
}
}
async fn close(capturer: &mut Capturer, injecting: &mut Injecting) {
let _ = tokio::time::timeout(SHUTDOWN_TIMEOUT, capturer.shutdown()).await;
let _ = tokio::time::timeout(SHUTDOWN_TIMEOUT, injecting.shutdown()).await;
}