input_emulation/
lib.rs

1use async_trait::async_trait;
2use std::{
3    collections::{HashMap, HashSet},
4    fmt::Display,
5};
6
7use input_event::{Event, KeyboardEvent};
8
9pub use self::error::{EmulationCreationError, EmulationError, InputEmulationError};
10
11#[cfg(windows)]
12mod windows;
13
14#[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
15mod x11;
16
17#[cfg(all(unix, feature = "wlroots", not(target_os = "macos")))]
18mod wlroots;
19
20#[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))]
21mod xdg_desktop_portal;
22
23#[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
24mod libei;
25
26#[cfg(target_os = "macos")]
27mod macos;
28
29/// fallback input emulation (logs events)
30mod dummy;
31mod error;
32
33pub type EmulationHandle = u64;
34
35#[derive(Clone, Copy, Debug, Eq, PartialEq)]
36pub enum Backend {
37    #[cfg(all(unix, feature = "wlroots", not(target_os = "macos")))]
38    Wlroots,
39    #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
40    Libei,
41    #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))]
42    Xdp,
43    #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
44    X11,
45    #[cfg(windows)]
46    Windows,
47    #[cfg(target_os = "macos")]
48    MacOs,
49    Dummy,
50}
51
52impl Display for Backend {
53    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54        match self {
55            #[cfg(all(unix, feature = "wlroots", not(target_os = "macos")))]
56            Backend::Wlroots => write!(f, "wlroots"),
57            #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
58            Backend::Libei => write!(f, "libei"),
59            #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))]
60            Backend::Xdp => write!(f, "xdg-desktop-portal"),
61            #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
62            Backend::X11 => write!(f, "X11"),
63            #[cfg(windows)]
64            Backend::Windows => write!(f, "windows"),
65            #[cfg(target_os = "macos")]
66            Backend::MacOs => write!(f, "macos"),
67            Backend::Dummy => write!(f, "dummy"),
68        }
69    }
70}
71
72pub struct InputEmulation {
73    emulation: Box<dyn Emulation>,
74    handles: HashSet<EmulationHandle>,
75    pressed_keys: HashMap<EmulationHandle, HashSet<u32>>,
76}
77
78impl InputEmulation {
79    async fn with_backend(backend: Backend) -> Result<InputEmulation, EmulationCreationError> {
80        let emulation: Box<dyn Emulation> = match backend {
81            #[cfg(all(unix, feature = "wlroots", not(target_os = "macos")))]
82            Backend::Wlroots => Box::new(wlroots::WlrootsEmulation::new()?),
83            #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
84            Backend::Libei => Box::new(libei::LibeiEmulation::new().await?),
85            #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
86            Backend::X11 => Box::new(x11::X11Emulation::new()?),
87            #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))]
88            Backend::Xdp => Box::new(xdg_desktop_portal::DesktopPortalEmulation::new().await?),
89            #[cfg(windows)]
90            Backend::Windows => Box::new(windows::WindowsEmulation::new()?),
91            #[cfg(target_os = "macos")]
92            Backend::MacOs => Box::new(macos::MacOSEmulation::new()?),
93            Backend::Dummy => Box::new(dummy::DummyEmulation::new()),
94        };
95        Ok(Self {
96            emulation,
97            handles: HashSet::new(),
98            pressed_keys: HashMap::new(),
99        })
100    }
101
102    pub async fn new(backend: Option<Backend>) -> Result<InputEmulation, EmulationCreationError> {
103        if let Some(backend) = backend {
104            let b = Self::with_backend(backend).await;
105            if b.is_ok() {
106                log::info!("using emulation backend: {backend}");
107            }
108            return b;
109        }
110
111        for backend in [
112            #[cfg(all(unix, feature = "wlroots", not(target_os = "macos")))]
113            Backend::Wlroots,
114            #[cfg(all(unix, feature = "libei", not(target_os = "macos")))]
115            Backend::Libei,
116            #[cfg(all(unix, feature = "remote_desktop_portal", not(target_os = "macos")))]
117            Backend::Xdp,
118            #[cfg(all(unix, feature = "x11", not(target_os = "macos")))]
119            Backend::X11,
120            #[cfg(windows)]
121            Backend::Windows,
122            #[cfg(target_os = "macos")]
123            Backend::MacOs,
124            Backend::Dummy,
125        ] {
126            match Self::with_backend(backend).await {
127                Ok(b) => {
128                    log::info!("using emulation backend: {backend}");
129                    return Ok(b);
130                }
131                Err(e) if e.cancelled_by_user() => return Err(e),
132                Err(e) => log::warn!("{e}"),
133            }
134        }
135
136        Err(EmulationCreationError::NoAvailableBackend)
137    }
138
139    pub async fn consume(
140        &mut self,
141        event: Event,
142        handle: EmulationHandle,
143    ) -> Result<(), EmulationError> {
144        match event {
145            Event::Keyboard(KeyboardEvent::Key { key, state, .. }) => {
146                // prevent double pressed / released keys
147                if self.update_pressed_keys(handle, key, state) {
148                    self.emulation.consume(event, handle).await?;
149                }
150                Ok(())
151            }
152            _ => self.emulation.consume(event, handle).await,
153        }
154    }
155
156    pub async fn create(&mut self, handle: EmulationHandle) -> bool {
157        if self.handles.insert(handle) {
158            self.pressed_keys.insert(handle, HashSet::new());
159            self.emulation.create(handle).await;
160            true
161        } else {
162            false
163        }
164    }
165
166    pub async fn destroy(&mut self, handle: EmulationHandle) {
167        let _ = self.release_keys(handle).await;
168        if self.handles.remove(&handle) {
169            self.pressed_keys.remove(&handle);
170            self.emulation.destroy(handle).await
171        }
172    }
173
174    pub async fn terminate(&mut self) {
175        for handle in self.handles.iter().cloned().collect::<Vec<_>>() {
176            self.destroy(handle).await
177        }
178        self.emulation.terminate().await
179    }
180
181    pub async fn release_keys(&mut self, handle: EmulationHandle) -> Result<(), EmulationError> {
182        if let Some(keys) = self.pressed_keys.get_mut(&handle) {
183            let keys = keys.drain().collect::<Vec<_>>();
184            for key in keys {
185                let event = Event::Keyboard(KeyboardEvent::Key {
186                    time: 0,
187                    key,
188                    state: 0,
189                });
190                self.emulation.consume(event, handle).await?;
191                if let Ok(key) = input_event::scancode::Linux::try_from(key) {
192                    log::warn!("releasing stuck key: {key:?}");
193                }
194            }
195        }
196
197        let event = Event::Keyboard(KeyboardEvent::Modifiers {
198            depressed: 0,
199            latched: 0,
200            locked: 0,
201            group: 0,
202        });
203        self.emulation.consume(event, handle).await?;
204        Ok(())
205    }
206
207    pub fn has_pressed_keys(&self, handle: EmulationHandle) -> bool {
208        self.pressed_keys
209            .get(&handle)
210            .is_some_and(|p| !p.is_empty())
211    }
212
213    /// update the pressed_keys for the given handle
214    /// returns whether the event should be processed
215    fn update_pressed_keys(&mut self, handle: EmulationHandle, key: u32, state: u8) -> bool {
216        let Some(pressed_keys) = self.pressed_keys.get_mut(&handle) else {
217            return false;
218        };
219
220        if state == 0 {
221            // currently pressed => can release
222            pressed_keys.remove(&key)
223        } else {
224            // currently not pressed => can press
225            pressed_keys.insert(key)
226        }
227    }
228}
229
230#[async_trait]
231trait Emulation: Send {
232    async fn consume(
233        &mut self,
234        event: Event,
235        handle: EmulationHandle,
236    ) -> Result<(), EmulationError>;
237    async fn create(&mut self, handle: EmulationHandle);
238    async fn destroy(&mut self, handle: EmulationHandle);
239    async fn terminate(&mut self);
240}