Skip to main content

input_capture/
lib.rs

1use std::{
2    collections::{HashMap, HashSet, VecDeque},
3    fmt::Display,
4    mem::swap,
5    task::{Poll, ready},
6};
7
8use async_trait::async_trait;
9use futures::StreamExt;
10use futures_core::Stream;
11
12use input_event::{Event, KeyboardEvent, scancode};
13
14pub use error::{CaptureCreationError, CaptureError, InputCaptureError};
15
16pub mod error;
17
18#[cfg(libei)]
19mod libei;
20
21#[cfg(target_os = "macos")]
22mod macos;
23
24#[cfg(layer_shell)]
25mod layer_shell;
26
27#[cfg(windows)]
28mod windows;
29
30#[cfg(x11)]
31mod x11;
32
33/// fallback input capture (does not produce events)
34mod dummy;
35
36pub type CaptureHandle = u64;
37
38#[derive(Copy, Clone, Debug, PartialEq)]
39pub enum CaptureEvent {
40    /// capture on this capture handle is now active
41    Begin,
42    /// input event coming from capture handle
43    Input(Event),
44}
45
46impl Display for CaptureEvent {
47    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
48        match self {
49            CaptureEvent::Begin => write!(f, "begin capture"),
50            CaptureEvent::Input(e) => write!(f, "{e}"),
51        }
52    }
53}
54
55#[derive(Debug, Clone, Copy, Eq, Hash, PartialEq)]
56pub enum Position {
57    Left,
58    Right,
59    Top,
60    Bottom,
61}
62
63impl Position {
64    pub fn opposite(&self) -> Self {
65        match self {
66            Position::Left => Self::Right,
67            Position::Right => Self::Left,
68            Position::Top => Self::Bottom,
69            Position::Bottom => Self::Top,
70        }
71    }
72}
73
74impl Display for Position {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        let pos = match self {
77            Position::Left => "left",
78            Position::Right => "right",
79            Position::Top => "top",
80            Position::Bottom => "bottom",
81        };
82        write!(f, "{pos}")
83    }
84}
85
86#[derive(Clone, Copy, Debug, Eq, PartialEq)]
87pub enum Backend {
88    #[cfg(libei)]
89    InputCapturePortal,
90    #[cfg(layer_shell)]
91    LayerShell,
92    #[cfg(x11)]
93    X11,
94    #[cfg(windows)]
95    Windows,
96    #[cfg(target_os = "macos")]
97    MacOs,
98    Dummy,
99}
100
101impl Display for Backend {
102    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
103        match self {
104            #[cfg(libei)]
105            Backend::InputCapturePortal => write!(f, "input-capture-portal"),
106            #[cfg(layer_shell)]
107            Backend::LayerShell => write!(f, "layer-shell"),
108            #[cfg(x11)]
109            Backend::X11 => write!(f, "X11"),
110            #[cfg(windows)]
111            Backend::Windows => write!(f, "windows"),
112            #[cfg(target_os = "macos")]
113            Backend::MacOs => write!(f, "MacOS"),
114            Backend::Dummy => write!(f, "dummy"),
115        }
116    }
117}
118
119pub struct InputCapture {
120    /// capture backend
121    capture: Box<dyn Capture>,
122    /// keys pressed by active capture
123    pressed_keys: HashSet<scancode::Linux>,
124    /// map from position to ids
125    position_map: HashMap<Position, Vec<CaptureHandle>>,
126    /// map from id to position
127    id_map: HashMap<CaptureHandle, Position>,
128    /// pending events
129    pending: VecDeque<(CaptureHandle, CaptureEvent)>,
130}
131
132impl InputCapture {
133    /// create a new client with the given id
134    pub async fn create(&mut self, id: CaptureHandle, pos: Position) -> Result<(), CaptureError> {
135        assert!(!self.id_map.contains_key(&id));
136
137        self.id_map.insert(id, pos);
138
139        if let Some(v) = self.position_map.get_mut(&pos) {
140            v.push(id);
141            Ok(())
142        } else {
143            self.position_map.insert(pos, vec![id]);
144            self.capture.create(pos).await
145        }
146    }
147
148    /// destroy the client with the given id, if it exists
149    pub async fn destroy(&mut self, id: CaptureHandle) -> Result<(), CaptureError> {
150        let pos = self
151            .id_map
152            .remove(&id)
153            .expect("no position for this handle");
154
155        log::debug!("destroying capture {id} @ {pos}");
156        let remaining = self.position_map.get_mut(&pos).expect("id vector");
157        remaining.retain(|&i| i != id);
158
159        log::debug!("remaining ids @ {pos}: {remaining:?}");
160        if remaining.is_empty() {
161            log::debug!("destroying capture @ {pos} - no remaining ids");
162            self.position_map.remove(&pos);
163            self.capture.destroy(pos).await?;
164        }
165        Ok(())
166    }
167
168    /// release mouse
169    pub async fn release(&mut self) -> Result<(), CaptureError> {
170        self.pressed_keys.clear();
171        self.capture.release().await
172    }
173
174    /// Drain and return every key the capture has forwarded as
175    /// down-but-not-up. The caller is expected to synthesize key-up
176    /// events to the remote peer for each — otherwise the peer
177    /// retains phantom-held keys after capture is released. The
178    /// canonical case is the release-bind chord
179    /// (Ctrl+Shift+Alt+Meta): the down events were sent while
180    /// capture was active, but the matching up events arrive after
181    /// the local tap has flipped to passthrough and never reach
182    /// the peer.
183    pub fn take_pressed_keys(&mut self) -> HashSet<scancode::Linux> {
184        std::mem::take(&mut self.pressed_keys)
185    }
186
187    /// destroy the input capture
188    pub async fn terminate(&mut self) -> Result<(), CaptureError> {
189        self.capture.terminate().await
190    }
191
192    /// creates a new [`InputCapture`]
193    pub async fn new(backend: Option<Backend>) -> Result<Self, CaptureCreationError> {
194        let capture = create(backend).await?;
195        Ok(Self {
196            capture,
197            id_map: Default::default(),
198            pending: Default::default(),
199            position_map: Default::default(),
200            pressed_keys: HashSet::new(),
201        })
202    }
203
204    /// check whether the given keys are pressed
205    pub fn keys_pressed(&self, keys: &[scancode::Linux]) -> bool {
206        keys.iter().all(|k| self.pressed_keys.contains(k))
207    }
208
209    fn update_pressed_keys(&mut self, key: u32, state: u8) {
210        if let Ok(scancode) = scancode::Linux::try_from(key) {
211            log::debug!("key: {key}, state: {state}, scancode: {scancode:?}");
212            match state {
213                1 => self.pressed_keys.insert(scancode),
214                _ => self.pressed_keys.remove(&scancode),
215            };
216        }
217    }
218}
219
220impl Stream for InputCapture {
221    type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>;
222
223    fn poll_next(
224        mut self: std::pin::Pin<&mut Self>,
225        cx: &mut std::task::Context<'_>,
226    ) -> Poll<Option<Self::Item>> {
227        if let Some(e) = self.pending.pop_front() {
228            return Poll::Ready(Some(Ok(e)));
229        }
230
231        // ready
232        let event = ready!(self.capture.poll_next_unpin(cx));
233
234        // stream closed
235        let event = match event {
236            Some(e) => e,
237            None => return Poll::Ready(None),
238        };
239
240        // error occurred
241        let (pos, event) = match event {
242            Ok(e) => e,
243            Err(e) => return Poll::Ready(Some(Err(e))),
244        };
245
246        // handle key presses
247        if let CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key, state, .. })) = event {
248            self.update_pressed_keys(key, state);
249        }
250
251        let len = self
252            .position_map
253            .get(&pos)
254            .map(|ids| ids.len())
255            .unwrap_or(0);
256
257        match len {
258            0 => Poll::Pending,
259            1 => Poll::Ready(Some(Ok((
260                self.position_map.get(&pos).expect("no id")[0],
261                event,
262            )))),
263            _ => {
264                let mut position_map = HashMap::new();
265                swap(&mut self.position_map, &mut position_map);
266                {
267                    for &id in position_map.get(&pos).expect("position") {
268                        self.pending.push_back((id, event));
269                    }
270                }
271                swap(&mut self.position_map, &mut position_map);
272
273                Poll::Ready(Some(Ok(self.pending.pop_front().expect("event"))))
274            }
275        }
276    }
277}
278
279#[async_trait]
280trait Capture: Stream<Item = Result<(Position, CaptureEvent), CaptureError>> + Unpin {
281    /// create a new client with the given id
282    async fn create(&mut self, pos: Position) -> Result<(), CaptureError>;
283
284    /// destroy the client with the given id, if it exists
285    async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError>;
286
287    /// release mouse
288    async fn release(&mut self) -> Result<(), CaptureError>;
289
290    /// destroy the input capture
291    async fn terminate(&mut self) -> Result<(), CaptureError>;
292}
293
294async fn create_backend(
295    backend: Backend,
296) -> Result<
297    Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
298    CaptureCreationError,
299> {
300    match backend {
301        #[cfg(libei)]
302        Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
303        #[cfg(layer_shell)]
304        Backend::LayerShell => Ok(Box::new(layer_shell::LayerShellInputCapture::new()?)),
305        #[cfg(x11)]
306        Backend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)),
307        #[cfg(windows)]
308        Backend::Windows => Ok(Box::new(windows::WindowsInputCapture::new())),
309        #[cfg(target_os = "macos")]
310        Backend::MacOs => Ok(Box::new(macos::MacOSInputCapture::new().await?)),
311        Backend::Dummy => Ok(Box::new(dummy::DummyInputCapture::new())),
312    }
313}
314
315async fn create(
316    backend: Option<Backend>,
317) -> Result<
318    Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
319    CaptureCreationError,
320> {
321    if let Some(backend) = backend {
322        let b = create_backend(backend).await;
323        if b.is_ok() {
324            log::info!("using capture backend: {backend}");
325        }
326        return b;
327    }
328
329    for backend in [
330        #[cfg(libei)]
331        Backend::InputCapturePortal,
332        #[cfg(layer_shell)]
333        Backend::LayerShell,
334        #[cfg(x11)]
335        Backend::X11,
336        #[cfg(windows)]
337        Backend::Windows,
338        #[cfg(target_os = "macos")]
339        Backend::MacOs,
340    ] {
341        match create_backend(backend).await {
342            Ok(b) => {
343                log::info!("using capture backend: {backend}");
344                return Ok(b);
345            }
346            Err(e) if e.cancelled_by_user() => return Err(e),
347            Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"),
348        }
349    }
350    Err(CaptureCreationError::NoAvailableBackend)
351}