use std::collections::BTreeMap;
use std::future::pending;
use std::time::Instant;
use a7n_core::focus::edge::{EdgeResistance, HeldButtons};
use a7n_core::focus::{Action, Cursor, Focus, Take};
use a7n_core::hotkey::{HotkeyMatcher, SHAKE_DEFAULT, ShakeDetector};
use a7n_core::input::{MotionCounter, MotionScale, MotionTracker, corner_push, edge_push};
use a7n_platform::input::{Bounds, Capture, CaptureControl, Captured, Inject, Injector};
use a7n_proto::v1::{Edge, Envelope, InputBatch, envelope::Body, input_event::Event};
use a7n_proto::{MachineId, Route};
use tokio::sync::oneshot;
use super::{Daemon, DaemonError};
mod actions;
mod hotkeys;
mod landing;
mod recording;
use landing::landing_point;
pub(super) struct Input {
pub(super) focus: Focus,
pub(super) capture: Option<Capture>,
pub(super) injector: Option<Injector>,
pub(super) unavailable: Option<String>,
pub(super) paused: bool,
trace_keys: bool,
counter: MotionCounter,
tracker: MotionTracker,
pub(super) scale: MotionScale,
pub(super) modifier_remap: BTreeMap<u32, u32>,
pub(super) hotkeys: HotkeyMatcher,
pub(super) shake: ShakeDetector,
pub(super) recording: recording::Recording,
resistance: EdgeResistance,
buttons: HeldButtons,
pub(super) failed: bool,
lost: Vec<MachineId>,
pub(super) retried: Option<oneshot::Receiver<Retried>>,
}
pub(crate) type Handles = (Capture, Injector);
pub(crate) type Retried = Result<Handles, String>;
impl Input {
pub(super) fn new(own: MachineId) -> Self {
Self {
focus: Focus::new(own),
capture: None,
injector: None,
unavailable: None,
paused: false,
trace_keys: false,
counter: MotionCounter::default(),
tracker: MotionTracker::default(),
scale: MotionScale::new(1.0),
modifier_remap: BTreeMap::new(),
hotkeys: HotkeyMatcher::new(a7n_ipc::config::Config::default().bindings()),
shake: ShakeDetector::new(SHAKE_DEFAULT),
recording: recording::Recording::default(),
resistance: EdgeResistance::new(0),
buttons: HeldButtons::default(),
failed: false,
lost: Vec::new(),
retried: None,
}
}
pub(super) fn keepalive(&mut self) {
if let Some(capture) = &self.capture {
if matches!(
capture.control.try_send(CaptureControl::Keepalive),
Err(tokio::sync::mpsc::error::TrySendError::Closed(_))
) {
self.closed();
}
}
}
pub(super) fn closed(&mut self) {
if !self.failed {
crate::log::event!("kind=input_backend_closed");
}
self.failed = true;
self.unavailable = Some("input backend closed".to_owned());
}
pub(super) async fn grab(&mut self, grabbed: bool) {
if let Some(capture) = &self.capture {
if capture
.control
.send(CaptureControl::Grab(grabbed))
.await
.is_err()
{
self.closed();
}
}
}
fn trace_key(&self, event: &a7n_proto::v1::InputEvent) {
if !self.trace_keys {
return;
}
if let Some(a7n_proto::v1::input_event::Event::Key(key)) = &event.event {
crate::log::event!("kind=trace_key usage=0x{:x} down={}", key.usage, key.down);
}
}
async fn inject(&mut self, command: Inject) {
if let Inject::Event(event) = &command {
self.trace_key(event);
if let Some(Event::Button(button)) = &event.event {
self.buttons.apply(button);
}
}
if let Some(injector) = &self.injector {
if injector.commands.send(command).await.is_err() {
self.closed();
}
}
}
}
pub(super) async fn captured(capture: &mut Option<Capture>) -> Option<Captured> {
match capture {
Some(capture) => capture.events.recv().await,
None => pending().await,
}
}
pub(super) async fn retried(retried: &mut Option<oneshot::Receiver<Retried>>) -> Option<Retried> {
match retried {
Some(receiver) => receiver.await.ok(),
None => pending().await,
}
}
pub(super) async fn feedback(injector: &mut Option<Injector>) -> Option<Captured> {
match injector {
Some(injector) => injector.pointer.recv().await,
None => pending().await,
}
}
pub(super) async fn deadline(deadline: Option<Instant>) {
match deadline {
Some(deadline) => tokio::time::sleep_until(deadline.into()).await,
None => pending().await,
}
}
impl Daemon {
pub(crate) fn with_input_error(mut self, error: Option<String>) -> Self {
self.input.unavailable = error;
self
}
pub(crate) fn with_trace_input(mut self, enabled: bool) -> Self {
self.input.trace_keys = enabled;
self
}
pub(crate) fn with_input(mut self, handles: Option<Handles>) -> Self {
if let Some(handles) = handles {
self.attach_input(handles);
}
self
}
pub(crate) fn with_input_retry(mut self, retried: oneshot::Receiver<Retried>) -> Self {
self.input.retried = Some(retried);
self
}
pub(super) fn finish_input_retry(&mut self, retried: Option<Retried>) {
self.input.retried = None;
match retried {
Some(Ok(handles)) => self.attach_input(handles),
Some(Err(reason)) => self.input.unavailable = Some(reason),
None => {}
}
}
fn attach_input(&mut self, (capture, injector): Handles) {
self.input.capture = Some(capture);
self.input.injector = Some(injector);
self.input.unavailable = None;
self.input.failed = false;
}
pub(super) async fn retry_input_after_permission_loss(&mut self) {
crate::log::event!("kind=input_permission_lost");
let actions = self.input.focus.go_home();
self.input_actions(actions).await;
let peers: Vec<_> = self.links.by_peer.keys().copied().collect();
for peer in peers {
let actions = self.input.focus.deny_drive(peer);
self.input_actions(actions).await;
}
self.input.grab(false).await;
self.input.capture = None;
self.input.injector = None;
self.input.failed = false;
self.input.unavailable = Some("input permissions missing".to_owned());
let (handles, retried) = oneshot::channel();
self.input.retried = Some(retried);
crate::input_retry::spawn(handles);
}
pub(super) async fn shutdown_input(&mut self) {
let peers: Vec<_> = self.links.by_peer.keys().copied().collect();
for peer in peers {
self.input_link_lost(peer);
}
self.flush_link_losses().await;
self.input.grab(false).await;
}
pub(super) async fn poll_focus(&mut self) {
let actions = self.input.focus.poll(now());
self.input_actions(actions).await;
}
pub(super) fn input_link_lost(&mut self, peer: MachineId) {
self.sync.drop_peer(peer);
self.clipboard_link_down(peer);
self.files_link_down(peer);
self.input.lost.push(peer);
}
pub(super) async fn flush_link_losses(&mut self) {
while let Some(peer) = self.input.lost.pop() {
let actions = self.input.focus.on_link_lost(peer);
self.input_actions(actions).await;
self.sync_grab().await;
}
}
pub(super) fn send_input(&mut self, peer: MachineId, body: Body) {
let Some(handle) = self.links.by_peer.get(&peer) else {
return;
};
if handle
.sender
.try_send(Envelope { body: Some(body) })
.is_err()
{
handle.connection.close(crate::link::CLOSE_NORMAL, b"");
self.links.by_peer.remove(&peer);
self.input_link_lost(peer);
}
}
pub(super) async fn receive_from(
&mut self,
peer: MachineId,
envelope: Envelope,
) -> Result<(), DaemonError> {
self.poll_focus().await;
let actions = match &envelope.body {
Some(Body::TransferRequest(request)) => {
if !self.files_request(peer, request) {
self.clipboard_request(peer, request);
}
return Ok(());
}
Some(Body::ClipboardAnnounce(wire)) => {
if wire.send {
self.receive_file_offer(peer, wire);
} else {
self.receive_clipboard(peer, wire);
}
return Ok(());
}
Some(Body::TransferCancel(cancel)) => {
self.files_cancel_from(peer, cancel.transfer_id);
return Ok(());
}
Some(Body::Layout(_) | Body::Settings(_) | Body::Desk(_)) => {
return self.receive_sync_from(peer, envelope, Instant::now()).await;
}
Some(Body::Focus(focus)) => {
let allow_drive = self.allow_drive(peer);
self.input.focus.on_focus(peer, focus, allow_drive, now())
}
Some(Body::Accept(accept)) => self.input.focus.on_accept(peer, accept),
Some(Body::Accepts(accepts)) => self.input.focus.on_accepts(peer, accepts.accepts),
Some(Body::Refuse(refuse)) => self.input.focus.on_refuse(peer, refuse),
Some(Body::Handover(handover)) if !self.input.paused => {
self.input.focus.on_handover(peer, handover, now())
}
Some(Body::Ack(ack)) => self.input.focus.on_ack(peer, ack),
Some(Body::Motion(motion)) => {
if self
.input
.focus
.on_remote_input(peer, Route(motion.route), now())
{
let delta = self
.input
.tracker
.apply(motion.motion_seq, motion.x, motion.y);
self.inject_motion(delta).await;
}
return Ok(());
}
Some(Body::Input(batch)) => {
self.receive_input(peer, batch).await;
return Ok(());
}
_ => return self.receive(envelope),
};
self.input_actions(actions).await;
self.sync_grab().await;
Ok(())
}
async fn receive_input(&mut self, peer: MachineId, batch: &InputBatch) {
let route = Route(batch.route);
if !self.input.focus.on_remote_input(peer, route, now()) {
return;
}
let delta = self
.input
.tracker
.resync(batch.motion_seq, batch.motion_x, batch.motion_y);
self.inject_motion(delta).await;
for event in &batch.events {
if self.input.focus.on_input_event(peer, route, event, now()) {
self.input.inject(Inject::Event(event.clone())).await;
}
}
}
async fn inject_motion(&mut self, delta: Option<(i32, i32)>) {
if let Some((dx, dy)) = delta {
let (dx, dy) = self.input.scale.apply(dx, dy);
self.input.inject(Inject::Motion { dx, dy }).await;
}
}
pub(super) async fn captured(&mut self, event: Option<Captured>) {
let Some(event) = event else {
self.input.closed();
return;
};
if self.record_capture(&event) {
return;
}
if let Captured::Input(input) = &event {
self.input.trace_key(input);
}
self.poll_focus().await;
if self.match_hotkey(&event).await {
return;
}
if let Cursor::Driving(peer) = self.input.focus.cursor() {
if self.match_shake(&event).await {
return;
}
self.forward_capture(peer, event);
return;
}
let was_driven = self.input.focus.is_driven();
let (allowed, actions) = self.input.focus.on_local_input(now());
self.input_actions(actions).await;
if !allowed {
return;
}
if let Captured::Input(input) = &event {
if let Some(Event::Button(button)) = &input.event {
self.input.buttons.apply(button);
}
}
if was_driven {
self.sync_grab().await;
match event {
Captured::Input(event) => self.input.inject(Inject::Event(event)).await,
Captured::Pointer { dx, dy, .. } => {
self.input.inject(Inject::Motion { dx, dy }).await;
}
}
return;
}
if let (Captured::Pointer { x, y, dx, dy }, Some(capture)) = (event, &self.input.capture) {
self.cross_edge(capture.bounds, x, y, dx, dy, false).await;
}
}
fn forward_capture(&mut self, peer: MachineId, event: Captured) {
let Some(route) = self.input.focus.driving_route() else {
return;
};
let body = match event {
Captured::Pointer { dx, dy, .. } => {
let mut motion = self.input.counter.push(dx, dy);
motion.route = route.0;
Body::Motion(motion)
}
Captured::Input(mut event) => {
self.transform_input(&mut event);
let motion = self.input.counter.current();
Body::Input(InputBatch {
route: route.0,
seq: 0,
motion_seq: motion.motion_seq,
motion_x: motion.x,
motion_y: motion.y,
events: vec![event],
})
}
};
self.send_input(peer, body);
}
pub(super) async fn pointer_feedback(&mut self, event: Option<Captured>) {
let Some(event) = event else {
self.input.closed();
return;
};
if !self.input.focus.is_driven() {
return;
}
if let (Captured::Pointer { x, y, dx, dy }, Some(injector)) = (event, &self.input.injector)
{
self.cross_edge(injector.bounds, x, y, dx, dy, true).await;
}
}
async fn cross_edge(&mut self, bounds: Bounds, x: i32, y: i32, dx: i32, dy: i32, driven: bool) {
if self.input.buttons.any_held() {
self.input.resistance.push(Edge::Unspecified, 0);
return;
}
if self.input.paused || (!driven && self.input.focus.is_locked()) {
return;
}
let Some((edge, overshoot, along)) = edge_push(
(bounds.x, bounds.y, bounds.width, bounds.height),
x,
y,
dx,
dy,
) else {
self.input.resistance.push(Edge::Unspecified, 0);
return;
};
if !self.input.resistance.push(edge, overshoot) {
return;
}
let Some((target, landing)) = self
.layout
.state
.exit(self.identity.machine_id, edge, along)
.or_else(|| {
let (edge, corner) = corner_push(
(bounds.x, bounds.y, bounds.width, bounds.height),
x,
y,
dx,
dy,
)?;
self.layout
.state
.exit_corner(self.identity.machine_id, edge, corner)
})
else {
return;
};
let actions = if driven {
self.wol.pending = None;
self.input.focus.handover(target, Some(landing))
} else {
if !self.is_linked(target) {
if self
.auto_wake(target, super::wol::WakeAction::Edge(landing), now())
.is_err()
{
crate::log::event!("kind=wake_failed");
}
return;
}
self.wol.pending = None;
self.take_peer(target, Some(landing), now())
};
self.input_actions(actions).await;
}
}
fn now() -> Instant {
tokio::time::Instant::now().into_std()
}
#[cfg(test)]
mod tests;