input_capture/
lib.rs

1use std::{
2    collections::{HashMap, HashSet, VecDeque},
3    fmt::Display,
4    mem::swap,
5    task::{ready, Poll},
6};
7
8use async_trait::async_trait;
9use futures::StreamExt;
10use futures_core::Stream;
11
12use input_event::{scancode, Event, KeyboardEvent};
13
14pub use error::{CaptureCreationError, CaptureError, InputCaptureError};
15
16pub mod error;
17
18#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
19mod libei;
20
21#[cfg(target_os = "macos")]
22mod macos;
23
24#[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
25mod layer_shell;
26
27#[cfg(windows)]
28mod windows;
29
30#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
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(all(unix, feature = "libei", not(target_os = "macos")))]
89    InputCapturePortal,
90    #[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
91    LayerShell,
92    #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
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(all(unix, feature = "libei", not(target_os = "macos")))]
105            Backend::InputCapturePortal => write!(f, "input-capture-portal"),
106            #[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
107            Backend::LayerShell => write!(f, "layer-shell"),
108            #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
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    /// destroy the input capture
175    pub async fn terminate(&mut self) -> Result<(), CaptureError> {
176        self.capture.terminate().await
177    }
178
179    /// creates a new [`InputCapture`]
180    pub async fn new(backend: Option<Backend>) -> Result<Self, CaptureCreationError> {
181        let capture = create(backend).await?;
182        Ok(Self {
183            capture,
184            id_map: Default::default(),
185            pending: Default::default(),
186            position_map: Default::default(),
187            pressed_keys: HashSet::new(),
188        })
189    }
190
191    /// check whether the given keys are pressed
192    pub fn keys_pressed(&self, keys: &[scancode::Linux]) -> bool {
193        keys.iter().all(|k| self.pressed_keys.contains(k))
194    }
195
196    fn update_pressed_keys(&mut self, key: u32, state: u8) {
197        if let Ok(scancode) = scancode::Linux::try_from(key) {
198            log::debug!("key: {key}, state: {state}, scancode: {scancode:?}");
199            match state {
200                1 => self.pressed_keys.insert(scancode),
201                _ => self.pressed_keys.remove(&scancode),
202            };
203        }
204    }
205}
206
207impl Stream for InputCapture {
208    type Item = Result<(CaptureHandle, CaptureEvent), CaptureError>;
209
210    fn poll_next(
211        mut self: std::pin::Pin<&mut Self>,
212        cx: &mut std::task::Context<'_>,
213    ) -> Poll<Option<Self::Item>> {
214        if let Some(e) = self.pending.pop_front() {
215            return Poll::Ready(Some(Ok(e)));
216        }
217
218        // ready
219        let event = ready!(self.capture.poll_next_unpin(cx));
220
221        // stream closed
222        let event = match event {
223            Some(e) => e,
224            None => return Poll::Ready(None),
225        };
226
227        // error occurred
228        let (pos, event) = match event {
229            Ok(e) => e,
230            Err(e) => return Poll::Ready(Some(Err(e))),
231        };
232
233        // handle key presses
234        if let CaptureEvent::Input(Event::Keyboard(KeyboardEvent::Key { key, state, .. })) = event {
235            self.update_pressed_keys(key, state);
236        }
237
238        let len = self
239            .position_map
240            .get(&pos)
241            .map(|ids| ids.len())
242            .unwrap_or(0);
243
244        match len {
245            0 => Poll::Pending,
246            1 => Poll::Ready(Some(Ok((
247                self.position_map.get(&pos).expect("no id")[0],
248                event,
249            )))),
250            _ => {
251                let mut position_map = HashMap::new();
252                swap(&mut self.position_map, &mut position_map);
253                {
254                    for &id in position_map.get(&pos).expect("position") {
255                        self.pending.push_back((id, event));
256                    }
257                }
258                swap(&mut self.position_map, &mut position_map);
259
260                Poll::Ready(Some(Ok(self.pending.pop_front().expect("event"))))
261            }
262        }
263    }
264}
265
266#[async_trait]
267trait Capture: Stream<Item = Result<(Position, CaptureEvent), CaptureError>> + Unpin {
268    /// create a new client with the given id
269    async fn create(&mut self, pos: Position) -> Result<(), CaptureError>;
270
271    /// destroy the client with the given id, if it exists
272    async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError>;
273
274    /// release mouse
275    async fn release(&mut self) -> Result<(), CaptureError>;
276
277    /// destroy the input capture
278    async fn terminate(&mut self) -> Result<(), CaptureError>;
279}
280
281async fn create_backend(
282    backend: Backend,
283) -> Result<
284    Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
285    CaptureCreationError,
286> {
287    match backend {
288        #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
289        Backend::InputCapturePortal => Ok(Box::new(libei::LibeiInputCapture::new().await?)),
290        #[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
291        Backend::LayerShell => Ok(Box::new(layer_shell::LayerShellInputCapture::new()?)),
292        #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
293        Backend::X11 => Ok(Box::new(x11::X11InputCapture::new()?)),
294        #[cfg(windows)]
295        Backend::Windows => Ok(Box::new(windows::WindowsInputCapture::new())),
296        #[cfg(target_os = "macos")]
297        Backend::MacOs => Ok(Box::new(macos::MacOSInputCapture::new().await?)),
298        Backend::Dummy => Ok(Box::new(dummy::DummyInputCapture::new())),
299    }
300}
301
302async fn create(
303    backend: Option<Backend>,
304) -> Result<
305    Box<dyn Capture<Item = Result<(Position, CaptureEvent), CaptureError>>>,
306    CaptureCreationError,
307> {
308    if let Some(backend) = backend {
309        let b = create_backend(backend).await;
310        if b.is_ok() {
311            log::info!("using capture backend: {backend}");
312        }
313        return b;
314    }
315
316    for backend in [
317        #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
318        Backend::InputCapturePortal,
319        #[cfg(all(unix, feature = "layer_shell", not(target_os = "macos")))]
320        Backend::LayerShell,
321        #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
322        Backend::X11,
323        #[cfg(windows)]
324        Backend::Windows,
325        #[cfg(target_os = "macos")]
326        Backend::MacOs,
327    ] {
328        match create_backend(backend).await {
329            Ok(b) => {
330                log::info!("using capture backend: {backend}");
331                return Ok(b);
332            }
333            Err(e) if e.cancelled_by_user() => return Err(e),
334            Err(e) => log::warn!("{backend} input capture backend unavailable: {e}"),
335        }
336    }
337    Err(CaptureCreationError::NoAvailableBackend)
338}