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
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(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: 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 async fn terminate(&mut self) -> Result<(), CaptureError> {
176 self.capture.terminate().await
177 }
178
179 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 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 let event = ready!(self.capture.poll_next_unpin(cx));
220
221 let event = match event {
223 Some(e) => e,
224 None => return Poll::Ready(None),
225 };
226
227 let (pos, event) = match event {
229 Ok(e) => e,
230 Err(e) => return Poll::Ready(Some(Err(e))),
231 };
232
233 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 async fn create(&mut self, pos: Position) -> Result<(), CaptureError>;
270
271 async fn destroy(&mut self, pos: Position) -> Result<(), CaptureError>;
273
274 async fn release(&mut self) -> Result<(), CaptureError>;
276
277 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}