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
33mod dummy;
35
36pub type CaptureHandle = u64;
37
38#[derive(Copy, Clone, Debug, PartialEq)]
39pub enum CaptureEvent {
40 Begin,
42 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: Box<dyn Capture>,
122 pressed_keys: HashSet<scancode::Linux>,
124 position_map: HashMap<Position, Vec<CaptureHandle>>,
126 id_map: HashMap<CaptureHandle, Position>,
128 pending: VecDeque<(CaptureHandle, CaptureEvent)>,
130}
131
132impl InputCapture {
133 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 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 pub async fn release(&mut self) -> Result<(), CaptureError> {
170 self.pressed_keys.clear();
171 self.capture.release().await
172 }
173
174 pub fn take_pressed_keys(&mut self) -> HashSet<scancode::Linux> {
184 std::mem::take(&mut self.pressed_keys)
185 }
186
187 pub async fn terminate(&mut self) -> Result<(), CaptureError> {
189 self.capture.terminate().await
190 }
191
192 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 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 let event = ready!(self.capture.poll_next_unpin(cx));
233
234 let event = match event {
236 Some(e) => e,
237 None => return Poll::Ready(None),
238 };
239
240 let (pos, event) = match event {
242 Ok(e) => e,
243 Err(e) => return Poll::Ready(Some(Err(e))),
244 };
245
246 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 async fn create(&mut self, pos: Position) -> Result<(), CaptureError>;
283
284 async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError>;
286
287 async fn release(&mut self) -> Result<(), CaptureError>;
289
290 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}